Kotlin的特点及各版本新特性

文章地址:sguotao.top/Kotlin-2018…

Kotlin语言的特点

Kotlin语义是一座小岛,是一种在Java虚拟机上运行的静态类型编程语言,Kotlin的目的就是要兼具现代编程语言的所有优点,同时还要具有Java语言的跨平台性,并且要做到简洁。它也可以被编译成为JavaScript源代码。Kotlin与Java 100%兼容,能够运行在Android平台和浏览器上。

Kotlin的应用场景

1.Kotlin Script 在IntellJ Idea中创建后缀为.kts的脚本文件,可以直接运行。Gradle在3.0之后部分支持Kotlin作为其脚本语言。

2.Java虚拟机应用 常见的Web应用,JavaFx应用,Kotlin都完美支持。Kotlin非常适合开发服务器端应用程序,允许编写简洁和富有表现力的代码,同时保持与现有基于Java的技术堆栈的完全兼容性和平滑的学习曲线;

3.前端开发 Kotlin从1.1版本开始,Kotlin可以编译成JavaScript代码,运行在浏览器上。目前Kotlin已经支持ECMAScript 5.1,有计划最终支持ECMAScript 2015。

4.Android应用开发 这是Kotlin应用的主战场,Kotlin非常适合开发Android应用程序,将现代语言的所有优势带入Android平台,而不会引入任何新的限制。

5.Native程序开发 直接将Kotlin代码编译成机器码,不依赖JVM,区别于JNI,能与C直接进行交互。Kotlin支持C语言,支持Objective-C的相互操作,其主要实现原理是将Kotlin编译为在没有任何VM的情况下运行的本机二进制文件。

Kotlin各版本的新特性

2016年2月15日,JetBrains发布了Kotlin的第一个官方的release版本,并承诺从此版本开始向后兼容。

Kotlin 1.1 的新特性

Kotlin1.1 开始支持JavaScript所有语言特性

在kotlin1.1版本中开始正式支持JavaScript的所有语言特性,并且提供了很多工具用来迁移前端的开发环境。

Kotlin1.1 引入协程

在kotlin1.1版本中提出了协程,支持async / await , yield and similar 的程序模式。协程可以认为是轻量级的线程,支持挂起和恢复。通过async { ... } 来开启一个协程,通过 await() 挂起协程。

Kotlin1.1 引入的现代语言特性

kotlin1.1中支持更多的现代编程语言的特性,诸如:

1. 输入别名

kotlin1.1中允许对已经存在的类型定义别名,这对集合类中的泛型,函数类型尤其适用。比如:

//使用typealias关键字定义别名
typealias OscarWinners = Map<String, String>

fun countLaLaLand(oscarWinners: OscarWinners) =
    oscarWinners.count { it.value.contains("La La Land") }
复制代码

2. ::引用

::在kotlin中是作为一个操作符存在的,使用 :: 操作符来获取指向特定对象实例的方法或属性的成员引用。比如获取一个类的kclass字节码,可以写为:

val c = MyClass::class
复制代码

同时 :: 也可以用在方法引用上,比如:

list.forEach(::println)
复制代码

这里就引用了kotlin标准库中的println方法。

3. 密封数据类

kotlin1.1中删除了在1.0中对密封类和数据类的限制。可以在同一个文件中的任意地方定义密封类的子类,而不是在密封类中使用嵌套的方式,数据类现在也可以扩展其他类。

密封类常用来表示首先的继承结构,某种意义上说,密封类是枚举的一种扩展,每一个枚举的常量是枚举的实例,枚举是实例有限,在密封类中,是子类有限。定义密封类使用sealed关键字,比如:

sealed class Expr
复制代码

数据类常用来作为实体bean来使用,使用data关键字进行声明,比如:

data class Const(val number: Double) : Expr()
复制代码

4. lambda中的解构

即将一个对象解构成多个变量方便使用。比如:

for ((key, value) in map) {
 // 使用该 key、value 做些事情
}
复制代码

5. 强调为未使用的参数

对具有多个参数的lambda表达式,可以使用“_”来替代没有使用到的参数,比如:

map.forEach { _, value -> println("$value!") }
复制代码

6. 强调了数字字面值

比如金额等,可以通过“_”来间隔,如:

val oneMillion = 1_000_000
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
复制代码

7. 短的语法属性

