3e8ade5ec0ae1616ae6c396fcd3bceaf
《Kotlin实战》第一部分读书摘要

本文是我读《Kotlin实战》第一部分的读书摘要,纯属记录我阅读过程中的重要知识点,请勿将本文当做Kotlin语言入门教程。若诚心想要入门Kotlin,建议从《Kotlin实战》搭配官方Kotlin语言指南,结合官方的在线快速上手教程一起从零开始。

说实话,自己不是很想学Kotlin,因为之前有接触过这门语言的一些语法,看完之后给我的感觉不是很舒服,对于我这个常年写Java代码又不深谙JVM之道的程序员来说,Kotlin给我的冲击不小,并没有像那些大神那么鼓吹的那么厉害那么喜欢。反倒是苹果主推的Swift语言让我感觉这才是新世界的大门,大概的区别就是Swift让我感觉是“哇,竟然还可以这么写!”,而Kotlin给我的感觉却是“咦,怎么还能这么写?!”,想想主要还是Java的世界给我留下了很多误以为是程序世界的固有思想。言归正传,本文就是一个摘要性的文章,请随意阅读,如有不明白的地方可以找到原书对应部分翻阅详情。

1. Kotlin语言

Kotlin语言的几个特性
目标平台多:服务器端、Android端等
静态类型:简洁、性能、可靠性
函数式编程核心概念:头等函数(函数可用变量保存和作为参数传递)、不可变性、无副作用
函数式编程的好处:安全性、可维护性、测试容易

Kotlin的设计哲学:务实、简洁、安全、互操作性

2. 语法基础

表达式和语句的区别就是:表达式有值,并且能作为其他表达式的一部分使用,而语句总是包围着它的代码块中的顶层元素,并且没有自己的值。

在Kotlin中,除了循环(for、do或者do-while)以外,大多数控制结构都是表达式。此外,Java中的赋值操作是表达式,在Kotlin中却是语句。

举个例子,在Java中可以这么写,其中的b = cursor.hasNext()就是一个表达式,其值作为if条件句的条件。

if (b = cursor.hasNetxt()) {
    //code
}

但是在Kotlin中,赋值操作是语句,例如下面的if(a > b) a else b在Java中是一个条件控制语句,但是在Kotlin中是一个表达式,它的值传给了函数max,连在一起就是一个语句。这种情况在Kotlin中很常见,函数除了我们Java中常见的代码块体,也可能是下面的表达式体,也就是函数直接返回一个表达式的结果。

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

规则:代码块中最后的表达式就是结果,在所有使用代码块并期望得到一个结果的地方都成立。这个规则对于常规函数不成立,因为常规函数要么是不具有代码块的表达式体函数,要么是具有显示return语句的代码块函数体。

Kotlin中唯一必须使用分号的地方是如果需要在枚举类中定义方法,就要使用分号把枚举常量列表和方法的定义分开。

Kotlin中的when结构比Java中的switch结构要强大得多,when结构中可以使用任何对象。

Kotlin中并不区分受检异常和未受检异常,不用指定函数抛出的异常,而且可以处理也可以不处理异常。此外,Kotlin中try-catch是个表达式,是有值的,try或者catch中的最后一个表达式的结果就是try-catch整个表达式的结果。

3. 函数

Java中函数没有默认参数值的概念,当你从Java中调用Kotlin函数的时候,必须显式地指定所有参数值。@JvmOverloads注解可以让编译器帮我们自动生成一系列的Java重载函数。

在Kotlin中函数可以作为顶层对象来定义,也就是说不用像Java中把通用的方法都放到某个Util的工具类中。当对应的Kotlin文件编译的时候,会生成一些类,因为JVM只能执行类中的代码。除了顶层函数,属性也可以放到文件的顶层定义。

Kotlin的扩展函数就是静态函数的高效语法糖,可以使用更具体的类型来作为接收者类型,而不是一个类。从Java中调用扩展函数的方式是调用这个静态函数,并把接收者对象作为第一个参数传进去。扩展函数和顶层函数一样,包含这个函数的的Java类的名称是由这个函数声明所在的文件名称决定的。

扩展函数是不能被子类重写的,因为扩展函数并不是类的一部分,它是声明在类之外的。尽管可以给基类和子类都分别定义一个同名的扩展函数,当这个函数被调用的时候,它会用到哪一个呢?这里,它是由该变量的静态类型决定的,而不是这个变量的运行时类型。

下面代码是扩展函数的例子,左侧的String是接收者类型,右侧this是接收者对象。

package strings
fun String.lastChar(): Char = this.get(this.lenght - 1)

当你导入了不同的包,包中有些重名的函数的时候,唯一的处理重名冲突的办法就是使用关键词as对导入的类或者函数进行重命名。

import strings.lastChar as last

在Kotlin中,可以使用扩展函数toRegex将字符串转换为正则表达式,库函数中还有一些可以用来获取在给定分隔符第一次或者最后一次出现之前或者之后的子字符串的函数,例如substringBeforeLast和substringAfterLast。

4. 类、对象和接口

在Kotlin中,类和方法默认是finalpublic的。注意,如果你重写了一个基类或者接口的成员,重写了的成员默认是open的,如果你想要阻止子类重写,可以显式声明为final的。

