Kotlin学习笔记

最近在学NDK相关的知识,本来Kotlin的学习,是被计划在后面的,因为暂时项目中也没有用到,担心学了估计很快就忘记。在某次写小demo代码做测试的时候,就在想,都是简单的语法,如果拿来做Kotlin的练手也是蛮不错的。
另外就是,现在Google官网上的代码实例,都贴上Kotlin实例了,github上新的开源框架,也很多用Kotlin,看来google是认真的了,Kotlin必学不可了。
刚好遇到国庆,除去出去玩还剩2、3天在家,那就立马把Kotlin学起来

[TOC]

学习流程

有了Java的基础,所以,主要就是学Kotlin的一些语法,明白和Java之间的差异,并通过一个完整的Android实践项目熟悉Kotlin在Android上的应用。

参考书籍

  • 《Kotlin官方参考文档》

    Kotlin官方的文档,比较详细介绍了Kotlin的各种语法,不过我只是把开始章节的基础语法、习惯用法、编码规范学好,接下来的章节,挑着看,比如对象的某个章节就详细看了一下。

  • 《Kotlin实战》

    在查某个知识点看到的书籍,介绍得很详细,入门比官方文档好,可以结合查阅。

  • 《kotlin-for-android-developers》

    介绍怎样用Kotlin编写一个Android简单项目,基本把各个知识点都介绍一篇,有点像N年前入门学Android看郭神的第一行代码的感觉。接下来的学习,主要是靠这本书,结合源码以及上面的两本参考书,外加google搜索不理解的知识点。

Kotlin基础语法

注释

1
2
3
4
// 这是一个行注释

/* 这是一个多行的
块注释。 */

Kotlin添加注释,为什么不会自动生成一些参数

  • AndroidStudio Kotlin注释生成问题

    看官方的说明,就是禁止用Java那种定义参数,然后后面补充说明的注释,而是将参数结合到文档的过程中结合上下文描述来说明参数的作用

  • 创建类模板和方法模板

    官方不建议使用,如果还是想创建文件自动生成模板注释,或者想和Java一样生成方法模板,可以参考该文件。

变量

var:可变变量
val:不可变变量,尽可能地使用不可变变量

  • Kotlin 中 var、val、const 关键字解析

    总结

    • varval 声明的变量分为三种类型:顶层属性、类属性和局部变量;
    • var 属性可以生成 getter 和 setter,是可变的(mutable),val 属性只有 getter,是只读的(read-only,注意不是 immutable);
    • 局部变量只是一个普通变量,不会有 getter 和 setter,它的 val 等价于 Java 的 final,在编译时做检查。
    • const 只能修饰没有自定义 getter 的 val 属性,而且它的值必须在编译时确定。

基本数据类型

