使用Kotlin提高开发效率

Kotlin是现代跨平台的静态类型程序设计语言,不仅仅是Android官方支持的开发语言,同时在Web后端也在大量的使用。就我目前的工作而言,已经在完全使用Kotlin来编写基于Spring Boot的后端服务。为什么要使用Kotlin来开发应用?主要是因为它百分之百兼容Java,可以编译成JVM的字节码。Java的各种第三方库和Spring生态成为企业大型应用开发的最佳方案,使用Kotlin可以提高开发效率。

也许是一个时代流行一个语法,好的概念总会被推进,无关语言。最近对比了同样流行的Go语言,发现这两者的语法有异曲同工之处。
尤其是Kotlin目前还在试验阶段的协程就和Go的协程非常像,更像是相互借鉴的感觉。
kotlin的main函数输出hello,world非常精简

1
2
3
fun main(args: Array<String>) {
println("Hello, world!")
}

kotlin同样借鉴了动态语言,如python的一些语法
比如要过滤掉集合中能被2整除的数

1
2
3
4
5
6
fun main(args: Array<String>) {
val numbers = listOf(1, 2, 3)
println(numbers.filter(::isOdd))
}

fun isOdd(x: Int) = x % 2 != 0

kotlin有非常多的新特性,具体可以查阅官网教程,此处不再重复啰嗦。

先从常见的语法进行分析
关于变量的使用
Java

1
String str = null;

Kotlin

1
var str: String?=null

也许你会说仅仅是把变量声明放变量名后面而已,方便在哪?除了不用写封号以外,我怎么感觉还多写了?别急,Kotlin可以这样

1
var str = "this is String Test"

自动类型推断,无需显示声明类型
也许你觉得自动类型推断不过如此?别急,看看下面这段代码

1
2
3
4
5
6
// 获取用户信息集合
val userList = userService.userList
// 遍历用户
for (user in userList){
println(user)
}

调用userService的userList方法,将调用的返回值赋值给变量userList,无需指定变量类型,Kotlin将会自动推断,包括变量循环时也无需声明变量类型,全部都会进行自动推断。
调用的方法没有参数,所以直接写方法名即可。
如果是Java,你需要这样写

1
2
3
4
List<User> userList = userService.getUserList();
for (User user : userList){
System.out.println(user);
}

Java需要为接收的变量指定变量类型,你觉得还不够?接着往下看

关于方法的使用
在java中,定义一个方法并且返回字符串

1
2
3
public String getInfoString(){
return "this is String Test";
}

在kotlin中

1
2
3
fun getInfoString(): String {
return "this is String Test"
}

Kotlin使用fun关键字定义一个方法,方法返回类型在方法的括号后面用 :加类型
也许你会觉得这和Java没差多少啊,别急,Kotlin还可以这样

1
fun getInfoString() = "this is String Test"

方法也支持自动类型推断,够精简了吧?
什么?还不够精简?那再对比一下java和kotlin
比较两个数的大小,返回大的那个数

1
2
3
4
5
6
7
8
// 老实人的写法
public int max(int a, int b){
if (a>b){
return a;
}else{
return b;
}
}

kotlin的写法

1
fun max(a: Int, b: Int) = if (a > b) a else b

除了方法返回值类型自动推断,连流程控制语句里的retrun都不需要
也许你觉得也就一般般?还不够?那就接着往下看

流程控制语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public static void main(String[] args) {

String language;
if (args.length == 0) {
language = "EN";
} else {
language = args[0];
}

switch (language) {
case "EN":
System.out.println("Hello!");
break;
case "CN":
System.out.println("你好");
break;
default:
System.out.println("抱歉,不支持当前语言" + language + "的输出");
break;
}
}

在java中,你会发现你写了很多重复的代码,比如case和break关键字,并且整个代码变得非常的沉长。
Kotlin可以这么写

1
2
3
4
5
6
7
8
fun main(args: Array<String>){
val language = if (args.isEmpty()) "EN" else args[0]
println(when (language){
"EN" -> "Hello!"
"CN" -> "你好!"
else -> "抱歉,支持当前语言$language 的输出"
})
}

Kotlin使用when代替Java中的switch语句,case匹配可以和lambada表达式一样精简的写法,可以在println中直接输出整个表达式,字符串内部可以使用美元符号$加变量名进行变量表达式输出。

也许你觉得其实也就这样而已?那继续往下看!

1
2
3
4
5
6
7
8
9
fun cases(obj: Any){
when(obj){
1 -> println("One")
"Hello" -> println("你好")
is Long -> println("Long")
!is String -> println("不是字符串")
else -> println("未知")
}
}

