共计 4141 个字符,预计需要花费 11 分钟才能阅读完成。
Sealed Class 密封类
如果想对可能创立出的子类做限度,能够应用密封类。
上面一个例子是没有应用密封类的:
interface 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 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{...}