判断两个数值是否相等(==),判断两个数值在内存中的地址是否相等(===

注意点
  • 数字类型中不会自动转型
  • 字符(Char)不能直接作为一个数字来处理
  • 声明一个变量可以不写明具体的类型,会自动推断。

字符串模板

模板表达式以美元符( $ )开头,由一个名字构成或者用花括号括起来的任意表达式

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {
var a = 1
val s1 = "a is $a"
a = 2
val s2 = "${s1.replace("is", "was")}, but now is $a"
println(s2)
}

// output
a was 1, but now is 2

可空性

Kotlin和Java的类型系统之间,最重要的一条区别就是,Kotlin对可空类型的显式支持。如果不做特别声明,在Kotlin中,该对象是不允许为null的,即默认都是非空的。如果想让对象可能为空,需要在类型名称后面加上?来标记它。

1
var 变量名:变量类型? = null

声明一个变量,可能为空,那么,在使用的时候,就要进行空判断了,在Java中,经常要写if xxx != null的判断,非常麻烦,尤其是多层调用,使用起来特别繁琐。
在Kotlin中,就可以用安全调用运算符?.,不为空则调用,空则返回null

1
2
3
4
5
6
7
val a : String? = a?.length()
// 等同于Java中的
if(a == null){
return null;
}else {
return a.length();
}

为空的话,默认返回null,如果想代替null的默认值,可以使用Elvis运算符?:

1
2
3
4
5
6
7
val a : String? = a?.length()?:0
// 等同于Java中的
if(a == null){
return 0;
}else {
return a.length();
}

安全转换as?

判断被转换的值是不是要转换的类型,不是则抛出异常,通常与Elvis运算符结合使用。

1
2
3
4
5
6
fun equals(o: Any?): Boolean{
val otherPerson = o as? Person ?: return false
return otherPerson.name == name
}
// 如果o为Person类型,则执行o as Person,返回Person类型的对象
// 如果o不为Person类型,则返回null,因为?:的判断,null则执行return false

非空断言!!

1
2
3
4
5
6
7
a!!
// 等同于
if(a != null){
return a;
}else{
throw new NullPointerException;
}

Unit和Nothing

用途之一

1
2
3
4
5
6
7
8
9
10
interface Processor<T> {
fun process():T
}

class NoResultProcessor : Processor<Unit> {
override fun process() {
// do something
// 这里可以不需要显式的return,编译器会隐形加上return Unit
}
}

函数

1
2
3
4
5
6
7
8
9
10
11
// 无返回值,返回类型可以深入
fun 方法名(参数名:参数类型,参数名:参数类型):返回值类型 {

}

// 实例
fun sum(a:Int,b:Int) :Int {
return a + b;
}
// 返回类型自动推断
fun sun(a:Int,b:Int) = a + b;

使用for循环

1
2
3
4
5
6
7
8
9
10
11
12
fun main(args: Array<String>) {
val items = listOf("apple", "banana", "kiwifruit")
// item为变量名,可以随便取
for (item in items) {
println(item)
}
}

// output
apple
banana
kiwifruit

When表达式

when取代了switch操作符,x不只是常量,还可以是表达式。else作用和default类似,上面都不符合则会执行else分支

1
2
3
4
5
6
7
when(x) {
1 -> print("x == 1")
2 -> print("x == 2")
else -> {
print("x is neither 1 nor 2")
}
}

默认任何类都是基础继承自 Any (与java中的 Object 类似),所有的类默认都是不可继承的(final),所以我们只能继承那些明确声明 open 或者 abstract 的类。
创建类的实例对象,不需要new关键字。

构造函数

在kotlin中,构造函数可以有一个主构造函数和多个次构造函数,主构造函数为类头的一部分,跟在类名后。

1
2
3
4
5
6
7
// name就直接被初始化了,可以直接使用了
class Person constructor(name: String){
}

// 主构造函数没有其他注解,可以省略constructor
class Person(name: String){
}
1
2
3
4
5
6
7
// java的写法,与上面的kotlin作用一样
class Person{
String name;
public Person(String name){
this.name = name;
}
}

主构造函数中不能有其他代码,如果要进行初始化,那么放在init代码块中。
执行顺序:主构造函数 —> 初始化块代码 —> 次构造函数

object 和 companion object

被companion object修饰的对象,就像Java中的静态属性或者方法一样,被这个类的所有对象所共享。

operator 运算符重载

在大多数编程语法中,我们预定了+作为算法运算符,也可以与String值一直使用。但是,算法预算符也就只是应用在基本数据类型,比如,定义一个点数据类Point,如下

1
data class Point(val x: Int, val y: Int)

如果想把两个点加在一起,需要分别取点的x、y加上另外一个点的x、y。那么,如果能直接用运算符+就好了,运算符重载就可以帮你实现。

+运算符的函数名为plus,那么,就来重载plus方法,如下代码

1
2
3
4
5
data class Point(val x: Int, val y: Int) {
operator fun plus(other: Point): Point {
return Point(x + other, y + other)
}
}
1
2
3
4
5
6
7
8
// 上面已经对Point进行重载算术运算符
val p1 = Point(10, 20)
val p2 = Point(30, 40)
println(p1 + p2)
// 可以看作为 p1.plus(p2) = Point(p1.x + p2.x, p1.x + p2.x)

// output
Point(x=40, y=60)
  • 《Kotlin实战》

    运算符重载的其他预定,包括重载二元算术运算、重载复合赋值运算、重载一元运算符。重载比较运算符的详细介绍,可以参考该书的第7章

    • Kotlin学习_运算符重载

      如果觉得看书的整一章太长,可以看该文章,就是书中的笔记,也是搜到这篇文章让我找到上面的书,才搞懂operator的作用

with

在很多语法中,经常要要同一个对象执行多次操作,如果使用了with函数,就不需要把对象的名称写出来。基本结构就是with(){}

1
2
3
4
5
6
7
8
9
10
11
12
fun alphabet(): String {
val result = StringBuilder()
for (letter in 'A'..'Z') {
result.append(letter)
}
result.append("\nNow I know the alphabet!")
return result.toString()
}
println(alphabet())
// output
ABCDEFGHIJKLMNOPQRSTUVWXYZ
Now I know the alphabet!

上面的result对象需要写很多次,使用with函数就可以减少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fun alphabet(): String {
val stringBuilder = StringBuilder()
return with(stringBuilder) { // 1 在你调用的方法中制定接收器的值
for (letter in 'A'..'Z') {
this.append(letter) // 2 通过显式的“this”调用接收器值的方法
}
append("\nNow I know the alphabet!") // 3 调用方法,忽略“this”
this.toString() // 4 从lambda中返回值
}
}

// 也可以写为
fun alphabet() = with(StringBuilder) {
for (letter in 'A'..'Z') {
append(letter)
}
append("\nNow I know the alphabet!")
toString()
}
}

