类定义
类声明由类名、类首部(指定类型参数,主构造器等)、类正文(用花括号包围)。类首部和正文都是可选的。因此最简单的类定义为:
class Empty // Implicitly inherits from Any
类似所有Java类都继承类Object,所有Kotlin类都继承类Any,但Any不是与特定平台(例如JVM)相关的,它不是一个java.lang.Object,这表明当Kotlin与Java代码进行互操作时,为了使用Object的成员方法,Kotlin提供了扩展函数。
例如为了调用Object的wait()/notify()方法,可以将一个kotlin对象foo转型为Object:
(foo as java.lang.Object).wait()
为了获取一个kotlin对象的Java类,Kotlin1.1之后可以使用class引用的java扩展属性,也可以直接使用扩展属性javaClass:
val fooClass = foo::class.java // class reference
val fooClass = foo.javaClass // extension property: javaClass
与Java代码进行互操作,Kotlin在语言层面上是这样设计的,但在编译为Java字节码以及JVM运行时中,每个类总是默认继承了Object。
构造器
Kotlin的构造器分为主构造器(primary constructor)和次要构造器(secondary constructors)。两者都是可选的,即可以不用明确定义任何构造器,这时如果该类不是抽象类,就采用默认生成的无参主构造器。主构造器只能有一个,次要构造器可以有多个,次要构造器必须代理到主构造器,可以是直接的代理或使用其它次要构造器间接代理。造器的可见性默认是public的。
这里讨论下主构造器,先了解下Java对象的初始化,大致说了Java对象初始化机制有三种:实例初始化器(初始化块)、实例变量初始化器、构造器。初始化块和实例变量初始化器可以认为是实例初始化器的两种不同形式,实例变量初始化器只不过定义了实例变量并使用表达式而非块进行了初始化。在对象初始化时,由于存在继承层次结构,每一层级类都需要进行初始化,一个对象从自身开始向上到父类,到父类的父类等等,直到Object类,完成整个类层次的初始化。所以该对象的构造器必须直接或间接地调用父类构造器,这也说明了为什么构造器不属于Java类成员,它不会被子类继承,不然会造成初始化的死循环。构造器是初始化的入口,但不是初始化的全部,并且为了保证初始化过程的顺序,编译器会为每个构造器生成一个<init>
方法,包含以下代码:
- 对另一个构造函数的调用(为了直接或间接调用父类构造器)
- 初始化器(包括变量初始器器和块初始化器,为了保证初始化器仅运行一次,如果第1步调用是本地另一个构造函数,那么便不需要 初始化器的代码)
- 构造函数正文
在Kotlin中,初始化块使用init
关键字声明:
init {
print("This is a intializer block.")
}
如果一个Kotlin类未继承其它另外定义的类,那么它默认继承类Any, 如下所示:
未定义主构造器
- 未定义次要构造器:编译器会为其生成一个无参主构造器
- 定义了次要构造器:那么它将仅有次要构造器,不会自动生成无参构造器
class Car { // ... constructor(type: String, name: String) { // ... } }
定义了主构造器,并且定义了次要构造器
class Car(type: String, name: String) {
// ...
constructor(brand: String, type: String, name: String) : this(type, name) {
// ...
}
}
如果一个Kotlin类继承了一个另外定义的类,假设
- 未定义主构造器
open class View {
constructor(ctx: Context) {
// ...
}
constructor(ctx: Context, attrs: AttributeSet) {
// ...
}
}
class CustomView : View {
constructor(ctx: Context) : super(ctx) {
// ...
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs) {
// ...
}
// ...
}
- 定义了主构造器:父类可以(并且必须)使用主构造器的参数在继承时进行初始化
open class Vehicle(type: String) {
// ...
}
class Car(type: String = "Car", name: String) : Vehicle(type) {
// ...
}
Kotlin中可以使用一个简洁的语法来从主构造函数中定义属性并进行初始化:
class Person(val firstName: String, val lastName: String, var age: Int) {
// ...
}
嵌套与内部类(Nested and Inner Classes)
类可以嵌套在其它类中:
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
和Java作点对比,Java中有嵌套类,并且分为静态和非静态的,静态的嵌套类实际上是一个顶级类,只不过为了包管理方便,将其嵌套在另一个包围类中,和静态方法一样,它不能直接访问类对象成员,而非静态的嵌套类可以,因为它持有外部类对象的引用,它有另一个专门的名称就是内部类。那么Kotlin中嵌套类默认是不能访问外部类的成员,它不属于内部类。内部类使用inner声明,此时内部类便持有了一个对外部类对象的引用:
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
匿名内部类
匿名内部类使用对象表达式声明,如果对象仅仅是一个Java中的功能接口(即仅有一个抽象方法的接口),那么可以使用一个lambda表达式来创建:
val listener = ActionListener { println("clicked") }
抽象类
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
接口
与Java8类似,Kotlin接口中的方法可以包含抽象方法,以及方法实现。与抽象类不同的是它们不能保存状态,即没有实例变量或者静态变量。它们可以有属性,但属性需要是抽象的或者提供访问器实现。
使用interface
关键词定义接口:
interface MyInterface {
fun bar()
fun foo() {
// optional body
}
}
一个类可以实现一个或多个接口:
class Child : MyInterface {
override fun bar() {
// body
}
}
接口中的属性
接口中声明的属性是抽象的,或者提供了自定义访问器实现,其中接口中的属性没有支撑域(backing field)。
interface MyInterface {
val prop: Int // abstract
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
接口继承冲突
如果一个类有多个超类,那么可能会继承同一个方法的多个实现,例如:
interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
override fun bar() {
super<B>.bar()
}
}
接口A和B都声明了函数foo()
和bar()
,两者都实现了foo()
,但只有B实现了bar()
,如果定义一个子类C继承A,那么显然我们需要为bar()
提供一个实现。
然而,如果定义一个类D实现A和B两个接口,我们需要实现继承的多个方法,并且明确应该如何实现。这一规则对只有一个实现的方法(bar()
)和有多个实现的方法(foo()
)都适用。
类方法
方法重写(Overriding Methods)
和类一样,Kotlin类中的方法默认是final
的,即子类不可以重写,父类方法需要使用open
声明,然后子类明确使用override
声明才可以。子类override
方法默认是可以重写的,如果要禁止重写,可以再加上final
。
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
final override fun v() {}
}
属性覆盖(Overriding Properties)
可见性修饰符
类、对象、接口、构造器、函数、属性和其setter可以有访问修饰符,getter的可见性总是和其属性一样。Kotlin中有四种可见性修饰符:private, protected, internal 和 public. 如果没有声明,默认是public的。
private, protected, public和Java基本一致,没有包可见性的概念,internal表示模块内可见,即编译为同一模块或同一应用中的代码。
类扩展
扩展函数
扩展属性
参考: