多平台项目是 Kotlin 1.2 中的一个新的实验性功能,允许你在 Kotlin – JVM, JavaScript 和(将来的)Native 上所支持的目标平台之间重用代码。在多平台项目中,你有三种模块:
一个通用(common)的模块 —— 包含非特定于任何平台的代码,以及不附带依赖于平台的 API 实现的声明。
平台(platform)模块 —— 包含用于特定平台的通用模块中与平台相关声明的实现,以及其他平台相关代码。
常规(regular)模块针对特定的平台,可以是平台模块的依赖,也可以是依赖的平台模块。
在为特定平台编译多平台项目时,会生成通用及特定平台相关部分的代码。
多平台项目支持的一个关键特性是可以通过 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
详细信息请参考此文档,并按照步骤构建多平台项目。
从 Kotlin 1.2 开始,注解的数组参数可以使用新的数组常量语法而不是 arrayOf
函数来传递:
@CacheConfig(cacheNames = ["books", "default"]) public class BookRepositoryImpl { // ... }
数组常量语法被限制为注释参数。
lateinit
修饰符现在可以用在顶级属性和局部变量上。例如,当一个 lambda 作为构造函数参数传递给一个对象时,后者可以用于引用另一个必须稍后定义的对象:
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() }}, ...") }
循环中的值:1, 2, 3, 1, 2, 3, 1, ...
目标平台:运行 Kotlin 1.2.0 版本的 JVM
现在可以在属性引用上使用 isInitialized
来检查 lateinit 变量是否已经被初始化:
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
目标平台:运行 Kotlin 1.2.0 版本的 JVM
内联函数现在允许其内联函数参数具有默认值:
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") }
defaultStrings = [1, 2, 3]
customStrings = [(1), (2), (3)]
目标平台:运行 Kotlin 1.2.0 版本的 JVM
Kotlin 编译器现在可以使用类型转换信息进行类型推断。如果调用一个返回类型参数 T 并将返回值转换为特定类型 Foo 的泛型方法,则编译器现在可以理解此调用的 T 需要绑定到 Foo 类型。
这对 Android 开发者来说尤其重要,因为编译器现在可以在 Android API level 26 中正确分析范型 findViewById
调用:
val button = findViewById(R.id.button) as Button
当一个变量从一个安全调用表达式中被赋值并且被检查为 null 时,smart cast 也被应用到安全调用接收器中:
un countFirst(s: Any): Int { val firstChar = (s as? CharSequence)?.firstOrNull() if (firstChar != null) return s.count { it == firstChar } // s: Any is smart cast to CharSequence val firstItem = (s as? Iterable<*>)?.firstOrNull() if (firstItem != null) return s.count { it == firstItem } // s: Any is smart cast to 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": 4
called on [1, 2, 3, 1, 2]: 2
目标平台:运行 Kotlin 1.2.0 版本的 JVM
而且,现在允许在 lambda 中进行智能的强制转换,这些局部变量只在 lambda 之前被修改:
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
目标平台:运行 Kotlin 1.2.0 版本的 JVM
现在可以使用 ::foo 替代 this::foo,写入一个绑定的可调用的引用,而不用明确的接收器。这也使得可调用的引用在你引用外部接收者的成员的 lambda 中更方便使用。
早些时候,Kotlin 使用了 try 块中的赋值,以在块之后进行 smart casts,这可能会破坏类型及 null 值的安全性并导致运行时失败。这个版本修复了此问题,使 smart casts 更严格,但破坏了一些依赖这种 smart casts 的代码。
要切换到旧的 smart casts 行为,传递 fallback 标志 -Xlegacy-smart-cast-after-try
作为编译器参数。它将在 Kotlin 1.3 中被弃用。
当从已经具有相同签名的拷贝函数的类型派生数据类时,为数据类生成的 copy 实现使用父类型的默认函数,会导致出现与预期相反的行为,如果父类型没有默认参数,则在运行时失败
导致复制冲突的继承已经被 Kotlin 1.2 中的警告所取代,并且在 Kotlin 1.3 中这将会提示是错误的。
在枚举项中,由于初始化逻辑中的问题,定义一个不是内部类的嵌套类型的功能已经被弃用。在 Kotlin 1.2 中这将会引起警告,并将在 Kotlin 1.3 中报错。
为了与注解中的数组常量保持一致,在命名的表单(foo(items = i)
) 中为 vararg 参数传递的单项目已被弃用。请使用具有相应数组工厂函数的展开运算符:
foo(items = *intArrayOf(1))
在这种情况下,有一种优化可以消除冗余数组的创建,从而防止性能下降。单一参数的表单在 Kotlin 1.2 中会引起警告,并将在 Kotlin 1.3 中被移除。
Kotlin 标准库现在完全兼容 Java 9 的模块系统,它会禁止对包进行拆分(多个 jar 包文件在同一个包中声明类)。为了支持这一点,引入了新的 artifacts kotlin-stdlib-jdk7
和 kotlin-stdlib-jdk8
,取代了旧的 kotlin-stdlib-jre7
和 kotlin-stdlib-jre8
。
新 artifacts 中的声明从 Kotlin 的角度来看在相同的包名下可见的,但是对 Java 而言它们有不同的包名。因此,切换到新的 artifacts 不需要对源代码进行任何更改。
确保与新模块系统兼容的另一个更改是从 kotlin-reflect 库中移除 kotlin.reflect 包中的弃用声明。如果使用它们,则需要使用 kotlin.reflect.full 包中的声明,自 Kotlin 1.1 以来该包是被支持的。
Iterable<T>
, Sequence<T>
和 CharSequence
的新扩展包含了诸如缓冲或批处理(chunked
),滑动窗口和计算滑动平均值 (windowed
)以及处理 subsequent item 对 (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\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") }
输出结果:
items: [1, 4, 9, 16, 25, 36, 49, 64, 81]
chunked into lists: [[1, 4, 9, 16], [25, 36, 49, 64], [81]]
3D points: [(1, 4, 9), (16, 25, 36), (49, 64, 81)]
windowed by 4: [[1, 4, 9, 16], [4, 9, 16, 25], [9, 16, 25, 36], [16, 25, 36, 49], [25, 36, 49, 64], [36, 49, 64, 81]]
sliding average by 4: [7.5, 13.5, 21.5, 31.5, 43.5, 57.5]
pairwise differences: [3, 5, 7, 9, 11, 13, 15, 17]
目标平台:运行 Kotlin 1.2.0 版本的 JVM
评论删除后,数据将无法恢复
评论(3)
引用来自“东东-”的评论
1.3 先别搞了, 搞个像 npm,cargo 一样的包管理器再说。