使用类型检测及自动类型转换

1
2
3
4
5
6
7
8
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// `obj` 在该条件分支内自动转换成 `String`,而不像Java那样,需要再显式转换
return obj.length
}
// 在离开类型检测分支后, `obj` 仍然是 `Any` 类型
return null
}

kotlin 委托

  • kotlin 委托

    菜鸟教程的文章,分别介绍了类委托、属性委托、标准委托、可观察属性 Observable

  • kotlin委托机制

    个别地方结合Java介绍,方便理解

Kotlin for Android

修改build.gradle支持kotlin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// build.gradle文件
buildscript {
// 统一定义kotlin的版本,如果需要Anko库,也可以定义ext.anko_version = xxx
ext.kotlin_version = '1.2.50'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
// 新增kotlin插件
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
1
2
3
4
5
6
7
8
9
10
11
12
// app/build.gradle文件
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android' // 新增
apply plugin: 'kotlin-android-extensions' // 新增

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// 新增kotlin库
implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// 如果需要Anko基本库,可以添加如下
// implementation "org.jetbrains.anko:anko-common:$anko_version"
}

Anko

Anko是一个Kotlin库,可以帮助你更快、更轻松地开发Android应用程序,而且,能让代码清晰易读。
Anko主要由四部分组成,使用的话,可以引用不同的依赖库

  • Anko Commons: 最常使用的一个轻量级的库,里面包含用于Intents;
    Dialogs and toasts;
    Logging;
    Resources and dimensions;
  • Anko Layouts: 能够通过快速且安全类型去编写Android动态布局的依赖库。
  • Anko SQLite: 查询DSL(领域专用语言)和解析Android SQLite数据库
  • Anko Coroutines: 基于kotlinx.coroutines的工具类库

build.gradle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
dependencies {
// Anko Commons
implementation "org.jetbrains.anko:anko-commons:$anko_version"

// Anko Layouts
implementation "org.jetbrains.anko:anko-sdk25:$anko_version" // sdk15, sdk19, sdk21, sdk23 are also available
implementation "org.jetbrains.anko:anko-appcompat-v7:$anko_version"

// Coroutine listeners for Anko Layouts
implementation "org.jetbrains.anko:anko-sdk25-coroutines:$anko_version"
implementation "org.jetbrains.anko:anko-appcompat-v7-coroutines:$anko_version"

// Anko SQLite
implementation "org.jetbrains.anko:anko-sqlite:$anko_version"
}

参考资料

公众号:亦袁非猿

欢迎关注,交流学习