在Kotlin中使用Any表示Object类型,when语句比java的switch语句要强大很多。
如果是Java中去做匹配Object类型的操作,
需要swtich case中用instanceof去匹配,java代码我已经无力写下去了

1
//java略...

下面来分析主要的几个部分,Kotlin简洁语法的背后到底为我们做了什么?Kotlin编译成JVM字节码后到底是什么样的?
关于简单的语法糖我们可以直接在IDEA一层一层往下翻,另外一些编译时生成的则需要通过反编译字节码。

接下来分析一个例子:
用Java来写一个POJO类,类里面有3个私有的成员变量,然后要提供公共的get\set方法。
User.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
public class User {

/**
* 姓名
*/
private String name;
/**
* 性别
*/
private String sex;
/**
* 年龄
*/
private Integer age;

public String getName() {
return name;
}

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

public String getSex() {
return sex;
}

public void setSex(String sex) {
this.sex = sex;
}

public Integer getAge() {
return age;
}

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

通过属性私有,方法公开的方式来对类进行封装,代码很多,而且很繁琐有没有?
虽然get/set可以自动生成,但其实这种模板化的代码干脆就不要出现在代码里,除非要单独封装某个属性值

使用Kotlin来写POJO类就变得非常精简,只需要几行就可以了,在类前使用data关键字修饰,
类体用()小括号包裹,里面定义成员变量即可。编译后的class文件会自动为我们添加上属性的get/set方法,并且重写toString()、hashCode()、equals()方法,以及构造函数和一些特殊方法。
User.kt

1
2
3
4
5
data class User(
var name: String?=null,
var sex: String?=null,
var age: Int?=null
)

口说无凭,你说有get/set就真的有?真的会生成带参和无参的构造函数?凭什么?
为了一探究竟,接下来直接反编译class字节码,看看Kotlin写的代码对应的JVM字节码到底是什么样的

为了翻遍查看对应类的信息,这里使用javap -c 并将class文件反编译后的数据输出到一个txt文件上

1
javap -c User.class > User.txt

反编译后的class字节码文件 User.txt内容如下

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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
Compiled from "User.kt"
public final class com.lanshiqin.springboot.User {
public final java.lang.String getName();
Code:
0: aload_0
1: getfield #11 // Field name:Ljava/lang/String;
4: areturn

public final void setName(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #11 // Field name:Ljava/lang/String;
5: return

public final java.lang.String getSex();
Code:
0: aload_0
1: getfield #20 // Field sex:Ljava/lang/String;
4: areturn

public final void setSex(java.lang.String);
Code:
0: aload_0
1: aload_1
2: putfield #20 // Field sex:Ljava/lang/String;
5: return

public final java.lang.Integer getAge();
Code:
0: aload_0
1: getfield #27 // Field age:Ljava/lang/Integer;
4: areturn

public final void setAge(java.lang.Integer);
Code:
0: aload_0
1: aload_1
2: putfield #27 // Field age:Ljava/lang/Integer;
5: return

public com.lanshiqin.springboot.User(java.lang.String, java.lang.String, java.lang.Integer);
Code:
0: aload_0
1: invokespecial #34 // Method java/lang/Object."<init>":()V
4: aload_0
5: aload_1
6: putfield #11 // Field name:Ljava/lang/String;
9: aload_0
10: aload_2
11: putfield #20 // Field sex:Ljava/lang/String;
14: aload_0
15: aload_3
16: putfield #27 // Field age:Ljava/lang/Integer;
19: return

public com.lanshiqin.springboot.User(java.lang.String, java.lang.String, java.lang.Integer, int, kotlin.jvm.internal.DefaultConstructorMarker);
Code:
0: iload 4
2: iconst_1
3: iand
4: ifeq 12
7: aconst_null
8: checkcast #37 // class java/lang/String
11: astore_1
12: iload 4
14: iconst_2
15: iand
16: ifeq 24
19: aconst_null
20: checkcast #37 // class java/lang/String
23: astore_2
24: iload 4
26: iconst_4
27: iand
28: ifeq 36
31: aconst_null
32: checkcast #39 // class java/lang/Integer
35: astore_3
36: aload_0
37: aload_1
38: aload_2
39: aload_3
40: invokespecial #41 // Method "<init>":(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V
43: return

public com.lanshiqin.springboot.User();
Code:
0: aload_0
1: aconst_null
2: aconst_null
3: aconst_null
4: bipush 7
6: aconst_null
7: invokespecial #43 // Method "<init>":(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
10: return

public final java.lang.String component1();
Code:
0: aload_0
1: getfield #11 // Field name:Ljava/lang/String;
4: areturn

public final java.lang.String component2();
Code:
0: aload_0
1: getfield #20 // Field sex:Ljava/lang/String;
4: areturn

public final java.lang.Integer component3();
Code:
0: aload_0
1: getfield #27 // Field age:Ljava/lang/Integer;
4: areturn

public final com.lanshiqin.springboot.User copy(java.lang.String, java.lang.String, java.lang.Integer);
Code:
0: new #2 // class com/lanshiqin/springboot/User
3: dup
4: aload_1
5: aload_2
6: aload_3
7: invokespecial #41 // Method "<init>":(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)V
10: areturn

public static com.lanshiqin.springboot.User copy$default(com.lanshiqin.springboot.User, java.lang.String, java.lang.String, java.lang.Integer, int, java.lang.Object);
Code:
0: iload 4
2: iconst_1
3: iand
4: ifeq 12
7: aload_0
8: getfield #11 // Field name:Ljava/lang/String;
11: astore_1
12: iload 4
14: iconst_2
15: iand
16: ifeq 24
19: aload_0
20: getfield #20 // Field sex:Ljava/lang/String;
23: astore_2
24: iload 4
26: iconst_4
27: iand
28: ifeq 36
31: aload_0
32: getfield #27 // Field age:Ljava/lang/Integer;
35: astore_3
36: aload_0
37: aload_1
38: aload_2
39: aload_3
40: invokevirtual #53 // Method copy:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Integer;)Lcom/lanshiqin/springboot/User;
43: areturn

public java.lang.String toString();
Code:
0: new #56 // class java/lang/StringBuilder
3: dup
4: invokespecial #57 // Method java/lang/StringBuilder."<init>":()V
7: ldc #59 // String User(name=
9: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
12: aload_0
13: getfield #11 // Field name:Ljava/lang/String;
16: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
19: ldc #65 // String , sex=
21: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
24: aload_0
25: getfield #20 // Field sex:Ljava/lang/String;
28: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: ldc #67 // String , age=
33: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: aload_0
37: getfield #27 // Field age:Ljava/lang/Integer;
40: invokevirtual #70 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
43: ldc #72 // String )
45: invokevirtual #63 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
48: invokevirtual #74 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
51: areturn

public int hashCode();
Code:
0: aload_0
1: getfield #11 // Field name:Ljava/lang/String;
4: dup
5: ifnull 14
8: invokevirtual #78 // Method java/lang/Object.hashCode:()I
11: goto 16
14: pop
15: iconst_0
16: bipush 31
18: imul
19: aload_0
20: getfield #20 // Field sex:Ljava/lang/String;
23: dup
24: ifnull 33
27: invokevirtual #78 // Method java/lang/Object.hashCode:()I
30: goto 35
33: pop
34: iconst_0
35: iadd
36: bipush 31
38: imul
39: aload_0
40: getfield #27 // Field age:Ljava/lang/Integer;
43: dup
44: ifnull 53
47: invokevirtual #78 // Method java/lang/Object.hashCode:()I
50: goto 55
53: pop
54: iconst_0
55: iadd
56: ireturn

public boolean equals(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: if_acmpeq 59
5: aload_1
6: instanceof #2 // class com/lanshiqin/springboot/User
9: ifeq 61
12: aload_1
13: checkcast #2 // class com/lanshiqin/springboot/User
16: astore_2
17: aload_0
18: getfield #11 // Field name:Ljava/lang/String;
21: aload_2
22: getfield #11 // Field name:Ljava/lang/String;
25: invokestatic #86 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
28: ifeq 61
31: aload_0
32: getfield #20 // Field sex:Ljava/lang/String;
35: aload_2
36: getfield #20 // Field sex:Ljava/lang/String;
39: invokestatic #86 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
42: ifeq 61
45: aload_0
46: getfield #27 // Field age:Ljava/lang/Integer;
49: aload_2
50: getfield #27 // Field age:Ljava/lang/Integer;
53: invokestatic #86 // Method kotlin/jvm/internal/Intrinsics.areEqual:(Ljava/lang/Object;Ljava/lang/Object;)Z
56: ifeq 61
59: iconst_1
60: ireturn
61: iconst_0
62: ireturn
}

可以看到使用data修饰的POJO类的Kotlin代码编译后,不仅仅默认为我们生成了get/set方法,还重写了toString()、
hashCode()、equals()方法,还有构造函数和特有的方法,另外我们还发现了类和所有的方法默认都是使用final作为修饰符的,这些特性对于快速开发非常有用。
其余的字节码指令部分后续再分析,挖个坑,今天先写到这。

0%