0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Kotlin 1.4の変更点

0
Posted at

※ソース記事はこちら
※Kotlin/JSやKotlin/Nativeについては個人的に使っていないので、割愛します。

Kotlin1.4では、すべての構成要素の中で、品質と性能にフォーカスした数多くの改善が出荷されている。以下に、Kotlin1.4.0での最も重要な変更点のリストがある。

言語の特徴と改善

Koltin 1.4.0には様々な異なる言語の特徴と改善が搭載されている。

  • KotlinインターフェイスのためのSAM構文
  • ライブラリ開発者のための明示的APIモード
  • 名前と位置の引数の混在
  • 末尾のカンマ
  • 関数参照の改善

KotlinインターフェイスのためのSAM構文

Kotlin1.4より前は、SAM(単一の抽象メソッド)構文は、KotlinからはJavaのメソッドとJavaのインターフェイスと連携するときのみ、適用することができた。今からは、Kotlinインターフェイスも同様にSAM規約を使うことができる。そうするには、Kotlinインターフェイスを
fun修飾子で明示的に関数としてマークする。
SAM構文は唯一の抽象メソッドを持つインターフェイスが引数として予想されているときに、ラムダを引数として渡す場合に適用される。この場合、コンパイラが自動で、ラムダを抽象メンバを実装したクラスのインスタンスに変換する。

fun interface IntPredicate {
    fun accept(i: Int): Boolean
}

val isEven = IntPredicate { it % 2 == 0 }

fun main() { 
    println("Is 7 even? - ${isEven.accept(7)}")
}

ライブラリ開発者のための明示的APIモード

Kotlinコンパイラは明示的APIモードをライブラリ開発者のために提供する。このモードでは、コンパイラは追加のチェックを実施して、ライブラリのAPIをよりクリアに、より一貫性のあるように手助けする。それはライブラリの公開APIにさらされた宣言に、次の必須チェックを追加する。

  • もしデフォルトの修飾子が公開APIにさらされている場合、可視性修飾子の宣言が必要になる。これは意図せず公開APIにさらされることを無くす手助けになる。
  • 公開APIにさらされているプロパティや関数は、明示的な型の指定が必要になる。これはAPIの使用者が自分の使うAPIメンバの型を意識することを保証する。
    設定に応じて、これらの明示的APIは、エラー(strictモード)か、警告(warningモード)を生成することができる。ある種の宣言は、読みやすさと常識のためにチェックが除外されている。
  • プライマリコンストラクタ
  • dataクラスのプロパティ
  • getterとsetterのプロパティ
  • overrideメソッド

明示的APIモードは、モジュールの本番ソースにのみ、分析を行う。
明示的APIモードでモジュールをコンパイルするには、Gradleビルドスクリプトに以下の行を追加する。

kotlin {
    // for strict mode
    explicitApi()
    // or
    explicitApi = ExplicitApiMode.Strict

    // for warning mode
    explicitApiWarning()
    // or
    explicitApi = ExplicitApiMode.Warning
}

コマンドラインコンパイラを使用するときは、-Xexplicit-apiコンパイラオプションを追加することで、明示的APIモードに切り替わる。

-Xexplicit-api={strict|warning}

名前と位置の引数の混在

Kotin 1.3では、名前つき引数で関数を呼ぶときは、最初の名前付き引数の前に、すべての引数を名前無し(位置引数)で配置しなければならなかった。例として、f(1, y = 2)は呼び出せるが、f(x = 1, 2)は呼び出せなかった。
このことはすべての引数が正しい位置にあるが、中間の一つの引数を名前付き引数を指定したいときに非常に悩ましかった。それはboolean属性や、null値が含まれていることを明確にするために特に役に立つ。
Kotlin 1.4では、そのような制限はない。位置引数の中間に名前付き引数を指定することができる。さらに正しい順序を維持する限り、位置と名前引数をどのようにでも混在することができる。

fun reformat(
    str: String,
    uppercaseFirstLetter: Boolean = true,
    wordSeparator: Char = ' '
) {
    // ...
}

// 中間の名前付き引数での関数呼び出し
reformat("This is a String!", uppercaseFirstLetter = false , '-')

末尾のカンマ

Kotlin 1.4では引数やパラメータリストやwhenエントリ、分割宣言の構成要素のような、列挙の中の末尾のカンマを追加することができる。末尾のカンマにより、カンマを追加したり削除したりせずに新規項目を追加したり、順番を変えることができる。
これは特に、パラメータあるいは値のために複数行の文法を使うなら、特に役に立つ。末尾のカンマを追加した後で、簡単にパラメータや値を行入れ替えすることができる。

fun reformat(
    str: String,
    uppercaseFirstLetter: Boolean = true,
    wordSeparator: Character = ' ', // 末尾のカンマ
) {
    // ...
}
val colors = listOf(
    "red",
    "green",
    "blue", // 末尾のカンマ
)

関数参照の改善

Kotlin 1.4は関数参照を使うためのより多くのケースをサポートしている。

  • デフォルト引数を持つ関数への参照
  • Unitを戻す関数内の関数参照
  • 関数内の引数の数に基づいて適用する参照
  • 関数参照についてのSuspend構文

デフォルト引数を持つ関数への参照

