エンジニアHubPowered by エン転職

若手Webエンジニアのための情報メディア

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

Kotlin1.3に加わったCoroutineとKotlin/Nativeという2つの機能を中心に、実践的なプログラミングのヒント、そしてKotlinの最新動向について紹介します。

Kotlin1.3メインカット

今や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」

Kotlin/Nativeは、Kotlinで記述されたプログラムをさまざまなプラットフォーム向けのネイティブバイナリにコンパイルする技術です。

通常、KotlinのプログラムはJavaバイトコードにコンパイルされ、JVM上で動作します(後述するマルチプラットフォーム・プロジェクトを利用すればJavaScriptエンジンで動作するコードにコンパイルすることもできます)。

これに対してKotlin/Nativeでは、KotlinのプログラムからJVM無しで実行可能なバイナリを生成します。各プラットフォーム・アーキテクチャ向けの専用のバイナリであるため、それぞれのプラットフォームの性能を最大限に発揮することができるというメリットがあります。

Kotlin/NativeはKotlin本体とは別の独立したプロジェクトとして開発が進められていますが、Kotlin 1.3ではその最新のベータ版が正式にバンドルされるようになりました。本稿執筆時点では、次のプラットフォーム向けのバイナリ生成がサポートされています。

  • iOS(arm32、arm64、simulator x86_64)
  • MacOS(x86_64)
  • Android(arm32、arm64)
  • Windows(mingw x86_64、x86)
  • Linux(x86_64、arm32、MIPS、MIPS little endian、Raspberry Pi)
  • WebAssembly(wasm32)

ちなみに、Kotlin/Nativeは[LLVM]という技術をベースに実装されています。LLVMは任意プログラミング言語のコンパイラを実装するためのライブラリ一式を提供してくれるツールセットです。これを利用することによって、自前の言語のコンパイラを効率良く開発できるのです。

ネイティブバイナリを作成する

Kotlin/Nativeを使いたい場合、通常のKotlinコンパイラに加えてKotlin/Native用のパッケージが必要になります。GitHubのリポジトリから入手することもできますが、IntelliJ IDEAを使えば簡単に対応するプロジェクトを作成できるのでより実用的です。

IntelliJ IDEAでKotlin/Nativeのプロジェクトを作成する場合(図1)には、プロジェクトの種類として(images/create-native-project.png)のようにKotlin → Native | Gradleを選択します。

IntelliJ IDEAでKotlin/Nativeのプロジェクトを作成する場合

プロジェクトのディレクトリ構成は図2のようになります。

プロジェクトのディレクトリ構成図

srcディレクトリ以下に、各プラットフォーム用のディレクトリが配置されます。筆者の環境はmacOSなので、macosMainとmacosTestというディレクトリができています。

build.gradleは次のようになっています。

plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.3.50'
}
repositories {
    mavenCentral()
}
kotlin {
    // For ARM, should be changed to iosArm32 or iosArm64
    // For Linux, should be changed to e.g. linuxX64
    // For MacOS, should be changed to e.g. macosX64
    // For Windows, should be changed to e.g. mingwX64
    macosX64("macos") {
        binaries {
            executable {
                // Change to specify fully qualified name of your application's entry point:
                entryPoint = 'sample.main'
                // Specify command-line arguments, if necessary:
                runTask?.args('')
            }
        }
    }
    sourceSets {
        // Note: To enable common source sets please comment out 'kotlin.import.noCommonSourceSets' property
        // in gradle.properties file and re-import your project in IDE.
        macosMain {
        }
        macosTest {
        }
    }
}

// Use the following Gradle tasks to run your application:
// :runReleaseExecutableMacos - without debug symbols
// :runDebugExecutableMacos - with debug symbols

macOSをターゲットとする場合、kotlin以下にmacosX64というエントリを作成します。コメントに書かれている通り、Linuxの場合はlinuxX64を、ARMプラットフォームの場合はiosArm32またはiosArm64のエントリを作成します。Windowsでは、[MinGW]をインストールすることでKotlin/Nativeが使用できます。その場合、mingwX64というエントリを追加します。

デフォルトではエントリポイントがsample.mainとなっているので、この場合はsrc/macosMain/kotlin以下のsampleパッケージに宣言されているmain()メソッドが、プログラムの起動時に最初に呼び出されるコードということになります。

IntelliJ IDEAでは初期状態で次のようなプログラムが作成されています。

package sample