对于私有化的成员变量,get方法可以省略成员变量的类型,编译器会推断出类型,比如:

data class Person(val name: String, val age: Int) {
val isAdult get() = age >= 20 // 属性类型推断为 “Boolean”
}
复制代码

8. 内联inline属性访问器

对于成员变量,当使用inline关键字修饰时,该成员变量对应的get()和set()会作为内联函数。所谓的内联函数,就是在函数声明时,使用inline关键字进行修饰,使用内联函数能够更好的提升性能。比如:定义一个高阶函数,接受一个lambda表达式(无参无返回值):

fun nonInlined(block: () -> Unit) {
    println("before")
    block()
    println("after")
}
复制代码

当调用这个函数时:

nonInlined {
    println("do something here")
}
复制代码

其实现的方式是创建一个Function实例,将lambda表达式中的内容包装在其中,类似这样的方式:

nonInlined(new Function() {
    @Override
    public void invoke() {
        System.out.println("do something here");
    }
});
复制代码

而使用inline的方式,不会创建Function实例,内联函数块内调用周围的代码会被拷贝到调用点,类似这样的方式:

System.out.println("before");
System.out.println("do something here");
System.out.println("after");
复制代码

因为没有Function实例的创建,减少了系统开销,提供了更好的性能。

9. 局部委托属性

在kotlin1.1中开始支持局部委托属性,那什么是委托属性呢?

有一些常见的属性类型,虽然我们可以在每次需要的时候手动实现它们, 但是如果能够只实现一次并放入一个库会更好。

委托属性的语法是:

val/var <属性名>: <类型> by <表达式>。
复制代码

在 by 后面的表达式是该委托, 因为属性对应的 get()(与 set())会被委托给它的 getValue() 与 setValue() 方法。 属性的委托不必实现任何的接口,但是需要提供一个 getValue() 或setValue()函数。使用var声明的属性需要提供这两个方法,使用val声明的属性,需要提供getValue()方法,一个自定义属性委托的示例:

class Example {
    var p: String by Delegate()
}

class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}
复制代码

当我们对Example的p属性进行访问时,

val e = Example()
println(e.p)

e.p = "NEW"
复制代码

会有如下的输出结果:

Example@33a17727, thank you for delegating ‘p’ to me!
NEW has been assigned to ‘p’ in Example@33a17727.
复制代码

10.拦截委托属性绑定

对于属性委托,可以使用provideDelegate操作符来拦截属性与委托之间的绑定。比如想要在绑定控件之前,检查属性的名称,可以通过委托属性来实现。

fun main(args: Array<String>) {
    val customUI: CustomUI = CustomUI()
    
    println(customUI.text)
    println(customUI.image)
}

class CustomDelegate<T>(t: T) {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, 这里委托了 ${property.name} 属性"
    }
}

class ResourceLoader<T>(id: Int) {
    operator fun provideDelegate(thisRef: CustomUI, prop: KProperty<*>): CustomDelegate<T?> {
        println("这里拦截了属性与委托之间的绑定...")
        checkProperty(thisRef, prop.name)
        // 创建委托
        var t: T? = null
        return CustomDelegate(t)
    }

    private fun checkProperty(thisRef: CustomUI, name: String) {
        println(name)
    }
}

fun <T> bindResource(id: Int): ResourceLoader<T> {
    return ResourceLoader<T>(id)
}

class CustomUI {
    val image by bindResource<String>(10000)
    val text by bindResource<String>(20000)
}
复制代码

11.通用的枚举值访问

在kotlin1.1中可以通过泛型的方式来列举枚举中的值。比如:

enum class RGB { RED, GREEN, BLUE }

inline fun <reified T : Enum<T>> printAllValues() {
    print(enumValues<T>().joinToString { it.name })
}
复制代码

12.DSL隐式接受者作用域进行了限制

在kotlin1.1中,@DslMarker 注解对DSL隐式接受者的作用域进行了限制,这在kotlin1.0中是没有涉及的。如Html构建器:

table {
     tr {
            td { + "Text" }
        }
}
复制代码

在 Kotlin 1.0 中,传递给 td 的 lambda 表达式中的代码可以访问三个隐式接收者:传递给 table 、tr 和 td 的。这允许你调用在上下文中没有意义 的方法,例如在 td 里面调用 tr ,从而在 中放置一个 标签。

