Kotlin 实践及原理

关于Kotlin在项目中的实践经验,及在Android平台的编译原理

Kotlin 实践及原理

语法回顾

常量与变量
  • Java
1
2
String name = "Amit Shekhar";
final String name = "Amit Shekhar";
  • Kotlin
1
2
var name = "Amit Shekhar"
val name = "Amit Shekhar"
空判断
  • Java
1
2
3
if (text != null) {
int length = text.length();
}
  • Kotlin
1
val length = text?.length()
字符串拼接
  • Java
1
2
3
String firstName = "Amit";
String lastName = "Shekhar";
String message = "My name is: " + firstName + " " + lastName;
  • Kotlin
1
2
3
val firstName = "Amit"
val lastName = "Shekhar"
val message = "My name is: $firstName $lastName"
三元表达式
  • Java
1
String text = x > 5 ? "x > 5" : "x <= 5";
  • Kotlin
1
val text = if (x > 5) "x > 5" else "x <= 5"
更灵活的case语句
  • Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
int score = // some score;
String grade;
switch (score) {
case 10:
case 9:
grade = "Excellent";
break;
case 8:
case 7:
case 6:
grade = "Good";
break;
case 5:
case 4:
grade = "OK";
break;
case 3:
case 2:
case 1:
grade = "Fail";
break;
default:
grade = "Fail";
}

  • Kotlin
1
2
3
4
5
6
7
8
9
var score = // some score
var grade = when (score) {
9, 10 -> "Excellent"
in 6..8 -> "Good"
4, 5 -> "OK"
in 1..3 -> "Fail"
else -> "Fail"
}

方法定义
  • Java
1
2
3
4
5
int getScore() {
// logic here
return score;
}

  • Kotlin
1
2
3
4
5
6
7
8
9
fun getScore(): Int {
// logic here
return score
}

// as a single-expression function

fun getScore(): Int = score

