类和继承
1 | class Invoice {} |
Kotlin 中类可以有一个主构造函数以及多个二级构造函数。
主构造函数是类头的一部分:跟在类名后面,如果构造函数有注解或可见性声明,需带constructor 关键字
语法格式class 类名 可见性 注解 constructor(参数名1:类型1,参数名2:类型2.....)
1 | class Customer public constructor (name: String) {...} |
没有注解或可见性说明,则 constructor 关键字是可以省略。
初始化代码可以放在以 init 做前缀的初始化块中初始化
1 | class Person(val firstName: String, val lastName: String, var age: Int) { |
在同一个类中代理另一个构造函数使用 this 关键字
1 | class Person(val name: String) { |
与java相同,如果一个非抽象类没有声明构造函数,会产生一个没有参数的构造函数。
如果不想类有公共的构造函数,就得声明一个拥有不可见的空主构造函数。
1 | class DontCreateMe private constructor () { |
创建类的实例
Kotlin 没有 new 关键字,直接通过类名(参数1,参数2...)
进行初始化
1 | val invoice = Invoice() |
属性和字段
每个字段对应的get,set写法
Getters 和 Setters
1 | var stringRepresentation: String |
备用字段
用field标识符来表示,只允许在属性的访问器函数内使用,起到局部变量的作用。
1 | var counter = 0 // 初始化值会直接写入备用字段 |
备用属性
感觉跟备用字段挺像的
1 | private var _table: Map<String, Int>? = null |
编译时常量
满足以下条件
在顶级声明的 或者 是一个object的成员
以String或基本类型进行初始化
没有自定义getter
这种属性可以被当做注解使用 @Deprected(SUBSYSTEM_DEPRECATED)
1 | const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated" |
延迟初始化属性
希望在访问这个属性的时候,避免非空检查,用lateinit
修饰符。
这个修饰符只能够被用在类的 var 类型的可变属性定义中,不能用在构造方法中,并且属性不能有自定义的 getter 和 setter访问器,这个属性的类型必须是非空的,同样也不能为一个基本类型。
在一个lateinit的属性初始化前访问他,会导致一个特定异常。
1 | public class MyTest { |
嵌套类
普通内部类
类可以标记为 inner
这样就可以访问外部类的成员。
1 | class Outer { |
对象表达式
相当于java中的匿名内部类,不一样的是对象表达式可以访问闭合范围内的变量,但不用final修饰
语法格式调用函数(object:父类名1(参数1,参数2...),父类名2(参数1,参数2...){需要重写的变量,需重写的函数1,需重写的函数2})
1 | fun countClicks(windows: JComponent) { |
父类有构造函数,则必须传递相应的构造参数。多个父类可以用逗号隔开,跟在冒号后面:
1 | open class A(x: Int) { |
如果没有父类
1 | val adHoc = object { |
对象声明
关键字object之后指定了一个名称, 那么它就不再是“对象表达式”,而是一个对“对象声明”。感觉像是匿名内部类的另一种写法。
1 | object MyInfo: Info("submit"),IClickListener { |
跟在 object 关键字后面是对象名,和变量声明一样。
可以继承父类
1 | object DefaultListener : MouseAdapter() { |
伴随对象
用 companion 关键字标记对象声明,跟java不一样,Kotlin中并不支持静态方法,调用方式有两种,一种是类名.伴随对象.XX
,另外一种方式是类名.xx
。
1 | class Books(var name: String, val page: Int) { |
对象表达式,对象声明,伴随对象区别:
- 对象表达式在使用的地方被立即执行。
- 对象声明是延迟加载的, 在第一次使用的时候被初始化。
- 伴生对象所在的类被加载,伴生对象被初始化,与Java静态成员一样。
继承
Kotlin 中所有的类都有共同的父类 Any ,类似java中都继承Object,它是一个没有父类声明的类的默认父类。
声明一个明确的父类,继承类的时候带了构造参数,继承接口的时候不带。
1 | open class Base(p: Int) |
默认情形下,kotlin 中所有的类都是 final,如果类有主构造函数,则基类必须在主构造函数中使用参数立即初始化。如果类没有主构造函数,则必须在每一个构造函数中用super
关键字初始化基类。
1 | class MyView : View { |
可见性修饰词
- private,protected,internal,以及 public 。默认的修饰符是 public。
- 与java不同的是,跟是否可重写没有关系。
- private 只在该类中可见
- protected 和 private 一样但在子类中也可见
- internal 在本模块的所有可以访问到声明区域可见
- public 任何地方可见
- 外部类不可以访问内部类的 private 成员。
- 如果你复写了一个protected成员并且没有指定可见性,那么该复写的成员具有protected可见性。
接口
都可以包含抽象方法,以及方法的实现,java中接口的方法都是抽象的,显得更强大了。和抽象类不同的是,接口不能保存状态。可以有属性但必须是val的,或者提供访问器的实现。接口属性不可以有后备字段。
接口用关键字 interface 来定义:
1 | interface MyInterface { |
一个类或对象可以实现一个或多个接口
1 | class Child : MyInterface { |
复写
复写方法
kotlin 需要把可以复写的成员都明确注解出来,类名,函数名前面加open
或者是abstract
,复写时加override
,java是不需要单独声明的。
1 | open class Base { |
复写属性
与方法类似,var属性覆盖一个val属性,但反之则不允许
1 | interface Foo { |
C 继承 a() 或 b() 的实现没有任何问题,因为它们都只有一个实现。但是 f() 有俩个实现,因此我们在 C 中必须复写 f() 并且提供自己的实现来消除歧义。
1 | open class A { |
抽象类
一个类或一些成员可能被声明成 abstract
。抽象方法默认带open
,在它的类中没有实现方法。可以用一个抽象成员去复写一个带open
的非抽象方法。如果函数没有函数体,那么默认是抽象的。
1 | open class Base { |
扩展
Kotlin 提供了一种,可以在不继承父类,也不使用类似装饰器设计模式的情况下对指定类进行扩展。 支持函数扩展和属性扩展。
函数扩展
fun 类名.扩展函数名(入参)
,为 MutableList<Int>
类添加一个 swap 函数:
1 | fun MutableList<Int>.swap(x: Int, y: Int) { |
扩展实际上并没有修改它所扩展的类。定义一个扩展,你并没有在类中插入一个新的成员,只是让这个类的实例对象能够通过.调用新的函数。扩展函数是静态分发的,扩展函数的调用决定于声明的参数的类型
1 | open class C |
同名同参数的成员函数和扩展函数都存在时,调用的是成员函数
1 | class C { |
即使是一个空对象仍然可以调用该扩展,然后在扩展的内部进行 this == null 的判断。
1 | fun Any?.toString(): String { |
属性扩展
1 | // 使用扩展属性(extension property) |
伴随对象扩展
语法格式类名.Companion.扩展函数名
1 | class MyClass { |
扩展域
1 | package foo.bar |
数据类
自带:
- equals()/hashCode 函数
- toString 格式是 “User(name=john, age=42)”
- 对应按声明顺序出现的所有属性
- copy() 函数
必须满足:
- 主构造函数应该至少有一个参数;
- 主构造函数的所有参数必须标注为 val 或者 var ;
- 数据类不能是 abstract,open,sealed,或者 inner ;
- 数据类不能继承其它的类(但可以实现接口)。
- 在 JVM 中如果构造函数是无参的,则所有的属性必须有默认的值,(参看Constructors);
1 | data class User(val name: String = "", val age: Int = 0) |
复制
会对一些属性做修改但想要其他部分不变
1 | val jack = User(name = "jack", age = 1) |
数据类和多重声明
1 | val jane = User("jane", 35) |
枚举类
每个枚举都是枚举类的一个实例,它们是可以初始化的。
1 | enum class Color(val rgb: Int) { |
可以有对应的方法,以及复写基本方法。用分号把枚举常量定义和成员定义分开。
1 | enum class ProtocolState { |
通过名字获得枚举常量
1 | EnumClass.valueOf(value: String): EnumClass |
泛型
泛型类
1 | class Box<T>(t: T){ |
类型可以推断的
1 | val box = Box(1)//1是 Int 型,因此编译器会推导出我们调用的是 Box<Int> |
声明处变型
通过注解类型参数 T 的来源,来确保它仅从 Source<T>
成员中返回(生产),并从不被消费。使用out 修饰符:
1 | abstract class Source<out T> { |
接受一个类型参数逆变:只可以被消费而不可以 被生产。使用in。
1 | abstract class Comparable<in T> { |
多重声明
可以通过给对象插入多个成员函数
1 | val (name, age) = person |
在for循环中使用
1 | for ((a, b) in collection) { ... } |
一个函数返回俩个值
1 | data class Result(val result: Int, val status: Status) |
代理
类代理
Derived 类可以继承 Base 接口并且指定一个对象代理它全部的公共方法:
1 | interface Base { |
代理属性
1 | class Example { |
语法结构是: val/var 属性名 属性类型 by 类名(参数1,参数2...)
在 by 后面的表达式就是代理,因为get() set() 对应的属性会被 getValue()
setValue()
方法代理。前面添加operator关键字,必须要提供getValues()
函数(如果是 var
还需要 setValue()
)。
1 | class Delegate { |
调用的时候直接创建对象,通过代理属性直接调用getValue()
,调用e.p=”test”是直接调用setValue()
方法
1 | val e = Example() |
标准代理
Kotlin 标准库为几种常用的代理提供了工厂方法
lazy()
是一个接受 lamdba 并返回一个实现延迟属性的代理,第一次调用 执行 lamdba 并传递 lazy()
并存储结果,以后每次调用时只是简单返回之前存储的值。
1 | val lazyValue: String by lazy { |
可观察熟悉
Delegates.observable()
需要两个参数:一个初始值和一个用于修改的值 。每次我们给属性赋值时都会调用赋值操作进行之前的值。它有三个参数:一个将被赋值的属性,旧值,新值:
1 | import kotlin.properties.Delegates |
在map中存储属性
这种操作经常出现在解析 JSON 或者其它动态的操作中。这种情况下你可以使用 map 来代理它的属性。
这是一个只读的map
1 | class User(val map: Map<String, Any?>) { |
一个可变的map
1 | class MutableUser(val map: MutableMap<String, Any?>) { |
调用的时候
1 | val user = User(mapOf( |
通过属性的名字直接取值
1 | println(user.name) // Prints "John Doe" |
反射
类引用
1 | val c = MyClass::class |
函数引用
::
操作符右边不能用重载函数。
1 | fun isOdd(x: Int) =x % 2 !=0 |
组合函数
1 | //f的入参等于g的返回,整个函数是g的入参,f的返回 |
属性引用
1 | var x = 1 |
访问类的属性
1 | class A(val p: Int) |
构造函数引用
1 | class Foo |
直接调用
1 | function(:: Foo) |
密封类
密封类用于代表严格的类结构,值只能是有限集合中的某中类型,不可以是任何其它类型。这就相当于一个枚举类的扩展。
但每个枚举常量只有一个实例,而密封类的子类可以有包含不同状态的多个实例。
密封类可以有子类但必须全部嵌套在密封类声明内部。
声明密封类需要在 class 前加一个 sealed
修饰符。
1 | sealed class Expr { |
与when表达式结合
1 | fun eval(expr: Expr): Double = when(expr) { |