Hello Kotlin

前世今生
有什么样的语法?
语法是怎么实现的?
如何编译的?
迁移成本
为我们带来什么改变?

前世今生

设计出发点

  1. Concise — 简洁
  2. Safe — 安全
  3. Interoperable — 协作

目标

Statically typed programming language for modern multiplatform applications
静态类型编程语言用于现代多平台应用

有什么样的语法?

语法对比

Kotlin vs Java kotlin Java
函数 fun sum(a: Int, b: Int): Int { return a + b } public int sum(int a, int b) {return a + b;}
变量 var a: Int = 1 int a = 1;
非空判断 text?.let { val length = text.length } if (text != null) { int length = text.length(); }
开关 var grade = when (score) {1, 2 -> “Excellent” } String grade; switch (score) { case 1: case 2: grade = “Excellent”; break; }

基础

数字

  1. 数字没有隐式拓宽转换,如 Java 中 int可以隐式转换为long
  2. 使数字常量更易,val oneMillion = 1_000_000
  3. 对于位运算,没有特殊字符来表示,而只可用中缀方式调用命名函数,例如:val x = (1 shl 2) and 0x000FF000,shl:有符号左移 and:位与

字符

  1. 字符用 Char 类型表示,它们不能直接当作数字,但可以显示转换
  2. 模板:val i = 10 val s = “i = $i” // 求值结果为 “i = 10”

控制流

If:是一个表达式,即它会返回一个值,没有三元运算符,使用if表达:val max = if (a > b) a else b

When:取代了类 C 语言的 switch 操作符,简单形式如下:

1
2
3
4
5
6
7
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> { // 注意这个块
print("x is neither 1 nor 2")
}
}

when 既可以被当做表达式使用也可以被当做语句使用。可以用任意表达式(而不只是常量)作为分支条件:

1
2
3
4
when (x) {
parseInt(s) -> print("s encodes x")
else -> print("s does not encode x")
}

返回和跳转

跳转表达式都可以用作更大表达式的一部分:
val s = person.name ?: return

标签:我们可以用标签限制 break 或者continue:

1
2
3
4
5
loop@ for (i in 1..100) {
for (j in 1..100) {
if (……) break@loop
}
}

类和对象

类声明:
Kotlin 中所有的类默认都是 final,由类名、类头(指定其类型参数、主 构造函数等)和由大括号包围的类体构成。类头和类体都是可选的; 如果一个类没有类体,可以省略花括号。通常如下:

1
2
class Invoice {
}

实例:
val invoice = Invoice(),Kotlin 并没有 new 关键字。

覆盖:
Kotlin力求清晰显式。与 Java 不同,Kotlin 需要显式 标注可覆盖的成员。如:override、open

静态方法:
Kotlin 中类没有静态方法。建议简单地使用包级函数。

属性和字段

声明一个属性的完整语法是:

1
2
3
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]

例:

1
2
3
4
5
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}

接口

Kotlin 的接口与 Java 8 类似,既包含抽象方法的声明,也包含实现。与抽象类不同的是,接口无法保存状态。它可以有 属性但必须声明为抽象或提供访问器实现。

使用关键字 interface 来定义接口

1
2
3
4
5
6
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}

可见性修饰符

Kotlin 中有这四个可见性修饰符:private、 protected、 internal 和 public, 默认可见性是 public。

可见性修饰符 internal 意味着该成员只在相同模块内可见。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:
一个 IntelliJ IDEA 模块;
一个 Maven 或者 Gradle 项目;
一次 <kotlinc> Ant 任务执行所编译的一套文件。

扩展

Kotlin 同 C# 和 Gosu 类似,能够扩展一个类的新功能而无需继承该类或使用像装饰者这样的任何类型的设计模式。 这通过叫做 扩展 的特殊声明完成。Kotlin 支持 扩展函数 和 扩展属性。

声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList 添加一个swap 函数:

1
2
3
4
5
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}

数据类

我们经常创建一些只保存数据的类。在这些类中,一些标准函数往往是从 数据机械推导而来的。在 Kotlin 中,这叫做 数据类 并标记为
data:data class User(val name: String, val age: Int)

