在Kotlin 1.1中,团队正式发布了JavaScript目标,允许开发者将Kotlin代码编译为JS并在浏览器中运行。在Kotlin 1.2中,团队增加了在JVM和JavaScript之间重用代码的可能性。现在,使用Kotlin编写的代码,可以在所有的应用程序中(包括后端,浏览器前端和Android移动应用程序)中重复使用。
想要体验Kotlin1.2新功能的同学,可以下载官方提供的IntelliJ IDEA 2017.3开发工具,或者升级老的IDE,当然也可以通过在线网站来体验。
跨平台
跨平台项目是 Kotlin 1.2 中的一个新的实验性功能,它允许开发者从相同的代码库构建应用程序的多个层——后端、前端和Android应用程序,在这个跨平台方案中,主要包含三个模块。
通用(common)模块:包含非特定于任何平台的代码,以及不附带依赖于平台的 API 实现的声明。
平台(platform)模块:包含用于特定平台的通用模块中与平台相关声明的实现,以及其他平台相关代码。
常规(regular)模块:针对特定平台,可以是平台模块的某些依赖,也可以是依赖的平台模块。
要从通用模块中调用特定于平台的代码,可以指定所需的声明:所有特定于平台的模块需要提供实际实现声明。而在为特定平台编译多平台项目时,会生成通用及特定平台相关部分的代码。可以通过 expected 以及 actual 声明来表达通用代码对平台特定部分的依赖关系。expected 声明指定了一个 API(类、接口、注释、顶层声明等)。actual 声明或是 API 的平台相关实现,或是在外部库中 API 现有实现的别名引用。下面是官方提供的相关例子:
通用模块
// expected platform-specific API:expect fun hello(world: String): Stringfun 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.URL12345
想要获取更多跨平台相关的信息,可以查看官方资料介绍。
请注意,目前跨平台项目只是一个实验性功能,这意味着该功能已经可以使用,但可能需要在后续版本中更改设计
编译性能
在1.2的开发过程中,团队花了很多精力来优化编译系统,据官方提供的资料显示,与Kotlin 1.1相比,Kotlin带来了大约25%的性能提升,并且看到了可以进一步改进的巨大潜力,这些改进将在1.2.x更新中发布。
下图显示了使用Kotlin构建两个大型JetBrains项目的编译时间差异。
语法与库优化
除了上面介绍的改动之外,Kotlin还在语法层面进行了部分改进,优化的部分有。
通过注解声明数组变量
自Kotlin1.2开始,系统允许通过注解声明数组参数,从而取代arrayOf函数的数组声明方式。例如:
@CacheConfig(cacheNames = ["books", "default"])public class BookRepositoryImpl {
// ...}
可见,新的数组参数声明语法依赖于注解方式。
关键字lateinit
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, ...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: falseisInitialized after assignment: true12
内联函数默认参数
自1.2版本开始,Kotlin允许允许给内联函数的函数参数填写默认参数了。
inline fun <E> Iterable<E>.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<String>) { println("defaultStrings = $defaultStrings")
println("customStrings = $customStrings")123456789
运行结果为:
defaultStrings = [1, 2, 3]customStrings = [(1), (2), (3)]12
变量类型推断
大家都知道,Kotlin的类型推断系统是非常强大的,现在Kotlin编译器也支持通过强制转换的信息,来推断出变量类型了。比如说,如果你在调用一个返回“T”的泛型方法时,并将它的返回值“T”转换为特定类型如“Foo”,编译器就会推断出这个方法调用中的“T”其实是“Foo”类型。
这个对安卓开发者而言尤其重要,因为自从API26(Android7.0)开始,findViewById变成了泛型方法,然后编译器也会正确分析该方法的调用返回值。
val button = findViewById(R.id.button) as Button1
智能转换
当一个变量为某个安全表达式(如校验非空)所赋值时,智能转换也同样运用于这个安全调用的接收者。
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<String>) { 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": 4called on [1, 2, 3, 1, 2]: 212
另外,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
foo的简写
为了简化调用成员的引用,现在可以不用this关键字,::foo而不用明确的接收者this::foo。这也使得可调用的引用在你引用外部接收者的成员的lambda中更方便。
弃用
Kotlin1.2版本也弃用了很多不合理的东西。
弃用:枚举条目中的嵌套类型
在枚举条目中,inner class由于初始化逻辑中的问题,定义一个非嵌套的类型已经被弃用了。这会在Kotlin 1.2中引起警告,并将在Kotlin 1.3中出错。
弃用:vararg单个命名参数
为了与注释中的数组文字保持一致,在命名形式(foo(items = i))中传递可变参数的单个项目已被弃用。请使用具有相应数组工厂功能的扩展运算符。
foo(items = *intArrayOf(1))1
在这种情况下,有一种优化可以消除冗余阵列的创建,从而防止性能下降。单参数形式在Kotlin 1.2中产生警告,并将被放在Kotlin 1.3中。
弃用:扩展Throwable的泛型内部类
继承的泛型类型的内部类Throwable可能会违反类型安全性,因此已被弃用,Kotlin 1.2中有警告,Kotlin 1.3中有错误。
弃用:只读属性的后台字段
field = …已经废弃了在自定义获取器中分配只读属性的后台字段,Kotlin 1.2中有警告,Kotlin 1.3中有错误。
标准库
Kotlin标准库与拆分包
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以来支持的声明。
窗口,分块,zipWithNext
为新的扩展Iterable,Sequence以及CharSequence覆盖这些用例如缓冲或批处理(chunked),滑动窗口和计算滑动平均(windowed),和随后的项目的处理对(zipWithNext)。
fun main(args: Array<String>) {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")
println("chunked into lists: $chunkedIntoLists")
println("3D points: $points3d")
println("windowed by 4: $windowed")
println("sliding average by 4: $slidingAverage")
println("pairwise differences: $pairwiseDifferences")
}
fill, replaceAll, shuffle/shuffled
为了操纵列表,Kotlin加入了一组扩展函数:fill,replaceAll和shuffle对MutableList,shuffled用于只读List。
fun main(args: Array<String>) {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。
常量:PI和E;
三角函数:cos,sin,tan和它们的反:acos,asin,atan,atan2,
双曲:cosh,sinh,tanh和它们的反:acosh,asinh,atanh
求幂:pow(扩展函数),sqrt,,hypot ;expexpm1
对数:log,log2,log10,ln,ln1p,
四舍五入: ceil,floor,truncate,round(半连)的功能;
roundToInt,roundToLong(半整数)扩展函数;
符号和绝对值: abs和sign功能; absoluteValue和sign扩展属性; withSign 扩展功能;max和min两个价值观;
二进制表示: ulp 扩展属性; nextUp,nextDown,nextTowards扩展函数;toBits,toRawBits,Double.fromBits(这些是在kotlin包)。
正则表达式可序列化
现在,Kotlin可以使用Serializable来序列化正则表达式的层次结构。
JVM
构造函数调用规范化
自1.0版以来,Kotlin支持复杂控制流的表达式,例如try-catch表达式和内联函数调用。但是,如果构造函数调用的参数中存在这样的表达式时,一些字节码处理工具不能很好地处理这些代码。为了缓解这种字节码处理工具的用户的这个问题,我们添加了一个命令行选项(-Xnormalize-constructor-calls=MODE),它告诉编译器为这样的结构生成更多的类Java字节码。
其中,这里的MODE有以下情况:
disable (默认) - 以和Kotlin 1.0和1.1相同的方式生成字节码;
enable - 为构造函数调用生成类似Java的字节码。这可以改变类加载和初始化的顺序;
preserve-class-initialization -为构造函数调用生成类似Java的字节码,确保保持类的初始化顺序。这可能会影响应用程序的整体性能;只有在多个类之间共享一些复杂的状态并在类初始化时更新时才使用它。
Java默认方法调用
在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)
调用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中强制执行此检查,如果接收方为空,则抛出异常。
JavaScript
TypedArrays支持
JS类型的数组支持将Kotlin原始数组(例如IntArray,DoubleArray)转换为JavaScript类型的数组,这以前是可选入功能,默认情况下已启用。
除此之外,Kotlin的编译器现在提供一个将所有警告视为错误的选项。使用-Werror命令行,或者修改如下配置:
compileKotlin {kotlinOptions.allWarningsAsErrors = true}123