Get Set 构造器
  • Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Developer {

private String name;
private int age;

public Developer(String name, int age) {
this.name = name;
this.age = age;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Developer developer = (Developer) o;

if (age != developer.age) return false;
return name != null ? name.equals(developer.name) : developer.name == null;

}

@Override
public int hashCode() {
int result = name != null ? name.hashCode() : 0;
result = 31 * result + age;
return result;
}

@Override
public String toString() {
return "Developer{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

  • Kotlin
1
2
data class Developer(val name: String, val age: Int)

类继承、实现接口
  • Java
1
2
3
4
public class Child extends Parent implements IHome {

}

  • kotlin
1
2
3
4
class Child : Parent(), IHome {

}

与Java互操作

相互调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.*

fun demo(source: List<Int>) {
val list = ArrayList<Int>()
// “for”-循环用于 Java 集合:
for (item in source) {
list.add(item)
}
// 操作符约定同样有效:
for (i in 0..source.size - 1) {
list[i] = source[i] // 调用 get 和 set
}
}

空安全与平台类型
1
2
3
4
5
6
val list = ArrayList<String>() // 非空(构造函数结果)
list.add("Item")
val size = list.size // 非空(原生 int)
val item = list[0] // 推断为平台类型(普通 Java 对象)
item.substring(1) // 允许,如果 item == null 可能会抛出异常

静态字段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 文件 example.kt

object Obj {
const val CONST = 1
}

class C {
companion object {
const val VERSION = 9
}
}

const val MAX = 239

int c = Obj.CONST;
int d = ExampleKt.MAX;
int v = C.VERSION;


kotlin 实践经验

优点
  • 语法简洁,能比java减少40%的代码,也能节约大量的时间
  • 语法级别的安全
  • 目前版本已较为稳定
缺点
  • 可能会有额外的开销
  • 少量的特性支持的还不健全,尤其在与Java互操作上,比如lateinit特性
坑、隐藏开销

kotlin代码是很简洁,但是简洁下面有时候会隐藏较大的开销。

伴生对象

如果我们需要创建类似Java中的静态成员,需要创建伴生对象,伴生对象通过companion object 创建,如下:

1
2
3
4
5
6
class Test {
companion object {
val version = 0
}
}

转换为同等的Java代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final class Test {
private static final int version = 0;
public static final Test.Companion Companion = new Test.Companion((DefaultConstructorMarker)null);

public static final class Companion {
public final int getVersion() {
return Test.version;
}

private Companion() {
}

public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}

也就是会多产生一次的函数调用开销,不过可以把val version 改为 const val version 避免这个问题

装箱拆箱

1
2
3
4
5
6
class Test {
val a: IntArray = intArrayOf(1)
val b: Array<Int> = arrayOf(1)
val c: Array<Int?> = arrayOf(null)
}

转为Java如下:

1
2
3
4
5
6
7
@NotNull
private final int[] a = new int[]{1};
@NotNull
private final Integer[] b = new Integer[]{1};
@NotNull
private final Integer[] c = new Integer[]{(Integer)null};

后两种产生了装箱处理,产生开销

For循环

kotlin 提供了downTo step until reversed函数简单使用循环,但这些函数组合使用也有可能产生较多的临时对象。

回滚

Tools ->Kotlin ->Show Kotlin Bytecode -> Decompile

Kotlin 编译原理

我们对kotlin比较大的疑问可能是kotlin是怎么和java混编的?或者说kotlin是怎么生成字节码的

kotlin整个都是开源的,可以从github clone下来,地址:https://github.com/JetBrains/kotlin

整个工程很庞大,源代码大概有四百多万行,可以使用 IntelliJ IDEA查看整个工程,具体操作可以看github 项目主页的建议。

编译流程图:

kotlin的maven id 为kotlin-gradle-plugin,我们做下全局搜索,发现路径为:root/libraries/tools/kotlin-gradle-plugin

每个插件都会有入口类,我们在module配置时都会添加:apply plugin: ‘kotlin-android’,kotlin-android代表的就是配置入口类文件的名字,所以我们看下下kotlin-android.properties文件内容,如下:

1
2
implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper

我们看到插件入口类为 KotlinAndroidPluginWrapper,接下来我们就从这个入口类分析下kotlin编译过程。

KotlinAndroidPluginWrapper 源码如下:

1
2
3
4
5
6
7
8
open class KotlinAndroidPluginWrapper @Inject constructor(
fileResolver: FileResolver,
protected val registry: ToolingModelBuilderRegistry
) : KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(project: Project, kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin<Project> =
KotlinAndroidPlugin(kotlinPluginVersion, registry)
}

真正的实现是在 KotlinAndroidPlugin 中,源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
internal open class KotlinAndroidPlugin(
private val kotlinPluginVersion: String,
private val registry: ToolingModelBuilderRegistry
) : Plugin<Project> {

override fun apply(project: Project) {
val androidTarget = KotlinAndroidTarget("", project)
val tasksProvider = AndroidTasksProvider(androidTarget.targetName)

applyToTarget(
project, androidTarget, tasksProvider,
kotlinPluginVersion
)
registry.register(KotlinModelBuilder(kotlinPluginVersion, androidTarget))
}

companion object {
fun applyToTarget(
project: Project,
kotlinTarget: KotlinAndroidTarget,
tasksProvider: KotlinTasksProvider,
kotlinPluginVersion: String
) {
// 省略无关代码

val variantProcessor = if (compareVersionNumbers(version, legacyVersionThreshold) < 0) {
LegacyAndroidAndroidProjectHandler(kotlinTools)
} else {
val android25ProjectHandlerClass = Class.forName("org.jetbrains.kotlin.gradle.plugin.Android25ProjectHandler")
val ctor = android25ProjectHandlerClass.constructors.single {
it.parameterTypes.contentEquals(arrayOf(kotlinTools.javaClass))
}
ctor.newInstance(kotlinTools) as AbstractAndroidProjectHandler<*>
}

variantProcessor.handleProject(project, kotlinTarget)
}
}
}

插件加载首先执行的是apply函数,跟进applyToTarget函数,省略掉无关代码,重点在最后一句handleProject

1
2
3
4
5
6
7
8
9
10
11
fun handleProject(project: Project, kotlinAndroidTarget: KotlinAndroidTarget) {

// ignore ..
forEachVariant(project) {
processVariant(
it, kotlinAndroidTarget, project, ext, plugin, kotlinOptions, kotlinConfigurationTools.kotlinTasksProvider
)
}
// ignore ..
}

省略掉无关代码,可以代码在processVariant,跟进去看下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private fun processVariant(
variantData: V,
target: KotlinAndroidTarget,
project: Project,
androidExt: BaseExtension,
androidPlugin: BasePlugin,
rootKotlinOptions: KotlinJvmOptionsImpl,
tasksProvider: KotlinTasksProvider
) {
// ignore ..
// 创建 kotlin 任务
val kotlinTask = tasksProvider.createKotlinJVMTask(project, kotlinTaskName, compilation)
// ignore ..
wireKotlinTasks(project, compilation, androidPlugin, androidExt, variantData, javaTask, kotlinTask)
}

其中会创建 kotlin 任务,创建任务入口先留意一下,先看下 wireKotlinTasks 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
override fun wireKotlinTasks(
project: Project,
compilation: KotlinJvmAndroidCompilation,
androidPlugin: BasePlugin,
androidExt: BaseExtension,
variantData: BaseVariantData<out BaseVariantOutputData>,
javaTask: AbstractCompile,
kotlinTask: KotlinCompile
) {
kotlinTask.dependsOn(*javaTask.dependsOn.toTypedArray())

configureJavaTask(kotlinTask, javaTask, logger)
}

configureJavaTask 比较可疑,跟进去看下:

1
2
3
4
5
6
internal fun configureJavaTask(kotlinTask: KotlinCompile, javaTask: AbstractCompile, logger: Logger) {
// ignore ..
javaTask.dependsOn(kotlinTask)
// ignore ..
}

我们看到函数核心是定义了kotlin task在java task之前执行,ok,那我们接下来跟进kotlin task的实现,我们返回上面的创建kotlin task的地方:tasksProvider.createKotlinJVMTask(project, kotlinTaskName, compilation),跟进去:

1
2
3
4
5
6
7
8
9
10
11
12
open fun createKotlinJVMTask(
project: Project,
name: String,
compilation: KotlinCompilation
): KotlinCompile {
val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask<KotlinCompile, KotlinCompileWithWorkers>(properties)
return project.tasks.create(name, taskClass).apply {
configure(this, project, properties, compilation)
}
}

大致意思就是根据任务名称创建任务,任务名称就来自泛型中定义的两个,那我们选择KotlinCompileWithWorkers,看下是如何定义的。

1
2
3
4
5
6
internal open class KotlinCompileWithWorkers @Inject constructor(
@Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor
) : KotlinCompile() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
}

看来是覆写了父类的compilerRunner,我们跟进去看看GradleCompilerRunnerWithWorkers的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
internal class GradleCompilerRunnerWithWorkers(
task: Task,
private val workersExecutor: WorkerExecutor
) : GradleCompilerRunner(task) {
override fun runCompilerAsync(workArgs: GradleKotlinCompilerWorkArguments) {
project.logger.kotlinDebug { "Starting Kotlin compiler work from task '${task.path}'" }
// todo: write tests with Workers enabled;
workersExecutor.submit(GradleKotlinCompilerWork::class.java) { config ->
config.isolationMode = IsolationMode.NONE
config.forkMode = ForkMode.NEVER
config.params(workArgs)
}
}

}

核心是提交了一个 runnable,这就比较明确了,我们看下GradleKotlinCompilerWork的实现,重点看run的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
override fun run() {
// ignore ..
val exitCode = try {
compileWithDaemonOrFallbackImpl()
} catch (e: Throwable) {
clearLocalStateDirectories(log, localStateDirectories, "exception when running compiler")
throw e
} finally {
if (buildFile != null && System.getProperty(DELETE_MODULE_FILE_PROPERTY) != "false") {
buildFile.delete()
}
}
// ignore ..
}

run 里面的核心就是compileWithDaemonOrFallbackImpl函数,跟进去:

1
2
3
4
5
6
7
8
private fun compileWithDaemonOrFallbackImpl(): ExitCode {
// ignore
if (executionStrategy == DAEMON_EXECUTION_STRATEGY) {
val daemonExitCode = compileWithDaemon()
}
// ignore
}

核心代码为:compileWithDaemon(),跟进去:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private fun compileWithDaemon(): ExitCode? {
// ignore
val targetPlatform = when (compilerClassName) {
KotlinCompilerClass.JVM -> CompileService.TargetPlatform.JVM
KotlinCompilerClass.JS -> CompileService.TargetPlatform.JS
KotlinCompilerClass.METADATA -> CompileService.TargetPlatform.METADATA
else -> throw IllegalArgumentException("Unknown compiler type $compilerClassName")
}
val exitCode = try {
val res = if (isIncremental) {
incrementalCompilationWithDaemon(daemon, sessionId, targetPlatform)
} else {
nonIncrementalCompilationWithDaemon(daemon, sessionId, targetPlatform)
}
exitCodeFromProcessExitCode(log, res.get())
} catch (e: Throwable) {
log.warn("Compilation with Kotlin compile daemon was not successful")
e.printStackTrace()
null
}
// ignore
return exitCode
}

选择编译平台,根据编译方式执行不同函数,我们选择nonIncrementalCompilationWithDaemon跟进去看下:

1
2
3
4
5
6
7
8
9
private fun nonIncrementalCompilationWithDaemon(
daemon: CompileService,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform
): CompileService.CallResult<Int> {
// ignore
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
}

继续,目前跟进到CompileServiceImpl#compile,忽略无关重点如下:

1
2
3
4
5
6
7
8
9
10
doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
val compiler = when (targetPlatform) {
CompileService.TargetPlatform.JVM -> K2JVMCompiler()
CompileService.TargetPlatform.JS -> K2JSCompiler()
CompileService.TargetPlatform.METADATA -> K2MetadataCompiler()
} as CLICompiler<CommonCompilerArguments>

compiler.exec(messageCollector, Services.EMPTY, k2PlatformArgs)
}

继续,忽略意义不大的跳转到K2JVMCompiler#doExecute,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
override fun doExecute(
arguments: K2JVMCompilerArguments,
configuration: CompilerConfiguration,
rootDisposable: Disposable,
paths: KotlinPaths?
): ExitCode {
// ignore
try {

if (arguments.buildFile != null) {

KotlinToJVMBytecodeCompiler.configureSourceRoots(configuration, moduleChunk.modules, buildFile)

KotlinToJVMBytecodeCompiler.compileModules(environment, buildFile, moduleChunk.modules)
} else if (arguments.script) {
return KotlinToJVMBytecodeCompiler.compileAndExecuteScript(environment, scriptArgs)
} else {
KotlinToJVMBytecodeCompiler.compileBunchOfSources(environment)
}
return OK
} catch (e: CompilationException) {
messageCollector.report(
EXCEPTION,
OutputMessageUtil.renderException(e),
MessageUtil.psiElementToMessageLocation(e.element)
)
return INTERNAL_ERROR
}
}

其中的KotlinToJVMBytecodeCompiler看起来是比较重要,跟进去其中一个分支看下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
// 词法 语法 分析
val generationState = analyzeAndGenerate(environment) ?: return false
// 查找主类
val mainClass = findMainClass(generationState, environment.getSourceFiles())
// 写入文件
try {
writeOutput(environment.configuration, generationState.factory, mainClass)
return true
} finally {
generationState.destroy()
}
}

