刚刚从java转kotlin难免会遇到一些不适应的语法点,在这里做一下记录。本文写于kotlin版本1.2.50。
kotlin有4种访问作用域:
乍看之下和java差不多,只不过friendly换成internal。需要注意的是:
kotlin中可以为一个已经定义的类扩展方法和属性,而无需任何包装类或继承。但是当扩展遇到多态,则多态的行为与直觉不一致:
open class C
class D: C()
fun C.print() {
print("C")
}
fun D.print() {
print("D")
}
fun printC(c: C) {
c.print()
}
fun printD(d: D) {
d.print()
}
printC(D()) //打印"C"
printD(D()) //打印"D"
还是上面这个例子,看看在泛型集合中的表现:
val list = arrayListOf(C(), D())
for (c: C in list) {
c.print()
}
//打印:
//c
//c
当父类和子类都定义了同名的扩展函数的时候,print()的行为取决于上下文环境,c的定义类型,在泛型例子中是C类。从printD()可以看出,如果声明类型为D,则print()行为才会与D一致。
另外,扩展(extend)总会让人想到js,但是不同的是,kotlin中通过扩展无法覆盖该类同签名(方法名和参数需求相同)的成员方法;kotlin中扩展的设计动机是为了代替java中的工具类,使用场景上和js还是有很大区别的。
在java中,泛型参数的边界由super和extend表示。在kotlin中,用变异标识in和out表示,其基本功能和C#一致:
//in承诺Foo的方法只接受T而不返回T类型
class Foo<in T> {
fun <T> doo(t: T) {}
}
//out承诺Boo的方法只返回T类型而不接受T类型
class BooT> {
fun <T> next(): T? {
return null
}
}
fun doFoo(f: Foo) {
//由于Foo的方法不返回T类型,任何Foo的操作对于Foo是安全的
val f2: Foo = f
}
fun doBoo(b: Boo) {
//由于Boo的方法不接受T作为参数,任何Boo的操作对于Boo是安全的
val b2: Boo = b
}
可以看出,在基本功用上,in可以类比super,而out类比extend,super/extend试图通过约束泛型的边界来达成类型安全,而in/out试图约束对泛型的操作来达成类型安全,而上面例子与java明显区别的是,java中Boo
注意:尽管如此,基于JVM的kotlin仍然存在类型擦除的问题,详见文档。
在java中,获取泛型参数的类型需要写一个工具类,而通常网上八九成的所谓的获取类型的代码,都有这样那样的问题,一旦开发中有这种需求,可能需要借鉴现成的类库,例如 Gson库中$Gson$Types.canonicalize()的写法。而ktolin中,可以这么写:
inline fun Activity.start() {
startActivity(Intent(this, T::class.java))
}
注意由于reified关键字,T可以直接通过.class获取类型。目前版本,reified关键字需要配合inline使用。
更多细节参考官方笔记。
按照官方例子,标识为inner的嵌套类才是内部类,可以访问外部类的变量。
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar
}
}
val demo = Outer().Inner().foo() // == 1
java中的匿名内部类被object表达式代替:
textView.setOnClickListener(object: View.OnClickListener {
override fun onClick(p0: View?) {
//...
}
})
当然,如果未实现的接口方法是唯一的,可以写成lambda表达式,这点和java一样:
textView.setOnClickListener {
//...
}
在java中深入探讨一个可靠的、线程安全的、开销小的单例其实需要花一定篇幅,而在kotlin中,可以通过object关键字定义单例:
object Singleton {
fun doo() {}
}
然后这样调用方法就行了:
Singleton.doo()
Singleton单例会被自动初始化,并且这个过程是线程安全的。