Kotlin 实践及原理

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"
更灵活的case语句
  • 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
Get Set 构造器
  • 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 {
   
}

与Java互操作

相互调用
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;

kotlin 实践经验

优点
  • 语法简洁,能比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比较大的疑问可能是kotlin是怎么和java混编的?或者说kotlin是怎么生成字节码的

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

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

编译流程图:

Kotlin 实践及原理_第1张图片

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 跨平台

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

你可能感兴趣的:(Kotlin 实践及原理)