今ではデフォルト引数を持つ関数への関数参照を使うことができる。もし引数を持たないfoo関数への関数参照の場合、デフォルト値の0が使われる。

fun foo(i: Int = 0): String = "$i!"

fun apply(func: () -> String): String = func()

fun main() {
    println(apply(::foo)) // 0!
}

以前は、デフォルト引数を使うためにapply関数のオーバーロードを追加で書く必要があった。

// some new overload
fun applyInt(func: (Int) -> String): String = func(0)

Unitを戻す関数内の関数参照

Kotlin 1.4では、Unitを戻り値にする関数内で、関数参照を使うことができる。Kotlin 1.4より前は、この場合、ラムダ引数を使うしかなかった。今ではラムダ引数と関数参照両方使うことができる。

fun foo(f: () -> Unit) { }
fun returnsInt(): Int = 42

fun main() {
    foo { returnsInt() } // 1.4より前はこれが可能な唯一の方法
    foo(::returnsInt) // Kotlin 1.4からはこれも使える
}

関数内の引数の数に基づいて適用する参照

今では可変長の引数(vararg)を渡す関数への関数参照を適用することができる。引数のリストの最後が同じ型であるどんな数のパラメータも渡すことができる。

fun foo(x: Int, vararg y: String) {}

fun use0(f: (Int) -> Unit) {}
fun use1(f: (Int, String) -> Unit) {}
fun use2(f: (Int, String, String) -> Unit) {}

fun test() {
    use0(::foo)
    use1(::foo)
    use2(::foo)
}

関数参照についてのSuspend構文

ラムダに関するsuspend構文に加えて、Kotlinは今では1.4.0から始まる関数参照に関するsuspend構文をサポートしている。

fun call() {}
fun takeSuspend(f: suspend () -> Unit) {}

fun test() {
    takeSuspend { call() } // 1.4より前はOK
    takeSuspend(::call) // Kotlin 1.4ではこれも動く
}

ループに含まれるwhen式の内部でのbreakとcontinueの使用

Kotlin 1.3では、ループに含まれるwhen式の内部でbreakとcontinueを無制限に使うことができなかった。これらのキーワードはwhen式のフォールスルーの挙動の可能性のため、予約されていることが原因だった。
このため、もしループ内のwhen式の内側でbreakとcontinueを使いたいなら、少々面倒だがラベル付けをする必要があった。

fun test(xs: List<Int>) {
    LOOP@for (x in xs) {
        when (x) {
            2 -> continue@LOOP
            17 -> break@LOOP
            else -> println(x)
        }
    }
}

Kotlin 1.4ではループ内のwhen式の内側でbreakとcontinueをラベル無しで使うことができる。これらは期待通り、最も近い囲っているループを終了するか、その次のステップに進む挙動をする。

fun test(xs: List<Int>) {
    for (x in xs) {
        when (x) {
            2 -> continue
            17 -> break
            else -> println(x)
        }
    }
}

whenの内側のフォールスルーの挙動については、さらに先の設計の対象である。

IDEの新しいツール(※割愛)

新しいコンパイラ

新しいKotlinのコンパイラは本当に速くなっている。すべてのサポート対象のプラットフォームを統一し、拡張のためのAPIを提供していく。それは長期にわたるプロジェクトであり、Kotlin 1.4でいくつかのステップを完了した。

  • 新しく強力な型推論アルゴリズムがデフォルトで有効
  • 新しいVMとJSのバックエンド。それらは安定化すればデフォルトとする。

新しく強力な型推論アルゴリズム

Kotlin 1.4は、 新しく強力な型推論アルゴリズムを使用する。この新しいアルゴリズムはKotlin 1.3でコンパイラオプションですでに試すことができたが、今ではデフォルトとして使われる。YouTrackの中で、新しいアルゴリズムで修正された全リストを見ることができる。ここでは最も注目すべき改善のいくつかを見ていく。

  • より多くのケースでの型推論
  • ラムダの最後の式のためのスマートキャスト
  • 関数参照のためのスマートキャスト
  • delegateプロパティのためのより良い推論
  • 異なる引数を持つJavaインターフェイスのためのSAM構文
  • Kotlin内でのJavaのSAMインターフェイス

より多くのケースでの型推論

新しい推論アルゴリズムは、古いアルゴリズムが明示的に指定する必要があった、多くのケースのための推論をする。例えば、次の例ではラムダのパラメータitの型は正しくString?と推論する。

val rulesMap: Map<String, (String?) -> Boolean> = mapOf(
    "weak" to { it != null },
    "medium" to { !it.isNullOrBlank() },
    "strong" to { it != null && "^[a-zA-Z0-9]+$".toRegex().matches(it) }
)

Kotlin 1.3で動かすためには、明示的にラムダパラメータを導入し、toを明示的なジェネリクス引数を持つPairに置き換える必要があった。

ラムダの最後の式のためのスマートキャスト

Kotlin 1.3では、ラムダの最後の式は、期待する型を指定しない限り、スマートキャストされなかった。したがって次の例では、Kotlin 1.3はresult変数の型としてString?を推論する。

val result = run {
    var str = currentValue()
    if (str == null) {
        str = "test"
    }
    str // the Kotlin compiler knows that str is not null here
}
// The type of 'result' is String? in Kotlin 1.3 and String in Kotlin 1.4

