Kotlin 1.3をサクッと学ぶ - CoroutineとKotlin/Nativeを触って理解しよう

Kotolin入門者に向け、Kotlin1.3に加わったCoroutineとKotlin/Nativeという2つの機能を中心に、実践的なプログラミングのヒントをお伝えします。

Kotlin 1.3をサクッと学ぶ - CoroutineとKotlin/Nativeを触って理解しよう

今やAndroidアプリ開発には欠かすことのできない言語となった「Kotlin」。2018年秋にバージョン1.3がリリースされ、その後も定期的にバージョンアップが進んでいます。Kotlin 1.3の大きな特徴は、CoroutineとKotlin/Nativeという2つの機能が追加されたことです。本記事では、この2つの機能を中心にKotlinの最新動向について紹介しながら、実践的プログラミングへのヒントをお届けします。

本記事のサンプルプログラムは、macOS上のIntelliJ IDEA Community Edition バージョン2019.2.3を使って動作確認しています。IntelliJ IDEA Community EditionはJetBrainsのサイトよりダウンロードできます。また、使用したKotlinのバージョンは1.3.50です。

Kotlinとは

Kotlin」は、統合開発環境「IntelliJ IDEA」などで知られるJetBrains社が中心となって開発を進めている静的型付けのオブジェクト指向プログラミング言語です。もともとはJava仮想マシン(以下、JVM)上で動作する言語として開発されたもので、Javaとの高い互換性を持つ一方で、Javaにはないさまざまなモダンな言語機能を導入している点が大きな特徴です。

KotlinはJavaと同様のコンパイル言語であり、コンパイラはJVM上で実行可能なJavaバイトコードを出力します。既存のJava言語やJavaライブラリとの互換性が保たれており、KotlinからJavaのコードを呼び出したり、逆にJavaからのKotlinのコードを呼び出すことができるなど、相互の親和性が極めて高いことが大きな強みです。

2017年になって、GoogleはKotlinをAndroidアプリの公式開発言語として採用しました。この発表によってKotlinはもっとも注目されるプログラミング言語の1つとなりました。すでに多くのAndroidアプリがKotlinによって開発されており、今後も大きくシェアを伸ばすことが予想されています。

Kotlin 1.3 最新動向

本稿執筆時点におけるKotlinの最新バージョンは、2019年8月22日にリリースされたKotlin 1.3.50です。これは2018年10月にリリースされたKotlin 1.3から続くマイナーバージョンアップになります。

Kotlin 1.3では、Kotlinの将来にとって非常に重要な2つの新機能が追加されました。1つは「Coroutine」、もう1つは「Kotlin/Native」です。

Coroutineは、非同期処理やノンブロッキング処理を行うため軽量なスレッドのようなものです。スレッドよりも軽量に動作し、実行の途中で処理を中断・再開することができます。Kotlin/Nativeは、Kotlinのコードからさまざまなプラットフォームのネイティブバイナリを生成することができるツールです。前者はKotlinアプリケーションの性能向上のために、後者はKotlinがマルチプラットフォーム言語へと進化していくために、それぞれ極めて大切な役割を担っています。

非同期処理の高速化を実現する「Coroutine」

1つのプログラムで複数の処理を並列で実行したい場合、KotlinではJavaなどと同様にスレッドを使用して実装するのが従来の方法でした。スレッドとはコンピュータのプログラムにおける実行単位で、1つのプログラム中で複数の処理を同時に実行したい場合、新しくスレッドを作ることでメインの処理を停止させることなく別の処理を行うことができます。

アプリケーション内で非同期な処理を行いたいときは、メインの処理とは別にバックグラウンドで実行される処理が必要となるため、通常はスレッドを利用して実装します。しかし、実行されるスレッドの制御はシステムスケジューラによって行われるため、切り替え時のオーバーヘッドが大きく、メモリ使用量も増えることから、性能低下につながりやすいといった問題があります。

Kotlin 1.3で新しく正式サポートされたCoroutineは、非同期処理やノンブロッキング処理を行う際にスレッドの代わりに利用できるより軽量な実行単位です。通常のプログラムの流れとは異なり、Coroutineは途中で処理を中断したり、あとから任意のタイミングで再開したりできます。処理の切り替えのタイミングはプログラム内で制御できるためオーバーヘッドが小さく、スレッドを使った非同期処理よりも軽量に動作するという強みがあります。数千や数万といった数のCoroutineでもスムーズに実行することもできるとのことです。

Coroutineは、Kotlin 1.2までは実験的な機能という扱いになっていましたが、Kotlin 1.3から正式版としてサポートされるようになりました。

Coroutineの基本的な使い方

それでは、さっそくCoroutineの使い方を見ていきましょう。IntelliJ IDEAでGradleを使ってプロジェクトをビルドする場合、build.gradleファイルは次のような記述になります。リポジトリにはjcenter()を指定し、依存関係にkotlinx-coroutines-coreを追加しています。

plugins {
    id 'org.jetbrains.kotlin.jvm' version '1.3.50'
}

version '1.0-SNAPSHOT'