看来已经找到关键函数入口了,跟进去analyzeAndGenerate,转到KotlinCodegenFacade#doGenerateFiles,如下:

1
2
3
4
5
6
7
8
9
10
11
public static void doGenerateFiles(
@NotNull Collection<KtFile> files,
@NotNull GenerationState state,
@NotNull CompilationErrorHandler errorHandler
) {
state.getCodegenFactory().generateModule(state, files, errorHandler);

CodegenFactory.Companion.doCheckCancelled(state);
state.getFactory().done();
}

跟进去CodegenFactory,关注generate开头的函数,又经过无数跳转到,MemberCodegen#genSimpleMember:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public void genSimpleMember(@NotNull KtDeclaration declaration) {
if (declaration instanceof KtNamedFunction) {
try {
functionCodegen.gen((KtNamedFunction) declaration);
}
catch (ProcessCanceledException | CompilationException e) {
throw e;
}
catch (Exception e) {
throw new CompilationException("Failed to generate function " + declaration.getName(), e, declaration);
}
}
else if (declaration instanceof KtProperty) {
try {
propertyCodegen.gen((KtProperty) declaration);
}
catch (ProcessCanceledException | CompilationException e) {
throw e;
}
catch (Exception e) {
throw new CompilationException("Failed to generate property " + declaration.getName(), e, declaration);
}
}
}