在 Kotlin 1.1 中,限制这种情况,通过定义标记有 @DslMarker 元注解的注解并将其应用于标记类的基类,以使只有在 td 的隐式接收者上定义的方法会在传给 td 的 lambda 表达式。

13.kotlin1.1中使用rem运算符替代了kotlin1.0中的mod运算符

即在kotlin1.1中表达式a%b对应的函数为a.rem(b)。

Kotlin1.1 对标准库的扩展

1.字符串到数字的转换

在kotlin1.1中对String类进行了扩展,如String.toIntOrNull(): Int?当无效数字转换为数字类型时,不再抛出异常,类似的还有String.toDoubleOrNull(): Double?等,比如:

  val port = System.getenv("PORT")?.toIntOrNull()?: 80
复制代码

2.onEach()

onEach()会对集合及序列中的每个元素执行一次操作,使用方法与forEach()相似,但是forEach()函数只对每个元素执行给定的[action]。而onEach()函数对每个元素执行给定的[action],然后返回集合本身,比如:

inputDir.walk()
        .filter { it.isFile && it.name.endsWith(".txt") }
        .onEach { println("Moving $it to $outputDir") }
        .forEach { moveFile(it, File(outputDir, it.toRelativeString(inputDir))) }
复制代码

3.also()、takeIf() 和 takeUnless()

also()使用方法与apply()相似,接收一个Receiver,执行一些动作,并且返回该接受者。区别是在also()内部可以使用it关键字,在apply()内部使用this关键字。示例:

fun Block.copy() = Block().also {
    it.content = this.content
}
复制代码

takeIf就像单个值的filter,它检查接收者是否满足该谓词,并在满足时返回该接收者否则不满足时返回null。结合elvis-操作符使用,比如:

// 对现有的 outDirFile 做些事情
 val outDirFile = File(outputDir.path).takeIf { it.exists() } ?: return false 
  // 对输入字符串中的关键字索引做些事情,鉴于它已找到
 val index = input.indexOf(keyword).takeIf { it >= 0 } ?: error("keyword not found")
复制代码

takeUnless与takeIf相同,只是它采用了反向谓词。当它不满足谓词时返回接收者,否则返回null。因此,上面的示例之一可以用takeUnless重写如下:

 val index = input.indexOf(keyword).takeUnless { it < 0 } ?: error("keyword not found")
复制代码

4.groupingBy()

可以用于按照键对集合进行分组,并同时折叠每个组。例如,它可以用于计算文本中字符的频率:

val frequencies = words.groupingBy { it.first() }.eachCount()
复制代码

5.Map.toMap() 和 Map.toMutableMap()

用于实现复制映射。

class ImmutablePropertyBag(map: Map<String, Any>) {
    private val mapCopy = map.toMap()
}
复制代码

6.Map.minus(key)

运算符 plus 提供了一种将键值对添加到只读映射中以生成新映射的方法,但是没有一种简单的方法来做相反的操作:从映射中删除一个键采用不那 么直接的方式如 Map.filter() 或 Map.filterKeys() 。现在运算符 minus 填补了这个空白。有 4 个可用的重载:用于删除单个键、键的集合、键 的序列和键的数组。

 val map = mapOf("key" to 42)
val emptyMap = map - "key"
复制代码

7.minOf() 和 maxOf()

这些函数可用于查找两个或三个给定值中的最小和最大值,其中值是原生数字或 Comparable 对象。每个函数还有一个重载,它接受一个额外的 Comparator 实例。示例:

val list1 = listOf("a", "b")
val list2 = listOf("x", "y", "z")
val minSize = minOf(list1.size, list2.size)
val longestList = maxOf(list1, list2, compareBy { it.size })
复制代码

8.类似数组的列表实例化函数

类似于 Array 构造函数,现在有创建 List 和 MutableList 实例的函数,并通过调用 lambda 表达式来初始化每个元素:

 val squares = List(10) { index -> index * index }
val mutable = MutableList(10) { 0 }
复制代码

9.Map.getValue()

Map 上的这个扩展函数返回一个与给定键相对应的现有值,或者抛出一个异常,提示找不到该键。如果该映射是用 withDefault 生成的,这个函数将 返回默认值,而不是抛异常。