Kotlin 1.4では、新しい推論アルゴリズムにより、ラムダの最後の式はスマートキャストされ、この新しくより正確な型は結果として生じるラムダ型の推論に使われる。したがってresult変数の型はStringになる。
Kotlin 1.3では、このケースを動かすためには、明示的なキャスト(!!as Stringのような型キャスト)の追加が度々必要だったが、今ではこれらのキャストは不要になった。

関数参照のためのスマートキャスト

Kotlin 1.3では、スマートキャストの型のメンバ参照にアクセスすることができなかった。今ではKotlin 1.4で可能。

import kotlin.reflect.KFunction

sealed class Animal
class Cat : Animal() {
    fun meow() {
        println("meow")
    }
}

class Dog : Animal() {
    fun woof() {
        println("woof")
    }
}
fun perform(animal: Animal) {
    val kFunction: KFunction<*> = when (animal) {
        is Cat -> animal::meow
        is Dog -> animal::woof
    }
    kFunction.call()
}

animal変数が特定の型であるCatDogにスマートキャストされた後であれば、animal::meowanimal::woofの異なるメンバ参照を使うことができる。型チェックの後はサブタイプに応じたメンバ参照へアクセスできる。

delegateプロパティのためのより良い推論

delegateプロパティの型はbyキーワードに続くdelegate表記を解析している間は、考慮されていなかった。例えば、次のコードは以前はコンパイルできなかったが、現在はコンパイラはoldnewパラメータをString?として正しく推論する。

import kotlin.properties.Delegates

fun main() {
    var prop: String? by Delegates.observable(null) { p, old, new ->
        println("$old → $new")
    }
    prop = "abc" // null -> abc
    prop = "xyz" // abc -> xyz
}

異なる引数を持つJavaインターフェイスのためのSAM構文

Kotlinは最初からJavaのインターフェイスのためのSAM構文をサポートしていたが、サポートされない一つのケースがあった。それは既存のJavaライブラリと連携するときにときどき迷惑だった。もし2つのSAMインターフェイスを取るJavaのメソッドをパラメータとして呼び出すなら、両方の引数はラムダか、通常のオブジェクトである必要があった。一つの引数はラムダで、もう一つはオブジェクトとして渡すことはできなかった。
新しいアルゴリズムは、この問題を修正し、どんなケースでもSAMインターフェイスの代わりにラムダを渡すことができ、それは当然期待通りに動作する方法です。

// FILE: A.java
public class A {
    public static void foo(Runnable r1, Runnable r2) {}
}
// FILE: test.kt
fun test(r1: Runnable) {
    A.foo(r1) {}  // Works in Kotlin 1.4
}

Kotlin内でのJavaのSAMインターフェイス

Kotlin 1.4では、KotlinでJavaのSAMインターフェイスを使うことができ、それらに対してSAM構文を適用することができる。

import java.lang.Runnable

fun foo(r: Runnable) {}

fun test() {
    foo { } // OK
}

Kotlin 1.3ではSAM構文を実行するためには、上記のfoo関数をJavaコードで宣言しなければならなかった。

統一されたバックエンドと拡張性

Kotlinには、実行モジュールを生成する3つのバックエンドがある。Kotlin/JVM、Kotlin/JS、KotlinNativeである。Kotlin/JVMとKotlin/JSはお互い独立して開発されたため、あまりコードを共有していない。Kotlin/NativeはKotlinコードのための中間表現(IR)を中心に建てられた新しい基礎をもとにしている。
現在、Kotlin/JVMとKotlin/JSは同じ中間表現に移行している。結果として3つすべてのバックエンドは多くのコードを共有し、統一した経路を持つ。このことはすべてのプラットフォームにとって、一度だけで多くの特徴、最適化、バグフィックスを実装することに役立つ。両方の新しい中間表現のバックエンドはAlpha状態である。
共通のバックエンド基盤はマルチプラットフォームのコンパイラーの拡張を可能にする。経路にプラグインし、すべてのプラットフォームで自動で動作するカスタム処理と変換を追加することが可能になるだろう。
我々は現在アルファ版である、新しいJVMの中間表現とJSの中間表現のバックエンドを使ってフィードバックを共有してもらうことをお勧めしている。

Kotlin/JVM

Kotlin 1.4は次のような多くのJVM固有の改善を含んでいる。

  • 新しいJVM中間表現のバックエンド
  • インターフェイスのデフォルトメソッドを生成する新しいモード
  • nullチェックのための統一した例外
  • JVMバイトコード内の型アノテーション

新しいJVM中間表現のバックエンド

Kotlin JSと並んで、Kotlin/JVMは統一した中間表現バックエンドへ移行しており、それは多くの特徴とバグフィックスをすべてのプラットフォームのために一度に実装することを可能にする。このことは、すべてのプラットフォームで動作するマルチプラットフォームの拡張を作ることにより、恩恵を受けることもできるだろう。
Kotlin 1.4はまだそのような拡張のための公開APIを提供していないが、Jetpack Composeに含まれるパートナーと綿密に協力しており、彼らはすでに私たちの新しいバックエンドを使ってコンパイラの拡張を構築している。
私たちは新しいKotlin/JVMのバックエンド、これは現在Alphaであり、を試し、issue trackerで何かの問題や要望を挙げることを勧めている。これはより早くコンパイラの経路を統合し、KotlinコミュニティにJetpack Composeのようなコンパイラ拡張をもたらすことに役立つ。
新しいJVMの中間表現バックエンドを有効化するには、追加のコンパイラオプションをGradleビルドスクリプトに指定する。

