Kotlin in Action 学习笔记 (1)

96
realxz
2017.12.14 16:59 字数 2440

Kotlin 是以俄罗斯圣彼得堡附近的一座岛屿命名

Kotlin 是一门全新的针对 Java 平台的新编程的语言,百分之百与 Java 兼容,它是一门静态类型的语言,并且支持类型推导

先从书中的第一段代码来看看 Kotlin 风格的代码

image_1c0oi1rmp12d211fq4q9p1p1shp9.png-60kB
image_1c0oi1rmp12d211fq4q9p1p1shp9.png-60kB
  • 第 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<String>) {
    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 中并不区分受检异常和非受检异常,不用去指定函数抛出的异常,而且可以处理也可以不处理异常。

日记本
Gupao