class关键字定义类。

1
class Car
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
定义类
[修饰符] class 类名 [constructor 主构造器] {
零到多个次构造器
零到多个属性
领导多个方法

}
修饰符
public | internal | private
final | open | abstract
属性(Kotlin) = 字段(Java) + getter + setter
[修饰符] var | val 属性名 : 类型 [= 默认值]
[<getter>]
[<setter>]
修饰符
public | protected | internal | private
final | open | abstract
1
2
3
4
5
6
7
8
9
10
11
12
13
14
1. 主构造器 参数 加 var | val 成为属性
2. 对于属性 使用public protected internal 和不使用效果一样
顶级函数不能用 protected abstract final 修饰
3. 如果对属性使用private 则该属性作为幕后属性使用
4. 关于final 和open
可以修饰类 属性 方法 表示其不可改变
为非抽象类,非抽象方法,非抽象属性自动添加final
final,open不能修饰局部变量
5. 编译时常量 用 const val 定义
6. 关于internal
- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集(例外是 `test` 源集可以访问 `main` 的 internal 声明);
- 一次 `` Ant 任务执行所编译的一套文件。

构造函数

构造器也是方法 可以设置默认值

主构造函数(Primary constructors)

自定义构造器

1
class Car(val wheels: List<Wheel>)

次构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class User(id: Int){

var id: Int
var name: String

init {
this.id = id
println("1")
}

init {
this.name = ""
println("2")
}


constructor(id: Int,name: String):this(id){//次构造函数
this.id = id
this.name = name
}
}

次构造函数必须要调用主构造函数,使用this(参数列表),注意次构造函数和主构造函数的参数列表不能一样,不然没意义。init{...}初始块用于初始化,它可以有多个,按照代码顺序来执行。

构造函数的默认参数

调用父类构造函数

  1. 直接主构造函数调用父类构造函数
  2. 调用主构造函数,来调用父类构造函数
  3. 次构造函数,初始化参数列表使用super关键字调用。

私有化构造函数

属性

属性={ 成员变量 | 成员常量 }  ,且该属性默认public

1
2
3
class Car {
val wheels = listOf<Wheel>()
}

类的成员函数

可以提供自定义的 getter 和 setter。

重写

override

lateinit

看名字的意思就是“延迟初始化”,很好,它就是延迟初始化,没什么好说的。

注意,8种基本类型(无论非空类型还是可空类型)都不能使用 lateinit ,需要直接进行初始化。

lateinit限制规则

  1. lateinit只能修饰var修饰的属性
  2. lateinit修饰的属性不能有自定义的getter 或者setter方法 (怎么办?)
  3. lateinit修饰的属性必须是非空类型
  4. lateinit修饰的属性不能是原生类型(java的8种基本类型对应的类型)
    另外Kotlin不会为属性执行默认初始化 “lateinit property name has not been initalized”

如何判断是否初始化?

1
2
3
4
5
6
7
8
9
10
11
class User{
lateinit var name:String//name交给我自己管理,jvm
var age:Int = 0
}

fun main(){
var user = User()
if(user.name!=){ // UninitializedPropertyAccessException: lateinit property name has not been initialized
println(user.name) //这是错误的,
}
}

如代码注释所示,会出现访问未初始化属性的异常,说明用比较null并不能判断是否初始化。如何解决呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class User2{
lateinit var name:String//name交给我自己管理,jvm
var age:Int = 0

fun isNameInit(): Boolean{
return ::name.isInitialized // name.isInitialized
}
}

val user = User2()

if(/**/user.isNameInit()/*user::name.isInitialized*/){//是不是初始化这个状态,name自己的
println(user.name)
}

至于为什么不能用user::name.isInitialized,那是因为幕后字段(backing field)并不能被外部访问。相关概率后面后提及。

getter和setter 访问器与修改器

在java与kotlin中,字段和属性是有区别的。java中的成员变量(常量),我们通常叫做字段。kotlin中我们叫做属性。

kotlin中定义一个普通属性时,会为该属性生成一个field,getter和setter,其中这个field被称为幕后字段(backing filed),getter和setter,我姑且管它们叫做访问器和修改器。这个概率我从javascript引入的。