val map = mapOf("key" to 42)
// 返回不可空 Int 值 42
val value: Int = map.getValue("key")
val mapWithDefault = map.withDefault { k -> k.length } // 返回 4
val value2 = mapWithDefault.getValue("key2")
// map.getValue("anotherKey") // <- 这将抛出 NoSuchElementException
复制代码

10.抽象集合

这些抽象类可以在实现 Kotlin 集合类时用作基类。对于实现只读集合,有 AbstractCollection 、AbstractList 、AbstractSet 和 AbstractMap ,而对于可变集合,有 AbstractMutableCollection 、AbstractMutableList 、AbstractMutableSet 和 AbstractMutableMap。在JVM上,这些抽象可变集合从JDK的抽象集合继承了大部分的功能。

11.数组处理函数

标准库现在提供了一组用于逐个元素操作的函数:比较( contentEquals和contentDeepEquals ),哈希码计算( contentHashCode和contentDeepHashCode )以及转换为字符串( contentToString和contentDeepToString )。 它们都支持JVM(它们作为java.util.Arrays的相应函数的别名)和JS(在Kotlin标准库中提供实现)

fun main(args: Array<String>) {
    val array = arrayOf("a", "b", "c")
    println(array.toString())  // JVM implementation: type-and-hash gibberish
    println(array.contentToString())  // nicely formatted as list
}
复制代码

Kotlin1.1 其它新特性

JVM后端

Java 8 字节码支持

Kotlin 现在可以选择生成 Java 8 字节码(命令行选项 -jvm-target 1.8 或者 Ant/Maven/Gradle 中的相应选项)。目前这并不改变字节码的语义 (特别是,接口和 lambda 表达式中的默认方法的生成与 Kotlin 1.0 中完全一样)。

Java 8 标准库支持

现在有支持在 Java 7 和 8 中新添加的 JDK API 的标准库的独立版本。如果你需要访问新的 API,请使用 kotlin-stdlib-jre7 和 kotlin- stdlib-jre8 maven 构件,而不是标准的 kotlin-stdlib 。这些构件是在 kotlin-stdlib 之上的微小扩展,它们将它作为传递依赖项带到项目中。

字节码中的参数名

Kotlin 现在支持在字节码中存储参数名。这可以使用命令行选项 -java-parameters 启用。

常量内联

编译器现在将 const val 属性的值内联到使用它们的位置。

可变闭包变量

用于在 lambda 表达式中捕获可变闭包变量的装箱类不再具有 volatile 字段。此更改提高了性能,但在一些罕⻅的使用情况下可能导致新的竞争条件。 如果受此影响,需要提供自己的同步机制来访问变量。

javax.scripting 支持

Kotlin 现在与javax.script AP(I JSR-223)集成。其 API 允许在运行时求值代码段:

val engine = ScriptEngineManager().getEngineByExtension("kts")!! engine.eval("val x = 3")
println(engine.eval("x + 2")) // 输出 5
复制代码

kotlin.reflect.full

为 Java 9 支持准备,在 kotlin-reflect.jar 库中的扩展函数和属性已移动到 kotlin.reflect.full 包中。旧包( kotlin.reflect )中的名称已弃用,将在 Kotlin 1.2 中删除。请注意,核心反射接口(如 KClass )是 Kotlin 标准库(而不是 kotlin-reflect )的一部分,不受移动影响。

JavaScript 后端

统一标准库

Kotlin 标准库的大部分目前可以从代码编译成 JavaScript 来使用。特别是,关键类如集合( ArrayList 、HashMap 等)、异常( IllegalArgumentException 等)以及其他几个关键类( StringBuilder 、Comparator )现在都定义在 kotlin 包下。在 JVM 平台上,一些名称是相应 JDK 类的类型别名,而在 JS 平台上,这些类在 Kotlin 标准库中实现。

更好的代码生成

JavaScript 后端现在生成更加可静态检查的代码,这对 JS 代码处理工具(如 minifiers、optimisers、linters 等)更加友好。

external 修饰符

如果你需要以类型安全的方式在 Kotlin 中访问 JavaScript 实现的类,你可以使用 external 修饰符写一个 Kotlin 声明。(在 Kotlin 1.0 中,使用了@native 注解。)与 JVM 目标平台不同,JS 平台允许对类和属性使用 external 修饰符。例如,可以按以下方式声明 DOM Node 类:

external class Node {
    val firstChild: Node
    fun appendChild(child: Node): Node
    fun removeChild(child: Node): Node
// 等等 }
复制代码

改进的导入处理

现在可以更精确地描述应该从 JavaScript 模块导入的声明。如果在外部声明上添加 @JsModule("<模块名>") 注解,它会在编译期间正确导入到模 块系统(CommonJS或AMD)。例如,使用 CommonJS,该声明会通过 require(......) 函数导入。此外,如果要将声明作为模块或全局 JavaScript 对象 导入,可以使用 @JsNonModule 注解。例如,以下是将 JQuery 导入 Kotlin 模块的方法:

external interface JQuery {
    fun toggle(duration: Int = definedExternally): JQuery
    fun click(handler: (Event) -> Unit): JQuery
}
@JsModule("jquery")
@JsNonModule
@JsName("$")
external fun jquery(selector: String): JQuery
复制代码

在这种情况下,JQuery 将作为名为 jquery 的模块导入。或者,它可以用作 $-对象,这取决于Kotlin编译器配置使用哪个模块系统。 你可以在应用程序中使用如下所示的这些声明:

fun main(args: Array<String>) {
    jquery(".toggle-button").click {
        jquery(".toggle-panel").toggle(300)
    }
}
复制代码

Kotlin 1.2 的新特性

在1.2的开发过程中,团队花了很多精力来优化编译系统,据官方提供的资料显示,与Kotlin 1.1相比,Kotlin带来了大约25%的性能提升,并且看到了可以进一步改进的巨大潜力,这些改进将在1.2.x更新中发布。

Kotlin1.2 引入多平台性

跨平台项目是 Kotlin 1.2 中的一个新的实验性功能,它允许开发者从相同的代码库构建应用程序的多个层——后端、前端和Android应用程序,在这个跨平台方案中,主要包含三个模块。

  1. 通用(common)模块:包含非特定于任何平台的代码,以及不附带依赖于平台的 API 实现的声明。
  2. 平台(platform)模块:包含用于特定平台的通用模块中与平台相关声明的实现,以及其他平台相关代码。
  3. 常规(regular)模块:针对特定平台,可以是平台模块的某些依赖,也可以是依赖的平台模块。

多平台项目支持的一个主要特点是可以通过预期声明与实际声明来表达公共代码对平台相关部分的依赖关系。一个预期声明指定一个AP(I 类、接口、注 解、顶层声明等)。一个实际声明要么是该 API 的平台相关实现,要么是一个引用到在一个外部库中该 API 的一个既有实现的别名。

Kotlin1.2 引入的现代语言特性

1.注解中的数组常量

从 Kotlin 1.2 开始,注解的数组参数可以使用新的数组常量语法而不是 arrayOf 函数来传递:

@CacheConfig(cacheNames = ["books", "default"]) //数组常量语法被限制为注释参数。
public class BookRepositoryImpl {
// ...... }
复制代码

2.lateinit 顶层属性与局部变量

lateinit 修饰符现在可以用在顶级属性和局部变量上。例如,当一个 lambda 作为构造函数参数传递给一个对象时,后者可以用于引用另一个必须稍后定义的对象:

class Node<T>(val value: T, val next: () -> Node<T>)
fun main(args: Array<String>) { // 三个节点的环:
   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() }}, ...")
}
复制代码

3.检查 lateinit 变量是否已初始化

现在可以通过属性引用的 isInitialized 来检测该 lateinit var 是否已初始化:

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
复制代码

4.内联函数带有默认函数式参数

内联函数现在允许其内联函式数参数具有默认值:

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)]
复制代码

5.源自显式类型转换的信息会用于类型推断

Kotlin编译器现在支持通过强制转换的信息,来推断出变量类型。如果你在调用一个返回“T”的泛型方法时,试图将它的返回值“T”转换为特定类型如“Foo”,编译器现在知道这个方法调用中的“T”其实是“Foo”类型。这个对安卓开发者而言尤其重要,因为自从API26(Android7.0)开始,findViewById变成了泛型方法,然后编译器也会正确分析该方法的调用返回值。比如下面这样:

val button = findViewById(R.id.button) as Button
复制代码

6.智能类型转换改进

当一个变量有安全调用表达式与空检测赋值时,其智能转换现在也可以应用于安全调用接收者:

fun 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
复制代码

