Sealed Class 密封类
如果想对可能创立出的子类做限度,能够应用密封类。

上面一个例子是没有应用密封类的:

interface Exprclass Num(val value: Int) : Exprclass Sum(val left: Expr, val right: Expr) : Exprfun eval(expr: Expr): Int {    return when(expr){        is Sum -> eval(expr.left) + eval(expr.right)        is Num -> expr.value        else -> throw IllegalArgumentException("Unknown Expression")    }}

这相似于只反对加法的形象语法树,Expr代表一个表达式,也就是语法树里的一个节点,同时Num代表数字节点,它只可能是叶子,Sum代表加法节点,不可能是叶子。

当初如果咱们要实现eval函数来计算形象语法树的最终后果,咱们发现,始终须要一个else来收尾,因为Expr可能还有其余实现类,可能既不是Sum又不是Num,只管代码里基本没有其余实现类。

密封类能解决这个问题。

sealed class Expr {    class Num(val value: Int) : Expr()    class Sum(val left: Expr, val right: Expr) : Expr()}fun eval(expr: Expr): Int {    return when(expr){        is Expr.Sum -> eval(expr.left) + eval(expr.right)        is Expr.Num -> expr.value    }}

密封类表明了该类不可能有除了Num和Sum之外的其余子类,所以编译器能够发现咱们when中的代码是无懈可击的,天然不必一个额定的else。

类委托
Java中有一套设计模式就是委托模式,就是指编写一个类,但它不提供实现,所有的性能都会委托给另一个类实现,在必要的时候对类进行加强。Java前面的代理、动静代理技术全部都是基于委托实现的,能够说它是Java世界的一个支柱。

Kotlin默认反对委托,不像Java,要么用IDE生成一大堆代码,要么在编译期应用其余动静代理工具生成,Kotlin默认提供了by关键字。

上面的类继承自MutableCollection,但它齐全不存储数据,而是通过by委托给innerSet。

class CountingSet<T> (    val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet{    var objectsAdded = 0    override fun add(element: T): Boolean {        objectsAdded++        return innerSet.add(element)    }    override fun addAll(elements: Collection<T>): Boolean {        objectsAdded += elements.size        return innerSet.addAll(elements)    }}fun main(){    val set = CountingSet<Int>()    set.add(10)    set.addAll(listOf(1,2,4,15,5,3))    set.remove(10)    println(set.objectsAdded)}

比拟罕用的就是委托类作为结构器参数传入,Java中比拟常见的就是基于委托的IO流,咱们常常这样写:

new BufferedInputStream(new FileInputStream(...));

这里BufferedInputStream并不会本人实现InputStream的读取性能,而是委托给FileInputStream并对它的性能进行加强(通过建设缓冲区)。

咱们下面编写的类也是,你能够调用CountingSet传入不同的Collection实现,不同的是咱们提供了一个默认值。

除了应用结构器参数,还能够间接新建一个类委托,因为有时候咱们就想让它委托同一个类,不想让用户本人抉择。

class MySet<T> () :     MutableCollection<T> by HashSet<T>(){}

属性委托
Jetpack Compose中有一个记录状态并自动更新UI的货色,就是var value by remember,这种监测数据更新并主动刷新UI的货色在现在数据驱动的框架中并不少见。Jetpack Compose就是通过属性委托来实现的数据监测。

class Remember{    lateinit var name: String    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {//        return name        return name    }    operator fun setValue(thisRef: Any?, property: KProperty<*>,value: String) {//        name = value        println("${property.name} is changed!!!")        name = value    }}fun main(){    var name: String by Remember()    name = "ASDFASDF"    println(name)}/*name is changed!!!ASDFASDF*/

被委托的类应该实现一个getValue和setValue办法,委托方的变量不再存储值,而是由被委托的类提供存储性能。

咱们接下来编写一个懒加载的属性委托,就是第一次拜访属性时才为属性赋值

class LazyDelegate<T>(private val compute: ()->T){    var t: T? = null    operator fun getValue(thisRef: Any?, property: KProperty<*>): T {        if (t == null) t = compute()        return t!!    }    operator fun setValue(thisRef: Any?, property: KProperty<*>,value: T) {        t = value    }}fun <T> lazy(compute: ()->T) = LazyDelegate<T>(compute)fun main(){    var name: String by lazy {        "HelloWorld"    }    println(name)}

这一次咱们提供了一个lazy办法,Lazy办法会返回咱们的委托人LazyDelegate,因为Kotlin官网就为一些自带的委托封装了办法,可能是Kotlin社区习用的编码标准,的确,这样难看一些,而且Jetpack Compose中的remember实际上也是这样写的。

而后,咱们还使用了泛型和lambda表达式,lambda用于返回一个值,个别应用懒加载的时候,这个lambda表达式都会是一个很简单并且可能并不罕用的运算,所以这样如果这个值如果没被须要,懒加载就不会执行。泛型用于反对全副类型的值。

伴生对象
Java中常常会应用动态工厂办法来结构对象,这是因为动态工厂办法比结构器更加实用于解决那些很多属性能够不在结构时提供的类。动态工厂办法更加具备可读性。Kotlin基本没有动态这一说,Kotlin代替动态的方法一个是object,一个是顶层函数。但这俩都不适用于动态工厂,因为动态工厂常常要拜访类中的公有成员。

伴生对象是用来干这些的。

class Person private constructor(    name: String?,     age: Int?,     address: String?){    companion object {        fun fromNameAndAge(name: String, age: Int): Person = Person(name,age,null)        fun fromName(name: String): Person = Person(name,null,null)        fun fromNameAndAddress(name: String, address: String): Person = Person("Lucy",null,address)        fun newPerson(name: String, age: Int, address: String): Person = Person(name,age,address)    }}fun main(){    Person.fromNameAndAge("Lucy",12)}

这对于Builder模式同样实用,对于绝大多数须要和类中公有成员进行交互的中央,都实用。

然而,别忘了Kotlin中的命名参数,下面的例子本能够用命名参数更加不便的解决。

class Person constructor(    name: String,     age: Int? = null,     address: String? = null)fun main(){    Person(name = "Lucy", age = 12)}

当然伴生对象能够命名

class Person constructor(    name: String,     age: Int? = null,     address: String? = null){    companion object Loader{        fun fromJson(json: String): Person{ ... }    }}fun main(){    Person.Loader.fromJson()}

伴生对象也能够有扩大函数,这是因为像下面的Loader这种伴生对象和类中的逻辑关系不大,拆散到内部能够实现关注点拆散。

class Person constructor(    name: String,     age: Int? = null,     address: String? = null){    companion object Loader{}}fun Person.Loader.fromJson(json: String): Person {    ...}fun main(){    Person.Loader.fromJson()}

如果是没有名字的伴生对象,也能够

fun Person.Companion.fromJson(json: String): Person{    ...}