Skip to content

类、属性、接口

使用关键字 class 声明类,类由下面三个部分组成

  • 类名
  • 类头(可选)(指定其类型参数、主构造函数等)
  • 类体(可选)(构造函数、初始化块、函数、属性、嵌套类、内部类、对象声明)

构造函数

一个类可以有一个主构造函数和一个或多个次构造函数,如果主构造函数没有任何注解或者可见性修饰符,可以省略 constructor 关键字

主构造函数不能包含任何代码,初始化代码可以放到 init 关键字作为前缀的初始化块中

主构造函数中声明的属性可以是可变的 var 或只读的 val,构造函数的属性可以有默认值

类可以声明前缀为 constructor 的次构造函数,如果类由一个主构造函数,每个次构造函数需要直接委托或通过其他次构造函数间接给主构造函数

所有初始化块中的代码都会在次构造函数体之前执行,即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块

kt
class Person(name: String, age: Int = 0) {
    private val children = mutableListOf<Person>()

    // 初始化块
    init {
        println("name is $name, age is $age")
    }

    // 次构造函数
    constructor(name: String, age: Int, parent: Person) : this(name, age) {
        parent.children.add(this)
    }
}

val person = Person("zhang")
val parent = Person("li", 20, person)

继承

所有的类都有一个共同的超类 Any,包含三个方法 equals()hashCode()toString()

如果类要被继承,需要使用 open 关键字修饰

如果派生类有一个主构造函数,必须使用其基类主构造函数进行参数就地初始化

如果派生类没有主构造函数,每个次构造函数必须使用 super 关键字初始化其基类类型,或委托给另一个构造函数

kt
open class Base(val p: Int) {
    open val vertexCount: Int = 0

    open fun draw() {
        println("Base.draw()")
    }

    constructor(base: String, p: Int) : this(p) {
        println("base is $base")
    }
}

class Derived(base: String, p: Int) : Base(base, p) {
    override var vertexCount: Int = 4

    override fun draw() {
        println("Derived.draw()")
    }
}

val derived = Derived("zhang", 1)
derived.draw()  // Derived.draw()

如果函数没有标注 open,那么子类不允许定义相同签名的函数

Derived.draw() 函数必须加上 override 修饰符

标记为 override 的成员本身是开放的,若想禁止再次被覆盖,可以使用 final 关键字

类及其中的某些成员剋被声明为 abstract,抽象成员在本类中可以不用实现,可以用一个抽象成员覆盖一个非抽象的开放成员

属性

kt
var <propertyName>[: <PropertyType>] [= <propertyInitializer>]
    [<getter>]
    [<setter>]

val <propertyName>[: <PropertyType>] [= <propertyInitializer>]
    [<getter>]

class Demo(private val list: MutableList<String>) {
    val size: Int
        get() = list.size

    var listToString: String
        get() = list.toString()
        set(value) {
            list.add(value)
        }
}

val demo = Demo(mutableListOf("a"))
println(demo.size)
println(demo.listToString)

常量可以用 const 修饰

如果要推后初始化属性和变量,可以用 lateinit 关键字

接口

接口可以既包含抽象方法的声明也包含具体实现;与抽象类不同的是,接口无法保存状态;它可以有属性但必须声明为抽象或提供访问器实现

kt
interface Named {
    val name: String
}

interface People : Named {
    val firstName: String
    val lastName: String

    override val name: String
        get() = "$firstName $lastName"
}

class Employee(
    // name 有默认实现
    override val firstName: String,
    override val lastName: String
) : People

可见性修饰

类、对象、接口、构造函数、方法、属性和它们的 setter 方法都可以有可见性修饰符

  • private(类内部所有成员可见)
  • protected(子类可见)
  • internal(本模块可见)
  • public(默认)

特殊类

数据类

数据类主构造函数需要至少有一个参数,所有参数标记为 valvar

数据类不能是抽象、开放、密封或者内部的

如果生成的类需要含有一个无参的构造参数,则所有的属性必须指定默认值

kt
data class User(val name: String = "", val age: Int = 0)

val jack = User("jack", 1)
val olderJack = jack.copy(age = 2)

// 析构 name, age 属性
val (name, age) = jack
println("$name, $age years of age")

密封类

密封类用于表示受限的类继承结构,即一个值为有限的几种类型且不能有任何其他类型,密封类的子类是包含状态的多个实例

密封类需要在类名前面加 sealed 修饰符,所有子类都必须在与密封类自身相同的文件中声明

密封类是抽象的,其构造函数默认是 private 的

kt
sealed class Expr

data class Const(val number: Double) : Expr()

data class Sum(val e1: Expr, val e2: Expr) : Expr()

fun eval(expr: Expr): Double = when (expr) {
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    // 不需要 else 子句,因为已经覆盖所有情况
}

嵌套类

嵌套在其他类内部的类,标记为 inner 的嵌套类能够访问其外部类的成员

kt
class OuterClass {
    val property = true