kotlinOptions.useIR = true

もしJetpack Composeを有効にするなら、自動で新しいJVMバックエンドが許可されるので、kotlinOptions内でコンパイラオプションを指定する必要はない。

コマンドラインコンパイラを使用しているときは、Xuse-irコンパイラオプションを追加する。

新しいバックエンドを有効にする場合のみ、新しいJVMの中間表現バックエンドによりコードがコンパイルされたコードを使うことができる。そうでない場合、エラーとなる。このことを考慮すると、ライブラリ開発者は、本番で新しいバックエンドに切り替えることはお勧めしない。

インターフェイスのデフォルトメソッドを生成する新しいモード

KotlinコードをJVM 1.8以上をターゲットにコンパイルしているとき、Kotlinインターフェイスの抽象でないメソッドをJavaのデフォルトメソッドにコンパイルできた。この目的のため、そのようなメソッドをマークするための@JvmDefaultアノテーションと、そのアノテーションを処理するための-Xjvm-defaultコンパイラオプションを含むメカニズムがあった。
1.4.0では、デフォルトメソッドを生成するための新しいモードが追加されている。-Xjvm-default=allは、Kotlinインターフェイスのすべての抽象でないメソッドをdefaultJavaメソッドでコンパイルする。default無しでコンパイルされたインターフェイスを使うコードとの互換性のため、all-compatibilityモードも追加された。
Java相互運用におけるデフォルトメソッドについて、詳細については相互運用の文書このブログ投稿を見てほしい。

nullチェックのための統一した例外

Kotlin 1.4から、すべての実行時nullチェックはKotlinNullPointerExceptionIllegalStateExceptionIllegalArgumentExceptionTypeCastExceptionの代わりにjava.lang.NullPointerExceptionをスローする。これは!!演算子や、メソッド構文での引数のnullチェックや、プラットフォーム型表現のnullチェックや、非null型でのas演算子に当てはまる。これはlateinitのnullチェックとcheckNotNullrequireNotNullのような明示的なライブラリ関数には当てはまらない。
この変更はKotlinコンパイラか、Android R8オプティマイザのような何種類ものバイトコード処理ツールによって、実行されるnullチェックの最適化の可能性を増やす。
注意すべきは、開発者の視点からは物事はそれほど変わらない:Kotlinコードは以前と同じエラーメッセージを持つ例外をスローするためである。例外の型は変わるが、情報は変わらず渡される。

JVMバイトコード内の型アノテーション

Kotlinは今では(ターゲットバージョン1.8以上の)JVMバイトコードで型アノテーションが生成するため、実行時のJavaリフレクションでそれらは利用できる。バイトコードに型アノテーションを発行するには、以下のステップに従うこと。

  1. 宣言したアノテーションが適切なアノテーションターゲット(JavaのElementType.TYPE_USEかKotlinのAnnotationTarget.TYPE)とリテンション(AnnotationRetention.RUNTIME)を持つこととを確認する。
  2. アノテーション宣言クラスがターゲットバージョン1.8以上でコンパイルすること。コンパイラオプションで-jvm-target=1.8を指定することで可能。
  3. アノテーションを使うコードをターゲットバージョン1.8以上でコンパイル(-jvm-target=1.8)することと、コンパイラオプションに-Xemit-jvm-type-annotationを追加すること。
    注意すべきは、標準ライブラリからの型アノテーションは、現在はバイトコードには発行されていない。なぜなら標準ライブラリはターゲットバージョン1.6でコンパイルされているからだ。
    今のところ、次のような基本的なケースのみがサポートされている。
  • メソッドパラメータとメソッドの戻り値とプロパティ型の型アノテーション
  • 型引数の不変の射影、例えばSmith<@Ann Foo>Array<@Ann Foo>
    次の例えは、String型についた@Fooアノテーションはバイトコードに発行され、その後ライブラリコードに使うことができる。
@Target(AnnotationTarget.TYPE)
annotation class Foo

class A {
    fun foo(): @Foo String = "OK"
}

Gradleプロジェクトの改善

Kotlin Multiplatform、Kotlin/JVM、Kotlin/JS固有のGradleプロジェクトの特徴や改善を別にして、すべてのKotlin Gradleプロジェクトに当てはまる変更がいくつか存在する。

  • デフォルトで標準ライブラリへの依存関係が追加される
  • KotlinプロジェクトはGradleの最新バージョンが必要
  • IDEでのKotlin Gradle DSLのための改善

デフォルトで標準ライブラリへの依存関係が追加される

