class, objects and interfaces

Basic

property

默认情况下,对于一个property,编译器会做以下几件事情:

interface - methods with default implementations

Kotlin的interface可以包含method的实现,抽象的property声明,但是不能包含状态。基本的定义如下:

interface Clickable {
	val label: String
    fun click()
    fun showOff() = println("I'm clickable")
}

继承了interface的非抽象类,需要实现interface中未实现的method;对于interface中已经实现了的method,你可以选择重写(override)或者沿用(什么都不用做)。

对于声明的抽象property,意味着继承了interface的类需要提供一种方式可以访问/修改这个property,在interface中并不会分配field来存这个property,因此也就意味着不会有state。

class Button(override val label: String): Clickable {
    override fun click() = println("I was clicked")
}

当一个类继承了多个interface,并且这些interface都实现了某个同名的method,那么这个类在定义的时候就必须重写这个method。重写的时候,如果你想访问某个interface中的那个实现,可以通过如下的方式:

...
override fun showOff() {
    super<Clickable>.showOff()
}
...

open vs final(*)

Modifier 应用到class 应用到member
final(默认) 不能被继承 不能被子类重写
open 可以被继承 可以被子类重写

例如:

open class RichButton: Clickable {
    override fun click() {} // open (because of overriden)
    fun disable() {} // final
    open fun animate() {} // open explicitly
    ...
}

如果你重写了parent class/interface的一个member(open),那么这个新的member也是open的。如果,你希望重写之后这个member为final,那么需要显示地在override之前指定final:

open class RichButton: Clickable {
    final override fun click() {}

abstract class

abstract class有以下几个特点:

  1. 不能被实例化
  2. 通常会包含abstract members,需要由子类实现,并且这些member默认是open (而那些有实现的member则是final)

例如:

abstract class Animated {
    abstract fun animate()
}

visibility modifiers: public(*), protected, private, internal

Modifier class member top-level declaration(class/function/.etc)
public(default) visible everywhere visible everywhere
protected visible in subclass N/A
private visible in current class visible in current file
internal visible in current module visible in current module

Kotlin要求class或member定义时使用的其他对象的visibility大于等于自身,例如:

internal open class TalkativeButton: Focusable {
    ...
}

fun TalkativeButton.giveSpeech() {
    ...
}

会报错:Error: 'public' member exposes its 'internal' receiver type TalkativeButton。修改的方式可以将TalkativeButton定义为public,或者将giveSpeech定义为internal.

值得注意的是,一个class的extension function不能访问其private或者protected的成员。

inner class vs nested classe(*)

Kotlin允许在一个类的内部定义类,但是这个内部的类默认情况下不可以访问外部类的成员(nested),除非显示地指定(inner)。

Modifier Meaning
(default) nested class, doesn’t store a reference to outer class
inner inner class, sotre a reference to outer class

示意图如下:

nest_vs_inner

从inner class访问外部类的语法为:this@Outer,例如:

class Outer {
    inner class Inner {
        fun getOuterReference(): Outer = this@Outer
    }
}

sealed class - defining restricted class hierarchies

举个例子:

interface Expr
class Num(val value: Int): Expr
class Sum(val left: Expr, val right: Expr): Expr

fun eval(e: Expr): Int =
when (e) {
    is Num -> e.value
    is Sum -> eval(e.right) + eval(e.left)
    else -> throw IllegalArgumentException("unknown expression")
}

上面的例子有两个地方不太智能:

  1. 编译器强行要求我们检查when(e)的默认分支,但是这个分支如果走到了,我们其实也干不了什么事情,只能抛出一个异常结束
  2. 当有一个新的类实现了Expr,你必须手动的往when(e)中添加分支(前提是你记得这么做)

Kotlin提供了一种叫做sealedclass的类。当你对某个类指定了sealed,那么你就限制了这个类所有可能的子类,这些子类必定是定义在这个类的内部的nested class.

注意,sealed自带open属性。

上面的例子可以改成下面这样:

sealed class Expr {
    class Num(val value: Int): Expr()
    class Sum(val left: Expr, val right: Expr): Expr()
}

fun eval(e: Expr): Int = 
when (e) {
    is Expr.Num -> e.value
    is Expr.Sum -> eval(e.right) + eval(e.left)
}

这样子,当有新的子类定义之后,并且没有在when(e)中加入响应的分支,那么编译器就会报错,提醒你需要加入该分支。同时,编译器也不会要求你处理默认分支。示意图如下:

sealed

sealed实际上是一个语法糖,它会为被修饰的类定义一个private的构造函数,于是只有定义在sealed类内的子类才可以被成功构造(成功调用父类的构造函数)。

注意,当前版本的sealed有很多限制,例如:

  1. 所有子类都必须是nested class
  2. 子类不可以是data class

Constructor

Kotlin 中存在两种构造函数:

其中,primary c’tor 至多有一个,secondary c’tor 可以有多个。

primary c’tor

以下定义了一个类User,并且定义了其primary constructor:

class User(val nickname: String)

其中括号括起来的部分就是primary constructor,注意,它作为类声明header的一部分。它有两个作用:

  1. 指定了构造函数的参数
  2. 使用构造函数的参数初始化properties

如果使用完整(繁琐)的方式来改写这个定义,如下所示:

class User constructor(_nickname: String) {
    val nickname: String

    init {
        nickname = _nickname
    }
}

上面的关键字:

class User(_nickname: String) {
    val nickname =_nickname
}

进一步,可以简化为:

class User(val nickname: String)

构造函数的参数也可以指定默认值:

class User(val nickname: String,
           val isSubscribed: Boolean = true)

如果某个类继承自一个父类,那么这个类的primary constructor在定义的时候也需要调用调用父类的构造函数:

open class User(val nickname: String) {...}

class TwitterUser(nickname: String): User(nickname) {...}

如果你希望定义一个不能被外部代码实例化的类(例如想实现一个singleton,当然更好的方式是使用object declaration),需要指定构造函数为private:

class Secretive private constructor() {}

secondary c’tor

语法如下:

open class View {
    constructor(ctx: Context) {
    }
}

如果一个类既有secondary c’tor,又有primary c’tor,那么secondary c’tor必须delegate to primary c’tor(可以使用this来引用自身的primary c’tor):

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

相反地,如果一个类只有secondary c’tor而没有primary c’tor,但是它有父类。那么每一个secondary c’tor都必须初始化父类(或者delegate给某一个初始化父类的secondary c’tor)。同样地情况,如果这个类还有initializer block,那么在实例化的时候initializer block会先于secondary c’tor的执行,例如:

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

输出:

Init block
Constructor

no c’tor

如果一个类既没有primary c’tor,又没有secondary c’tor,那么编译器会为其自动生成一个空的primary c’tor.

properties

properties declared in interface

interface在kotlin中可以声明property,称为abstract property,例如:

interface User {
    val nickname: String
}

这代表继承了这个interface的类必须提供一个方法可以读取nickname。由于interface中的proerty并没有定义它到底是由某个存储字段读取的还是由某个getter()函数获得的,因此,在interface定义当中并没有一个状态位对应这个property,即符合interface没有状态的条件。

下面举几个类,它们实现了这个interface:

class PrivateUser(override val nickname: String): User

class SubscribingUser(val email: String): User {
    override val nickname: String
        get() = email.substringBefore('@')
}

class FacebookUser(val accountId: int): User {
    override val nickname = getFacebookName(accountId)
}

其中,SubscribingUser每次通过email这个property计算得到nickname,并没有一个backing field去保存nickname;而FacebookUser在初始化的时候调用一次getFacebookName(),并且将返回的nickname保存起来,以后每一次调用就直接返回这个保存的值即可,被称为是有backing field的.

interface中的property可以定义其getter和setter,只要它们没有backing field,即不会保存状态即可。因此,像SubscribingUser也可以定义成interface:

interface SubscribingUser {
    val email: String
    val nickname: String
        get() = email.substringBefore('@')
}

其中,email必须由子类重写,而nickname则可以继承。

properties declared in class

在class中定义的property,class对其是有full access的。上面我们看到既可以通过初始化property并保存的方式访问(FacebookUser),也可以通过定义getter的方式每次计算得到property(e.g. SubscribingUser)。而class带来的full access允许我们将这两种方式合并起来,即既将值保存起来,又可以在get或者set的时候提供额外的逻辑。

例如:

class User(val name: String) {
    var address: String = "unspecified"
    set(value: String) {
        println("""
            Address was changed for $name:
            "$field" -> "$value".""".trimIndent())
        field = value
    }
}

注意,对mutable的property(var)只能重定义getter或者setter。上面的例子中,getter直接返回保存的field.