    init {
        InnerClass()
    }

    inner class InnerClass {
        init {
            println(property)
        }
    }
}

枚举类

枚举类最基本的用法是实现类型安全的枚举,每一个枚举都可以初始化

枚举常量还可以声明其带有相应的方法及覆盖了基类方法的匿名类

kt
enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },
    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}

泛型

Kotlin 的类型系统有声明处型变(declaration-site variance)与类型投影(type projection)

kt
class Box<T>(t: T) {
    val value = t
}

// 实例化时,设置类型为 Int
val box = Box<Int>(1)

泛型包含两种型变注解 outin,由于它在类型参数声明处提供,所以我们称之为声明处型变

out 表示泛型是协变类型,当类 C 的类型参数 T 被声明为 out 时,它就只能出现在 C 成员的输出位置,这样,C<Base> 可以安全地作为 C<Derived> 的超类

类 C 在参数 T 上是协变的,或者说 T 是一个协变的类型参数,C 是 T 的生产者,而不是 T 的消费者

kt
// 定义协变类型
interface Source<out T> {
    fun next(): T
}

fun demo(str: Source<String>) {
    // 声明处型变,T 是一个 out 参数
    val objs: Source<Any> = str
}

in 表示泛型是逆变类型,只可以被消费而不可以被生产

kt
// 定义逆变类型
interface Comparable<in T> {
    operator fun compareTo(other: T): Int
}

fun demo(x: Comparable<Number>) {
    // 1.0 具有类型 Double,它是 Number 的子类型
    x.compareTo(1.0)
    // 因此,我们可以将 x 赋给类型 Comparable<Double> 的变量
    val y: Comparable<Double> = x
}

将类型参数 T 声明为 out 非常方便,但是有些类实际上不能被限制为只返回 T,如 Array,该类在 T 上既不能是协变的也不能是逆变的

例如,无法将一个 Array<Int> 数组复制到 Array<Any> 数组

kt
fun copy(from: Array<Any>, to: Array<Any>) {
    assert(from.size == to.size)
    for (i in from.indices)
        to[i] = from[i]
}

val from = arrayOf(1, 2, 3)
val to = Array<Any>(3) { "" }
copy(from, to)

我们唯一要确保的是,copy() 不会做任何坏事,如果想阻止它写到 from,可以用这样的语句

kt
fun copy(from: Array<out Any>, to: Array<Any>) {
    assert(from.size == to.size)
    for (i in from.indices) {
        to[i] = from[i]
    }
}

这就是类型投影。我们所说的 from 不仅仅是一个数组,还是一个受限制的(投影的)数组,其只可以调用返回类型为类型参数 T 的方法,如上,这意味着只能调用 get()

同理,也可以用 in 投影一个类型,可以传递一个 CharSequence 数组或一个 Object 数组给 fill() 函数

kt
// 类型投影
fun fill(dest: Array<in String>, value: String) {
    for (i in dest.indices) {
        dest[i] = value
    }
}

fun fillValue() {
    val dest = arrayOfNulls<Any>(2)
    fill(dest, "hello")
    println(dest.asList())
}

委托

委托模式已经被证明是实现继承的一个很好的替代方式

委托类 DelegateDerived 可以通过将其所有公有成员委托给指定对象来实现一个接口 Delagate

委托类的超类型列表中的 by 子句表示 b 将会在委托类内部存储,并且编译器将生成转发给 b 的所有 Delagate 的方法

编译器会使用 override 覆盖的实现而不是委托对象中的实现

以这种方式重写的成员不会在委托对象的成员中调用,委托对象的内部成员只能访问自己实现的接口成员

kt
interface Delegate {
    val message: String
    fun print()
}

class DelegateImpl(x: Int) : Delegate {
    override val message: String = "DelegateImpl: x = $x"
    override fun print() = println(message)
}

// 委托类
class DelegateDerived(b: Delegate) : Delegate by b {
    // 在 b 的 print 实现中不会访问到这个属性
    override val message: String = "message of Derived"
}

val b = DelegateImpl(10)
val derived = DelegateDerived(b)
// DelegateImpl: x = 10
derived.print()
// message of Derived
println(derived.message)

Kotlin 支持委托属性、延迟属性、可观察属性等

kt
val/var <属性名>: <类型> by <表达式>

by 后面的表达式是委托类,这是因为属性对应的 get()set() 会被委托给它的 getValue()setValue() 方法

属性的委托不必实现任何接口,但是需要提供一个 getValue() 函数,对于 var 定义的属性,还需要提供一个 setValue() 函数

kt
// 代理类
class DelegateClass {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String =
        "$thisRef, thank you for delegating '${property.name}' to me"

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) =
        println("$value has been assigned to '${property.name}' in $thisRef")
}

class Example {
    // 委托属性
    var p: String by DelegateClass()
}

val e = Example()
println(e.p)
e.p = "NEW"

最近更新:

All articles are under CC BY 4.0 license