具体的生成细节,如果是function就由functionCodegen生成,如果属性就由propertyCodegen生成,跟进去functionCodegen:

1
2
3
4
5
6
7
8
9
public void gen(@NotNull KtNamedFunction function) {

if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) {
// ignore
generateMethod(JvmDeclarationOriginKt.OtherOrigin(function, functionDescriptor), functionDescriptor, strategy);
}
// ignore
}

忽略无关的跳转,转到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private void generateMethodBody(
@NotNull JvmDeclarationOrigin origin,
@NotNull FunctionDescriptor functionDescriptor,
@NotNull MethodContext methodContext,
@NotNull FunctionGenerationStrategy strategy,
@NotNull MethodVisitor mv,
@NotNull JvmMethodSignature jvmSignature,
boolean staticInCompanionObject
) {
OwnerKind contextKind = methodContext.getContextKind();
if (!state.getClassBuilderMode().generateBodies || isAbstractMethod(functionDescriptor, contextKind)) {
generateLocalVariableTable(
mv,
jvmSignature,
functionDescriptor,
getThisTypeForFunction(functionDescriptor, methodContext, typeMapper),
new Label(),
new Label(),
contextKind,
typeMapper,
Collections.emptyList(),
0);

mv.visitEnd();
return;
}

if (!functionDescriptor.isExternal()) {
generateMethodBody(mv, functionDescriptor, methodContext, jvmSignature, strategy, memberCodegen, state.getJvmDefaultMode(),
state.getLanguageVersionSettings().supportsFeature(LanguageFeature.ReleaseCoroutines));
}
else if (staticInCompanionObject) {
// native @JvmStatic foo() in companion object should delegate to the static native function moved to the outer class
mv.visitCode();
FunctionDescriptor staticFunctionDescriptor = JvmStaticInCompanionObjectGenerator
.createStaticFunctionDescriptor(functionDescriptor);
Method accessorMethod = typeMapper.mapAsmMethod(memberCodegen.getContext().accessibleDescriptor(staticFunctionDescriptor, null));
Type owningType = typeMapper.mapClass((ClassifierDescriptor) staticFunctionDescriptor.getContainingDeclaration());
generateDelegateToStaticMethodBody(false, mv, accessorMethod, owningType.getInternalName(), false);
}

endVisit(mv, null, origin.getElement());
}