幕后字段不能提供给外部使用。

何时会产生幕后字段呢?

还有一个概念,幕后属性:private修饰的属性。

幕后字段特点

  1. 如果Kotlin类的属性有幕后字段,则要求为该属性显式指定初始值
    1.1 要么在定义时指定
    1.2 要么在构造器中指定
    2.反之,如果该属性没有幕后自段,则不允许指定初始值(很明显,没field,即使指定了也没地方保存)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1. val 和var 的区别
把kotlin代码通过
Tools -> Kotlin -> Show Kotlin ByteCode
-> Decompile成Java代码
查看它们的区别

2. 属性与字段,幕后字段的区别
class Student{
var name
var age
var sex
//重写三个构造函数,必须写一个主构造函数
//重新getter setter 然后查看编译出来的Java代码 体会它们的区别
}
Tools -> Kotlin -> Show Kotlin ByteCode
-> Decompile成Java代码
查看它们的区别

3. 定义一个Person类,然后让Student继承自Person

4. lateinit的作用与限制规则 和 by lazy的区别

5. 非空类型 与可空类型的区别
思考 可以把 Student? 赋值给Student的变量吗?

数据类 (Data classes)

定义一个类:

1
data class user(var id:Int,var name:String,var age:Int);

将其反汇编成java代码如下:

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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
package com.lautung.kotlin_test;

import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\b\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0011\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B\u001d\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005\u0012\u0006\u0010\u0006\u001a\u00020\u0003¢\u0006\u0002\u0010\u0007J\t\u0010\u0012\u001a\u00020\u0003HÆ\u0003J\t\u0010\u0013\u001a\u00020\u0005HÆ\u0003J\t\u0010\u0014\u001a\u00020\u0003HÆ\u0003J'\u0010\u0015\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u00052\b\b\u0002\u0010\u0006\u001a\u00020\u0003HÆ\u0001J\u0013\u0010\u0016\u001a\u00020\u00172\b\u0010\u0018\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0019\u001a\u00020\u0003HÖ\u0001J\t\u0010\u001a\u001a\u00020\u0005HÖ\u0001R\u001a\u0010\u0006\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\b\u0010\t\"\u0004\b\n\u0010\u000bR\u001a\u0010\u0002\u001a\u00020\u0003X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\f\u0010\t\"\u0004\b\r\u0010\u000bR\u001a\u0010\u0004\u001a\u00020\u0005X\u0086\u000e¢\u0006\u000e\n\u0000\u001a\u0004\b\u000e\u0010\u000f\"\u0004\b\u0010\u0010\u0011¨\u0006\u001b"},
d2 = {"Lcom/lautung/kotlin_test/user;", "", "id", "", "name", "", "age", "(ILjava/lang/String;I)V", "getAge", "()I", "setAge", "(I)V", "getId", "setId", "getName", "()Ljava/lang/String;", "setName", "(Ljava/lang/String;)V", "component1", "component2", "component3", "copy", "equals", "", "other", "hashCode", "toString", "Kotlin_test.app"}
)
public final class user {
private int id;
@NotNull
private String name;
private int age;

public final int getId() {
return this.id;
}

public final void setId(int var1) {
this.id = var1;
}

@NotNull
public final String getName() {
return this.name;
}

public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}

public final int getAge() {
return this.age;
}

public final void setAge(int var1) {
this.age = var1;
}

public user(int id, @NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.id = id;
this.name = name;
this.age = age;
}

public final int component1() {
return this.id;
}

@NotNull
public final String component2() {
return this.name;
}

public final int component3() {
return this.age;
}

@NotNull
public final user copy(int id, @NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new user(id, name, age);
}

// $FF: synthetic method
public static user copy$default(user var0, int var1, String var2, int var3, int var4, Object var5) {
if ((var4 & 1) != 0) {
var1 = var0.id;
}

if ((var4 & 2) != 0) {
var2 = var0.name;
}

if ((var4 & 4) != 0) {
var3 = var0.age;
}

return var0.copy(var1, var2, var3);
}

@NotNull
public String toString() {
return "user(id=" + this.id + ", name=" + this.name + ", age=" + this.age + ")";
}

