不重要的废话
前段时间看了一遍《Programming Kotlin》,主要目的是想提高自己的英文阅读能力,能力提高了没有不知道,反正回想起来,书中的内容啥也没记住。现在在看《Kotlin in Action》,想着不能再像之前了,要记录点东西,本篇文章算是《Kotlin in Action》的读书笔记。这里我要顺便夸夸《Kotlin in Action》这本书了,这本书的两位作者都是Kotlin语言的核心开发人员,内容深入浅出,大大增加了我对Kotlin语言的理解。
本篇记录的都是一些tips,相对比较简短,如果有看不懂的,语法相关的问题,可以去Kotlin语言中文站查看相关语法;其他的问题可以直接查看《Kotlin in Action》这本书(中文版《Kotlin实战》也已经出版),或者搜索给出的关键词进一步学习,或者给我留言。文章中很多地方给出了对应的英文,不是为了装13,因为很多内容不好翻译,未免言不达意,给出相应的英文,也方便在《Kotlin in Action》中查找相关内容。
方法(method)和函数(function):函数 是可以通过其名称调用的一段代码。方法 是与类实例关联的 方法。简单来说,在类内部的函数称为方法。Java中只有方法,Kotlin中既有函数也有方法。为了表述方便,Kotlin中的函数和方法,在本文中统称为函数。
Kotlin Basics
- 声明和表达式(statements and expressions):两者的区别在于,表达式有返回值,可以用在另一个表达式中,而声明没有返回值。在Kotlin中,除了循环(for,do,while)外,其它流程控制结构(像是if,when)都是表达式。另一方面,赋值在Java中是表达式,但是在Kotlin中则是声明,这避免了比较和赋值之间的困惑。(This helps avoid confusion between comparisons and assignments, which is a common source of mistakes.)
- when表达式:when相比于Java中的switch更加的强大。switch的分支必须使用常量(enum,string,number literal),而when的分支可以是任意对象或者是布尔表达式( boolean expression),如果是对象的话则进行相等判断(equality check)。
-
range and progression:如果可以遍历所有range中的值的话,这样的range称为progression(有界的非无穷)。任何实现了Comparable接口的类都可以用于创建range。range的范围可以是无穷的,这样的range不能遍历,但是可以判断一个对象是否包含在其中(例如
"Kotlin" in "Java".."Scala"
)。
Function
- 扩展函数(extension function):显然扩展函数不能破坏类的封装,不能访问类中 private 和 protected 的成员。
- 扩展函数是静态绑定:扩展函数在底层其实是类的静态方法,属于静态绑定(static binding),不能重写(override),也没有多态。
- 扩展函数和成员函数:如果扩展函数和成员函数的签名一样的话,则优先调用成员函数。注意,如果在类中增加一个和扩展函数有一样签名的成员函数,则会调用该成员函数,这样会改变原来的意义。( If you add a member function with the same signature as an extension function that a client of your class has defined, and they then recompile their code, it will change its meaning and start referring to the new member function.)
Class,Object and Interface
- 类的默认特性:Kotlin在一些类特性的默认选择上跟Java截然相反,这体现出Kotlin的一些设计理念。类是open还是final:默认final; 可见性:默认public;内部类还是嵌套类:默认嵌套类,也就是不含外部类的引用。(Inner and nested class: nested by default.)
- 扩展函数/扩展类的可见性:对于扩展函数/扩展类,它们的可见性只能比扩展接收者类/被扩展类的可见性保持一致或缩小,不能扩大,这是为了保证当扩展函数/扩展类可访问时,所有涉及的类都可以访问。(This is a case of a general rule that requires all types used in the list of base types and type parameters of a class, or the signature of a method, to be as visible as the class or method itself. This rule ensures that you always have access to all types you might need to invoke the function or extend a class. )
internal open class TalkativeButton
//error! TalkativeButton是internal的,但是giveSpeech却是public的
fun TalkativeButton.giveSpeech() {
}
- 伴生对象(companion object):伴生对象可以实现接口,并且可以使用包含类的类名作为该接口的实例。(you can use the name of the containing class directly as an instance of an object implementing the interface.)
interface JSONFactory<T> {
fun fromJSON(jsonText: String): T
}
class Person(val name: String) {
companion object : JSONFactory<Person> {
override fun fromJSON(jsonText: String): Person = ...
}
}
fun loadFromJSON<T>(factory: JSONFactory<T>): T {
...
}
loadFromJSON(Person)//传入的是Person的伴生对象,只是可以使用Person这个类名去代表
- 伴生对象上的扩展函数:可以在伴生对象上定义扩展函数。譬如,类C有伴生对象companion object,在C.Companion上定义扩展函数func,可以这样调用它C.func()。那为什么不直接把函数func定义在伴生对象内部呢?不是能达到一样的效果(所谓一样的效果是指在类C上像调用静态方法那样调用func),大概是为了“代码整洁之道”,不想在C类和其伴生对象中包含一些“不相关”的代码?
Lambda expression
捕获变量(capturing variables):lambda表达式可以捕获其所在作用域的val和var。一般的,一个局部变量的生命周期跟声明它的函数是一致的,但是当它被lambda表达式捕获时,它会被存储在lambda表达式内,以便之后使用。当捕获的是val时,该变量会直接存储在lambda表达式内部;当捕获的是var时,会创建一个包含此变量包装类,然后lambda表达式会存储指向该包装类对象的引用,以便之后可以改变该变量。之所以在Kotlin中可以捕获非final变量(var),就是因为Kotlin在底层实现上,使用了我们经常在Java中使用的小技巧:声明一个final的包装类对象,包装类中含有要捕获的可变变量。
SAM转换:可以把lambda表达式作为参数传递给一个需要SAM(single abstract method)接口的Java方法,编译器会帮我们生成一个实现该SAM接口的匿名类的对象,如果lambda表达式中没有捕获任何变量,那么只会存在一个该SAM接口的对象,每次调用会重用该对象;如果lambda表达式中捕获了变量,那么不可能再重用对象,每次方法调用都会产生一个新的接口对象。
/* Java */
void postponeComputation(int delay, Runnable computation);
/* Kotlin */
/* 没有捕获变量的lambda表达式,只存在一个此Runnable接口的对象
每次调用postponeComputation都重用该对象 */
postponeComputation(1000) {
println(42)
}
//以上代码等效实现如下
val runnable = Runnable { println(42) }
postponeComputation(1000, runnable) //每次调用都使用runnable变量
/* 捕获变量的lambda表达式,
每次handleComputation调用都会创建一个新对象以包含捕获变量 */
fun handleComputation(id: String) {
postponeComputation(1000) {
println(id)
}
}
-
lambda表达式实现的细节:除了内联的lambda表达式,其它lambda表达式在底层上都被编译成了匿名类(以Java 8为目标平台的,可以避免为每个lambda表达式编译一个单独的class文件)。如果lambda表达式捕获了变量,匿名类会包含每个捕获变量的域,并且每次调用方法都会生成一个新的匿名类的对象。如果lambda表达式没有捕获变量,则只有一个匿名类的对象。匿名类的名字(对于我们而言是匿名类,对于编译器来说还是需要类名的)是lambda表达式所在函数的名字再加上后缀,例如,
HandleComputation$1
。如果decompile字节码,会有类似如下的代码:
//捕获的id变量,会有对应的域
class HandleComputation$1(val id: String) : Runnable {
override fun run() {
println(id)
}
}
fun handleComputation(id: String) {
postponeComputation(1000, HandleComputation$1(id))
}
以上说的这些只是lambda表达式用于SAM转换时底层的实现细节,Kotlin本身的lambda表达式(除了内联的之外)都会被编译成实现Function0(没有参数),Function1(有一个参数),Function2等接口的类(详见本文最后一条tip)。这与上面所说的实现细节是类似的,都是编译成了匿名类,只是匿名类实现的接口不同。
- SAM构造器(SAM constructor):SAM构造器是编译器生成的函数,可以用来显式地把一个lambda表达式转换为SAM接口的实例。可以用在返回SAM接口的方法中,或者把SAM接口对象存储在一个变量中,等等:
//返回SAM接口
fun createAllDoneRunnable(): Runnable{
return Runnable {
println("All done!")
}
}
//变量存储SAM接口对象
val listener = OnClickListener { view ->
val text = when (view.id) {
R.id.button1 -> "First button"
R.id.button2 -> "Second button"
else -> "Unknown button"
}
toast(text)
}
button1.setOnClickListener(listener)
button2.setOnClickListener(listener)
- lambda表达式中的this:在lambda表达式内不能使用this,因为lambda表达式其实是个匿名类的对象。(Note that there's no this in a lambda as there is in an anonymous object: there's no way to refer to the anonymous class instance into which the lambda is converted.)在编译器看来lambda表达式只是一段代码块,不是一个对象,因此不能使用this来指代。如果在lambda表达式里面使用this,也是指向的包含lambda表达式的类的对象。如果确实需要在lambda表达式中使用this指代其本身,可以使用对象表达式。
- 带接收者的lambda表达式(lambdas with receiver):带接收者的lambda表达式,可以使用this,指代其接收者,当然也可以省略。
Sequence
- 序列(sequence):序列提供了懒集合操作(lazy collection operation)的能力。集合上的操作是即刻完成的,会创建临时的集合来存储每一步操作的中间结果,而序列则是懒操作的,不会创建临时集合。
- 中间操作和终端操作(intermediate and terminal operation):序列上的操作分为中间操作(intermediate operation)和终端操作(terminal operation),中间操作返回一个序列,而终端操作返回一个结果。(An intermediate operation returns another sequence, which knows how to transform the elements of the original sequence. A terminal operation returns a result, which may be a collection, an element, a number, or any other object that’s somehow obtained by the sequence of transformations of the initial collection.) 中间操作总是“懒”的,只有终端操作才会触发所有的中间操作执行。
- 序列上的操作:集合上的操作与序列上的操作是不同的
listOf(1, 2, 3, 4).asSequence()
.map { it * it }.find { it > 3 }
Higher-order functions
- 内联(inline):集合上的操作(例如filter,map等)大都是inline的,因此传入的lambda表达式不会产生匿名类,会编译成内联的,但是,集合上的每步操作都会产生临时集合,如果集合包含的元素很多,这将会是很大的开销,可以考虑转换为序列。序列上的操作不是inline的,会生成匿名类对象,但是不会有临时序列。一般来说,如果集合不是特别大,没有必要使用序列,使用集合速度更快。
- 非局部返回(non-local return):在内联的lambda表达式中,return 会从包含该lambda表达式的函数中返回,而不是lambda表达式本身。这称为非局部返回。如果需要从lambda表达式返回,需要使用带标签的返回(return with a label)。一般情况下并不需要从lambda表达式中返回,lambda表达式最后一个表达式的值就是lambda表达式的值。
-
匿名函数(anonymous function):
return
会从最接近的使用fun
定义的函数中返回。(return
returns from the closest function declared using thefun
keyword)显然匿名函数会从自身返回,这与lambda表达式不同。如果代码块中有多处退出点,使用匿名函数更加方便。(You can use them if you need to write a block of code with multiple exit points.)
- 匿名函数就是lambda表达式:尽管匿名函数看上去像是普通的函数,但是它们只是lambda表达式的另外一种语法形式,关于lambda表达式的实现的细节以及它们是如何内联的,这些对于匿名函数仍然适用。(Note that despite the fact that an anonymous function looks similar to a regular function declaration, it's another syntactic form of a lambda expression. The discussion of how lambda expressions are implemented and how they're inlined for inline functions applies to anonymous functions as well.)
Kotlin Type System
- 可空的类型参数(nullability of type parameter):默认情况下,所有泛型类和泛型方法的类型参数(也就是我们经常用的那个 T)是可空的。Kotlin中用 "?" 标记的类型是可以为null的,而没有 "?" 标记的类型不能为null的,唯一的例外就是类型参数。
- Nothing:用于表示函数没有返回或者说函数没有正常结束(抛出异常等)。Nothing类型没有值,只有用作函数的返回类型或者类型参数才有意义。
- 集合和数组(collections and arrays): 对于 collection interface 始终要记住,只读的集合不一定是不可变的,也不总是线程安全的。(Read-only collections aren't necessarily immutable. Read-only collections aren't always thread-safe.)
- Kotlin collections and Java:每一个Kotlin的集合都是对应的Java集合接口的实例。但是,每个Java集合接口都在Kotlin中有两种表示:只读的和可变的。(Every Kotlin collection is an instance of the corresponding Java collection interface. But every Java collection interface has two representations in Kotlin: a read-only one and a mutable one.)
Kotlin中的集合接口的基本继承结构跟Java中的集合是一样的。(As you can see, the basic structure of the Kotlin read-only and mutable interfaces is parallel to the structure of the Java collection interfaces in the java.util package. )Kotlin中的mutable interface都继承于相应的read-only interface。
图中的ArrayList和HashSet是Java中的类。在Kotlin中它们被分别看做 MutableList 和 MutableSet 的子类。对于其它的Java集合类(像是LinkedList, SortedSet 等)也是一样的。通过这种方式,Kotlin的集合既和Java的集合兼容,又区分开了只读的和可变的。
Collection type | Read-only | Mutable |
---|---|---|
List | listOf() | arrayListOf() |
Set | setOf() | hashSetOf(), linkedSetOf(), sortedSetOf() |
Map | mapOf() | hashMapOf(), linkedMapOf(), sortedMapOf() |
但是,只有在Kotlin中才能保证一个集合是只读的,如果将一个只读集合传递给Java方法,那么没有什么能够阻止Java改变该集合,甚至是破坏空类型安全(例如向集合中添加null),所以,我们自己必须小心控制(确定Java方法是否会改变集合,以传入正确的Kotlin集合类型;验证被Java改变的集合的类型等)。
Kotlin没有构建自己的集合类,仍然使用了Java中的集合类。Kotlin是通过只读的集合接口来保证集合是只读的,在底层实现上仍然是使用了Java中对应的可变集合。
-
集合的平台类型(Collections as platform types):Java中的集合在Kotlin中有两种表示:只读的和可变的。再加上是否为可空类型,因此,在Kotlin中重写或实现Java中含有集合类型的方法,你需要根据以下情况选择合适Kotlin类型:
- 集合是否为空。
- 集合中的元素是否为空。
- 是否需要修改集合。(read-only or mutable)
Operator overloading and other conventions
- 约定(conventions):在Java中实现了Iterable接口的类可以用在for循环中,Kotlin也有类似的语言特性,并且更加方便。与Java需要实现特定接口不同,Kotlin通过绑定特定名称的函数来实现类似的特性,这种技术称为约定,Kotlin中的运算符重载(operator overloading)就是使用这种技术。例如,如果一个类上定义了名为plus的函数,那么就可以按 约定 在这个类的对象上使用 + 运算符。
- rangeTo:前面说过任何实现了Comparable接口的类都可以用于创建range。这是因为Kotlin标准库定义了
operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
有了以上定义,我们可以创建一个range,并且判断一个变量是否包含(in)在这个range中,但是,不能在for循环中遍历这个range,因为返回的 ClosedRange<T>
没有定义iterator
,要想遍历还需要在 ClosedRange<T>
上实现iterator
:
//LocalDate类是Java 8标准库中的类,实现了Comparable接口
operator fun ClosedRange<LocalDate>.iterator(): Iterator<LocalDate> =
object : Iterator<LocalDate> {
var current = start
override fun hasNext() =
current <= endInclusive
override fun next() = current.apply {
current = plusDays(1)
}
}
>>> val newYear = LocalDate.ofYearDay(2017, 1)
>>> val daysOff = newYear.minusDays(1)..newYear
>>> for (dayOff in daysOff) { println(dayOff) }
2016-12-31
2017-01-01
- in操作符(in operator):in操作符不仅仅可以用在range上,任意类只要实现了contains函数都可以使用in,例如:
data class Rectangle(val upperLeft: Point, val lowerRight: Point)
operator fun Rectangle.contains(p: Point): Boolean {
return p.x in upperLeft.x until lowerRight.x &&
p.y in upperLeft.y until lowerRight.y
}
>>> val rect = Rectangle(Point(10, 20), Point(50, 50))
>>> println(Point(20, 30) in rect)
true
- 委托属性(delegated properties):
class Foo {
var c: Type by MyDelegate()
}
//编译器生成类似这样的代码
class Foo {
private val <delegate> = MyDelegate()
var c: Type
set(value: Type) = <delegate>.setValue(c, <property>, value)
get() = <delegate>.getValue(c, <property>)
}
Generics
- 原生类型(raw type):不存在的!Kotlin中的泛型必须指定类型参数(type argument),可以是显式指定或者是编译器推断。如果想实现类似Java原生类型那样的功能,可以使用星投影。
- 标记类型参数为非空的(making type parameters non-null):如果不对类型参数做限制,那么它可以是可空的类型。如果想保证类型参数非空,需要指定一个非空的上界,例如:
class Processor<T : Any> {
fun process(value: T) {
value.hashCode()
}
}
- 具体化(reified):reified可以具体化类型参数,奥秘在于inline而不是reified。因为函数定义为内联的,编译器将字节码插入到每次函数调用的地方,生成的字节码指向了具体的类型,而不是类型参数,所以编译器知道类型参数的具体类型,不受类型擦除的影响。
- 具体化类型参数的限制(restrictions on reified type parameters):
可以:
- 用于类型检测和转型(is , !is , as , as?)
- 使用Kotlin反射(::class)
- 获取对应的 java.lang.Class (::class.java)
- 作为类型参数调用别的函数
不可以:
- 创建类型参数类的实例(new instance)
- 调用类型参数类伴生对象的函数(Call methods on the companion object of the type parameter class)
- 使用非具体化的类型参数调用具体化类型参数的函数(Use a non-reified type parameter as a type argument when calling a function with a reified type parameter)
- 在类,属性,非内联函数上使用reified
有的是限制是因为内在原理,有的限制是因为现有的实现,可能会在Kotlin之后的版本中放松。(Some are inherent to the concept, and others are determined by the current implementation and may be relaxed in future versions of Kotlin.)其实我没有发现哪一条是因为现有实现而来的限制,我怎么觉得每一条都是因为内在原理。
-
子类和子类型(subclass and subtype):A type
B
is a subtype of a typeA
if you can use the value of the typeB
whenever a value of the typeA
is required. 子类一定是子类型,子类型不一定是子类。例如,在Kotlin中,String 类是String? 类的子类型,但是不是其子类。还有一种情况就是在泛型类的型变中。我另外一篇文章有详细的介绍:Java和Kotlin中泛型的协变、逆变和不变。 -
星投影(star projection):当你不知道泛型类的类型参数是什么或者是什么并不重要时,可以使用星投影的语法。星投影对应于Java中的 无界通配符 ,即Kotlin中的
MyType<*>
对应于Java中的MyType<?>
。一个使用星投影的例子,在Map中存储泛型类对象:
interface FieldValidator<in T> {
fun validate(input: T): Boolean
}
object DefaultStringValidator : FieldValidator<String> {
override fun validate(input: String) = input.isNotEmpty()
}
object DefaultIntValidator : FieldValidator<Int> {
override fun validate(input: Int) = input >= 0
}
object Validators {
private val validators =
mutableMapOf<KClass<*>, FieldValidator<*>>()
//保证了类型安全
fun <T: Any> registerValidator(
kClass: KClass<T>, fieldValidator: FieldValidator<T>) {
validators[kClass] = fieldValidator
}
//转型不会有问题
@Suppress("UNCHECKED_CAST")
operator fun <T: Any> get(kClass: KClass<T>): FieldValidator<T> =
validators[kClass] as? FieldValidator<T>
?: throw IllegalArgumentException( "No validator for ${kClass.simpleName}")
}
>>> Validators.registerValidator(String::class, DefaultStringValidator)
>>> Validators.registerValidator(Int::class, DefaultIntValidator)
>>> println(Validators[String::class].validate("Kotlin"))
true
>>> println(Validators[Int::class].validate(42))
true
Annotations and Reflection
- 注解的目标(annotation targets):很多时候,Kotlin中单一的声明对应于Java中多个声明。例如,Kotlin中的property对应于Java中的 field, getter, and possibly a setter, as well as the parameters of the accessors. A property declared in the primary constructor has one more corresponding element: the constructor parameter. 因此需要标明注解的是哪个元素,这称为使用目标(use-site target)声明,如下:
使用Java中定义的注解去注解Kotlin中的属性,默认是应用在对应的field上的,可以使用下面的使用目标进行指定:
- property (Java annotations can't be applied with this use-target);
- field (the field generated for the property);
- get (property getter);
- set (property setter);
- receiver (receiver parameter of an extension function or property);
- param (constructor parameter);
- setparam (property setter parameter);
- delegate (the field storing the delegate instance for a delegated property);
- file (the class containing top-level functions and properties declared in the file).
-
使用注解控制Java API(Controlling the Java API with annotations):Kotlin中提供了很多注解来控制Kotlin中的声明怎么编译成Java字节码,以及怎样被Java调用。一些Java中的keywords转换成了Kotlin中的注解,例如
@Volatile
和@Strictfp
就是Java中volatile
和strictfp
的代替。还有一些控制Kotlin中的声明对Java调用者的可见性,例如:-
@JvmName
changes the name of a Java method or field generated from a Kotlin declarations; -
@JvmStatic
can be applied to methods of an object declaration or a companion object to expose thom as static Java methods; -
@JvmOverloads
instructs the Kotlin compiler to generate overloads for a method which has default parameter values; -
@JvmField
can be applied to a property to expose that property as a public Java field with no getters or setters.
-
-
Kotlin反射的接口:所有的表达式都可以被注解,因此反射接口都继承自
KAnnotatedElement
。KClass
代表class和object。KProperty
代表任意属性,它的子类KMutableProperty
代表可变属性(var)。KProperty
和KMutableProperty
内部还定义了Getter
和Setter
接口,当需要把 property accessors 当做函数时可以使用。图中没有显示KProperty0
接口,它可以用来代表顶层属性(top-level property)。
与Java的不同
- 可变参数(vararg):与Java中使用三个点(...)不同,Kotlin使用vararg来表示 可变参数。另一个不同是,在Java中可以直接向 可变参数 传递一个数组,但是在Kotlin中,你必须显式地把数组“展开”,这称为伸展操作符(spread operator),实际就是在数组前加上 * 号,例如:
fun main(args: Array<String>) {
val list = listOf("args: ", *args)
println(list)
}
- 可见性:在Java中可以在同一个包内访问protected成员,但是在Kotlin中不可以。Kotlin中,protected成员只对该类及其子类可见。另一点不同是,Kotlin中的外部类不能访问 内部类/嵌套类 的private成员。但是在Java中可以。
- 对象表达式(object expression):对象表达式取代了Java中的匿名内部类,但是可以用来实现多个接口。还有一点与Java不同的是,在Kotlin的对象表达式的内部可以访问创建它的函数的变量,不仅限于final变量。(Just as with Java's anonymous classes, code in an object expression can access the variables in the function where it was created. But unlike in Java, this isn’t restricted to final variables)
- 内联(inline):Java中的方法不支持inline,在Java中可以调用Kotlin中inline的函数,但是这些函数并不会内联。如果Kotlin中定义的函数是inline并且reified,那么Java不能调用这样的函数,Java不支持方法内联,自然也就不能具体化参数类型。
- 注解(annotations):Java中的注解仅能注解类和方法声明或类型,而Kotlin中的注解可以注解任意表达式和文件。(Note that unlike Java, Kotlin allows you to apply annotations to arbitrary expressions, and not only to class and method declarations or types. )
-
元注解(meta-annotation):元注解
@Retention
的默认值在Java中是 RetentionPolicy.CLASS ,即注解保留在 .class 文件中,但是运行时不能获取;在Kotlin中是 RetentionPolicy.RUNTIME ,即运行时可以使用。一般来说,Kotlin中不需要特别声明@Retention
,因为我们一般就是要使用 RetentionPolicy.RUNTIME。
与Java互操作
- 命名参数(named argument):在Kotlin中调用Java的方法不能使用命名参数(包括JDK和Android Framework中的方法)。在 .class 文件中存储参数名称是从Java 8开始的一项可选特性。Kotlin兼容Java 6,因此编译器不能识别命名参数,并把其匹配到方法的定义中。(Kotlin maintains compatibility with Java 6. As a result, the compiler can't recognize the parameter names used in your call and match them against the method definition.)
-
参数默认值(default parameter value):Java中没有这个概念,因此从Java中调用Kotlin函数,必须使用所有参数。为方便起见,也可以在Kotlin中定义函数时加上
@JvmOverloads
,这样编译器会生成所有的 Java 重载方法。 -
顶层函数(top-level function):因为有顶层函数,所以Kotlin不需要静态工具类(static utility class)。但是由于在JVM中,所有的代码都必须在类中,因此,Kotlin中的顶层函数最终还是被编译成了静态工具类中的方法,类名是文件名(Utils.kt -> UtilsKt)。在Java中调用Kotlin的顶层函数,即是调用这些静态工具类中的方法。想要修改这些静态工具类的类名可以使用
@file:JvmName("NameYouWant")
注解(写在文件的开始,package之前)。 - 扩展函数(extension function):在底层,扩展函数仅是把接收者(receiver object)作为第一个参数的静态方法。在Java中可以像调用顶层函数那样调用扩展函数,只是将接收者作为第一个参数传入。
- 接口的默认实现:Kotlin接口中定义的函数可以包含有默认实现,但是Kotlin是兼容Java 6的,Java 6中不支持接口的默认实现的,因此,Kotlin中带有默认实现的接口被编译成了普通接口和包含有接口实现的类的组合。(Therefore, it compiles each interface with default methods to a combination of a regular interface and a class containing the method bodies as static methods. The interface contains only declarations, and the class contains all the implementations as static methods. )所以说,在Java中还是需要实现Kotlin接口的所有方法,不论它在Kotlin中是否存在默认实现。Kotlin现在可以生成Java 8的字节码,如果选择Java 8作为target,则Kotlin接口的默认实现会编译成像Java 8那样。
- 可见性:Kotlin中的private类(只能对当前文件可见)被编译成了包可见性,因为Java中不允许一个类是private的。而internal都被编译成了public的(在字节码层面)。这也就是为什么有的在Kotlin中不可见的类、函数在Java中反而可见。
- 空安全:Java不支持空安全,Java中的类型会转变成Kotlin中的平台类型(platform type),平台类型本质上是没有空信息的类型,既可以当做可空类型,也可以当做非空类型。
- 函数类型(function types):在底层,函数类型都被声明为了普通接口,像是Function0<R>(没有参数),Function1<P1, R>(有一个参数)等等,每个接口中只包含一个 invoke 函数。在Java中调用Kotlin使用函数类型的函数(高阶函数)很简单,使用Java 8 的话,Java 8 中的lambda表达式自动转换为了对应的函数类型;使用Java 8 之前的版本,需要传入实现相应接口匿名内部类对象。
/* Kotlin declaration */
fun processTheAnswer(f: (Int) -> Int) {
println(f(42))
}
/* Java 8*/
processTheAnswer(number -> number + 1);
/* Java 8 之前的版本*/
processTheAnswer(new Function1<Integer, Integer>() {
@Override
public Integer invoke(Integer number){
System.out.println(number);
return number + 1;
}
});