Kotlin 是以俄罗斯圣彼得堡附近的一座岛屿命名
Kotlin 是一门全新的针对 Java 平台的新编程的语言,百分之百与 Java 兼容,它是一门静态类型的语言,并且支持类型推导
先从书中的第一段代码来看看 Kotlin 风格的代码
- 第 3 行使用 data class 关键字声明了一个数据类,会帮我们自动生成 Person 类的属性,getter,setter,equals 等方法
- 第 4 行在 Person 类的主构造函数中声明 age 属性的时候使用了可空类型
Int?
,并且制定了默认值为null
- 第 6 行声明了一个顶层函数
- 第 7 行使用 listOf 函数来创建一个 List
- 第 8 行在创建 Person 对象的时候使用了命名参数
- 第 10 行使用了 Lambda 表达式和 Elvis 运算符
- 第 11 行使用 println() 来替代 java 中的 System.out.println() 来输出信息,并且使用了全新的字符串模板
$oldest
可以看到,上述代码只有 12 行而,如果是 Java 代码想要实现同样的功能,仅仅是 Person 这个实体类可能就要写上好几十行,上面我们提到了数据类、属性、可空类型、顶层函数等概念,都会在下文一一介绍。
Kotlin 基础概念
函数和变量
在 Kotlin 中我们省略了类型的声明,并且声明的变量默认都是不可变的也就是 final
的
函数
定义一个比较函数
fun max(a: Int, b: Int): Int {
return if (a > b) a else b
}
使用关键字 fun
来声明一个函数,之后是函数名称,在函数名称后面是参数列表,最后就是函数的返回类型,用 :
隔开,可以看见最后返回了一个 if 语句,在 Java 当中 if 是控制语句,但是在 Kotlin 中大多数控制结构都是表达式。
在 Java 中赋值操作是表达式,这样就可能会在比较和赋值之间产生混淆,在 Kotlin 中赋值操作是语句,从而避免了这种混淆
有的时候我们会这样写比较语句
int a;
a==5
如果不小心写成了 a=5
就有可能产生隐患
如果上述写法之外,这个求和函数还可以这样写
fun max(a: Int, b: Int) = if (a > b) a else b
max 函数的函数体是由单个表达式构成,可以用这个表达式来作为完成的函数体,去除了花括号和 return 语句,这种形式成为表达式体,而另一种则成为代码块体.
类型推导
在 max 函数的表达式体当中,我们省略了返回类型,这似乎与 静态类型 相悖,这是因为编译器会分析表达式的返回类型,并将它的类型作为函数的返回类型,这种分析成为类型推导
变量
在 Kotlin 中是这样声明变量的
val name : String //显示指出了声明变量的类型
var name = "dada" // 编译器会分析出 dada 的类型是 String 类型,将其作为变量 name 的类型
声明变量的语法是 关键字 - 变量名称 - 变量类型(可省略),声明变量的关键字有两个
- val (value) 不可变引用,同 Java 的 final 变量
- var (variable) 可变引用,对应 Java 的非 final 变量
所有的变量默认的可见性是 Public 的,这点与 Java 不同,Java 的默认可见性是包内可见的
字符串模板
Kotlin 中提供了一种新的特性 字符串模板 使用 $
符号加上变量,就可以引用该变量,如果想要引用表达式,只需要在 $
后用花括号将表达式括起来即可,这样可比字符串拼接要方便的多
//java
String name = "dada";
System.out.println("Hello" + dada);
//kotlin
val name = "dada"
println("Hello $ name)
//kotlin 引用表达式
val a = 10
val b = 20
println("$a + $b = ${a + b}")
类和属性
想看一个 Java 类
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
在我们的项目中,存在着大量的这种数据类,同样的类在 Kotlin 中是这样的
class Person(val name: String)
代码量的对比已经很明显了,类中的属性越多,相对于 Java 来说,Kotlin 就越能帮我们减少代码量。
之前我们用 Java 代码声明了一个 Person 类,类中只有一个 name 字段,通常类中会给每个字段提供访问器方法,一个 getter 方法,如果字段不是 final 类型的话,还会提供一个 setter 方法。这种字段+访问器方法的组合在 Java 中叫做属性,而在 Kollin 中属性完全替代了字段和访问器方法,在类中声明一个属性就如同声明一个变量一样:使用 var 和 val 关键字进行声明
class Person(
val name: String,//只读属性,生成一个name字段和对应的getter方法
var isMarried: Boolean//可变属性,生成一个字段和对应的setter,getter方法
)
在 Kotlin 中,每当声明一个属性的时候,就默认声明了对应的访问器,访问器的默认实现包含一个存储值的字段,一个更新值值的 setter,一个获取值的 getter。
在 Kotlin 中使用 Person 类
fun main(args: Array) {
val 达达 = Person("达达", false)
println(达达.name)
println(达达.isMarried)
}
可以看到在 Kotlin 中创建对象不用关键字 new ,并且可以直接使用对象.属性的方式直接引用属性。
自定义访问器
声明属性的时候,提供了默认的访问器方法,Kotlin 也允许我们自定义访问器方法
//自定义 setter
var age: Int = age
get() {
return field - 1
}
set(value) {
field = value * 2
}
//自定义 getter
val isAdult:Boolean
get() {
return age>18
}
控制结构和循环结构
枚举
在 Kotlin 中声明一个枚举类
enum class Color {
RED, ORANGE, YELLOW, GREEN, BLUE, INDIGO, VIOLET
}
使用关键字 enum 和 class 来声明枚举类,关键字 enum 只有在 class 前面的时候才有意义,其他地方可以把它当成普通的字符串使用。
在 Kotlin 中声明一个带属性的枚举类
enum class Color(
val r: Int, val g: Int, val b: Int
) {
RED(255, 0, 0), ORANGE(255, 165, 0),
YELLOW(255, 255, 0), GREEN(0, 255, 0), BLUE(0, 0, 255),
INDIGO(75, 0, 130), VIOLET(238, 130, 238);
fun rgb() = (r * 256 + g) * 256 + b
}
当你声明每一个枚举常量的时候,必须要提供属性值。用 ;
将枚举常量和方法定义分隔开,这是 Kotlin 当中唯一需要使用分号的地方
控制结构
使用 when 处理枚举类
when结构同样是一个表达式,用来替代 Java 当中的 switch 语句,下面来看下 when 的用法
fun getMnemonic(color: Color) =
when (color) {
Color.RED -> "Richard"
Color.ORANGE -> "Of"
Color.YELLOW -> "York"
Color.GREEN -> "Gave"
Color.BLUE -> "Battle"
Color.INDIGO -> "In"
Color.VIOLET -> "Vain"
}
在 when 结构中使用任意对象
在 Java 当中 Switch 语句只能够接收常量作为分支条件,而在 Kotlin 当中 when 允许使用任何对象,例如接收一个 Set 作为分支条件
when (setOf(c1, c2)) {
setOf(RED, YELLOW) -> ORANGE
setOf(YELLOW, BLUE) -> GREEN
setOf(BLUE, VIOLET) -> INDIGO
else -> throw Exception("Dirty color")
}
使用不带参数的 when
when {
(c1 == RED && c2 == YELLOW) ||
(c1 == YELLOW && c2 == RED) ->
ORANGE
(c1 == YELLOW && c2 == BLUE) ||
(c1 == BLUE && c2 == YELLOW) ->
GREEN
(c1 == BLUE && c2 == VIOLET) ||
(c1 == VIOLET && c2 == BLUE) ->
INDIGO
else -> throw Exception("Dirty color")
}
没有给 when 表达式提供参数,每一个分支的条件都是一个布尔表达式
智能转换
来看看书中列举的求和的例子
interface Expr //标记接口,提供公共类型
class Num(val value: Int) : Expr//数字类
class Sum(val left: Expr, val right: Expr) : Expr//求和类
先展示一段 Java 风格的代码
fun eval(e: Expr): Int {
if (e is Num) {// java 风格
val n = e as Num
return n.value
}
if (e is Sum) { // kotlin 风格
return eval(e.right) + eval(e.left)
}
throw IllegalArgumentException("Unknown expression")
}
在 kotlin 中使用 is
来检查一个变量是否属于某种类型,使用 as
来显示的将变量转换到指定的类型,在 Java 中我们使用 instanceOf
关键字类来判断类型,并在之后进行强制类型转换。而在 Kotlin 当中,检查过变量的类型之后,不需要对它进行强制类型转换,就可以把它当成是你检查过的类型使用,这种行为成为 智能转换
使用 when 来代替 if 结构
if的返回值结构
fun eval(e: Expr): Int =
if (e is Num) {
e.value
} else if (e is Sum) {
eval(e.right) + eval(e.left)
} else {
throw IllegalArgumentException("Unknown expression")
}
when 结构
fun eval(e: Expr): Int =
when (e) {
is Num ->
e.value
is Sum ->
eval(e.right) + eval(e.left)
else ->
throw IllegalArgumentException("Unknown expression")
}
当分支逻辑过于复杂的时候可以使用代码块,代码块中的最后一个表达式,就是分支的返回值
规则-“代码块中最后的表达式就是结果”,在所有使用代码块并期望得到一个结果的地方成立。但是对函数不成立,一个函数要么具有不是代码块的表达式体,要么是显示 return 的代码块体
循环结构
while 结构
Kotlin 中的 while 结构与 Java 中的 While 一模一样
while(condition){
//...
}
do{
//...
}while(condition)
区间
本质上是两个值之间的间隔,一个是起始值,一个是结束值,使用 ..
运算符来表示区间,并且区间是闭合的始终包含结束值。
for 循环
来看看 for 循环的运行,它不同于 Java 中的循环
for (i in 1..100) {
println("i=$i")
}
如果你想倒叙输出呢,很简单
for (i in 100 downTo 1) {
println("i=$i")
}
任意步长
for (i in 1..100 step 2){
println("i=$i")
}
不包括其结束元素的区间
for (i in 1 until 100) {
println("i=$i")
}
迭代 map
val map = mapOf(1 to "a", 2 to "b", 3 to "c")
for ((key, value) in map) {
println("$key=$value")
}
迭代 list
val list = listOf("a", "b", "c")
for (s in list) {
println(s)
}
println("a" in list)
println("d" !in list)
in
运算符用来检查给定值是否在集合中,!n
运算符用来检查给定值是否不在集合中
Kotlin 中的异常
Kotlin 中异常处理语句与 Java 类似,抛出异常的方式以差不多:
fun readNumber(reader: BufferedReader): Int? {
try {
val line = reader.readLine()
return Integer.parseInt(line)
}
catch (e: NumberFormatException) {
return null
}
finally {
reader.close()
}
}
略有不同的是,不需要使用 new 关键字来创建异常实例,另外 throw 结构也是一个表达式,能作为另外一个表达式的一部分使用。
try,catch 和 finally
和 Java 一样使用 带有 catch 和 finally 子句的 try 结构来处理异常,不同的是 try 和 if,when 一样都是表达式,不过 try 的主体需要用花括号括起来。如果主体包含多个表达式,那么最后一个表达式就是 try 表达式的值。
如果 try 代码块一切正常,则 try 代码块中的最后一个表达式就是返回值,如果捕获到了异常,则 catch 代码块中的最后一个表达式就是结果。
不必抛出 Checked Exception
将上段的 try 代码使用 Java 来写:
public static int readNumber(BufferedReader reader) throws IOException {
try{
String line = null;
try {
line = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return Integer.parseInt(line);
}catch (NumberFormatException e){
System.out.println(e.toString());
return 0;
}finally {
reader.close();
}
}
可以看到在方法体前面要求你抛出 IOException。因为IOException是一个受检异常,在 Java 当中要求你必须处理该异常,或者在调用函数中声明抛出该异常,在 Kotlin 中并不区分受检异常和非受检异常,不用去指定函数抛出的异常,而且可以处理也可以不处理异常。