mulitplatformを含め、どのGradleプロジェクトでも、もはやstdlibライブラリへの依存性を宣言する必要はない。
自動的に追加された標準ライブラリは同一のバージョニングのため、Kotlin Gradleプラグインと同じバージョンである。
プラットフォーム固有のソース群のため、対応するプラットフォーム固有のライブラリの異なるものが使われる。共通の標準ライブラリがその他残りに追加されるとしても。Kotlin Gradleプラグインは、GradleビルドスクリプトのkotlinOptions.jvmTargetコンパイラオプションに依存した、適切なJava標準ライブラリを選択する。

Kotlinプロジェクトのための最低Gradleバージョン

Kotlinプロジェクトの特徴を楽しむため、Gradleを最新バージョンに更新すること。MultiplatformプロジェクトはGradle 6.0以上を必要とし、他のKotlinプロジェクトはGradle 5.4以上と連携する。

IDEでの改善された*.gradle.ktsのサポート

1.4.0ではGradle Kotlin DSLスクリプト(*.gradle.kts)のためIDEサポートが引き続き改善されている。

  • より良いパフォーマンスのための明示的なスクリプト構成のローディング。以前はビルドスクリプトへの変更は、バックグラウンドで自動的にロードされていた。パフォーマンスを改善するため、1.4.0ではビルドスクリプト構成の自動ローディングは無効にされた。今では、明示的に適用するときのみ、IDEで変更をロードする。
    バージョン6.0より前のGradleでは、エディタで構成のロードをクリックすることでスクリプト構成を手動でロードする必要がある。
    Gradle 6.0以上とIntelliJ IDEA 2020.1では、もう一つのアクションスクリプト構成のロードが追加されている。これはプロジェクト全体を更新することなく、スクリプト構成の変更をロードする。これはプロジェクト全体を再インポートするよりもかなり短い時間で済む。

新しく作られたプロジェクトか、新しいKotlinプラグインでプロジェクトを始めて開くときも、スクリプト構成のロードをするべきである。
Gradle 6.0以上では、以前の実装では、スクリプトを個々にロードしていたのに対して、今では一度にすべてのスクリプトのロードができるようになっている。それぞれの要求は実行時にGradle構成フェーズが必要になるので、このことは大きなGradleプロジェクトでリソースの節約になるだろう。
現在、このようなローディングはbuild.gradle.ktssetting.grdle.ktsファイルに限定されている。init.gradle.ktsや提供されているスクリプトプラグインを強調するには、古いメカニズム(スタンドアロンスクリプトとして追加)を使用すること。そのスクリプトのための構成は、必要となった時に個別にロードされる。そのようなスクリプトのために自動リロードを有効するにすることもできる。

  • より良いエラー報告。以前はGradleデーモンからのエラーは別のログファイルを見ることができた。今ではGradleデーモンはエラーに関するすべての情報を直接返却し、ビルドツールウィンドウで表示される。これは時間と労力の節約になる。

標準ライブラリ

ここでは、1.4.0でKotlin標準ライブラリへのもっとも重要な変更をリストする。

  • 共通のエラー処理API
  • 配列とコレクションのための新しいAPI
  • 文字列操作のための関数
  • ビット操作
  • deletegeプロパティの改善
  • KTypeからJava Typeへの変換
  • KotlinリフレクションのためのProguard設定
  • 既存のAPIの改善
  • 標準ライブラリ成果物のためのmodule-info記述子
  • 非推奨
  • 非推奨の実験的コルーチンの除外

共通のエラー処理API

次のAPI要素は標準ライブラリに移動されている。

  • Throwable.stackTraceToString()拡張関数、これは、スタックトレースに付随するthrowableの詳細な説明を返却する。Throwable.printStackTrace()。これは標準エラー出力の詳細を出力する。
  • Throwable.addSuppressed()関数。これは他の例外に渡すために隠す例外を指定する。Throwable.suppressedExceptionsプロパティ。これはすべての隠された例外を返却する。
  • @Throwsアノテーション。これは関数が、JVMかNativeプラットフォームのプラットフォームメソッドにコンパイルするときにチェックされる例外型をリストする。

配列とコレクションのための新しいAPI

コレクション

1.4.0では、標準ライブラリはコレクションと連携する多くの有益な関数を含んでいる。

  • setOfNotNull()は、提示された引数の中ですべての非nullの要素からなる一式を作成する。
val set = setOfNotNull(null, 1, 2, 0, null)
println(set) // [1,2,0]
  • シーケンスのためのshuffled()
val numbers = (0 until 50).asSequence()
val result = numbers.map { it * 2 }.shuffled().take(5)
println(result.toList()) // 5個の100未満のランダムな偶数
  • onEach()flatMap()のための対応する*Indexed() これらがコレクションの要素に適用された演算は、パラメータとして要素インデックスを持つ。
listOf("a", "b", "c", "d").onEachIndexed {
    index, item -> println(index.toString() + ":" + item)
} // 0:a 1:b 2:c 3:d

val list = listOf("hello", "kot", "lin", "world")
val kotlin = list.flatMapIndexed { index, item ->
    if (index in 1..2) item.toList() else emptyList() 
} 
// [k, o, t, l, i, n]
  • *OrNull()の対応であるrandomOrNull()reduceOrNull()reduceIndexedOrNull()これらは空のコレクションについて、nullを返却する。