Kotlin也是使用abstract关键词来声明抽象类和抽象函数,此外,抽象类中的抽象成员始终是open的,其他的非抽象成员并不是默认open的,可以标注为open或者非open

类中访问修饰符的意义
(1)final:不能被重写;类中成员默认是final的
(2)open:可以被重写;需要明确地声明
(3)abstract:必须被重写;只能在抽象类中使用,抽象成员不能有实现
(4)override:重写父类或者接口中的成员;如果没有使用final声明,重写的成员默认是open的

Java默认的可见性是包私有,Kotlin中并没有使用,但是它提供了internal,表示只在模块内部可见。类的扩展函数不能访问类的privateprotected成员。

Kotlin中可见性修饰符
(1)public:所有地方可见
(2)internal:模块中可见
(3)protected:子类中可见
(4)private:类中可见

Kotlin中默认嵌套类(class A)等价于Java中的静态嵌套类(static class A),而内部类(inner class A)等价于Java中的内部类(class A)。

密封类(sealed类)是用来定义受限的类继承结构,在一些场景例如when结构中就可以知道所有可能的类情况,这样就不用添加多余的else分支了。

sealed类使用方式如下,密封类不能在类外部拥有子类,所有子类都以嵌套类的方式定义在密封类中。

sealed class Expr {
    class Num(val value:Int): Expr()
    class Sum(val left:Int, val right:Int): Expr()
}

如果类没有主构造方法,那么每个从构造方法必须初始化基类或者委托给另一个这样做了的构造方法。

在Kotlin中,==运算符是比较两个对象的默认方式,本质上就是通过调用equals来比较两个对象的值。因此,如果equals方法在你的类中被重写了,你就能够很安全地使用==来比较实例。如果想要实现Java中对象的引用的比较,那么就要使用===运算符。

Kotlin默认会帮data类自动生成toString、equals和hashCode方法,另外还有一个copy方法,用于方便地创建实例的副本,副本有着单独的生命周期并且不会影响代码中引用原始实例的位置。

Kotlin中的object关键词
核心理念:定义一个类并同时创建一个实例,也就是一个对象

主要场景:
(1)对象声明中使用object关键词是定义单例的一种方式,它和类的声明基本一致,就是不允许声明构造方法

object CustomComparator: Comparator<Person> {
    //code
}

//kotlin
persons.sortedWith(CustomComparator)
//java
persons.sortedWith(CustomComparator.INSTANCE)

(2)伴生对象可以持有工厂方法和其他与这个类相关,但在调用时并不依赖类实例的方法,它们的成员可以通过类名来访问。Kotlin中类的伴生对象会被编译成常规对象:类中的一个引用了它的实例的静态字段。如果伴生对象没有命名的话,在Java代码中可以通过Companion引用来访问。伴生对象可以扩展,这样的话就可以方便定义一些可以直接通过类名访问的静态方法。

class User {
    companion object {
        fun newWithName(name:String) {
            //code
        }
        fun newWithEmail(email:String) {
            //code
        }
    }
}

//kotlin 扩展伴生对象
fun User.Companion.newWithAccount(account:String) {
    //code
}

//java
User.Companion.newWithName("name");

(3)对象表达式用来替代Java的匿名内部类:对象表达式每次执行都会创建一个新的对象实例,和Java的匿名内部类一样,它可以访问创建它的函数中的变量。但是与Java不同的话,访问不仅没有被限制在final变量,它甚至可以在对象表达式中修改变量的值。

var clickCount = 0

window.addMouseListener(object: MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        clickCount ++
        //code
    }
})

5. Lambda

Lambda:作为函数参数的代码块

Lambda表达式语法:始终用花括号包围,实参没有用括号括起来,箭头将实参列表和lambda的函数体分开来。

//形如
{ x:Int, y:Int -> x + y }

val persons = listOf(Person("Alice", 20), Person("Bob", 30))
persons.maxBy { it.age }
persons.maxBy(Person::age)

你可以把Lambda表达式赋值给一个变量,把这个变量当做普通函数对待,还可以直接调用Lambda表达式。

Lambda表达式简化规则
(1)如果Lambda表达式是函数调用的最后一个实参,那么它可以放到括号的外边。
(2)当Lambda表达式是函数唯一的实参的时候,函数调用的括号可以去掉。
(3)如果Lambda表达式的参数类型可以被推导出来,那么就不需要显式地指定类型。一般情况下都能推导出来,所以可以先不声明类型,等编译器报错后再来指定类型也可以。
(4)如果当前上下文期望的是只有一个参数的Lambda且这个参数的类型可以推断出来,就可以使用默认参数名称it代替命名参数。

Lambda表达式内能够访问这个函数的参数,以及在Lambda之前定义的局部变量。在Java中匿名内部类只允许访问final变量,如果你想要访问可变变量的时候,你可以使用下面两种技巧:1.要么声明一个单元素的数组,其中存储可变值;2.要么创建一个包装类的实例,其中存储要改变的值的引用。而在Kotlin中不限制在final变量,并且可以在Lambda表达式内修改变量的值,其实现原理类似Java中的第二种方式。

成员引用:如果你想要当做函数参数传递的代码已经被定义成了函数,那么你可以把这个函数转换成一个值而直接传递给函数。成员引用提供了创建一个调用单个方法或者访问单个属性的函数值,双冒号把类名和你要引用的成员名称隔开。

```java

top Created with Sketch.