智能转换现在也允许用于在 lambda 表达式中局部变量,只要这些局部变量仅在 lambda 表达式之前修改即可:

val flag = args.size == 0
var x: String? = null
if (flag) x = "Yahoo!"
run {
    if (x != null) {
println(x.length) // x 会智能转换为 String 
    }
}
复制代码

7.支持 ::foo 作为 this::foo 的简写

现在写绑定到 this 成员的可调用引用可以无需显式接收者,即 ::foo 取代 this::foo 。这也使在引用外部接收者的成员的 lambda 表达式中使 用可调用引用更加方便。

8.破坏性变更:try 块后面的 sound smart casts

早些时候,Kotlin 使用了 try 块中的赋值,以在块之后进行 smart casts,这可能会破坏类型及 null 值的安全性并导致运行时失败。这个版本修复了此问题,使 smart casts 更严格,但破坏了一些依赖这种 smart casts 的代码。

要切换到旧的 smart casts 行为,传递 fallback 标志 -Xlegacy-smart-cast-after-try 作为编译器参数。它将在 Kotlin 1.3 中被弃用。

9.弃用:数据类的覆写性拷贝

当从已经具有相同签名的拷贝函数的类型派生数据类时,为数据类生成的 copy 实现使用父类型的默认函数,会导致出现与预期相反的行为,如果父类型没有默认参数,则在运行时失败导致复制冲突的继承已经被 Kotlin 1.2 中的警告所取代,并且在 Kotlin 1.3 中这将会提示是错误的。

10.弃用:枚举条目中的嵌套类型

在枚举项中,由于初始化逻辑中的问题,定义一个不是内部类的嵌套类型的功能已经被弃用。在 Kotlin 1.2 中这将会引起警告,并将在 Kotlin 1.3 中报错。

11.弃用:vararg 单个命名参数

为了与注解中的数组常量保持一致,在命名的表单(foo(items = i)) 中为 vararg 参数传递的单项目已被弃用。请使用具有相应数组工厂函数的展开运算符:

  foo(items = *intArrayOf(1))
复制代码

在这种情况下,有一种优化可以消除冗余数组的创建,从而防止性能下降。单一参数的表单在 Kotlin 1.2 中会引起警告,并将在 Kotlin 1.3 中被移除。

12.弃用:扩展 Throwable 的泛型类的内部类

继承自 Throwable 的泛型类的内部类可能会在 throw-catch 场景中违反类型安全性,因此已弃用,在 Kotlin 1.2 中会是警告,而在 Kotlin 1.3中会是错误。

13.弃用:改变只读属性的 backing 字段

在自定义 getter 中通过赋值 field = ... 来改变只读属性的 backing 字段已被弃用,在 Kotlin 1.2 中会被警告,在 Kotlin 1.3 中将会报错。

Kotlin1.2 对标准库的扩展

1.Kotlin 标准库 artifacts 及拆分包

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 以来该包是被支持的。

2.windowed、chunked、zipWithNext

Iterable, Sequence 和 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]
复制代码

3.fill、replaceAll、shuffle/shuffled

添加了一系列扩展函数用于处理列表:针对 MutableList 的 fill, replaceAll 和 shuffle ,以及针对只读 List 的 shuffled:

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]
复制代码

4.kotlin-stdlib 中的数学运算

为满足用户长期以来的需求,Kotlin 1.2 中增加了用于数学运算的 kotlin.math API,也是 JVM 和 JS 的通用 API,包含以下内容:

  • 常量:PI 和 E
  • 三角函数:cos, sin, tan 及其逆函数 acos, asin, atan, atan2
  • 双曲三角函数:cosh, sinh, tanh 及其逆函数 acosh, asinh, atanh
  • 指数函数:pow (扩展函数), sqrt, hypot, exp, expm1
  • 对数函数:log, log2, log10, ln, ln1p
  • Round 函数: ceil, floor, truncate, round (半值取偶)函数 roundToInt, roundToLong (半值取整)扩展函数
  • 符号函数和绝对值: abs 和 sign 函数 absoluteValue 和 sign 扩展属性 withSign 扩展函数
  • 两个数值的 max 和 min
  • 二分表示: ulp 扩展属性 nextUp, nextDown, nextTowards 扩展函数 toBits, toRawBits, Double.fromBits (这些都位于 kotlin 包中)

