扩展 Extensions
kotlin中有一种特殊的用法,叫做Extensions,作用是在不修改和继承类的情况下,给类添加一个额外的方法,这是kotlin种很神奇也很有用的一个语法糖。
扩展方法
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
val l = mutableListOf(1, 2, 3)
l.swap(0, 2)
比如这里,我给MutableList<Int> 这个类,添加了swap方法。然后我们就可以像使用类内方法一样使用swap。其中this表示调用该方法的对象。
当然我们也可以使用泛型来实现
fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // 'this' corresponds to the list
this[index1] = this[index2]
this[index2] = tmp
}
扩展是静态的
当我们使用扩展方法的时候,我们需要知道,我们实际上并没有向类的内部注入了方法,实际上的处理方式是在编译的时候生成了一个隐藏的新类叫做xxxkt(无法被显示调用的)。
看下面代码
open class C
class D: C()
fun C.foo() = "c"
fun D.foo() = "d"
fun printFoo(c: C) {
println(c.foo())
}
printFoo(D())
printFoo中传入了D的对象,但是由于printFoo方法中使用的类型是C,所以调用的就是C的方法,最终输出了"c"
class C {
fun foo() { println("member") }
}
fun C.foo() { println("extension") }
对于这种情况 member优先级永远大于extension。
可空类型扩展
fun Any?.toString(): String {
if (this == null) return "null"
// after the null check, 'this' is autocast to a non-null type, so the toString() below
// resolves to the member function of the Any class
return toString()
}
X?的类型也能够扩展,但是一般需要使用 this == null的判断。
属性扩展
属性的扩展相对来说多了更多限制
val <T> List<T>.lastIndex: Int
get() = size - 1
// val <T> List<T>.lastIndex: Int //错误,需要初始化
// val <T> List<T>.lastIndex: Int = 1 //错误,扩展属性不能初始化
//var <T> List<T>.lastIndex: Int //错误需要初始化
// get() = size - 1
//var <T> List<T>.lastIndex: Int
// get() = size - 1
// set(value){field = value} //错误 没有 field 字段
//var <T> List<T>.lastIndex: Int
// get() = lastIndex
// set(value){lastIndex = value} 编译通过,set 和 get的时候循环调用报错
所以我们看到属性扩展的功能存在局限性,由于不能调用field,所以实际上限制了很多set的get的使用。
Companion Object 扩展
关于companion后面我们会讲,现在你只需要知道,这个东西差不多类似于static
class MyClass {
companion object { } // will be called "Companion"
}
fun MyClass.Companion.foo() {
// ...
}
比如这样,compaion object也可以扩展。
扩展的作用域
如果我们将扩展定义在文件的最外层,那么这个扩展在全局都是可用的,只要import这个扩展方法的名字即可。
//file 1
package foo.bar
fun Baz.goo() { ... }
//file2
package com.example.usage
import foo.bar.goo //然后就能调用goo方法了
如果我们将一个扩展定义在类内部,那么这个扩展就相当于这个类的成员函数,拥有和成员函数一样的性质。
还有一种冲突
class C {
fun D.foo() {
toString() // calls D.toString()
this@C.toString() // calls C.toString()
}
Data Class
如果一个类的作用仅仅是用来保存数据,就像c语言中的结构体Struct,那么kotlin中可以使用data类
data class User(val name: String,val age: Int = 10){
var isWoman: Boolean = false
}
data类必须提供一个主构造函数,并且需要至少一个参数,而且必须使用val或者var。可以包含变量,方法,这个和普通类一样的。
不同点在于,Data Class自动重写了部分函数:
equal 和 hashCode方法,只要主构造函数中变量的值相同,就会有相同的hashCode 并且equal返回true(其他变量不管)。
重写了toString方法,会以"User(name=John, age=42)"格式输出
再次重写这些方法会覆盖默认实现的!!
能够使用componentN来访问变量。比如上面 user.component1 实际上就代表了name变量,user.component2代表了age变量。
最后它默认实现了一个很厉害的copy方法
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
我们可以在copy的时候修改部分属性,其余属性原样保持。
枚举 Enum
kotlin不出意外,也提供了枚举这种东西。
enum class Direction {
NORTH, SOUTH, WEST, EAST
}
关于枚举,不论是java还是kotlin,枚举的本质还是一个类,而其中每一个枚举元素就是这个类的一个实例(使用枚举实现单例模式还是很舒服的。)
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
可以看到,我们为类提供了一个主构造函数,然后当我们产生实例的时候就需要提供参数。很类有没有!
enum class ProtocolState{
WAITING{
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
fun print(){
Log.d("sss","ProtocolState.print")
}
}
enum 有print方法,所以实例都能够调用,也有一个abstract方法,所有实例都需要重写它。其实这个操作很有用的(java中也一样,一种优雅的分支。)
每一个enum隐藏了两个变量
val name: String //实例名字
val ordinal: Int //实例序号
还是和java一样,我们已通过valueOf方法获取指定名字的实例(如果不存在会报错IllegalArgumentException)
EnumClass.valueOf(value: String): EnumClass
或者获取所有定义的实例
EnumClass.values(): Array<EnumClass>
Sealed Class 密封类
考虑枚举,枚举中的每一个元素的类型都是这个枚举类型的,我们无法让每个元素拥有不同的操作和方法。
所以kotlin提供了一种叫做Sealed Class的类,用来实现上述功能
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr() //object我们在后面介绍
我定义了一个sealed的类,然后定义了几个其他的类,继承与这个类。
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// the `else` clause is not required because we've covered all the cases
}
与普通class的继承相比,我们省略了else分支,因为系统知道我们已经罗列出了所有状况。
什么就这点作用吗?枚举的意义在哪里,它的意义就在哪里……
PS 关于sealed的定义,如果是在包下面的,那么可以像上面这样定义,但是所有直接继承与sealed的类都必须定义在同一个文件中。
如果sealed是定义在某个类内部的,那么就需要这样定义
sealed class Expr{
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr() //object我们在后面介绍
}
泛型
class Box<T>(t: T) {
var value = t
}
val box: Box<Int> = Box<Int>(1)
和java类似这是泛型的最简单使用,如果T的类型可推测,那么我们甚至可以胜率T,像这样
val box = Box(1)
我们知道1 是int类型的,所以T的类型应该是int。
Java的通配符类型
这里要插播一些java的知识,在java中,泛型的类型是固定的,比如List<String> 整个是一个类型,另外,他并不是List<Object>的子类。
所以,如果你打算这样做
List<String> strs = new ArrayList<String>();
List<Object> objs = strs;
那么编译器会报错。
为了实现将strs中的数据搬运到objs中,假设我们自己来实现addAll,理所当然的相反是这样的
interface Collection<E> ... {
void addAll(Collection<E> items);
}
然后我们调用addAll
objes.addAll(strs)。抱歉,报错了,因为List<String>的类型并不是List<Object>的子类,所以参数类型错误!这个时候java就推出了通配符类型
? extends E
interface Collection<E> ... {
void addAll(Collection<? extends E> items);
}
List<String> 是List<? extends Object>的子类!!
还有一种类型是 ? super E ,相对来说用得就比较少了,List<Object> 是 List<? super String>的子类。一般会用在泛型返回值类型的时候。
在kotlin中也有这样的问题
abstract class Source<T> {
abstract fun nextT(): T
}
fun doSomething(){
var strs: Source<String> = object : Source<String>() {
override fun nextT(): String {
Log.d("sss","haha")
return "haha"
}
}
var objects : Source<Any> = strs //报错!!,类型不符合
}
比如上面代码,我们尝试吧Source<String> 赋值给Source<Any>。
怎么办,进行小修改
abstract class Source<out T> {
abstract fun nextT(): T
}
我只要在模板类T前天添加 out 关键字,这样就能够通过编译。
out关键字的作用是提示编译器,这个模板参数只会用作输出,类型和你不搭嘎的。
abstract class Source<out T> {
abstract fun take(t:T) //报错,因为T是out类型,只能用作输出
abstract fun nextT(): T
}
相应的,也提供了一个in 类型
abstract class Origin<in T> {
abstract fun originT(other: T): Int
}
fun demo() {
var strs :Origin<Any> = object :Origin<Any>(){
override fun originT(other: Any): Int {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
var objs :Origin<String> = strs
}
被out 修饰的参数在使用中可以使用父类代替(使用Any 代替了 String),被in 修饰的参数使用中可以被子类代替(使用String 代替了 Any)。
虽然看上去很美好,但是实际上,大多数时候我们无法确定一个模板参数只被输入,或者只被返回。
class Array<T>(val size: Int) {
fun get(index: Int): T { /* ... */ }
fun set(index: Int, value: T) { /* ... */ }
}
我们写一个copy函数,然后调用它
fun copy(from: Array<Any>, to: Array<Any>) {
assert(from.size == to.size)
for (i in from.indices)
to[i] = from[i]
}
val ints: Array<Int> = arrayOf(1, 2, 3)
val any = Array<Any>(3) { "" }
copy(ints, any) //报错类型不符合!!
还是遇到了最开始的问题,如何解决?修改copy方法
fun copy(from: Array<out Any>, to: Array<Any>) {
// ...
}
在from的模板参数前加入out方法,这就说明只会调用from中返回模板的方法,不会调用输入模板的方法。然后编译器就乖乖接受了这个设定,成功运行。
同样的in也可以这种用,只是含义和out相反而已。其实吧 kotlin弄了自己的一套东西,但是实际上和java的没有区别,也不见得更方便。
其他
方法的泛型写法和java 几乎一模一样
fun <T> singletonList(item: T): List<T> {
// ...
}
需要在方法开头使用<T>标注泛型类型。
另外,我们可以给泛型添加约束
fun <T : Comparable<T>> sort(list: List<T>) {
// ...
}
比如这里的T的类型需要是Comparable<T>的子类。
默认的约束条件是 Any? 表示所有类型都可以。
内部类
kotlin有两种正宗的内部类(还有一种匿名类,这里就不算内部类了,晚点介绍)
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
}
}
val demo = Outer.Nested().foo() // == 2
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo()
第一种就是普通的类内定义类,第二种是使用inner修饰。
第一种类相当于java中 static 内部类,不持有外部类对象,能够单独定义。
第二种就相当于java中的普通内部类,持有外部类对象,依附于外部类存在。
Object表达式 (匿名类)
java中,我们经常使用匿名类来定义一个接口对象。在kotlin中不再有这样的匿名类概念,而是使用Object表达式来完成这项工作。
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
这是最简单的应用,在java中就相当于 new MouseAdapter。
在kotlin中,object表达式有着更广泛的应用,它用来完成直接生成一个类对象(省略类定义)。
它能实现这样的功能
open class A(x: Int) {
public open val y: Int = x
}
interface B {...}
val ab: A = object : A(1), B {
override val y = 15
}
生成一个匿名类,继承与A和B,并且产生一个对象,名为ab。
上面继承了多个类,我们甚至能够不继承任何类
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
object定义了一种匿名的类型,但是这个类型有很奇特的性质,只有是private的才能在作用域内保持稳定,如果他是一个public的成员变量,或者是一个public方法的返回值,这个就会退化成object所继承的类,如果没有继承,那么久退化成Any.
class C {
// Private function, so the return type is the anonymous object type
private fun foo() = object {
val x: String = "x"
}
// Public function, so the return type is Any
fun publicFoo() = object {
val x: String = "x"
}
fun bar() {
val x1 = foo().x // Works
val x2 = publicFoo().x // ERROR: Unresolved reference 'x'
}
}
关于object匿名类,和inner相当,依附于类对象,所以能够访问类对象内部的变量。
object 声明
object还有一种非常有用的用法,实现单例模式!!
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ...
}
val allDataProviders: Collection<DataProvider>
get() = // ...
}
DataProviderManager.registerDataProvider(...)
相比java,这倒是简单很多。这个和类的定义基本相同的,也可以继承其他类。
companion object
kotlin有种定义在类内部的特殊对象,叫做companion object
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
companion object是一个特殊的成员变量,可以通过类名直接调用内部的方法。甚至我们能够省略这个对象的成员变量名,或者继承于某个类
class MyClass {
companion object:Adapter() {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
虽然这个东西使用上,甚至在理解上都和java中的static一样。但是实际上,这个东西并不是一个static类型的变量,而是一个成员变量。
代理(Delegation)
在介绍代理之前,可能需要先知道一下基本设计模式中的代理模式,打个比方,用java来写
首先定一个明星的接口
public intreface IStar{
//表演的方法
public void show();
}
具体的明星,比如刘德华
public class LiuDeHua implements IStar{
//具体的表演
public void show(){
//刘德华在表演
}
}
好了我们应用程序要刘德华表演了
void main(){
IStar star=new LiuDeHua();
star.show();
}
看上面的情况,我们需要创建一个刘德华的对象,就好比我们需要和刘德华协商出场费什么的。
但是这个我们能和刘德华搭上话吗?显然不能,只能找他的经纪人,也就是我们的核心,代理
public class ProxyStar implements IStar{
private IStar star;
public ProxyStar(){
star=new LiuDeHua();
}
public void show(){
star.show();
}
}
修改我们的程序
void main(){
ProxyStar proxy=new ProxyStar();
proxy.show();
}
好了现在我们直接和他的经纪人接头,并告诉经济人让他表演吧。
这就是基本的代理模式,他的作用就是隐藏真正的实现类。
但是大多数时候代理模式都需要这样
void main(){
IStar star=new LiuDeHua();
ProxyStar proxy=new ProxyStar(star);
proxy.show();
}
代理对象的构造函数会要求传入一个被代理的对象。
类代理
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // prints 10
}
kotlin中有代理模式的官方实现方式,通过by关键字,这时候Derived会自动保存b对象,并且自动实现Base的所有方法。
如果在Dervied中重写print方法,那么这会覆盖代理方法!!
属性代理
这是kotlin种一个比较有意思的语法。有时候我们在定义一个类的时候,其中有一些属性是能够提出来统一操作的,放到一个工具类中。比如lazy 属性(只有在第一次使用的时候才会真正去计算值),观察者属性,属性map等。
kotlin中有一种有趣的写法
class Example {
var p: String by Delegate()
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$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.'")
}
}
val e = Example()
println(e.p) //Example@33a17727, thank you for delegating ‘p’ to me!
e.p = "NEW" //NEW has been assigned to ‘p’ in Example@33a17727.
如果我定义了一个变量p,并且使用by定义了它的代理类。当我们调用这个方法的get 和set的时候,实际上会调用代理类中的getValue和setValue方法。
其中代理的写法规则是这样 val/var <property name>: <Type> by <expression>
其中experssion是一个表达式,不一定只能是对象的定义,也可以调用一个返回代理对象的方法。
关于代理类,对于val变量,需要提供一个getValue方法,带有两个参数:
thisRef 类型一定是被代理的 属性的拥有者 的类型或者其父类型。对于定义在顶层的属性或者本地变量,属性的拥有者为空,那么这个时候,他的类型就会是 Nothing?
property 固定KProperty<*> 类型,或者他的父类型。
返回值的类型需要和被代理属性的类型相同,或者其父类型。
setValue也差不多,但是多了一个参数value,表示设置的新值,类型需要和被代理属性的类型相同,或者其父类型。
标准代理类
lazy
kotlin的标准库提供了一些系统实现
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main(args: Array<String>) {
println(lazyValue)
println(lazyValue)
}
//最终输出
//computed!
//Hello
//Hello
我们看到lazy方法会返回一个lazy对象,第一次调用get的时候回调用表达式中的内容,然后记下值,下一次调用的时候直接返回上一次的值。
默认的lazy修饰的属性会是同步的,只会在一个线程中进行计算,并且所有线程都会返回同一个值。但是,如果我们不需要同步,那么可以这样
val lazyValue: String by lazy(LazyThreadSafetyMode.PUBLICATION, {
println("computed!")
"Hello"
})
如果我们不关注是否同步,我们确定它只会在一个线程中计算值,那么可以使用
LazyThreadSafetyMode.NONE
被观察者(Observable)
class User {
var name: String by Delegates.observable("<no name>") {
prop, old, new ->
println("$old -> $new")
}
}
fun main(args: Array<String>) {
val user = User()
user.name = "first"
user.name = "second"
}
//<no name> -> first
//first -> second
通过by Delegates.observable,需要两个参数,一个初始值 ("<no name>"),还有一个表达式,每次给name赋值的时候都会调用表达式。表达式有三个参数,分别是被赋值的对象,原来的值,和新的值。(赋值的真实操作不是被观察者考虑的,它考虑的是被赋值之后的操作!但是通过Delegates.vetoable可以在赋值之前调用表达式,如果该表达式返回false,那么赋值会被拦截。)
map
将属性存储在map中,在json操作或者一些其他场合,这样的操作还是有一定存在意义的,我们可以使用默认的map代理来实现这个功能。
class User(val map: Map<String, Any?>) {
val name: String by map
val age: Int by map
}
//如果想要使用var类型的变量那么map类型需要时mutableMap
//class User(val map: MutableMap<String, Any?>) {
// var name: String by map
// var age: Int by map
//}
定义特别简单,提供一个map的主构造函数(只要提供一个map变量并初始化就行),只需要by map就可以了。
我们可以这样
val user = User(mapOf(
"name" to "John Doe",
"age" to 25
))
println(user.name) // Prints "John Doe"
println(user.age) // Prints 25
当构造user的时候,name和age的值就已经存入到map中,当我调用user.name实际上就是从map中去获取键name的值。
如果使用MutableMap,那么通过set去设置user.name实际上就是将name保存到map中。
PS:在早起kotlin版本中,代理属性只能用在类的成员变量上,但是现在不是了,它可以用在所有地方,就连方法的本地变量都能够使用代理!
代理原理
其实delegate的原理挺简单的
class C {
var prop: Type by MyDelegate()
}
// this code is generated by the compiler instead:
class C {
private val prop$delegate = MyDelegate()
var prop: Type
get() = prop$delegate.getValue(this, this::prop)
set(value: Type) = prop$delegate.setValue(this, this::prop, value)
}
比如哦们代理了属性prop,实际上编译器会生成一个类型为代理类型的prop$delegate的属性,然后重写prop属性的get和set方法,直接使用代理类型的 getValue 和setValue 方法。