代码中有几个地方 visitor 还有 visitEnd,我们看下具体的引用:

1
2
3
4
5
import org.jetbrains.org.objectweb.asm.*;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import org.jetbrains.org.objectweb.asm.commons.Method;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;

看来是利用ASM框架去生成字节码,例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 如果生成一个类使用ClassWriter
ClassWriter cw = new ClassWriter(0);
// 定义类的方法
cw.visitMethod(Opcodes.ACC_PUBLIC+Opcodes.ACC_ABSTRACT, "compareTo", "(Ljava/lang/Object;)I",null, null).visitEnd();
// 完成
cw.visitEnd();
// 将cw转换成字节数组
byte[] data = cw.toByteArray();
// 写入文件
File file = new File("/Users/test/Comparable.class");
FileOutputStream fout = new FileOutputStream(file);
fout.write(data);
fout.close();

ok,kotlin的编译过程基本就是,插件 - kotlin任务 - 编译器 - 生成方法、属性 - 利用ASM生成字节码

Kotlin 跨平台

kotlin 在语法上是支持跨平台的,是编译期跨平台,而不是容器类跨平台,目前支持JS、iOS、Server。

打赏
  • 版权声明: 本博客所有文章除特别声明外,均采用 Apache License 2.0 许可协议。转载请注明出处!
  • © 2017-2023 Jacky

所有的相遇,都是久别重逢

支付宝
微信