public int hashCode() {
int var10000 = this.id * 31;
String var10001 = this.name;
return (var10000 + (var10001 != null ? var10001.hashCode() : 0)) * 31 + this.age;
}

public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof user) {
user var2 = (user)var1;
if (this.id == var2.id && Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
return true;
}
}

return false;
} else {
return true;
}
}
}

除了一些熟悉的类以外还发现有很

1
2
3
4
5
6
7
8
9
10
11
12
public final int component1() {
return this.id;
}

@NotNull
public final String component2() {
return this.name;
}

public final int component3() {
return this.age;
}

这三东西叫做解构。其作用像ES6中的解构赋值,如下所示:

1
2
val (id,name,age) = User	// 解构赋值
val (id,_,age) =User //不用赋值的元素用下划线表示

类型推断(Type inference)

单例(Singletons)

继承:

1
2
3
4
class BaseActivity : AppCompatActivity(){


}

我们注意到除了冒号继承以外,AppCompatActivity还有()。实际情况就是先调用AppCompatActivity的默认构造函数。

open 关键字

koltin中的类,默认相当于java中的final。

不使用 open ,对于JVM而言就是 Java 使用了 final 修饰类,这就导致了无法继承。

重写

属性 | 方法的继承也是需要用open修饰,子类需要用override修饰属性 | 方法

多态

1
2
3
4
5
6
7
使用is检查类型,会自动转型
使用as 强制转型
as: 不安全,如果失败,引发ClassCastException
导包:
import java.util.Date
import java.sql.Date as SDate
as?: 安全,如果失败,返回null

不可变类与可变类

不可变指创建该类的实例后,该类的属性值是不可以改变的

抽象类与接口

密封类本质是抽象类
区别:体现在设计目的上

  • 接口体现的是一种规范
  • 抽象类体现的是一种模板模式设计

嵌套类

  • 相当于Java的静态内部类
  • 嵌套类不能访问外部类的任何其他成员,只能访问另外一个嵌套类

内部类

非静态内部类,需要用inner修饰

枚举类

对象表达式和对象声明

object [: 0~N个父类型]{

//对象表达式的类型部分
}

伴生对象

使用compainion修饰的对象
每个类最多只能定义一个

对象表达式和对象声明之间有一个重要的语义差别:

对象表达式是在使用他们的地方立即执行(及初始化)的;
对象声明是在第一次被访问到时延迟初始化的;
伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配

类委托(Class delegation)

类委托是代理模式的应用,可作作为继承的一种不错的替代

属性委托

对于一个可变属性(即 var 声明的),委托必须额外提供一个名为 setValue 的函数,该函数接受以下参数:
thisRef —— 同 getValue();
property —— 同 getValue();
new value —— 必须与属性同类型或者是它的子类型

委托工厂 provideDelegate

延迟属性(lazy properties): 其值只在首次访问时计算;

可观察属性(observable properties): 监听器会收到有关此属性变更的通知;

把多个属性储存在一个映射(map)中,而不是每个存在单独的字段中。

componentN方法与解构

operator fun componetN() : 返回值类型{}

“_”占位符的使用

数据类与返回多个值的函数

在Lambda表达式中解构 “(a,b)” 使用圆括号就表示使用解构

1
2
3
4
5
6
7
8
9
10
数据类 使用data修饰 还必须满足的条件
1. 主构造器至少需要一个参数
2. 主构造器的参数需要使用val|var声明为属性
3. 数据类不能使用abstract,open sealed修饰,也不能定义成内部类
4. 数据类自1.1后可以实现接口也可以继承其他类
特性
1. 生成equals()/hashCode()
2. 自动重写toString()
3. 为每个属性自动生成operator修饰的componentN()
4. 生成copy()

方法扩展

Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展 的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性

扩展是静态解析的

可空接收者

伴生对象的扩展

扩展声明为成员

关于可见性的说明

扩展的可见性与相同作用域内声明的其他实体的可见性相同。例如:
在文件顶层声明的扩展可以访问同一文件中的其他 private 顶层声明;
如果扩展是在其接收者类型外部声明的,那么该扩展不能访问接收者的 private 成员。