在Kotlin 1.1中,团队正式发布了JavaScript目标,允许开发者将Kotlin代码编译为JS并在浏览器中运行。在Kotlin 1.2中,团队增加了在JVM和JavaScript之间重用代码的可能性。现在,使用Kotlin编写的代码,可以在所有的应用程序中(包括后端,浏览器前端和Android移动应用程序)中重复使用。
想要体验Kotlin1.2新功能的同学,可以下载官方提供的IntelliJ IDEA 2017.3开发工具,或者升级老的IDE,当然也可以通过在线网站来体验。
跨平台项目是 Kotlin 1.2 中的一个新的实验性功能,它允许开发者从相同的代码库构建应用程序的多个层——后端、前端和Android应用程序,在这个跨平台方案中,主要包含三个模块。
要从通用模块中调用特定于平台的代码,可以指定所需的声明:所有特定于平台的模块需要提供实际实现声明。而在为特定平台编译多平台项目时,会生成通用及特定平台相关部分的代码。可以通过 expected 以及 actual 声明来表达通用代码对平台特定部分的依赖关系。expected 声明指定了一个 API(类、接口、注释、顶层声明等)。actual 声明或是 API 的平台相关实现,或是在外部库中 API 现有实现的别名引用。下面是官方提供的相关例子:
通用模块
// expected platform-specific API:
expect fun hello(world: String): String
fun greet() {
// usage of the expected API:
val greeting = hello("multi-platform world")
println(greeting)
}
expect class URL(spec: String) {
open fun getHost(): String
open fun getPath(): String
}
JVM 平台代码
actual fun hello(world: String): String =
"Hello, $world, on the JVM platform!"
// using existing platform-specific implementation:
actual typealias URL = java.net.URL
想要获取更多跨平台相关的信息,可以查看官方资料介绍。
请注意,目前跨平台项目只是一个实验性功能,这意味着该功能已经可以使用,但可能需要在后续版本中更改设计
在1.2的开发过程中,团队花了很多精力来优化编译系统,据官方提供的资料显示,与Kotlin 1.1相比,Kotlin带来了大约25%的性能提升,并且看到了可以进一步改进的巨大潜力,这些改进将在1.2.x更新中发布。
下图显示了使用Kotlin构建两个大型JetBrains项目的编译时间差异。
除了上面介绍的改动之外,Kotlin还在语法层面进行了部分改进,优化的部分有。
自Kotlin1.2开始,系统允许通过注解声明数组参数,从而取代arrayOf函数的数组声明方式。例如:
@CacheConfig(cacheNames = ["books", "default"])
public class BookRepositoryImpl {
// ...
}
可见,新的数组参数声明语法依赖于注解方式。
lateinit 和lazy一样,是 Kotlin中的两种不同的延迟初始化技术。在Kotlin1.2版本中,使用lateinit修饰符能够用于全局变量和局部变量了,也就是说,二者都允许延迟初始化。例如,当lambda表达式在构造一个对象时,允许将延迟初始化属性作为构造参数传过去。
class Node<T>(val value: T, val next: () -> Node<T>)
fun main(args: Array<String>) {
// A cycle of three nodes:
lateinit var third: Node<Int>
val second = Node(2, next = { third })
val first = Node(1, next = { second })
third = Node(3, next = { first })
val nodes = generateSequence(first) { it.next() }
println("Values in the cycle: ${nodes.take(7).joinToString { it.value.toString() }}, ...")
}
运行上面的代码,输出结果如下:
Values in the cycle: 1, 2, 3, 1, 2, 3, 1, ...
通过访问属性的isInitialized字段,现在开发者可以检查一个延迟初始化属性是否已经初始化。
class Foo {
lateinit var lateinitVar: String
fun initializationLogic() {
println("isInitialized before assignment: " + this::lateinitVar.isInitialized)
lateinitVar = "value"
println("isInitialized after assignment: " + this::lateinitVar.isInitialized)
}
}
fun main(args: Array<String>) {
Foo().initializationLogic()
}
运行结果为:
isInitialized before assignment: false
isInitialized after assignment: true
自1.2版本开始,Kotlin允许允许给内联函数的函数参数填写默认参数了。
inline fun Iterable.strings(transform: (E) -> String = { it.toString() }) =
map { transform(it) }
val defaultStrings = listOf(1, 2, 3).strings()
val customStrings = listOf(1, 2, 3).strings { "($it)" }
fun main(args: Array) {
println("defaultStrings = $defaultStrings")
println("customStrings = $customStrings")
运行结果为:
defaultStrings = [1, 2, 3]
customStrings = [(1), (2), (3)]
大家都知道,Kotlin的类型推断系统是非常强大的,现在Kotlin编译器也支持通过强制转换的信息,来推断出变量类型了。比如说,如果你在调用一个返回“T”的泛型方法时,并将它的返回值“T”转换为特定类型如“Foo”,编译器就会推断出这个方法调用中的“T”其实是“Foo”类型。
这个对安卓开发者而言尤其重要,因为自从API26(Android7.0)开始,findViewById变成了泛型方法,然后编译器也会正确分析该方法的调用返回值。
val button = findViewById(R.id.button) as Button
当一个变量为某个安全表达式(如校验非空)所赋值时,智能转换也同样运用于这个安全调用的接收者。
fun countFirst(s: Any): Int {
val firstChar = (s as? CharSequence)?.firstOrNull()
if (firstChar != null)
return s.count { it == firstChar } // 输入参数s被智能转换为CharSequence类型
val firstItem = (s as? Iterable<*>)?.firstOrNull()
if (firstItem != null)
return s.count { it == firstItem } // 输入参数s被智能转换为Iterable<*>类型
return -1
}
fun main(args: Array) {
val string = "abacaba"
val countInString = countFirst(string)
println("called on \"$string\": $countInString")
val list = listOf(1, 2, 3, 1, 2)
val countInList = countFirst(list)
println("called on $list: $countInList")
}
运行结果为:
called on "abacaba": 4
called on [1, 2, 3, 1, 2]: 2
另外,Lamba表达式同样支持对局部变量进行智能转换,前提是该局部变量只在Lamba表达式之前修改过。
fun main(args: Array<String>) {
val flag = args.size == 0
var x: String? = null
if (flag) x = "Yahoo!"
run {
if (x != null) {
println(x.length) // x is smart cast to String
}
}
}
运行结果为:
6
为了简化调用成员的引用,现在可以不用this关键字,::foo而不用明确的接收者this::foo。这也使得可调用的引用在你引用外部接收者的成员的lambda中更方便。
Kotlin1.2版本也弃用了很多不合理的东西。
在枚举条目中,inner class由于初始化逻辑中的问题,定义一个非嵌套的类型已经被弃用了。这会在Kotlin 1.2中引起警告,并将在Kotlin 1.3中出错。
为了与注释中的数组文字保持一致,在命名形式(foo(items = i))中传递可变参数的单个项目已被弃用。请使用具有相应数组工厂功能的扩展运算符。
foo(items = *intArrayOf(1))
在这种情况下,有一种优化可以消除冗余阵列的创建,从而防止性能下降。单参数形式在Kotlin 1.2中产生警告,并将被放在Kotlin 1.3中。
继承的泛型类型的内部类Throwable可能会违反类型安全性,因此已被弃用,Kotlin 1.2中有警告,Kotlin 1.3中有错误。
field = …已经废弃了在自定义获取器中分配只读属性的后台字段,Kotlin 1.2中有警告,Kotlin 1.3中有错误。
Kotlin标准库现在完全兼容Java 9模块系统,该系统禁止拆分包(多个jar文件在同一个包中声明类)。为了支持这一点,新的文物kotlin-stdlib-jdk7 和kotlin-stdlib-jdk8介绍,取代旧的kotlin-stdlib-jre7和kotlin-stdlib-jre8。
为确保与新模块系统的兼容性,Kotlin做出的另一个更改是将kotlin.reflect从kotlin-reflect库中移除。如果您正在使用它们,则需要切换到使用kotlin.reflect.full软件包中的声明,这是自Kotlin 1.1以来支持的声明。
为新的扩展Iterable,Sequence以及CharSequence覆盖这些用例如缓冲或批处理(chunked),滑动窗口和计算滑动平均(windowed),和随后的项目的处理对(zipWithNext)。
fun main(args: Array) {
val items = (1..9).map { it * it }
val chunkedIntoLists = items.chunked(4)
val points3d = items.chunked(3) { (x, y, z) -> Triple(x, y, z) }
val windowed = items.windowed(4)
val slidingAverage = items.windowed(4) { it.average() }
val pairwiseDifferences = items.zipWithNext { a, b -> b - a }
println("items: $items\n")
println("chunked into lists: $chunkedIntoLists")
println("3D points: $points3d")
println("windowed by 4: $windowed")
println("sliding average by 4: $slidingAverage")
println("pairwise differences: $pairwiseDifferences")
}
为了操纵列表,Kotlin加入了一组扩展函数:fill,replaceAll和shuffle对MutableList,shuffled用于只读List。
fun main(args: Array) {
val items = (1..5).toMutableList()
items.shuffle()
println("Shuffled items: $items")
items.replaceAll { it * 2 }
println("Items doubled: $items")
items.fill(5)
println("Items filled with 5: $items")
}
运行结果为:
Shuffled items: [5, 3, 1, 2, 4]
Items doubled: [10, 6, 2, 4, 8]
Items filled with 5: [5, 5, 5, 5, 5]
为了满足一些特殊的需求,Kotlin 1.2添加了一些常见的数学运算API。
现在,Kotlin可以使用Serializable来序列化正则表达式的层次结构。
自1.0版以来,Kotlin支持复杂控制流的表达式,例如try-catch表达式和内联函数调用。但是,如果构造函数调用的参数中存在这样的表达式时,一些字节码处理工具不能很好地处理这些代码。为了缓解这种字节码处理工具的用户的这个问题,我们添加了一个命令行选项(-Xnormalize-constructor-calls=MODE),它告诉编译器为这样的结构生成更多的类Java字节码。
其中,这里的MODE有以下情况:
在Kotlin 1.2之前,接口成员在针对JVM 1.6的情况下重写Java默认方法会在超级调用上产生一个警告:Super calls to Java default methods are deprecated in JVM target 1.6. Recompile with ‘-jvm-target 1.8’。在Kotlin 1.2中,会出现一个错误,因此需要使用JVM target 1.8来编译这些代码。
调用x.equals(null)上被映射到Java原始(平台类型Int!,Boolean!,Short!, ,Long!,Float!,Double!)Char!返回不正确true时x为空。从Kotlin 1.2开始,调用x.equals(…)一个平台类型的null值会抛出一个NPE (但是x == …不会)。
要返回到1.2之前的行为,请将该标志传递-Xno-exception-on-explicit-equals-for-boxed-null给编译器。
在以前的版本中,在平台类型的空值上调用的内联扩展函数没有检查接收器是否为null,并因此允许null转义到其他代码中。Kotlin 1.2中强制执行此检查,如果接收方为空,则抛出异常。
JS类型的数组支持将Kotlin原始数组(例如IntArray,DoubleArray)转换为JavaScript类型的数组,这以前是可选入功能,默认情况下已启用。
除此之外,Kotlin的编译器现在提供一个将所有警告视为错误的选项。使用-Werror命令行,或者修改如下配置:
compileKotlin {
kotlinOptions.allWarningsAsErrors = true
}
想要了解更多的官方知识介绍,请查看:Kotlin 1.2带来了什么新特性