同系列(但不包括常量)的函数也针对 Float 型参数提供了。

5.用于 BigInteger 与 BigDecimal 的操作符与转换

Kotlin 1.2 引入了一组用于操作 BigInteger 和 BigDecimal 以及使用从其他数字类型进行转换的函数。这些函数是:

  • 用于 Int 和 Long 类型的 toBigInteger
  • 用于 Int, Long, Float, Double, 和 BigInteger 类型的 toBigDecimal
  • 算术和位运算符函数: 二元运算符 +, -, *, /, % 和中缀函数 and, or, xor, shl, shr 一元运算符 -, ++, -- 和一个函数 inv

6.浮点数到比特的转换

添加了新的函数,用于将 Double 和 Float 转换成位表示形式:

  • toBits 和 toRawBits 对于 Double 类型返回 Long,而对于 Float 返回 Int
  • Double.fromBits 和 Float.fromBits 用于从位表示形式中转换为浮点数

7.正则表达式现在可序列化

kotlin.text.Regex 类已成为可序列化的类,现在可以在可序列化的层次结构中使用。

8.如果满足条件,Closeable.use 可以调用 Throwable.addSuppressed

在一些其他异常处理后,关闭资源期间抛出异常时,Closeable.use 函数可调用 Throwable.addSuppressed。要启用这个行为,你需要在你的依赖关系中包含 kotlin-stdlib-jdk7。

Kotlin1.2 其它新特性

JVM后端

构造函数调用标准化

自 1.0 以来,Kotlin 开始支持复杂控制流的表达式,例如 try-catch 表达式和内联函数调用。根据 Java 虚拟机规范这样的代码是合法的。不幸的是,当构造函数调用的参数中存在这样的表达式时,一些字节码处理工具不能很好地处理这些代码。

为了减少使用此类字节码处理工具的用户的这个问题,我们添加了一个命令行选项 (-Xnormalize-constructor-calls=MODE),它会告诉编译器为这样的结构生成更多的类 Java 字节码。这里 MODE 的值是以下之一:

  • disable (默认值)—— 以和 Kotlin 1.0 和 1.1 相同的方式生成字节码
  • enable —— 为构造函数调用生成类 Java 字节码。这可以改变类加载和初始化的顺序
  • preserve-class-initialization —— 为构造函数调用生成类 Java 字节码,以确保保持类初始化顺序。这可能会影响应用程序的整体性能;仅在多个类之间共享一些复杂的状态并在类初始化时更新时才使用它。

“手工”的解决方法是将控制流的子表达式的值存储在变量中,而不是直接在调用参数中对它们进行求值。它类似于 -Xnormalize-constructor-calls=enable。

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 1.8 来编译这些代码。

破坏性变更:平台类型的 x.equals(null) 一致行为

在映射到 Java 原语 (Int!, Boolean!, Short!, Long!, Float!, Double!, Char!) 的平台类型上调用 x.equals(null) 时,如果 x 为 null,则会不正确地返回 true。从 Kotlin 1.2 开始,在平台类型的空值上调用 x.equals(...) 会抛出 NPE(但 x == ... 时并不会)。

要返回到 1.2 之前的行为,请将 -Xno-exception-on-explicit-equals-for-boxed-null 标志传递给编译器。

破坏性变更:通过内联的扩展接收器修复平台的 null 转义

在平台类型空值上调用的内联扩展函数并没有检查接收器是否为 null,并因此允许 null 转义到其他代码中。Kotlin 1.2 在调用点强制执行此检查,如果接收方为空,则抛出异常。

要切换到旧行为,请将 fallback 标志 -Xno-receiver-assertions 传递给编译器。

JavaScript 后端

默认启用对类型化数组(TypedArrays)的支持

S typed arrays 支持将 Kotlin 基本数组(如 IntArray, DoubleArray)转换为 JavaScript 的类型数组,以前这是可选功能,现在默认情况下已启用。

工具

将警告视为错误

编译器现在提供了将所有警告视为错误的选项。在命令行中使用 -Werror,或使用以下的 Gradle 代码:

compileKotlin {
    kotlinOptions.allWarningsAsErrors = true
}
复制代码

学习资料

  1. Kotlin Bootcamp for Programmers
  2. Kotlin Koans

猜你喜欢

转载自juejin.im/post/5bdaceddf265da396442c1a0
今日推荐