val empty = emptyList<Int>()
empty.reduceOrNull { a, b -> a + b }
//empty.reduce { a, b -> a + b } // 例外: Empty collection can't be reduced.
  • runningFold()は、scan()runningReduce()の同義語であり、fold()reduce()と同様にコレクションの要素に順番に与えられた演算を適用する。違いは、これらの新しい関数は、中間結果のすべての連続を返却する。
val numbers = mutableListOf(0, 1, 2, 3, 4, 5)
val runningReduceSum = numbers.runningReduce { sum, item -> sum + item }
// [0, 1, 3, 6, 10, 15]
val runningFoldSum = numbers.runningFold(10) { sum, item -> sum + item }
// [10, 10, 11, 13, 16, 20, 25]
  • sumOf()はセレクタ関数をとり、コレクションのすべての要素の合計を返却する。sumOf()IntLongDoubleUIntULongの型の合計を生成する。JVMでは、BigIntegerBigDecimalも利用可能である。
val order = listOf<OrderItem>(
    OrderItem("Cake", price = 10.0, count = 1),
    OrderItem("Coffee", price = 2.5, count = 3),
    OrderItem("Tea", price = 1.5, count = 2))

val total = order.sumOf { it.price * it.count } // Double
val count = order.sumOf { it.count } // Int
  • min()max()拡張関数はKotlinコレクションAPI中で使用されている命名規約に適合させるため、minOrNull()maxOrNull()に改名された。関数名の*orNullサフィックスはレシーバーのコレクションが空の場合、nullを返却するという意味である。1.4では同様のことがminBy()maxBy()minWith()maxWith()でも当てはまる。これらは*orNullの類義語を持つ。
  • 新しいminOf()maxOf()拡張は、コレクション要素について、与えられたセレクタ関数の最小と最大の値を返却する。
data class OrderItem(val name: String, val price: Double, val count: Int)

fun main() {
    val order = listOf<OrderItem>(
        OrderItem("Cake", price = 10.0, count = 1),
        OrderItem("Coffee", price = 2.5, count = 3),
        OrderItem("Tea", price = 1.5, count = 2))
    val highestPrice = order.maxOf { it.price }
    println("The most expensive item in the order costs $highestPrice") // 10.0
}

minOfWith()maxOfWith()もあり、これらは引数としてComparatorをとる。また、すべての4つの関数の*OrNullバージョンは、空のコレクションについてはnullを返却する。

  • flatMapflatMapToの新しいオーバーロードにより、レシーバーの型と合わない戻り値の型の変換をすることができる。つまり
    • IterableArrayMapについてはSequenceへの変換
    • SequenceについてはIterableへの変換
fun main() {
    val list = listOf("kot", "lin")
    val lettersList = list.flatMap { it.asSequence() }
    val lettersSeq = list.asSequence().flatMap { it.toList() }    
    println(lettersList)
    println(lettersSeq.toList())
}
  • removeFirst()removeLast()はミュータブルなリストから要素を削除するショートカットであり、これらの関数の*orNull()版がある。

配列