fun hello(): String = "Hello, Kotlin/Native!"

fun main() {
    println(hello())
}

プロジェクトをビルドすると、build/bin/macos/releaseExecutableディレクトリ以下に.kexeという拡張子の実行ファイルができています。このファイルは、次のようにターミナルなどから直接実行することができます。

$ ./KotlinNativeHello.kexe
Hello, Kotlin/Native!

C言語のダイナミックリンクライブラリを作成する

次に、Kotlin/Nativeを使ってC言語用のダイナミックリンクライブラリを作成してみましょう。さきほどと同様にKotlin/Native用のプロジェクトを作成したら、build.gradleのネイティブバイナリ用(本稿の例ではmacOS用)のエントリを次のように書き換えます。

 macosX64("macos") {
        binaries {
            sharedLib {
                baseName = "macos"
            }
        }
    }

DLL化するプログラムの本体は、sampleパッケージ以下にhello.ktとして次のようなファイルを用意しました。

package sample

fun strings(str: String) : String? {
    return "This is '$str' from C."
}

このプロジェクトをビルドすると、build/bin/macos/releaseSharedディレクトリ以下に、libmacos.dylibとlibmacos_api.hという2つのファイルが生成されます。前者がダイナミックリンクライブラリで、後者がC言語用のヘッダファイルです。

詳細な解説は省きますが、このヘッダファイルにはlibmacos_symbols(void)という関数が定義されており、この関数はlibmacos_ExportedSymbols*というポインタを返します(関数名・ポインタ名のプレフィックスはbuild.gradleのbasenameに依存)。このポインタがKotlinで作成したコードへのエントリポイントになります。

今回の例ではsampleパッケージにstrings()というメソッドを作りましたが、このメソッドをC言語のプログラムから呼び出すには、次の例のようにlibmacos_ExportedSymbols*のkotlin.root.sample.strings()関数を実行します。

#include <stdio.h>
#include "libmacos_api.h"

int main() {
    libmacos_ExportedSymbols* lib = libmacos_symbols();

    const char* str = "GIHYO";
    const char* response = lib->kotlin.root.sample.strings(str);

    printf("%s¥n", response);
}

このライブラリを使用したプログラムをtest.cという名前で作成し、コンパイルして実行してみましょう。Kotlinで記述したコードがライブラリ化されてC言語から呼び出せることが確認できます。

$ clang test.c libmacos.dylib
$ ./a.out
This is 'GIHYO' from C.

C言語のライブラリをKotlinから利用する

続いて、C言語用のライブラリをKotlin/Nativeのプログラムから利用するサンプルを紹介します。今回は、LinuxやmacOSなどでよく利用されるcurlコマンドのライブラリ、「libcurl」を使ってみます。

curlは、指定したURLから簡単にデータを取得することができるコマンドライン・ツールです。libcurlはこのcurlの機能をさまざまなプログラムで利用できるようにしたライブラリで、C言語向けのAPIはcurl.hとして提供されています。

以下に紹介するサンプルを試すにはlibcurlのパッケージが必要です。Linuxであればapt-getやyumで簡単にインストールできます。maxOSの場合、Xcodeのコマンドラインツールに付属するほか、Homebrewなどを使えば手軽にインストールできます。WindowsであればMinGWに付属しています。

まず、これまでと同じようにIntelliJ IDEAでKotlin/Nativeのプロジェクトを作成します。次に、Kotlin/Nativeのプログラムからlibcurlライブラリへの橋渡しとなる設定ファイルを用意します。srcディレクトリ以下に、nativeInterop/cinteropディレクトリを作り、その中にlibcurl.defという名前のファイルを作成してください。プロジェクトの構成は図3のようになります。

プロジェクト構成

libcurl.defには次のように記述します。

