Kotlin 实践及原理
如代码格式异常,可点击原文查看
Java
String name = "Amit Shekhar";
final String name = "Amit Shekhar";
Kotlin
var name = "Amit Shekhar"
val name = "Amit Shekhar"
Java
if (text != null) {
int length = text.length();
}
Kotlin
val length = text?.length()
Java
String firstName = "Amit";
String lastName = "Shekhar";
String message = "My name is: " + firstName + " " + lastName;
Kotlin
val firstName = "Amit"
val lastName = "Shekhar"
val message = "My name is: $firstName $lastName"
Java
String text = x > 5 ? "x > 5" : "x <= 5";
Kotlin
val text = if (x > 5) "x > 5" else "x <= 5"
Java
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
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
int getScore() {
// logic here
return score;
}
Kotlin
fun getScore(): Int {
// logic here
return score
}
// as a single-expression function
fun getScore(): Int = score
Java
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
data class Developer(val name: String, val age: Int)
Java
public class Child extends Parent implements IHome {
}
kotlin
class Child : Parent(), IHome {
}
import java.util.*
fun demo(source: List) {
val list = ArrayList()
// “for”-循环用于 Java 集合:
for (item in source) {
list.add(item)
}
// 操作符约定同样有效:
for (i in 0..source.size - 1) {
list[i] = source[i] // 调用 get 和 set
}
}
val list = ArrayList() // 非空(构造函数结果)
list.add("Item")
val size = list.size // 非空(原生 int)
val item = list[0] // 推断为平台类型(普通 Java 对象)
item.substring(1) // 允许,如果 item == null 可能会抛出异常
// 文件 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;
语法简洁,能比java减少40%的代码,也能节约大量的时间
语法级别的安全
目前版本已较为稳定
可能会有额外的开销
少量的特性支持的还不健全,尤其在与Java互操作上,比如lateinit特性
kotlin代码是很简洁,但是简洁下面有时候会隐藏较大的开销。
伴生对象
如果我们需要创建类似Java中的静态成员,需要创建伴生对象,伴生对象通过companion object
创建,如下:
class Test {
companion object {
val version = 0
}
}
转换为同等的Java代码,如下:
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
避免这个问题
装箱拆箱
class Test {
val a: IntArray = intArrayOf(1)
val b: Array = arrayOf(1)
val c: Array = arrayOf(null)
}
转为Java如下:
@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是怎么和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文件内容,如下:
implementation-class=org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
我们看到插件入口类为 KotlinAndroidPluginWrapper,接下来我们就从这个入口类分析下kotlin编译过程。
KotlinAndroidPluginWrapper 源码如下:
open class KotlinAndroidPluginWrapper @Inject constructor(
fileResolver: FileResolver,
protected val registry: ToolingModelBuilderRegistry
) : KotlinBasePluginWrapper(fileResolver) {
override fun getPlugin(project: Project, kotlinGradleBuildServices: KotlinGradleBuildServices): Plugin =
KotlinAndroidPlugin(kotlinPluginVersion, registry)
}
真正的实现是在 KotlinAndroidPlugin 中,源码如下:
internal open class KotlinAndroidPlugin(
private val kotlinPluginVersion: String,
private val registry: ToolingModelBuilderRegistry
) : Plugin {
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
fun handleProject(project: Project, kotlinAndroidTarget: KotlinAndroidTarget) {
// ignore ..
forEachVariant(project) {
processVariant(
it, kotlinAndroidTarget, project, ext, plugin, kotlinOptions, kotlinConfigurationTools.kotlinTasksProvider
)
}
// ignore ..
}
省略掉无关代码,可以代码在processVariant,跟进去看下
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 实现:
override fun wireKotlinTasks(
project: Project,
compilation: KotlinJvmAndroidCompilation,
androidPlugin: BasePlugin,
androidExt: BaseExtension,
variantData: BaseVariantData,
javaTask: AbstractCompile,
kotlinTask: KotlinCompile
) {
kotlinTask.dependsOn(*javaTask.dependsOn.toTypedArray())
configureJavaTask(kotlinTask, javaTask, logger)
}
configureJavaTask 比较可疑,跟进去看下:
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)
,跟进去:
open fun createKotlinJVMTask(
project: Project,
name: String,
compilation: KotlinCompilation
): KotlinCompile {
val properties = PropertiesProvider(project)
val taskClass = taskOrWorkersTask(properties)
return project.tasks.create(name, taskClass).apply {
configure(this, project, properties, compilation)
}
}
大致意思就是根据任务名称创建任务,任务名称就来自泛型中定义的两个,那我们选择KotlinCompileWithWorkers,看下是如何定义的。
internal open class KotlinCompileWithWorkers @Inject constructor(
@Suppress("UnstableApiUsage") private val workerExecutor: WorkerExecutor
) : KotlinCompile() {
override fun compilerRunner() = GradleCompilerRunnerWithWorkers(this, workerExecutor)
}
看来是覆写了父类的compilerRunner,我们跟进去看看GradleCompilerRunnerWithWorkers的实现:
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的实现:
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函数,跟进去:
private fun compileWithDaemonOrFallbackImpl(): ExitCode {
// ignore
if (executionStrategy == DAEMON_EXECUTION_STRATEGY) {
val daemonExitCode = compileWithDaemon()
}
// ignore
}
核心代码为:compileWithDaemon(),跟进去:
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跟进去看下:
private fun nonIncrementalCompilationWithDaemon(
daemon: CompileService,
sessionId: Int,
targetPlatform: CompileService.TargetPlatform
): CompileService.CallResult {
// ignore
return daemon.compile(sessionId, compilerArgs, compilationOptions, servicesFacade, compilationResults = null)
}
继续,目前跟进到CompileServiceImpl#compile,忽略无关重点如下:
doCompile(sessionId, daemonReporter, tracer = null) { _, _ ->
val compiler = when (targetPlatform) {
CompileService.TargetPlatform.JVM -> K2JVMCompiler()
CompileService.TargetPlatform.JS -> K2JSCompiler()
CompileService.TargetPlatform.METADATA -> K2MetadataCompiler()
} as CLICompiler
compiler.exec(messageCollector, Services.EMPTY, k2PlatformArgs)
}
继续,忽略意义不大的跳转到K2JVMCompiler#doExecute,如下:
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看起来是比较重要,跟进去其中一个分支看下:
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,如下:
public static void doGenerateFiles(
@NotNull Collection files,
@NotNull GenerationState state,
@NotNull CompilationErrorHandler errorHandler
) {
state.getCodegenFactory().generateModule(state, files, errorHandler);
CodegenFactory.Companion.doCheckCancelled(state);
state.getFactory().done();
}
跟进去CodegenFactory,关注generate开头的函数,又经过无数跳转到,MemberCodegen#genSimpleMember:
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:
public void gen(@NotNull KtNamedFunction function) {
if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) {
// ignore
generateMethod(JvmDeclarationOriginKt.OtherOrigin(function, functionDescriptor), functionDescriptor, strategy);
}
// ignore
}
忽略无关的跳转,转到:
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,我们看下具体的引用:
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框架去生成字节码,例子:
// 如果生成一个类使用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 在语法上是支持跨平台的,是编译期跨平台,而不是容器类跨平台,目前支持JS、iOS、Server。