赞
踩
作为一个接触 Java 几年的人,虽然我很喜欢 Java 清晰的代码结构,但是在写工程的时候还是会有力不从心之感。Java 的语法简单,但是代码会变得冗长,同时作为一个强迫症,在编写一块新功能的时候,总是无法第一时间去编写实现实际功能的代码,而去考虑如何编写抽象类或者接口,来适应今后可能出现的对该功能进行扩充的需求,但是进行这种工作的时候脑子里对于实现具体功能的思路一直萦绕着,感觉十分折磨。
之后我就接触到了 Kotlin,它是开发了可以称作是 Java 最好用的 IDE——IntelliJ IDEA 的 JetBrains 公司开发的一门与 Java 十分贴近的语言,Kotlin 代码能够编译成 Java 字节码并且在 JVM 上运行,也能够调用 Java 代码,且其语法进行了足够良好的设计,用作 Java 的上位替代大约是十分合适的,而且 Google 宣布将 Kotlin 作为 Android 开发的推荐语言。因此我便开始学习 Kotlin,来提高我的开发效率。
我在阅读一些 Kotlin 书籍时,发现这些书的信息密度非常低,假如你是一个对编程一窍不通的人,这些书对你或许很有帮助。但是作为一个很熟悉 Java 的人,阅读这些书时就会感觉好不容易读完一章,几十页,几千个字,所阐述的东西就只有那么一点,但是我却花费了阅读这么多字的精力(可见速读能力是多么重要!),因此,为了我学习 Kotlin 进行记录之用,也为了帮助已经比较熟悉 Java 的人能够快速上手 Kotlin,我开启了这个系列。
我学习 Kotlin 所使用的书是《Kotlin 编程权威指南》
事实上,我觉得如果你即使不熟悉 Java,本系列的内容你大概也能看懂相当一部分。
Kotlin 并不像 Java 一样,一切元素都是类的成员。Kotlin中的类不再像 Java 中那样具有至高无上的地位,我们的 main
函数就直接写在任何地方,然后 IntelliJ IDEA 就可以以这个 main
函数为入口来执行。
fun main() {
println("Hello world")
}
可以看到,在 Kotlin 中调用 println
不需要使用冗长的 System.out
前缀,且语句无需以 ;
号结尾,但同时又保留了大括号。我认为 Kotlin 是统合并优化了 C++、Java、Python等语言的优点的一门语言
省流:
- var 声明变量,val 声明常量
- 可以为变量指定类型,也可以通过赋值让编译器推导出它的类型
- Kotlin 的基本类型对应 Java 中基本类型的封装,因此 Kotlin 取消了装箱和拆箱的概念
Java 中规定的基本数据类型就是 Kotlin 中规定的基本数据类型,只是在 Kotlin 中使用类型名时相比 Java 会首字母大写,例如 Java 中的 long
类型对应 Kotlin 中的 Long
类型。
在 Kotlin 中声明变量的基本语句如下:
var [变量名]:[类型] = 字面量
val [常量名]:[类型] = 字面量
其中 var
表示声明变量,val
表示声明常量,它们对应的 Java 语句为:
[类型] [变量名] = 字面量;
final [类型] [常量名] = 字面量;
此外,Kotlin 有类似于 Python 的对象类型推导功能,也就是说我们在一些时候可以不去写类型名,例如:
var x = 100
就已经是创建了一个 Int
类型的变量,这里我们没有明着指出 x
的类型,但是 Kotlin 可以根据后面的100的类型推导出这个 x
应当是 Int
类型的。
为了方便叙述,以后把 val
与 var
均称作变量。
事实上,Kotlin 这些所谓的基本数据类型更像是 Java 中基本数据类型的封装(也就是类似于
int
封装成Integer
),Kotlin 就全面采用这种封装的类型,也就取消了拆箱和装箱的概念。
fun main() {
val a = 20
val b = 40
println("" + a + "+" + b + "=" + (a + b))
println("$a+$b=${a + b}")
}
输出结果:
20+40=60
20+40=60
在字符串中使用美元符号,可以让字符串中的一些字符作为代码执行。
当然,如果想要打印美元符号,可以在美元符号前添加 \
符号,即可取消其作为模板标识符的作用。
区间有其对应的类名:CharRange
, IntRange
, LongRange
区间有两种字面量:a..b
表示区间 [a, b]
,a until b
表示区间 [a, b)
,同时可以使用关键字 in
来判断某个数在数值上是否属于某个区间。
fun main() {
val C1 : CharRange = 'a' until 'z'
val C2 : CharRange = 'a' .. 'z'
val N : IntRange = 1 .. 50
println("${'z' in C1}, ${'z' in C2}, ${49 in N}, ${72 in 68 .. 100}")
}
输出结果:
false, true, true, true
Java 中有基本数据类型的数组和对象数组,Kotlin 中对基本数据类型的数组都专门定制了类,类名就是 [类型名]+Array,例如 Int
的数组就是 IntArray
,函数arrayOf
可以创建一个数组对象,而 intArrayOf
可以创建一个 IntArray
对象。此外,也可以直接使用 IntArray
的“构造函数”来创建对象。
var arr1 = intArrayOf(1, 2, 3, 4, 5)
var arr2 = IntArray(5)
分别与 Java 中
int[] arr1 = {1, 2, 3, 4, 5};
int[] arr2 = new int[5];
功能相同。
而 Kotlin 中 arr1
对象也封装了一些操作,此处不再赘述。当然,Kotlin 也可使用形如 arr1[0]
写法访问数组元素。
Kotlin 中有两种比较相等符号,==
和 ===
==
相当于 Java 中 Object
类内的 equals
函数,按照对象的值进行比较;===
类似于 Java 中的 ==
符号,按照变量的地址值进行比较;fun main() {
val t1 = "test"
var t2 = "te"
t2 += "st"
println("${t1 == t2}, ${t1 === t2}")
}
输出结果:
true, false
Kotlin 函数定义的格式如下:
fun [函数名]([参数1][类型], [参数2][类型], ...) : [返回值] {
}
如果没有返回值,就无需填写返回值。针对函数返回值, Kotlin 并未提供类似于 var
和 val
语句的类型推导功能。
fun plus(a:Int, b:Int) : Int {
return a + b
}
fun display(x:Int) {
println("$x")
}
就相当于 Java 中的:
public static int plus(int a, int b) {
return a + b;
}
public static void display(int x) {
System.out.println(x);
}
此外,Kotlin 的函数可以定义在函数内部
例如:
fun main() {
fun plus(a:Int, b:Int) : Int {
return a + b
}
fun display(x:Int) {
println("$x")
}
println("${plus(20, 50)}")
display(90)
}
输出结果:
70
90
Kotlin 的函数与 Python 一样,能够为参数设定默认值。例如:
fun plus(a:Int, b:Int, c:Int = 5) {
println("${a + b + c}")
}
fun main() {
plus(1, 2)
}
运行结果:
8
此外,Kotlin 还像 Python 那样能够为指定的参数赋值,例如:
fun plus(a:Int, b:Int = 5, c:Int) {
println("${a + b + c}")
}
fun main() {
plus(1, c = 5)
}
运行结果:
11
如果一个函数只含有一个语句,那么可以写成这种形式:
fun single(x:Int) = println("$x is $x")
fun main() {
single(2)
}
运行结果:
2 is 2
这种语法糖更多应用在有返回值的情况:
fun single(x:Int) = x * x * x
fun main() {
println(single(5))
}
运行结果:
125
{[参数列表]->[代码]}
代码举例:
fun main() {
val a = {x:Int, y:Int ->
println("$x and $y")
x + y
}(1, 5)
println(a)
}
运行结果:
1 and 5
6
这段代码等价于:
fun plus(x:Int, y:Int): Int{
println("$x and $y")
return x + y
}
fun main() {
val a = plus(1, 5)
println(a)
}
可见,Lambda 表达式可以省略 return 符号,而且其能够推断返回值类型。(事实上,如果在这里加上 return 符号,则这个 Lambda 表达式就会被作为一个 Unit 类型(相当于 Java 中的 void 类型)的函数处理),在我们这个示例程序中就会报错。
能够用一个变量来存储 Lambda 表达式。当然,既然是用变量存储,那么变量也具有某种类型。存储 Lambda 表达式的变量的类型为:
([参数列表]) -> [返回值]
如果没有返回值,则返回值为 Unit
代码举例:
fun main() {
val a : (Int, Int) -> String = {x: Int, y: Int ->
println("You called me?")
"$x + $y = ${x + y}"
}
println(a(1, 5))
}
运行结果:
You called me?
1 + 5 = 6
而且,由于 Lambda 表达式能够使用变量来存储,这也意味着一个函数的参数可以是 Lambda 表达式类型
代码举例
fun printTwice(lmb : (x:Int, y:Int) -> String) {
val str = lmb(2, 5)
println(str)
println(str)
}
fun main() {
val a : (x:Int, y:Int) -> String = {x: Int, y: Int ->
println("You called me?")
"$x + $y = ${x + y}"
}
printTwice(a)
}
运行结果:
You called me?
2 + 5 = 7
2 + 5 = 7
可以注意到,我们之前创建一个 Lambda 表达式类型的变量的时候,虽然在类型上声明了参数列表,但是在具体编写函数体的时候还会写一次参数列表。而对于单参数的 Lambda 表达式,则无需在函数体那里再写一次参数列表,可以使用 it
关键字来访问这个参数。
代码举例:
fun main() {
val a : (Int)->String = {
"$it * $it = ${it * it}"
}
println(a(5))
}
运行结果:
5 * 5 = 25
fun func(t:()->Unit, x:()->Unit) {
t()
x()
}
fun main() {
func({println("ttttttttttttttt")}) {
println("xxxxxxxxxxxxxxx")
}
}
输出结果:
ttttttttttttttt
xxxxxxxxxxxxxxx
可见,如果一个函数的最后一个参数是 Lambda 表达式,可以不把这个 Lambda 表达式写在括号内部,而可以直接使用大括号编写这个 Lambda 函数的内容,这个大括号里面的内容就是传入函数的 Lambda 表达式变量的内容。
代码举例:
fun main() { val arrPrint:(IntArray)->Unit = { for(x in it) { print("$x, ") } println() } val arrInit:IntArray.()->Unit = { for(x in withIndex()) { set(x.index, x.index * 10 + 1) } } val arr = IntArray(10) arr.arrInit() arrPrint(arr) }
输出结果:
1, 11, 21, 31, 41, 51, 61, 71, 81, 91,
注意这里的 arrInit
,它在声明 Lambda 表达式的类型时,在前面加上了一个IntArray.
,这样一来在 Lambda 表达式内部编写代码时,就可以使用 this
或者直接来调用 IntArray
类中的成员。参观其调用方式,它仿佛真的就是为 IntArray
类添加了一个 arrInit
方法。
注:这里涉及到的
for
循环会在后文进行讲解,此处其作用就是输出一个整数数组的内容。
也就是说,apply, run, let, with, also
五个函数。
它们与 Lambda 表达式均有紧密关系,或者说,辅助 Lambda 表达式的作用。
日后再深入探究五个看似没用的函数之间的异同点。
省流:内联函数能够用来优化程序资源开销,与C++中的
#define A(x)
类似,正因如此,内联函数不能递归。
实际上就是辅助编译器优化的功能,也就是使用一个 inline
关键字来声明函数,这样一来凡是调用这个函数的地方,在编译的时候都会把这段代码给直接复制粘贴到对应位置(有点类似于 C 中的宏函数)此外,内联函数不能递归
做个简单比较:
inline fun plus(a:Int, b:Int) : String{
return "$a + $b = ${a + b}"
}
fun main() {
val t = plus(1, 5)
println(t)
}
其编译成 Java 字节码然后反编译回的 Java 代码如下:
public final class HelloKt { @NotNull public static final String plus(int a, int b) { int $i$f$plus = 0; return a + " + " + b + " = " + (a + b); } public static final void main() { byte a$iv = 1; int b$iv = 5; int $i$f$plus = false; String t = a$iv + " + " + b$iv + " = " + (a$iv + b$iv); System.out.println(t); } // $FF: synthetic method public static void main(String[] var0) { main(); } }
去除 inline
关键字后:
fun plus(a:Int, b:Int) : String{
return "$a + $b = ${a + b}"
}
fun main() {
val t = plus(1, 5)
println(t)
}
反编译的 Java 代码如下:
public final class HelloKt { @NotNull public static final String plus(int a, int b) { return a + " + " + b + " = " + (a + b); } public static final void main() { String t = plus(1, 5); System.out.println(t); } // $FF: synthetic method public static void main(String[] var0) { main(); } }
if-else
表达式这与 Java 中的 if-else
用法基本相同,能够使用 if-else if-else
的结构来控制程序流程。
kotlin
中 if
和 else
关键字可以用来写三目表达式,例如:
val a1 = 10
val a2 = 20
val a3 = if(a1 > a2) a1 else a2
这就相当于 Java 中的:
final int a1 = 10;
final int a2 = 20;
final int a3 = (a1 > a2) ? a1 : a2;
在
kotlin
中甚至写:var a = if(2 > 3) 3 else if(5 > 9) 2 else 9
,可以将其理解为多个选择分支,也可以理解成嵌套三目,按前者理解,其逻辑更加一目了然。
when
表达式省流:相当于一大串
if-else if-else
语句
when
表达式是 switch
表达式的强化版,它既可以作为传统的 switch-case
结构使用,也可以作为一大组 if-else if-else
语句使用,亦可以二者混合使用,本质上还是相当于一大组 if-else if-else
fun main() { var a = Integer.parseInt(Scanner(System.`in`).nextLine()) when(a) { 10 -> { println("is 10!") } 8 -> { println("is 8!") } in 1..20 -> { println("in [1, 20]!") } else -> { println("else") } } }
我这里习惯性地使用了 Java 中从标准输入流中读入数字的函数,可见 Kotlin 是能够直接调用 Java 代码的。当然,这里的代码也是靠 IntelliJ IDEA 辅助写出来的。
当输入 10 时,程序输出 is 10!
,当输入 21 时,程序输出 in [1, 20]!
,当输入 100 时,程序输出 else
从中我们得到三点信息:
when
结构中无需使用 break
关键字,它只会执行满足条件的其中一个分支包含的代码when
结构会从上到下查找,并且只会执行第一个满足条件的分支包含的代码。因为在测试用例中,输入 10 时,它满足第一个与第三个分支的条件,但是却仅执行了第一个分支包含的代码。这个 when
语句与以下代码等价:
if(a == 10) {
println("is 10!")
}else if(a == 8) {
println("is 8!")
}else if(a in 1..20) {
println("in [1, 20]!")
}else {
println("else")
}
此外,既然 when
本质上可以看作是一组 if-else if-else
,它也可以作为类似于三目运算符使用。
当然,
when
后面可以不用带括号,因为它相当于一组if-else if-else
结构,此前when
括号内添加变量导致的衍生语法(直接写10就代表了 a == 10)更像是个语法糖。
Kotlin 中有 while
和 do-while
循环,其用法与 Java 中的完全一致,此处不再赘述。
Kotlin 中彻底取消了能够追溯到 C语言的 for([初始化语句]; 条件判断语句; [每次循环后执行的语句])
的格式。
for(A; B; C) { xxxxxx... }
- 1
- 2
- 3
与
A; while(B) { xxxxxx... C; }
- 1
- 2
- 3
- 4
- 5
功能完全一致,for 循环原本在 C 语言中的结构没必要保留了。
在 Java 和 Python 中,for 循环可以用来遍历集合对象(例如数组等)中的所有元素,在 Java 中又称作所谓“增强 for”,而 Python 和 Kotlin已经取消了 C语言式 for循环,它们的 for 循环都相当于 Java 的“增强for”
我们在 Java 中可以使用这样的语句:
for(int i = 0; i < array.length; i++) {array[i]......}
或者for(int ele : array) {ele.....}
的方式来处理数组,前者不仅能够读取数组元素,还能定位数组元素并对数组元素进行修改,后者就只能遍历一遍数组,除非额外添加外部变量辅助,python 的 for 循环就只有类似于后者的功能所以某些场景下使用起来未必方便,而 Kotlin则提供了一个更好的解决方案
Kotlin 本质上只有一种 for 循环结构,也就是 Java 中所谓的增强 for
fun main() {
val arr = intArrayOf(10, 20, 30, 40, 50, 60, 70, 80)
for(x in arr) {
print("$x, ")
}
}
输出结果:
10, 20, 30, 40, 50, 60, 70, 80,
但是别忘了 Kotlin 中的数组对象进行了封装并集成了一些其他有用的功能,假设我们的处理中需要使用各元素的下标号:
fun main() {
val arr = intArrayOf(10, 20, 30, 40, 50, 60, 70, 80)
for((i, x) in arr.withIndex()) {
println("arr[$i] = $x")
}
}
输出结果:
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
arr[5] = 60
arr[6] = 70
arr[7] = 80
上述代码也可以写成:
fun main() {
val arr = intArrayOf(10, 20, 30, 40, 50, 60, 70, 80)
for(t in arr.withIndex()) {
println("arr[${t.index}] = ${t.value}")
}
}
这里实质就是在遍历数组的下标及其内容组成的对子。
此外,for
也可以利用区间:
fun main() {
for (i in 1..4) {
print(i)
}
}
输出结果:
1234
省流:
- 一般类型的变量无法赋
null
值- 声明变量时类型名后面加
?
号就可以给这个变量赋null
值,称之为可空变量- 可控变量无法直接调用对象方法,需要用其他手段调用
?.
调用,安全,不强制!!.
调用,不安全,强制- Java 式先判定是否为空的方法。
Kotlin 对变量能否赋值为 null
做了限制,具体参见上面的省流部分。示例代码:
var x : Int? = null
(如果这里把 ?
号去掉,会报错)
?
号表明这个变量可能为空,因此不允许这个变量直接调用它的方法。下面讲解如何让可空变量调用方法,一共有三种方法。
参考以下代码:
fun main() {
var x = readLine()
if(x == "NULL") {
x = null
}
println("${x == null}, ${x?.capitalize()}")
}
其中capitlize()
函数的作用是根据 x
生成一个字符串,相比 x
会让 x
首字母大写。
readLine()
就是从标准输入流中读入一行。readLine
函数返回的是一个 String?
类型,因此变量 x
就是一个可空字符。
运行此程序,输入 NULL
,输出结果为:
true, null
这表明 x
的确赋值为了 null
,后面让 x
调用 capitalize()
方法,但是却输出为 null
,且程序没有报错,这就是 kotlin
对空指针异常的防范,也就是允许可空变量使用 ?.
调用方法。
此外,也可以改成如下的调用形式:
x!!.capitalize()
这就是不管 x
是否为 null
,均强制调用这个方法。这样的话可能会抛出 NullPointerException
异常。
也可以使用类似于 Java
中对于可能为空的变量的处理方法,也就是用 if(x == null)
来判断 x
是否为空。
使用
?:
符号来为空对象提供一个默认的解决方案。
fun main() {
var x = readLine()
if(x == "NULL") {
x = null
}
println("${x == null}, ${x?.capitalize()?:"this is null"}")
}
运行结果 (输入NULL
):
true, this is null
省流:和 Java 类似的
try-catch
以及throw
,但是 Kotlin 不需要像 Java 那样在函数声明时使用throws
关键字标记会抛出的异常的类型。
fun except(){
throw RuntimeException("Just a test")
}
fun main() {
try {
except()
}catch (e:Exception){
e.printStackTrace()
}
}
输出结果:
java.lang.RuntimeException: Just a test
at HelloKt.except(Hello.kt:2)
at HelloKt.main(Hello.kt:6)
at HelloKt.main(Hello.kt)
省流:看 IDEA 的内置提示基本上能看出来这个函数是干啥的
substring
用于截取字符串。
fun main() {
val a = "123456789"
val b = a.substring(2)
val c = a.substring(2, 4)
val d = a.substring(2..4)
println(b)
println(c)
println(d)
}
输出结果:
3456789
34
345
split
用于分割字符串
fun main() {
val a = "111 222 333 444"
val b = a.split(" ")
println(b)
}
输出结果:
[111, 222, 333, 444]
replace
这里涉及到正则表达式的相关内容,在之后专题篇章中会专门讲解。
List
Kotlin 可以使用 listOf
函数创建一个 List
对象。
fun main() {
val t:List<String> = listOf("a", "b", "c")
println(t)
}
输出结果:
[a, b, c]
(示例代码中的 List<String>
可省略)
List
对象只能读取其中的内容,而无法修改其中的内容。
可以使用 t[0]
这样的方法来访问其中的内容,但是无法修改其中的内容,也就是说,类似于 Python 中的元组。
下面的代码演示遍历一个 List
:
fun main() {
val t = listOf("a", "b", "c")
for(str in t) {
println(str)
}
}
输出结果:
a
b
c
Set
Kotlin 可以使用 setOf
函数创建一个集合。一个集合中的元素是不可重复的,创建集合时如果出现重复元素则会自动去重。示例代码:
fun main() {
val set = setOf("I", "Love", "You", "And", "You")
println(set)
}
输出结果:
[I, Love, You, And]
使用 contains
和 containAll
函数来判定集合中是否含有某个或某些元素:
fun main() {
val set = setOf("I", "Love", "You", "And", "You")
println(set.contains("I"))
println(set.containsAll(setOf("I", "Love")))
}
输出结果:
true
true
使用 elementAt
函数能够按照索引查找集合中的元素,但是由于 Kotlin 的集合的内部实现为链表,因此按照这种方式查找集合中的元素耗时较长,如果有按照索引查找元素的需求,应当采用 List
fun main() {
val set = mutableSetOf("I", "Love", "You", "And", "You")
set.add("kotlin")
println(set)
}
输出结果:
[I, Love, You, And, kotlin]
Map
Kotlin 可以使用 mapOf
函数创建一个集合。
fun main() {
val map = mapOf("Tim" to 24, Pair("Alice", 14), Pair("Jack", 26))
println(map["Tim"])
}
输出结果:
24
to 是一个中缀函数,我们在之后讲解 Kotlin 的面向对象体系时会介绍这个内容。这里
Tim to 24
的作用与Pair("Tim", 24)
一致。
fun main() {
val map = mapOf("Tim" to 24, Pair("Alice", 14), Pair("Jack", 26))
for(i in map) {
println("${i.key}, ${i.value}")
}
}
输出结果:
Tim, 24
Alice, 14
Jack, 26
fun main() {
val map = mutableMapOf("Tim" to 24, Pair("Alice", 14), Pair("Jack", 26))
map["Tim"] = 20
map["Jim"] = 50
println("Tim = ${map["Tim"]}. Jim = ${map["Jim"]}")
}
需要使用 mutableMapOf
创建一个可以修改的 map
用起来与 Python 十分相似。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。