headers = curl/curl.h
headerFilter = curl/*

compilerOpts.osx = -I/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include
compilerOpts.linux = -I/usr/include -I/usr/include/x86_64-linux-gnu
linkerOpts.osx = -L/opt/lib -lcurl
linkerOpts.linux = -L/usr/lib/x86_64-linux-gnu -lcurl

headersおよびheaderFilterは、includeするヘッダファイルの指定です。compilerOptsはコンパイラに渡すオプション、linkerOptsはリンカに渡すオプションで、ターゲットとなるプラットフォームごとに用意する必要があります。筆者の環境はmacOSなので、compilerOpts.osxにlibcurlのヘッダファイルがある場所へのパスを、linkerOpts.osxにダイナミックリンクライブラリがある場所へのパスを設定してあります。指定するパスは環境に応じて書き換えてください。

なお、今回は使用しませんが、この定義ファイルにはそのほかにもC言語との橋渡しのためのさまざまな設定が記述でいます。

build.gradleは、macosX64("macos")のエントリの記述を次のように書き換えます。

 macosX64("macos") {
        compilations.main {
            cinterops {
                libcurl {
                    defFile project.file("src/nativeInterop/cinterop/libcurl.def")
                }
            }
        }
        binaries {
            executable {
                // Change to specify fully qualified name of your application's entry point:
                entryPoint = 'sample.main'
                // Specify command-line arguments, if necessary:
                runTask?.args('')
            }
        }
    }

ここで重要なのはcompilations.mainの記述で、ここでlibcurlのための定義ファイルとして先ほど作成したlibcurl.defのパスを設定します。

次のプログラムは、libcurlを利用して指定されたURLからコンテンツを取得するKotlin/Nativeのコード例です。

package sample

import libcurl.*
import kotlinx.cinterop.*
import platform.posix.size_t

fun main(args: Array<String>) {
    val url = "http://www.example.com/"

    val curl = curl_easy_init()     // curlを初期化
    if (curl != null) {
        // curlに対象URLをセット
        curl_easy_setopt(curl, CURLOPT_URL, url)
        curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L)

        // データを受信した際に呼び出されるコールバック関数をセット
        val writeData = staticCFunction(::write_callback)
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeData)
        // 受信データの書き出し先をセット(KotlinのStringを、C言語のvoid*に変換して渡す)
        val result = String()
        val stableRef = StableRef.create(result)
        val voidPtr = stableRef.asCPointer()
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, voidPtr);

        // 接続
        val res = curl_easy_perform(curl)
        if (res != CURLE_OK) {
            println("curl_easy_perform() failed " + curl_easy_strerror(res)?.toKString())
        }

        // クリーンアップ
        curl_easy_cleanup(curl)
        stableRef.dispose()
    }
}

fun write_callback(buffer: CPointer<ByteVar>?, size: size_t, nitems: size_t, userdata: COpaquePointer?): size_t {
    if (buffer == null) return 0u
    val bytes = buffer.readBytes((size * nitems).toInt())
    println(bytes.decodeToString().trim())
    return size * nitems
}

ここでimportしているlibcurlパッケージが、C言語のlibcurlライブラリとの橋渡しになります。kotlinx.cinteropは、C言語との相互運用をサポートするパッケージです。Kotlin/Mativeでは、Kotlinの型以外にPOSIXの型を利用することもできます。platform.posix.size_tは、POSIXのsize_tを表します。

curl_easy_init()curl_easy_setopt()などはlibsurlライブラリで提供されている関数です。Kotlinの関数と同様に呼び出すことができます。注意が必要なのは、引数としてポインタやコールバック関数が要求される場合です。

今回の例では、データを受信した際に呼び出されるコールバック関数としてreceive_callback()を定義しています。これはKotlinの関数なので、そのままではC言語のcurl_easy_setopt()に渡すことができません。

curl_easy_setopt()が要求するのはC言語の関数ポインタです。このような場合には、次のようにstaticCFunction()メソッドを使うことで、Kotlinの関数をC言語の関数ポインタに変換できます。

// Kotlinのwrite_callback関数をC言語の関数ポインタに変換
val receiveData = staticCFunction(::receive_callback)
// この関数呼び出しの第3引数にはコールバック関数へのポインタを指定する必要がある
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, receiveData)

C言語のプログラムでは、コールバックにデータを渡すためにポインタを引数に指定するケースがよくあります。Kotlin/Nativeでは、次のようにStableRefを利用することでKotlinのオブジェクトをC言語のポインタとして関数に渡すことができます。Kotlin/NativeにはC言語のポインタを表すCPointerCOpaquePointerなどといったクラスが定義されており、これらが実際の橋渡しになります。

// StringオブジェクトからStableRefを作成し
val result = String()
val stableRef = StableRef.create(result)
// それをC言語のポインタにラップする
// vPtrはCOpaquePointerのインスタンスで、これはCのポインタを表す
val vPtr = stableRef.asCPointer()
// この関数呼び出しの第3引数には、void*を渡す必要がある
curl_easy_setopt(curl, CURLOPT_WRITEDATA, vPtr);

このプロジェクトをビルドすると、build/bin/macos/releaseExecutableディレクトリ以下に.kexeという拡張子を持った実行ファイルが生成されます。これを実行すると、指定したURLからコンテンツを取得して内容が表示されます。

Kotlinはマルチプラットフォーム言語へと進化した

Kotlin/Nativeのサポートからもわかるように、現在のKotlinの進化において注目すべき点は、さまざまなプラットフォーム上で動作するマルチプラットフォーム言語へと成長しようとしていることです。冒頭で触れたように、KotlinはもともとJVM上で動作する言語として開発されました。しかしKotlin 1.1の頃から、そのターゲットをJVM以外のプラットフォームにも広げはじめました。

Kotlin 1.1、1.2の状況

まずKotlin 1.1では、JavaScriptターゲットのサポートが導入され、JavaScriptエンジンをターゲットとしたプロジェクトを作成できるようになりました。

Kotlin 1.2ではそれがさらに推し進められ、マルチプラットフォームプロジェクトが導入されました。マルチプラットフォームプロジェクトでは、複数のプラットフォームで動作するプログラムを、単一のコードベースから生成することができます。Kotlin 1.1ではJVM用とJavaScriptエンジン用で異なるコードベースが必要でしたが、Kotlin 1.2からは同じコードベースで両方のプラットフォーム向けのアプリケーションを作成できるようになったわけです。Kotlin 1.3では、そこにKotlin/Nativeがターゲットの1つとして加わりました。

Kotlin 1.3の状況

Kotlin 1.3からは、マルチプラットフォームプロジェクトのモジュールの構成も従来から大きく変更されています。従来のマルチプラットフォームプロジェクトでは、プラットフォーム固有のコードを記述するモジュールと、複数プラットフォーム共通のコードを記述するモジュールで区別して配置する必要がありました。共通モジュールからプラットフォーム固有モジュールを呼び出す場合には、expectedBy宣言によって依存関係を表現する仕組みになっています。

Kotlin 1.3では、このモジュール分けが廃止され、プラットフォームに依存しない共通コードとプラットフォーム固有のコードが同じモジュールの異なるソースルートに配置できるようになりました。これによってプロジェクトの構成がよりシンプルになっています。

また、Kotlin 1.3にはHTTP通信やシリアライゼーション、Coroutineの管理などといったよく使われる処理をサポートするためのマルチプラットフォーム・ライブラリが付属しています。これらのライブラリは、プラットフォーム固有の依存関係を隠蔽した共有APIを提供します。したがって、これをうまく活用することで、より簡単にプラットフォームに依存しないコードを記述することができるようになります。

まとめ

本稿では、Kotlin 1.3で追加された新機能から、とくに大きなアップデートであるCoroutineとKitlin/Nativeに焦点を当てて、簡単なサンプルコードを交えて紹介ました。この2つの機能は非常に強力なもので、本稿で紹介したのはごく基本的な使い方でしかありません。公式サイトのリファレンスには、各機能のより詳細な使い方が解説されています。

KotlinはJavaと同様に安全な静的型システムを持ちながら、最新の機能を率先して取り入れるモダンな言語である点が評価され、近年Androidだけではなくさまざまな分野で需要が高まっています。Javaとの互換性が高いことから、Javaベースのプロジェクトからの以降や相互運用を検討する声も少なくありません。

一方で、Javaにはない機能が多く取り入れられていることから、Javaに慣れ親しんだプログラマにとっては最初は少し戸惑う部分もあるかもしれません。

本文でも触れたように、最近のKotlinはマルチプラットフォーム対応に注力する方針を明らかにしていることから、今後ますます活用の場は広がっていくものと思われます。いざ導入となったときに困ることがないよう、早い段階で新しい機能に慣れ親しんでおくことが大切です。

杉山貴章 (すぎやま・たかあき) @zinbe

杉山貴章
有限会社オングスに所属するプログラマ。Javaを中心としたソフトウェア開発や、プログラミング関連書籍の執筆、IT系の解説記事やニュース記事の執筆などを手がけている。そのかたわら、専門学校の非常勤講師としてプログラミングやソフトウェア開発の基礎などを教えている。『Javaアルゴリズム+データ構造完全制覇』(刊:技術評論社)など著作も多い。

【修正履歴】ご指摘により一部誤記および、runBlockingに関する一部表示を修正しました。(2020年2月6日15時)

関連記事

編集:馮 富久(技術評論社)