语法实现原理

Null Safe Operator实现原理
示例函数如下:

1
2
3
fun testNullSafeOperator(string: String?) {
System.out.println(string?.toCharArray())
}

生成的字节码如下:

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
public final testNullSafeOperator(Ljava/lang/String;)V
@Lorg/jetbrains/annotations/Nullable;() // invisible, parameter 0
L0
LINENUMBER 15 L0
GETSTATIC java/lang/System.out : Ljava/io/PrintStream;
ALOAD 1
DUP
IFNULL L1
ASTORE 2
ASTORE 3
L2
ALOAD 2
DUP
IFNONNULL L3 //非空判断
NEW kotlin/TypeCastException
DUP
LDC "null cannot be cast to non-null type java.lang.String"
INVOKESPECIAL kotlin/TypeCastException.<init> (Ljava/lang/String;)V
ATHROW
L3
INVOKEVIRTUAL java/lang/String.toCharArray ()[C
DUP
LDC "(this as java.lang.String).toCharArray()"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
L4
ASTORE 4
ALOAD 3
ALOAD 4
L5
LINENUMBER 15 L5
GOTO L6
L1
POP
ACONST_NULL
L6
INVOKEVIRTUAL java/io/PrintStream.println ([C)V
L7
LINENUMBER 16 L7
RETURN
L8
LOCALVARIABLE this Ltech/jackywang/kotlindemo/Test; L0 L8 0
LOCALVARIABLE string Ljava/lang/String; L0 L8 1
MAXSTACK = 5
MAXLOCALS = 5

反编译为Java代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final void testNullSafeOperator(@Nullable String string) {
PrintStream var10000 = System.out;
char[] var10001;
if(string != null) {
PrintStream var3 = var10000;
if(string == null) {
throw new TypeCastException("null cannot be cast to non-null type java.lang.String");
}

char[] var5 = string.toCharArray();
Intrinsics.checkExpressionValueIsNotNull(var5, "(this as java.lang.String).toCharArray()");
char[] var4 = var5;
var10000 = var3;
var10001 = var4;
} else {
var10001 = null;
}

var10000.println(var10001);
}

由此可见kotlin实现的非空 语法,内部就是利用非空判断实现。

与Java互操作

Kotlin 中自然地调用现存的 Java 代码,并且在 Java 代码中也可以很顺利地调用 Kotlin 代码。

空安全和平台类型
Java 中的任何引用都可能是 null,这使得 Kotlin 对来自 Java 的对象要求严格空安全是不现实的。 Java 声明的类型在 Kotlin 中会被特别对待并称为平台类型。对这种类型的空检查会放宽, 因此它们的安全保证与在 Java 中相同

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

受检异常
在 Kotlin 中,所有异常都是非受检的,这意味着编译器不会强迫你捕获其中的任何一个。 因此,当你调用一个声明受检异常的 Java 方法时,Kotlin 不会强迫你做任何事情:

1
2
3
4
5
fun render(list: List<*>, to: Appendable) {
for (item in list) {
to.append(item.toString()) // Java 会要求我们在这里捕获 IOException
}
}

如何编译?

编译入口

kotlinc Hello.kt 开始分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
cygwin=false;
case "`uname`" in
CYGWIN*) cygwin=true ;;
esac

...

declare -a kotlin_app

//运行入口
if [ -n "$KOTLIN_RUNNER" ];
then
java_args=("${java_args[@]}" "-Dkotlin.home=${KOTLIN_HOME}")
kotlin_app=("${KOTLIN_HOME}/lib/kotlin-runner.jar" "org.jetbrains.kotlin.runner.Main")
else
//编译入口
[ -n "$KOTLIN_COMPILER" ] || KOTLIN_COMPILER=org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
java_args=("${java_args[@]}" "-noverify")
kotlin_app=("${KOTLIN_HOME}/lib/kotlin-preloader.jar" "org.jetbrains.kotlin.preloading.Preloader" "-cp" "${KOTLIN_HOME}/lib/kotlin-compiler.jar" $KOTLIN_COMPILER)
fi

"${JAVACMD:=java}" $JAVA_OPTS "${java_args[@]}" -cp "${kotlin_app[@]}" "${kotlin_args[@]}"
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
fun compileBunchOfSources(environment: KotlinCoreEnvironment): Boolean {
val moduleVisibilityManager = ModuleVisibilityManager.SERVICE.getInstance(environment.project)

val friendPaths = environment.configuration.getList(JVMConfigurationKeys.FRIEND_PATHS)
for (path in friendPaths) {
moduleVisibilityManager.addFriendPath(path)
}

if (!checkKotlinPackageUsage(environment, environment.getSourceFiles())) return false

//词法、语法分析、语义分析、目标代码生成等过程
val generationState = analyzeAndGenerate(environment) ?: return false

// 找到运行主类
val mainClass = findMainClass(generationState, environment.getSourceFiles())

try {
//写入文件
writeOutput(environment.configuration, generationState.factory, mainClass)
return true
}
finally {
generationState.destroy()
}
}

目标代码的生成

在代码类生成的过程中,又包括生成类名、类体、字段、函数方法等环节,相关的生成类有ClassBodyCodegen、ClassFunctionCodegen、MemberCodegen、ExpressionCodegen、PropertyCodegen等。

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
public class KotlinCodegenFacade {
public static void doGenerateFiles(
@NotNull Collection<KtFile> files,
@NotNull GenerationState state,
@NotNull CompilationErrorHandler errorHandler
) {
...
for (KtFile file : files) {
...
Set<FqName> obsoleteMultifileClasses = new HashSet<FqName>(state.getObsoleteMultifileClasses());
for (FqName multifileClassFqName : Sets.union(filesInMultifileClasses.keySet(), obsoleteMultifileClasses)) {
doCheckCancelled(state);
//目标代码类生成
generateMultifileClass(state, multifileClassFqName, filesInMultifileClasses.get(multifileClassFqName), errorHandler);
}

Set<FqName> packagesWithObsoleteParts = new HashSet<FqName>(state.getPackagesWithObsoleteParts());
for (FqName packageFqName : Sets.union(packagesWithObsoleteParts, filesInPackages.keySet())) {
doCheckCancelled(state);
//目标代码类包生成
generatePackage(state, packageFqName, filesInPackages.get(packageFqName), errorHandler);
}

doCheckCancelled(state);
//生成结束
state.getFactory().done();
}
}

Kotlin在目标代码生成环节做了更多的处理,在该环节实现了自动生成Getter、Setter的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class PropertyCodegen {
private void gen(
@Nullable KtProperty declaration, // 属性声明
@NotNull PropertyDescriptor descriptor, //描述,包括权限修饰符、注解、类型等。
@Nullable KtPropertyAccessor getter, // 决定是否生成getter
@Nullable KtPropertyAccessor setter //决定是否生成setter
) {
assert kind == OwnerKind.PACKAGE || kind == OwnerKind.IMPLEMENTATION || kind == OwnerKind.DEFAULT_IMPLS
: "Generating property with a wrong kind (" + kind + "): " + descriptor;
//生成注解信息
genBackingFieldAndAnnotations(declaration, descriptor, false);

//根据注解和权限修饰符等信息判断是否自动生成Getter代码
if (isAccessorNeeded(declaration, descriptor, getter)) {
generateGetter(declaration, descriptor, getter);
}
//根据注解和权限修饰符等信息判断是否自动生成Setter代码
if (isAccessorNeeded(declaration, descriptor, setter)) {
generateSetter(declaration, descriptor, setter);
}
}
}

Kotlin到Java的迁移成本

  1. 文件自动转换,点击菜单栏 Code | Convert Java File to Kotlin File
  2. 语法兼容性
  3. 第三方库兼容性

Kotlin为Android开发者带来什么改变?

  1. 效率
  2. 多平台

参考:Kotlin编译过程分析研究学习Kotlin的一些方法

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

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

支付宝
微信