repositories {
    jcenter()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

最も基本的なCoroutineの作成方法は、次のようにlaunch関数を使用します。launchの引数のラムダを利用して処理を記述することで、新しいCoroutineが作られてその上で記述した処理が実行されます。

GlobalScope.launch {
    // Coroutineの処理
}

たとえば次のようなプログラムを書いたとします。delay()は指定されたミリ秒だけCoroutineの処理を中断するメソッドです。

import kotlinx.coroutines.*

fun main(args: Array<String>) {
    println("Start")

    // Coroutineを開始
    GlobalScope.launch {
        delay(1000)
        println("Hello!")
    }

    println("End")
}

これを実行すると次のように出力されます。

Start
End

これではCoroutine内の処理が実行されていないように見えます。実はlaunchで作られたCoroutineは現在のスレッドの処理をブロックしないため、Coroutineの終了を待つことなく次の処理が実行され、この場合はそのままプログラム本体が終わってしまうからです。

launchのようなCoroutineを作成することができる関数をCoroutineビルダーと呼びます。KotlinにはさまざまなタイプのCoroutineを作るCoroutineビルダーが用意されています。現在のスレッドの処理をブロックしたい場合には、次のようにrunBlockingというCoroutineビルダーを使用します。

        println("Start")

        // Coroutineを開始
        runBlocking {
                delay(1000)
                println("Hello!")
        }

        println("End")

これを実行すると次のように出力されます。

Start
Hello!
End

まず「Start」が出力され、Coroutineが起動して「Hello」が出力されたあとで、その終了を待って「End」が出力されていることがわかります。

Coroutineの内部でlaunchを使って非ブロッキングでほかのCoroutineを立ち上げ、その終了を待ちたい場合には、次のようにlaunchの戻り値に対してjoin()を使用します。なお、launchが戻り値として返すのはJobインスタンスで、これはCoroutineがバックグラウンドで実行する処理を表します。

 runBlocking {
        println("Start")
        GlobalScope.launch {
            println("Hello!")
        }.join()
        println("End")
    }

Coroutineから戻り値を受け取りたい場合には、次のようにasyncというCoroutineビルダーが利用できます。

 runBlocking {
        println("Start")
        val deferred = GlobalScope.async {
            delay(1000)
            "Hello!"
        }
        println("Here")

        val text = deferred.await()
        println(text)

        println("End")
    }

asyncはDeferredのインスタンスを返します。このインターフェースにはawait()メソッドが定義されており、これはasyncで起動されたCoroutineの処理が終了するまで現在のCoroutineをブロックします。終了したCoroutineが返す値はawait()の戻り値として取得できます。

これを実行すると、次のように、await()のタイミングでCoroutineが終了するまで処理が中断しているのがわかります。

Start
Here
Hello!
End

join()await()は現在のCoroutineの処理を中断することができます。このようなメソッドのことを中断関数(Suspending Function)と呼びます。中断関数は、Coroutineやほかの中断関数の内部でしか呼び出すことができないという決まりがあるため、Coroutineでない通常のスレッドで使うことはできません。

非同期処理でHTTP GETを実行する

次に示す例は、Coroutineを利用して非同期処理で通信を行うプログラムです。10秒に1回、HTTP GETで指定されたURLのコンテンツを読み込みます。

import java.net.*
import java.util.stream.Collectors
import kotlinx.coroutines.*

class HttpUtil {
    // Coroutineを作成
    fun httpGet(url: URL): Deferred<String?> = GlobalScope.async {
        // 指定URLからコンテンツを読み込む
        with(url.openConnection() as HttpURLConnection) {
            requestMethod = "GET"
            val result = inputStream.bufferedReader().use {
                it.lines().collect(Collectors.joining("¥n"))
            }
            return@async result;
        }
    }
}

HttpUtilクラスのhttpGet()メソッドは、asyncでCoroutineを作成して、Deferred<String?>インスタンスを返します。このCoroutineの処理は、指定されたURLのコンテンツをGETメソッドで読み込んでその内容をリターンするというものです。

import java.net.URL
import java.util.*
import kotlinx.coroutines.*

fun main(args: Array<String>) {
    // 10秒に一度実行されるタスクをセット
    Timer().schedule( object: TimerTask() {
        override fun run() {
            // Coroutineを開始
            GlobalScope.launch {
                val httpUtil = HttpUtil()
                val url = URL("http://example.com/")
                val result = httpUtil.httpGet(url).await()
                println(result)
            }

            /* Some tasks */
        }
    }, 0, 10000)
}

呼び出し元では、await()httpGet()の処理が終了するのを待った上で、その結果を表示しています。await()は中断関数なのでCoroutineの中でした実行することができません。ここでは、HTTP通信の処理全体をlaunchでCoroutine化しています。そのため、/* Some tasks */部分の処理は、通信処理の結果を待つことなく実行されます。

複数プラットフォーム向けのネイティブバイナリを生成する「Kotlin/Native」

エンジニアHubに会員登録すると
続きをお読みいただけます(無料)。
登録のメリット
  • すべての過去記事を読める
  • 過去のウェビナー動画を
    視聴できる
  • 企業やエージェントから
    スカウトが届く