Kotlin学习笔记
Kotlin优势
- 更安全,避免NPE(NullPointerException),强制判断再使用
- 更多的语言特性(如java8新特性,类扩展等等),使用代码更简洁
- 与Java互调,能使用Java所有的工具库(原因:Kotlin编译为JVM上运行的字节码,与java的原生字节码基本一致,部份情况下,性能更强)
Kotlin基础类型
Kotlin是强类型语言,即确定类型后,就不能再修改其类型。JavaScript就是弱类型语言
类型
抛弃了Java的基本类型,都是引用类型:
- 整数:Byte-1, Short-2, Int-4, Long-8
- 浮点型:Float-4, Double-8
- 字符型:Char,’a’
- Boolean类型:Boolean
- 字符串:String,”abc”
- 字符串模板:”图书价格是: ${bookPrice }”
- 类型别名:typealias 类型别名=已有类型
变量定义
语法:var|val 变量名[:类型] [= 初始值]
1 | // 读写变量 |
val表示只读变量,不同的变量类型,其初始值有一定的区别:
- 局部变量:只要在第一次使用之前初始化值就行
- 类属性:可以声明时,或构造函数里初始化
1 | class MainActivity : AppCompatActivity() { |
Null安全
通过如下语法保证Null安全:
- 类属性没有默认值,强制设置初始值
- var a:String 不支持null值
- var a:String? 支持null值,但不能直接调用其方法与属性
1 | class MainActivity : AppCompatActivity() { |
可空变量使用姿式:
- 先判空再使用
1
2
3if(a != null){
Log.d("liuyang", "${ a.length }")
} - 安全调用
1
Log.d("liuyang", "${ a?.length }") // 当a==null时,返回null
- Elvis运算,对安全调用的补充,允许修改为null时的返回值
1
Log.d("liuyang", "${ a?.length ?: "" }")
- 强制调用,可能引发NPE异常 — 不推荐
1
Log.d("liuyang", "${ a!!.length }")
Kotlin运算符
两个关键点:
- 运算符通过方法实现,即运算符都是编译时使用,编译后都是方法
- 支持运算符重载
1 | class Animal(name: String, age: Int) { |
常用运算符对应表
运算符 | 对应方法 |
---|---|
a - b | a.minus(b) |
a + b | a.plus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a[i] | a.get(i) |
a[i]=b | a.set(i, b) |
a == b | a?.equals(b) ?: (b === null) |
a != b | !(a?.equals(b) ?: (b === null))) |
a in b | b.contains(a) |
a !in b | !b.minus(a) |
三个等号===
=== 三个等号的意思,则比较的是内存地址,如下:
1 | var a = "字符串" |
但对于Int类型的变量有差异,有兴趣的同学可以进一步再了解
Kotlin流程控制
关键点:
- 没有三目运算符,if表达式支持返回值
1
2
3
4
5// 不支持三目运算符
// min = (a > b) ? a : b
// 通过if替换
min = if (a > b) a else b - when 替换 switch
1
2
3
4
5
6
7
8var score = 'B'
when (score){
'A' -> println("优秀")
'B' -> println("良好")
'C' -> println("中")
'D' -> println("及格")
else -> println("不及格")
} - for的语法:for (常量名 in 对象) {}
1
2
3
4
5
6
7
8
9
10
11
12
13// 遍历1-5
for(i in 1..5){
println(i)
}
var list: Array<String> = arrayOf("a", "b", "c");
for(key in list){
println("${key}");
}
//下面的写法不支持
//for(int i=0; i<list.length; i++){
// println("${list[i]}");
//}
Kotlin的数组和集合
Array,Set,List
- 创建对象
- xxxOf(参数) — 长度固定
- mutableXXXOf(参数) — 长度可变(Array除外)
- 常用功能
- 长度:xxx.size属性
- 包含:”java” in xxx 对应方法:xxx.contains(“java”)
- …
- 遍历
- 遍历值:for (book in books) { }
- 遍历下标:for (i in books.indices) {}
- 遍历下标与值:for ( (index, value) in books.withindex() ) { }
1 | // 创建数组 |
Map
- 创建对象
- mapOf(”Java” to 86, “xxx” to xx) — 长度固定
- mutableMapOf(”Java” to 86, “xxx” to xx) — 长度可变
- 常用功能
- 长度:map.size属性
- 包含:key in map 对应方法:map.contains(key)
- 遍历
- 遍历Entry:for (en in map.entries) { en.key en.value}
- 解构遍历key,value:for ( (key, value) in map) {}
- 遍历key:for (key in map.keys ) {}
1 | // 创建Map |
Kotlin的函数或方法
关键点:
- 独立存在称为函数(function),存在类里的称为方法(method)
- 语法:
1
2
3
4
5
6
7fun 函数名(参数名 : 参数类型)[:返回值类型]{
// 函数体
}
注意:
1. 无法返回值:省略 或 :Unit(相当于Java的void)
2. 参数:支持命名参数,默认值,可变参数(vararg) - 函数可当变量的类型:var myfun : (Int , Int) -> Int = ::pow
- 函数在字节码里,通过类来实现
- 匿名函数的语法:fun(参数名 : 参数类型)[:返回值类型]{ }
- 内联函数:
- 语法:inline fun 函数名(参数名:参数类型)[:返回值类型]{ }
- 意义:提升代码量很少,但调用很频繁的函数开销
- 原理:增加代码来减少函数调用的时间开销,适用于代码量非常少的函数,如单表达式
1 | // 函数 |
Lambda表达式
关键点
- 语法:
1
2
3
4
5
6{ 参数名 : 参数类型 -> 函数体 }
注意:
1. 最后一行默认return
2. 如果只有一个参数,可以省略参数,使用it代替
3. 显示添加return语句,不是返回其本身,而是返回其所在的函数 - 意义:
- 简化局部函数
- 简化函数式接口(函数式接口:只包含一个抽象方法的接口)
- 使用注意点:
- 方法的参数是函数或函数式接口时:
- 只一个参数:可省略括号
- 最后一参数时:Lambda表达式可写在圆括号外面
- 与局部函数或匿名内部类一样,可以访问所在函数的局部变量 — 注意:是变量的副本
- 支持解构,括号里的参数表示是解析的变量,如下两种写法:
- map.mapValues { entry ->”${entry.key}-${entry.value}!”} // 正常参数
- map.mapValues { (key, value) -> ”${key}-${value}!” } // 解构
- 方法的参数是函数或函数式接口时:
1 | class MainActivity : AppCompatActivity() { |
Kotlin的面向对象:类
构造器
关键点:
- 分为主构造器与次构造器,互为重载方法,其语法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31// 主构造器,可以省略constructor
class Animal(name: String, age: Int) {
var name: String
var age: Int
// 初始化块,相当于主构造器的函数体,注意:可以有多个初始化块
init {
this.name = name
this.age = age;
}
// 次构造器,必须调用主构造器
constructor() :this("", 0) {
}
// 次构造器,必须调用主构造器
constructor(name: String): this(name, 0) {
}
// 次构造器,必须调用主构造器
constructor(age: Int): this("", age) {
}
operator fun plus(other: Animal): Animal{
this.name = this.name + other.name
this.age = this.age + other.age
return this;
}
} - 主构造器的参数使用var|val修饰时,即表示形参,也表示类的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 主构造器,可以省略constructor
class Animal(var name: String, var age: Int) {
// var name: String
// var age: Int
//
// init {
// this.name = name
// this.age = age;
// }
operator fun plus(other: Animal): Animal{
this.name = this.name + other.name
this.age = this.age + other.age
return this;
}
} - 意义:把构造器中相同的逻辑放在初始化块中,个性化的放在次构造器中
属性
关键点:
- 语法与变量一样
- 对属性,默认生成getter,setter方法(val属性只提供getter),编译为字节码后生成如下成员:
- backing field: xxx属性
- getter方法: getXXX()
- setter方法: setXXX()
- getter,setter方法可重载,在重载方法里,通过field访问backing field(为防止死循环)
- 注意:
- val属性会出现两种情况:— 判断依据:getter方法里没有调用field,就是计算属性
- 只有getter方法 — 称为计算属性
- backing field 和 getter方法 — 只读属性
- private属性,默认不会生成getter, setter方法,但如果重写getter,setter方法后,会生成
- val属性会出现两种情况:— 判断依据:getter方法里没有调用field,就是计算属性
- 意义:很方便实现数据监听机制,类型Vue的MVVM框架
1 | // 只读属性 |
类的方法
方法与函数基本一致,略
对象
关键点:
- 创建对象省略new关键字,如:var animal: Animal = Animal()
- 访问属性,本质是调用getter,setter方法:
1
2
3var animal: Animal = Animal()
var name: String = animal.name // 实现是调用animal.getName()
animal.age = 3 // 实际是调用animal.setAge(3) - 支持解构:(解构:相当于一个运算符,通过operator重载)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33// Animal通过重载,支持解构
class Animal constructor(name: String, age: Int) {
var name: String
var age: Int
init {
this.name = name
this.age = age;
}
// 重载解构
operator fun component1(): String{
return this.name
}
// 重载解构
operator fun component2(): Int{
return this.age
}
operator fun plus(other: Animal): Animal{
this.name = this.name + other.name
this.age = this.age + other.age
return this;
}
}
// 使用场景1
var animal: Animal = Animal("", 1)
var (name, age) = animal
// 使用场景2
var list:List<Animal> = mutableListOf();
for((name, age) in list){
printlin("${name}-${age}")
} - 数据类:
- 语法:data class XXX()
- 意义:用于替换Java的Bean,自动提供解构方法,使用很方便
1
2
3
4
5
6// 通过数据类定义Animal
data class Animal(val name: String, val age: Int)
// 自动支持解构
var animal:Animal = Animal("", 1)
var (name, age) = animal
- import支持起别名
- 意义:方便包名不同,名称相同的类的使用
- 语法:import xxx as 别名
权限
关键点:
- 类,方法,属性默认情况下:final public,通过open修饰后,才能被继承与重写
- 注意:在Kotlin里,final表示的含义与java有区别,只表示不能继承与重写,不表示只读,只读与常量的写法:
- 只读:val 变量名 —》java里的final 变量名
- 常量:const val 变量名 —》java里的static final 变量名 //同时只能定义在top-level,属于文件,不属于类
1 | // 定义常量,需处于top-level |
继承与多态
关键点:
- 继承:
- 语法:class SubClass : Superclass {}
- 顶级父类是Any,不是Object,区别:方法较少
- 构造器的执行顺序:
- 父类的主构造器(即初始化块)
- 父类的次构造器(前题是子类调了相应的次构造器)
- 子类的主构造器(即初始化块)
- 子类的次构造器
- 重写(方法和属性)
- 方法重写:override fun xxx() {}
- 属性重写:override var xxx: String = “图片”
- 使用:
- 类型判断:is 或 !is —>java里的instanceOf
- 强制转换:
- xxx as 类:强制转换,可能崩溃
- xxx as? 类:安全的强制转换,转换失败返回null
1 | open class Animal constructor(name: String, age: Int) { |
类的扩展
关键点:
- 语法:
- 方法扩展:fun Raw.info() { }
- 属性扩展:var Raw.fullName: String get(){} – 注意:由于只是添加了getter,setter方法,没有backing field,所以只能是计算属性
- 扩展的意义:
- 扩展可动态地为己有的类添加方法或属性,方式不在限定于继承或动态代理来实现
- 扩展能以更好的形式组织一些工具方法,更好的面向对象的代码风格,如Collections.sort(),应该是list.sort()
- 扩展的实现机制:Java是静态语言,类定义后,不支持扩展,kotlin的扩展不是真正的修改类,而是创建了一个函数,通过编译时,进行替换为调用对应的函数实现
1 | var list: List<Int> = mutableListOf(5, 4, 2, 1) |
抽象与接口
关键点:
- 抽象类:通过abstract修饰的类
- 接口:
- 语法:interface修饰,没有构造器与初始化块
- 方法:除了抽象方法外,还可包含有非抽象方法
- 属性:没有backing field,无法保存数据,默认都是抽象属性,但通过提供getter方法,可以改为非抽象属性
1 | interface Action { |
对象表达式 && 对象声明 && 伴生对象
关键点:
- 对象表达式:
- 作用:用于创建匿名内部类(区别在于:可以实现多个接口)
- 语法:object[: 0~N 个父类型] { //对象表达式的类体部分 }
- 注意:接口是函数式接口时,可以使用Lambda表达式,进一步简写,不一定要用对象表达式
- 对象声明:
- 作用:用于创建单例,无法再创建新的对象
- 语法:object ObjectName[: 0咽个父类型]{ } ObjectName是单例的名称
- 伴生对象
- 作用:用于实现Java里的静态成员,Kotlin为了保证面向对象的纯度,通过对象来实现静态成员的能力
- 语法:在类中定义的对象声明,可使用 companion修饰,这样该对象就变成了伴生对象
- 注意:
- 一个类只能定义一个伴生对象
- 伴生对象的对象名称可以省略
1 | var btn: Button = findViewById(R.id.btn) |
类委托 && 属性委托
关键点:
- 类委托
- 用处:让多个对象共享同一个委托对象,代理模式的应用,继承的一种替代,让本类需要实现的部分方法委托给其他对象
- 语法:接口 by 对象
- 属性委托
- 用处:多个类的类似属性统一交给委托对象集中实现
- 语法:var属性名:属性类型 by 对象
1 | interface Outputable { |
Kotlin的异常处理
关键点:
- 与java的区别:Kotlin抛弃了checked异常,所有异常都是runtime异常,可捕获也可不捕获
- finally块里的return语句会导致try catch里的return语句失效
Kotlin的泛型
关键点:
- 泛型的语法与Java的类似:open class Apple
{ } - 型变:
- 用处:当实际类型是泛型的子类时,Kotlin使用型变替换了Java的通配符
- 分为:声明处型变,类型投影等等,就不详细介绍了,网上有很多资源
与Java的泛型的对比
1 | // Java的基本泛型 |
Kotlin的注解
流程与Java类似,三个过程:
- 注解定义:annotation class Test(val name: String)
- 注解使用:@Test(name=“xx”) class MyClass{ } — 注意:属性名为value时,可以省略属性名value
- 读取注解:val anArr = Test: :info .annotations
修饰注解:元注解
- @Retention:注解的保留时间,SOURCE,BINARY,RUNTIME
- @Target:修饰哪些程序单元,即范围。CLASS,FUNCTION,FIELD
- @MustBeDocumented:此注解将会被提取到Api文档里
- @Repeatable:可重复注解
Kotlin与Java互调的注意点
看如下例子:
1 | // java类 |
调用方法的正确姿势:(应该养成如下编译习惯)
- 了解方法的返回值文档,了解其是否会返回null — 不管是调用Java方法还是Kotlin方法
- 变量指定类型,而不是使用推断类型(推断有时不是那么的智能)
参考
感谢您的阅读,本文由 刘阳 版权所有。如若转载,请注明出处:刘阳(https://handsomeliuyang.github.io/2019/05/07/%E6%97%A5%E5%B8%B8%E5%AD%A6%E4%B9%A0-Kotlin%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/)