異なるコンテナ型と連携するときに一貫した体験を提供するために、配列のためにも新しい関数を追加した。

  • shuffle()はランダムな順序で配列の要素を入れる
  • onEach()はそれぞれの配列の要素に対してアクションを実行し、配列そのものを返却する。
  • associateWith()associateTo()は、配列の要素をキーとしたmapを組み立てる。
  • 配列の部分範囲に対するreverse()は、部分範囲の要素の順序を逆にする。
  • 配列の部分範囲に対する`sortDescending()は、部分範囲内の要素を降順にソートする。
  • 配列の部分範囲に対するsort()sortWith()は、今では共通ライブラリで利用可能である。
fun main() {
    var language = ""
    val letters = arrayOf("k", "o", "t", "l", "i", "n")
    val fileExt = letters.onEach { language += it }
        .filterNot { it in "aeuio" }.take(2)
        .joinToString(prefix = ".", separator = "")
    println(language) // "kotlin"
    println(fileExt) // ".kt"

    letters.shuffle()
    letters.reverse(0, 3)
    letters.sortDescending(2, 5)
    println(letters.contentToString()) // [k, o, t, l, i, n] ※これは順序の変更には左右されない
}

さらに、CharArray/ByteArrayStringの間の変換のために新しい関数がある。

  • ByteArray.decodeToString()String.encodeToString()
  • CharArray.concatToString()String.toCharArray()
fun main() {
    val str = "kotlin"
    val array = str.toCharArray()
    println(array.concatToString()) // kotlin
}

ArrayDeque

ArrayDequeクラス、両端キューの実装が追加された。両端キューにより、定数の償却時間でキューの先頭と末尾に要素を追加、または削除することができる。コードでキューやスタックが必要なときは、デフォルトで両端キューを使うことができる。

fun main() {
    val deque = ArrayDeque(listOf(1, 2, 3))

    deque.addFirst(0)
    deque.addLast(4)
    println(deque) // [0, 1, 2, 3, 4]

    println(deque.first()) // 0
    println(deque.last()) // 4

    deque.removeFirst()
    deque.removeLast()
    println(deque) // [1, 2, 3]
}

ArrayDequeの実装は、水面下でリサイズ可能な配列を使う。それは循環バッファ、Array内に中身を保存し、一杯になったらArrayをリサイズする。

文字列操作のための関数

1.4.0の標準ライブラリは文字列操作のためにAPI内に多くの役立つ改善を含んでいる。

  • StringBuilderは役立つ新しい拡張関数を持つ。set()setRange()deleteAt()deleteRangeappendRangeなど。
fun main() {
    val sb = StringBuilder("Bye Kotlin 1.3.72")
    sb.deleteRange(0, 3)
    sb.insertRange(0, "Hello", 0 ,5)
    sb.set(15, '4')
    sb.setRange(17, 19, "0")
    print(sb.toString()) // Hello Kotlin 1.4.0

    }

ビット操作

ビット操作のための新しい関数

  • countOneBits()
  • countLeadingZeroBits()
  • countTralingZerobBits()
  • takeHighestOneBit()
  • takeLowestOneBit()
  • rotateLeft()rotateRight()(実験的)
fun main() {
    val number = "1010000".toInt(radix = 2)
    println(number.countOneBits()) // 2
    println(number.countTrailingZeroBits()) // 4
    println(number.takeHighestOneBit().toString(2)) // 1000000
}

delegateプロパティの改善

1.4.0では、Kotlinのdelegateプロパティで体験を改善する新しい特徴が追加されている。

  • 今ではプロパティは別のプロパティにdelegate可能
  • 新しいPropertyDelegateProviderインターフェイスが単一の宣言でdelegateプロパティを作ることを助ける
  • ReadWritePropertyは今はReadOnlyPropertyを拡張し、どちらも読み取り専用のプロパティのために使用できる
    新しいAPIとは別に、結果として生成されるバイトコードのサイズを減らすいくつかの最適化を行った。この最適化はこのブログ記事に記載されている。

KTypeからJava Typeへの変換

標準ライブラリの新しい拡張プロパティKType.javaType(現在実験的)は、全体のkotlin-reflect依存性を使わずに、Kotlin型からjava.lang.reflect.Typeを取得するのに役立つ。

import kotlin.reflect.javaType
import kotlin.reflect.typeOf

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T> accessReifiedTypeArg() {
   val kType = typeOf<T>()
   println("Kotlin type: $kType")
   println("Java type: ${kType.javaType}")
}

@OptIn(ExperimentalStdlibApi::class)
fun main() {
   accessReifiedTypeArg<String>()
   // Kotlin type: kotlin.String
   // Java type: class java.lang.String
  
   accessReifiedTypeArg<List<String>>()
   // Kotlin type: kotlin.collections.List<kotlin.String>
   // Java type: java.util.List<java.lang.String>
}

KotlinリフレクションのためのProguard設定

1.4.0以降、kotlin-reflect.jarの中に、KotlinリフレクションのためのProguard/R8の設定が埋め込まれている。この準備とともに、R8またはProguardを使っているほとんどのAndroidプロジェクトは、追加の構成を必要とせず、kotlin-reflectと連携するはずである。もはやkotlin-reflect内部のためにProguardルールをコピーペーストする必要はない。しかし留意すべきは、リフレクションをしようとするすべてのAPIを明示的に一覧化する必要はまだある。

既存のAPIの改善

  • いくつかの関数は今ではnullレシーバと連携する。例えば、
    • 文字列についてのtoBoolean()
    • 配列についてのcontentEquals()contentHashCode()contentToString()
  • DoubleFloatNaNNEGATIVE_INFITINYPOSITIVE_INFINITYは、今ではconstとして宣言されているため、それらをアノテーションの引数として使用することができる。
  • 新しい定数であるDoubleFloatSIZE_BITSSIZE_BYTESは、バイナリ形式でインスタンスを表現するために使用するビットやバイト数を含んでいる。
  • maxOf()minOf()トップレベル関数は、可変長引数(vararg)を受け取ることができる。

標準ライブラリ成果物のためのmodule-info記述子

Kotlin1.4.0では、デフォルトの標準ライブラリの成果物に、モジュール情報であるmodule-info.javaが追加されている。これはjlinkツールで使用し、アプリで必要なプラットフォームモジュールのみを含むカスタムなJavaランタイムを生成する。以前からKotlin標準ライブラリとjlinkを使用することはできていたが、そのために成果物を、あるものは「modular」分類名として、分割する必要があり、全体のセットアップが簡単ではなかった。
Androidでは、Android Gradleプラグインバージョン3.2以上を使うことを確認してほしいが、module-infoを使って正しくjarを処理することができる。

非推奨

DoubleとFloatのtoShort()、toByte()

DoubleとFloatのtoShort()、toByte()は非推奨とされた。なぜなら、これらは値の範囲と値のサイズをナローイングするために、予期せぬ結果を引き起こすことがありうるためである。
浮動小数をByteShortに変換するためには、2ステップでの変換を使うこと。1つめはIntに変換し、そのあとで目的の型にそれを再度変換すること。

浮動小数の配列についてのcontains()、indexOf()、lastInexOf()

FloatArrayDoubleArraycontains()indexOf()lastIndexOf()拡張関数は非推奨となった。なぜなら、それらはIEEE754の等値性を使用しており、いくつかのまれなケースで全体の順序の等値性に矛盾するからである。詳細はこの問題を参照のこと。

min()とmax()コレクション関数

minOrNull()maxOrNull()のため、min()max()コレクション関数は非推奨となった。これは空のコレクションについてはnullを返却するという、ふるまいをより反映している。詳細はこの問題を参照のこと。

非推奨の実験的コルーチンの排除

kotlin.coroutines.experimentalAPIは、1.3.0でkotlin.coroutinesのため、非推奨だった。1.4.0では、標準ライブラリからの削除をすることで、kotlin.coroutines.experimentalのための非推奨サイクルを完了した。JVMでまだそれを使用する人々のため、すべての実験的コルーチンAPIをもつkotlin-coroutines-experimental-compat.jarという互換性成果物を提供した。それはMavenに配信され、票運ライブラリと並んで、Kotlinディストリビューションに含まれている。

安定したJSONシリアライズ

Kotlin1.4.0とともに、kotlinx.serialization 1.0.0-RCの最初の安定バージョンが出荷されている。今では安定したkotlinx-serialization-core(以前はkotlinx-serialization-runtimeとして知られていた)でJSONシリアライズAPIを宣言できることを喜ばしく思う。他のシリアライズ形式のライブラリはコアライブラリのいくつかの高度な部分と一緒に、実験的なままである。
JSONシリアライズのためのAPIは、より一貫性を持ち、使いやすくするため、大幅に修正された。今後は後方互換のやり方で、JSONシリアライズAPIは開発され続けるだろう。しかし、以前のバージョンを使っていた人は、1.0.0-RCに移行するときに、いくつかコードを修正する必要がある。これを助けるため、Kotlin Serialization Guideが提供されている。これはkotlinx-serializationのドキュメントの完全なセットである。これは、最も重要な特徴を使う処理を案内し、直面するかもしれない、いかなる問題に対処することに役立つ。

注意:kotlinx-serialization 1.0.0-RCはKotlinコンパイラ1.4とのみ、連携する。より古いコンパイラバージョンとは互換性が無い。

スクリプトとREPL

1.4.0では、Kotlinのスクリプトは他の更新と一緒に多くの機能的、性能的な改善から、恩恵を受けている。ここにいくつかの重要な変更がある。

  • 新しい依存性解決API
  • 新しいREPL API
  • コンパイルされたスクリプトキャッシュ
  • 成果物の改名
    Kotlinでのスクリプトをより親しみやすくするため、サンプルプロジェクトを用意した。これは標準的なスクリプト(*.main.kts)の例と、Kotlin Scripting APIとカスタムスクリプト定義の使い方のを含んでいる。どうか試してissue trackerを使ってフィードバックを共有してほしい。

新しい依存性解決API

1.4.0では(Maven成果物のような)外部依存性を解決する新しいAPIがその実装と一緒に導入された。このAPIは新しい成果物であるkotlin-scripting-dependencieskotlin-scripting-dependencies-mavenで発行されている。以前のkotlin-script-utilライブラリでの依存性解決機能は、今は非推奨である。

新しいREPL API

新しい実験的REPL APIは今は、Kotlin Scripting APIの一部である。発行された成果物にいくつかの実装もあり、いくつかはコード補完のような高度な機能も持っている。このAPIはKotlin Jupyterカーネルで使われており、今では自分のカスタムシェルやREPLで試すことができる。

コンパイルされたスクリプトキャッシュ

Kotlin Scripting APIは今はコンパイルされたスクリプトキャッシュを実装する能力を提供しており、変更されていないスクリプトの後続の実行を大幅に高速化する。デフォルトの高度な実装であるkotlin-main-ktsはすでに自分のキャッシュを持っている。

成果物の改名

成果物の名前についての混乱を避けるため、kotlin-scripting-jsr223-embeddablekotlin-scripting-jvm-host-embeddableを単にkotlin-scripting-jsr223kotlin-scripting-jvm-hostに改名した.これらの成果物はkotlin-compiler-embeddable成果物に依存しており、それは利用の衝突を避けるため、同梱のサードパーティのライブラリを隠している。この改名とともに、スクリプト成果物のデフォルトであるkotlin-compiler-embeddable(一般により安全である)を利用していく。もし何らかの理由で、隠されていないkotlin-compiler成果物に依存した成果物が必要なら、-unshaededサフィックスのついた成果物バージョン、例えばkotlin-scripting-jsr223-unshaededを使用すること。留意すべきは、この成果物の改名は、直接使用することになっているスクリプト成果物にのみ影響し、他の成果物の名前は変更がない。

Kotlin 1.4への移行

Kotlinプラグインの移行ツールは、Kotlinの以前のバージョンから1.4へ移行をするのに役立つ。
単にKotlinバージョンを1.4.0に変更し、GradleまたはMavenプロジェクトを再インポートすることで、IDEは移行するか尋ねるだろう。
もし同意すると、移行コードのインスペクションが動き、コードをチェックし、1.4.0では動作しない、あるいは非推奨のものの修正を提案するだろう。
コードインスペクションは異なる深刻度を持ち、提案を受け入れるか無視するかの決定を支援する。
Kotlin 1.4.0は機能リリースであり、それ故、言語に対して互換性のない変更をもたらすことがありうる。そのような変更の詳細は、Kotlin 1.4互換性ガイドを参照のこと。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?