Google Maps Platform Android SDK は、非同期操作を管理するコードを記述するのに役立つ、リアクティブ プログラミングの拡張機能をサポートしています。
ユーザーのタッチイベントから、ネットワーク呼び出し完了の待機やプッシュ通知の受け取りに至るまで、モバイルアプリでは非同期イベントはあらゆるタイミングで発生する可能性があります。こうしたイベントを考慮しながら他の非同期イベントと組み合わせてコードを記述するのは、アプリ デベロッパーにとって手間のかかる作業です。リアクティブ プログラミングを使えば個々のイベントのコールバックを渡す必要がなくなるため、非同期イベントを扱うプロセスを簡素化できます。リアクティブ プログラミングでは、イベントはストリーミングとしてモデル化され、アイテムが 1 つずつ処理されます。
リアクティブなコード記述には、Kotlin Flows と RxJava の 2 つのライブラリを使用できます。次の 2 つの動画で、それぞれのライブラリの使い方を説明します。
Kotlin でコルーチンと Flow を使用する場合は、KTX ライブラリを使えば Kotlin Flow でイベントを受け取ることができます。Maps KTX ライブラリには Kotlin Flow オブジェクトを返す拡張機能が含まれているため、リアクティブにイベントをリッスンできます。Kotlin Flows では、アクティビティを一時停止して 1 個の値を返すのではなく、時間の経過とともに複数の値を 1 個ずつ返すことができます。例えばカメラのように時間の経過とともに変化するイベントも、Flow を使って受け取ることができます。アプリで Kotlin Flow を使うには、build.gradle ファイルの依存関係セクションに Maps KTX ライブラリを追加します。
一般に広く使用されている Android ライブラリ RxJava を Google Maps Platform の SDK に統合します。RxJava はリアクティブ拡張機能を Java で実行するもので、観測可能なシーケンスを使って非同期かつイベントベースのプログラムを記述するためのライブラリです。RxJava を使えば、連鎖的に発生する変化をコールバック ベースの非同期コードで記述できます。この仕組みと、RxJava のその他の使い方を 3 つ目の動画で説明します。
今回の 3 部構成の YouTube 動画シリーズでリアクティブ プログラミングの考え方について理解を深め、レスポンシブでリアクティブなモバイルアプリの作成にお役立ていただければ幸いです。役に立つ動画のアイデアがあれば、私たちの動画のコメント欄に投稿してください(どの動画でもかまいません)。最新情報、チュートリアル、お客様事例などをお届けする Google Maps Platform の YouTube チャンネルに、ぜひご登録ください。
Google Maps Platform に関する詳しい情報はこちらをご覧ください。ご質問やフィードバックはページ右上の「お問い合わせ」より承っております。
WorkManager は、非同期タスクのスケジュールを簡単に設定するための一連の API を提供します。これにより、アプリが閉じられた場合やデバイスが再起動した場合にも実行されることが期待されるタスクを即時実行または遅延実行できます。また、WorkManager は Kotlin ユーザーに最大級のコルーチンのサポートを提供します。この投稿では、WorkManager Codelab の内容に基づいて、WorkManager とコルーチンの基本について説明します。では早速始めましょう。
ユーザーが特定の画面から離れたり、アプリがバックグラウンド状態になったり、デバイスが再起動したりしても実行を続ける必要があるタスクには、WorkManager ライブラリを使うことが推奨されています。一般的には、以下のようなタスクが考えられます。
ユーザーが画面などの特定のスコープを離れたときに即時実行したタスクが終了する可能性がある場合は、直接 Kotlin コルーチンを使うことを推奨します。
WorkManager Codelab では、画像をぼかしてその結果をディスクに保存します。これを実現するために必要なことを確認しましょう。
まず、work-runtime-ktx 依存性を追加しました。
implementation "androidx.work:work-runtime-ktx:$work_version"
そして、独自の Worker クラスを実装します。ここに、バックグラウンドで実行する実際の作業に必要なコードを含めます。Worker クラスを拡張し、doWork() メソッドをオーバーライドします。これは一番重要なクラスなので、のちほど詳しく説明します。最初の実装は次のようになります。
次に、作業のリクエストを作成します。今回の場合は、作業を一度だけ行いたいので、OneTimeWorkRequest.Builder を使います。入力として、ぼかしたい画像の Uri を設定します。
Kotlin のヒント: 入力データを作成するために、workDataOf 関数を使うことができます。この関数は、データビルダーを作成し、キーと値のペアを格納してデータを作成します。
作業のスケジュールを設定して実行するには、WorkManager クラスを使います。その際に、実行するタスクと、タスクへの制約を指定できます。
Worker を使うと、WorkManager は自動的にバックグラウンド スレッドで Worker.doWork() を呼び出します。doWork() から返される Result は、WorkManager サービスに作業が成功したかどうかと、失敗した場合には作業を再試行すべきかどうかを通知します。
Worker.doWork() は同期呼び出しです。バックグラウンドの作業全体をブロックありで実行し、メソッドが終了するときには完了していることになります。doWork() で非同期 API を呼び出して Result を返すと、コールバックが正しく動作しない場合があります。
もう少し複雑な例として、ぼかしをかけたすべてのファイルの Uri をデータベースに保存する場合を考えてみましょう。
これを実現するために、以下を作成しました。
実装はこちらをご覧ください。
Kotlin でデータベースへのデータの保存やネットワーク リクエストなどの非同期作業を行う必要がある場合は、CoroutineWorker の利用を推奨します。
CoroutineWorker を使うと、Kotlin コルーチンを使って非同期作業を行えます。
doWork() メソッドは suspend メソッドです。つまり、中断を伴う dao を簡単に呼び出すことができます。
デフォルトで、doWork() は Dispatchers.Default を使います。この動作は必要な Dispatcher でオーバーライドできます。今回は、既に Room が挿入作業を別の Dispatcher に移動させているので、これを行う必要はありません。詳しくは、Room Kotlin API の投稿をご覧ください。
ぜひ CoroutineWorker を使って、ユーザーがアプリを閉じても完了しなければならない非同期作業を実行してみてください。
WorkManager についてもっと詳しく知りたい方は、今後のシリーズで詳しく解説しますので、ご期待ください。それまでの間は、Codelab やドキュメントをご覧ください。
この記事は Florina Muntenescu による Android Developers Blog の記事 "MAD Skills Kotlin and Jetpack: wrap-up" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
今回は MAD Skills シリーズの 1 つ、Kotlin と Jetpack についての動画と記事をまとめました。Android コードの表現力と簡潔さ、安全性を向上させ、Kotlin で非同期コードを実行しやすくするさまざまな方法を取り上げています。
それぞれのエピソードから、Kotlin と Jetpack についての最新情報をご確認ください。いくつかの具体的な API を取り上げ、API の使い方だけでなく、API が内部的にどのように動作しているか解説しています。また、すべてのエピソードには対応するブログ投稿があり、そのほとんどにサンプルか Codelab へのリンクが含まれているので、実際に試してみたり、コンテンツに関する理解を深めたりできます。また、Jetpack や Kotlin のエンジニアが登場するリアルタイム Q&A も実施しました。
このエピソードでは、Jetpack KTX 拡張機能を使って、Android と Jetpack のコーディングを簡単、快適、そして Kotlin らしくする方法を取り上げました。現在のところ、20 以上のライブラリに KTX 版があり、その中から特に重要なものを紹介します。core-ktx は、Android プラットフォームに由来する API を Kotlin らしく書けるようにする機能を提供しています。また、LiveData や ViewModel などの API と組み合わせてユーザー エクスペリエンスの向上を図れるいくつかの Jetpack KTX ライブラリも紹介します。
動画またはブログ記事(英語)をご覧ください。
エピソード 2 では、コルーチンと Flow を使って API をシンプルにする方法と、suspendCancellableCoroutine API と callbackFlow API を使って独自のアダプタを作る方法について説明します。このトピックを実際に試してみたい方は、Kotlin 拡張機能ライブラリの作成 Codelab をご覧ください。
動画を視聴するか、ブログ記事(英語)でご確認ください。
このエピソードでは、実際に Room を使ってみます。その上で、Kotlin を使って Room のテーブルやデータベースを作る方法、挿入などの 1 回限りの suspend 操作を実装する方法、Flow を使った監視可能クエリーについて簡単に確認します。コルーチンと Flow を使うと、Room はすべてのデータベース操作をバックグラウンド スレッドに移してくれます。Room のクエリーの実装方法やテスト方法については、動画またはブログ記事(英語)をご覧ください。実際に試してみたい方は、ビューで Room を使う Codelab をご覧ください。
エピソード 4 では、WorkManager を使って作業を簡単にする方法について説明します。この機能を使うと、非同期タスクのスケジュールを設定して、アプリが閉じられた場合や、デバイスが再起動した場合にも実行されることが期待されるタスクを、即時実行または遅延実行できます。このエピソードでは、WorkManager の基本について説明し、CoroutineWorker などの Kotlin API についても解説しています。
こちらの動画またはこちらのブログ記事(英語)をご覧ください。また、ぜひ WorkManager Codelab で実際に体験してみてください。
エピソード 5 では、Android の Google Developer Expert の Magda Miu さんが Kotlin の基本 API と CameraX を使った経験についてお話ししています。
最後のエピソードでは、リアルタイム Q&A を実施しました。司会の Chet Haase のほか、ゲストとして Architecture Components テックリードの Yigit Boyar、Kotlin プロダクト マネージャーの David Winer、そしてデベロッパー リレーションズ エンジニアの Manuel Vivo と私が参加し、YouTube、Twitter などから寄せられた質問に回答しています。
Reviewed by Yuichi Araki - Developer Relations Team and Hidenori Fujii - Google Play Developer Marketing APAC
この記事は Florina Muntenescu による Android Developers - Medium の記事 "More productivity with Kotlin" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Kotlin は簡潔なプログラミング言語として知られています。そしてそれは、高い生産性を意味します。そして実際に、Kotlin を使っている Android デベロッパーの 67% が、生産性が向上したと述べています。このブログ投稿では、Kotlin を使ってパートナーのエンジニアたちが生産性を向上させた方法をいくつか共有し、そのために役立つ Kotlin の機能も紹介します。
レビューやメンテナンスの担当者は、読むコードが少なくなるので、コードが行っていることを理解しやすくなります。そのため、レビューやメンテナンスが楽になります。
その一例として、Flipkart のチームを紹介しましょう。
「弊社の社内調査によると、デベロッパーの 50% が、Kotlin でモジュールを書くと [機能を完成させるまでの] 見積りが小さくなると回答しました」(Flipkart)
Kotlin の機能のほとんどは、簡潔さと高い可読性を持つため、高い生産性につながります。特によく使われる機能について見てみましょう。
Java プログラミング言語では、コンストラクタのパラメータが省略可能な場合、一般的に次の 2 つの方法のどちらかを利用します。
Kotlin ではデフォルト引数を利用できるので、どちらも必要ありません。デフォルト引数を使うと、ボイラープレートを追加することなく、関数のオーバーロードを実装できます。
Cash App チームが Kotlin を使い始めたとき、多くのビルダーを削減し、書く必要があるコードの量を減らすことができました。場合によっては、コードのサイズが 25% 少なくなりました。
たとえば、以下の一例は、 Task オブジェクトの実装で、タスクの名前のみが必須パラメータになっています。ビルダーを使った場合と、デフォルト引数を使った場合でそれぞれどうなるかを示しています。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */- public class Task {- private final String name;- private final Date deadline;- private final TaskPriority priority;- private final boolean completed;-- private Task(String name, Date deadline, TaskPriority priority, boolean completed) {- this.name = name;- this.deadline = deadline;- this.priority = priority;- this.completed = completed;- }-- public static class Builder {- private final String name;- private Date deadline;- private TaskPriority priority;- private boolean completed;-- public Builder(String name) {- this.name = name;- }-- public Builder setDeadline(Date deadline) {- this.deadline = deadline;- return this;- }-- public Builder setPriority(TaskPriority priority) {- this.priority = priority;- return this;- }-- public Builder setCompleted(boolean completed) {- this.completed = completed;- return this;- }-- public Task build() {- return new Task(name, deadline, priority, completed);- }- }-}+ data class Task(+ val name: String,+ val deadline: Date = DEFAULT_DEADLINE,+ val priority: TaskPriority = TaskPriority.LOW,+ val completed: Boolean = false+)
/* Copyright 2020 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
- public class Task {
- private final String name;
- private final Date deadline;
- private final TaskPriority priority;
- private final boolean completed;
-
- private Task(String name, Date deadline, TaskPriority priority, boolean completed) {
- this.name = name;
- this.deadline = deadline;
- this.priority = priority;
- this.completed = completed;
- }
- public static class Builder {
- private Date deadline;
- private TaskPriority priority;
- private boolean completed;
- public Builder(String name) {
- public Builder setDeadline(Date deadline) {
- return this;
- public Builder setPriority(TaskPriority priority) {
- public Builder setCompleted(boolean completed) {
- public Task build() {
- return new Task(name, deadline, priority, completed);
-}
+ data class Task(
+ val name: String,
+ val deadline: Date = DEFAULT_DEADLINE,
+ val priority: TaskPriority = TaskPriority.LOW,
+ val completed: Boolean = false
+)
デフォルト引数の詳細については、連載シリーズ Kotlin Vocablary のブログ記事「Kotlin のデフォルト引数」をご覧ください。
おそらく、シングルトン パターンはソフトウェア開発で特によく使われるパターンの 1 つでしょう。オブジェクトのインスタンスを 1 つだけ作成し、他のオブジェクトから共有してアクセスできるようにしたい場合に役立ちます。
シングルトンを作るには、インスタンスが 1 つだけ存在するようにオブジェクトの作成方法を制御し、コードがスレッドセーフであることを保証する必要があります。Kotlin では、object キーワードだけでこれを実現できます。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ - public class Singleton{- private static volatile Singleton INSTANCE;- private Singleton(){}- public static Singleton getInstance(){- if (INSTANCE == null) { // Single Checked- synchronized (Singleton.class) {- if (INSTANCE == null) { // Double checked- INSTANCE = new Singleton();- }- }- }- return INSTANCE;- }- private int count = 0;- public int count(){ return count++; }- }+ object Singleton {+ private var count = 0+ fun count(): Int {+ return count+++ }+ }
- public class Singleton{
- private static volatile Singleton INSTANCE;
- private Singleton(){}
- public static Singleton getInstance(){
- if (INSTANCE == null) { // Single Checked
- synchronized (Singleton.class) {
- if (INSTANCE == null) { // Double checked
- INSTANCE = new Singleton();
- return INSTANCE;
- private int count = 0;
- public int count(){ return count++; }
+ object Singleton {
+ private var count = 0
+ fun count(): Int {
+ return count++
+ }+ }
Kotlin 言語の簡潔さとシンプルさは、演算子オーバーロード、分割代入、文字列テンプレートなどの機能から明らかです。そのため、コードはとても読みやすくなります。
たとえば、本を集めたライブラリがあるとしましょう。ライブラリから本を取り出し、そのタイトルだけを出力する場合、コードは次のようになります。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */fun borrow(){ library -= book val (title, author) = book println("Borrowed $title")}
fun borrow(){
library -= book
val (title, author) = book
println("Borrowed $title")
}
使われている Kotlin の機能は次のとおりです。
Kotlin を使うと、コードは読みやすく、書きやすくなります。シングルトンや委譲などのパターンが言語に組み込まれており、たくさんのコードを書く必要がないため、バグが紛れ込む確率が低くなり、メンテナンスの負荷も軽減されます。また、文字列テンプレート、ラムダ式、エクステンション関数、演算子オーバーロードなどの機能で、コードをさらに簡潔かつシンプルにできます。書くコードが少なくなれば、読むコードやメンテナンスするコードも少なくなり、エラーが減って生産性が上がります。
詳しくは、Kotlin と Android Kotlin でより優れたアプリを作成するをお読みください。また、各社のケーススタディをご覧ください。デベロッパーにとっての Kotlin のメリットが確認できます。世界で特に好まれている開発言語の 1 つである Kotlin を使ってみたい方は、入門ページをご覧ください。
この記事は David Winer による Android Developers Blog の記事 "The future of Kotlin Android Extensions" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
Android Kotlin Extensions Gradle プラグイン(Android KTX と混同しないでください)は、2017 年にリリースされ、Kotlin による Android 開発に次の 2 つの便利な新機能をもたらしました。
findViewById
kotlinx.android.synthetic
@Parcelize
しかしその後、Android ビルド ツールチェーンと密接に統合された公式サポート ライブラリ View Binding for Android がリリースされ、Kotlin synthetics と同等の機能が提供されるようになりました。Parcelize は現在も使用が推奨されていますが、Kotlin synthetics の使用についてはたとえば以下のようにさまざまな欠点が明らかになりました。
Android Kotlin Extensions プラグインは、もともと JetBrains が開発したものです。そして私たちは、JetBrains とともに、synthetics のメンテナンスを続けることの賛否について議論してきました。その中では、可能な限り API を長期にわたってサポートできるよう努力すべきという視点もありましたが、その一方でユーザーにとって使いやすいアプリを作れるよう、デベロッパーの皆さんにベスト プラクティスを提供してコードベースを健全化することの重要性も指摘されました。
そして結論として、私たちのチームは共同で、推奨オプションである View Binding のサポートを続けることを優先し、来年 1 年をかけて synthetics のサポートを終了することを決めました。
kotlin-parcelize
android-kotlin-extensions
サポートの終了期間は、2020 年 11 月 23 日(日本時間 11 月 24 日)にリリースされた Kotlin 1.4.20 から始まります。android-kotlin-extensions は少なくとも 1 年間は残されますが、2021 年 9 月またはそれ以降の Kotlin リリースでは削除されます。kotlin-parcelize プラグインのメンテナンスは長期的に続ける予定です。Parcelize で発生する問題についてのご連絡は、今後も Android Studio Issue Tracker にお寄せください。
Reviewed by Takeshi Hagikura - Developer Relations Team and Hidenori Fujii - Google Play Developer Marketing APAC
ユーザーは皆さんのアプリにシームレスな体験を期待しています。アプリがクラッシュすれば、低評価のレビューやアンインストールが増え、ブランドにダメージを与えてしまうでしょう。その一方で、コミュニティの皆さんとの対話の中で、Kotlin を採用する主な理由の 1 つはコードの高い安全性であるということをよく耳にします。実際に何社かのパートナーは、Kotlin を使ってコードの安定性を改善しています。この投稿では、その方法をいくつか紹介するとともに、Google Play ストアの統計結果にも注目し、Kotlin とクラッシュ数との間に相関関係があるかどうかを確認してみたいと思います。
アプリの品質が影響するのは、ユーザー エクスペリエンスだけではありません。クラッシュ多いと、他のいくつかの要素にも悪影響が生じます。
Kotlin で構築したアプリはクラッシュする可能性が 20% 低い
Google Play のトップ 1,000 アプリを調査してみたところ、Kotlin を使っているアプリは、それ以外のアプリよりもユーザー 1 人あたりのクラッシュ発生率が 20% 低いことがわかりました。
その具体例として、コードの 74% が Kotlin である Swiggy のエンジニアリング チームは、新機能の開発を Kotlin に移行して以来、クラッシュを 50% 減らしました。こういった結果を Kotlin はどのように実現しているのか見ていきましょう。
Google Play でのクラッシュの原因ナンバーワンは NullPointerException です。Google Home チームは、2018 年よりすべての新機能を Kotlin で書いています。その結果、1 年前と比べて null ポインタによるクラッシュが 33% 減少しました。
Google Home が NullPointerException を 33% 削減
NullPointerException を避けるには、メソッドを呼び出したりメンバーにアクセスしたりする前に、扱っているオブジェクトの参照が null でないことを確認しなければなりません。Kotlin では、null 可能性が型システムの一部として組み込まれています。たとえば、変数は最初から null 可能か null 不可能かを宣言する必要があります。null 可能性を型システムの一部として組み込むことで、コードベースについての自分の記憶や知識、またコンパイル時警告(フィールドやパラメータに @Nullable アノテーションを付けた場合)に頼る必要はなくなります。null 可能性を強制することで、単なる警告ではなく、コンパイル時にエラーが発生します。null 可能性を扱う方法については、こちらのページをご覧ください。
NullPointerException
@Nullable
私たちデベロッパーは、気づかないうちにアプリにたくさんの問題を紛れ込ませています。そのほとんどはあまりに軽微で、調査するのは難しいかもしれません。そのような問題のうち、Kotlin を使うことで回避できるものをいくつか紹介しましょう。
hashCode()
2 つのオブジェクトが等しい場合、それらのハッシュコードも同じである必要があります。しかし、どちらかのメソッドを実装し忘れたり、クラスに新しいプロパティを追加したときに更新し忘れてしまったりすることがよくあります。データを保持するためだけのクラスを扱う場合は、Kotlin のデータクラスを使うようにしましょう。データクラスを使うと、コンパイラが hashCode() と equals() を生成してくれるので、クラスのプロパティを変更すると自動的に更新されます。
equals()
2 つのオブジェクトは構造が等しい(中身が同じ)のでしょうか。それとも、参照が等しい(ポインタが同じ)のでしょうか。Java プログラミング言語では、プリミティブには常に == を使います。そのため、実際には構造が等しいかどうかを確認(equals() を呼び出してチェック)したいのに、オブジェクトにも ==(参照が等しい)を使ってしまうというのがよくある誤りです。まず、Kotlin にはプリミティブ型はなく、Int や String などのクラスを使います。つまり、すべてがオブジェクトなので、オブジェクトとプリミティブ型の区別を気にする必要はありません。次に、Kotlin では == は構造が等しい、=== は参照が等しいと定義されています。したがって、誤って参照が等しいかどうかを確認してしまうことはありません。
==
Int
String
===
enum を扱う場合、通常はすべての可能なケースを記述しなければなりません。そのため、switch や if else チェーンを使うことになります。enum を修正して新しい値を追加する場合、enum を使っている各コード スニペットを手動でチェックし、新しいケースに対応しているかどうかを確認する必要があります。しかし、これはエラーにつながりがちです。Kotlin では、when を式として使うと、この確認をコンパイラに任せることができます。すべての可能な分岐に対応していない場合、コンパイラ エラーが発生します。
switch
if else
ユーザーやブランドにとって、アプリの安定性は重要です。Kotlin を使い始めてクラッシュ率を減らし、ユーザーの満足度を高めてアプリの高評価を守り、ユーザーの維持や獲得につなげましょう。
詳しくは、Kotlin でより優れたアプリを作成するためにできることをお読みください。また、ケーススタディをご覧いただき、Kotlin のメリットをご確認ください。世界でデベロッパーに広く使われている言語の 1 つである Kotlin を使ってみたい方は、入門ページをご覧ください。
この記事は Florina Muntenescu による Android Developers - Medium の記事 "Don’t argue with default arguments" を元に翻訳・加筆したものです。詳しくは元記事をご覧ください。
短くて使いやすいデフォルト引数を利用すると、ボイラープレートを書くことなく関数のオーバーロードを実現できます。多くの Kotlin の機能と同じように、この機能も魔法のように便利です。その秘密を知りたいと思いませんか?この記事では、デフォルト引数の内部の仕組みを紹介します。
関数のオーバーロードが必要な場合、同じ関数を複数回実装する代わりに、デフォルト引数を使うことができます。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 -->// instead of:fun play(toy: Toy){ ... }fun play(){ play(SqueakyToy)} // use default arguments: fun play(toy: Toy = SqueakyToy)fun startPlaying() { play(toy = Stick) play() // toy = SqueakyToy}
<!-- Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->
// instead of:
fun play(toy: Toy){ ... }
fun play(){
play(SqueakyToy)
// use default arguments:
fun play(toy: Toy = SqueakyToy)
fun startPlaying() {
play(toy = Stick)
play() // toy = SqueakyToy
デフォルト引数はコンストラクタにも適用できます。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 -->class Doggo( val name: String, val rating: Int = 11)val goodDoggo = Doggo(name = "Tofu")val veryGoodDoggo = Doggo(name = "Tofu", rating = 12)
class Doggo(
val name: String,
val rating: Int = 11
)
val goodDoggo = Doggo(name = "Tofu")
val veryGoodDoggo = Doggo(name = "Tofu", rating = 12)
デフォルトでは、Java はデフォルト値のオーバーロードを認識しません。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> // kotlinfun play(toy: Toy = SqueakyToy) {... }// javaDoggoKt.play(DoggoKt.getSqueakyToy());DoggoKt.play(); // error: Cannot resolve method 'play()'
// kotlin
fun play(toy: Toy = SqueakyToy) {... }
// java
DoggoKt.play(DoggoKt.getSqueakyToy());
DoggoKt.play(); // error: Cannot resolve method 'play()'
コンパイラにオーバーロード メソッドを生成するよう指示するには、Kotlin 関数に @JvmOverloads アノテーションを追加します。
@JvmOverloads
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */@JvmOverloadsfun play(toy: Toy = SqueakyToy) {… }
fun play(toy: Toy = SqueakyToy) {… }
コンパイラが生成した内容を確認するため、逆コンパイルした Java コードを見てみましょう。[Tools] -> [Kotlin] -> [Show Kotlin Bytecode] を選択し、[Decompile] ボタンを押します。
[Tools] -> [Kotlin] -> [Show Kotlin Bytecode]
[Decompile]
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */fun play(toy: Toy = SqueakyToy)...fun startPlaying() { play(toy = Stick) play() // toy = SqueakyToy}// decompiled Java codepublic static final void play(@NotNull Toy toy) { Intrinsics.checkNotNullParameter(toy, "toy");}// $FF: synthetic methodpublic static void play$default(Toy var0, int var1, Object var2) { if ((var1 & 1) != 0) { var0 = SqueakyToy; } play(var0);}public static final void startPlaying() { play(Stick); play$default((Toy)null, 1, (Object)null);}
...
// decompiled Java code
public static final void play(@NotNull Toy toy) {
Intrinsics.checkNotNullParameter(toy, "toy");
// $FF: synthetic method
public static void play$default(Toy var0, int var1, Object var2) {
if ((var1 & 1) != 0) {
var0 = SqueakyToy;
play(var0);
public static final void startPlaying() {
play(Stick);
play$default((Toy)null, 1, (Object)null);
コンパイラが 2 つの関数を生成していることがわかります。
play
Toy
play$default
int
Object
null
play$default の int パラメータの値は、デフォルト引数が渡された引数の数と、そのインデックスに基づいて計算されます。Kotlin コンパイラは、どのパラメータを使って play 関数を呼び出すかを、このパラメータの値に基づいて判断します。
この例の play() の呼び出しでは、インデックス 0 の引数がデフォルト引数を使っています。そのため、int var1 = 2⁰ を使って play$default を呼び出します。
play()
int var1 = 2⁰
これで、play$default の実装は、var0 の値をデフォルト値で置き換えなければならないことを認識できます。
var0
この int パラメータの動作を確認するため、もう少し複雑な例を見てみましょう。play 関数を拡張し、この関数を呼び出す際に doggo と toy をデフォルト引数として使うようにします。
doggo
toy
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ fun play(doggo: Doggo = goodDoggo, doggo2: Doggo = veryGoodDoggo, toy: Toy = SqueakyToy) {...}fun startPlaying() { play2(doggo2 = myDoggo)}
fun play(doggo: Doggo = goodDoggo, doggo2: Doggo = veryGoodDoggo, toy: Toy = SqueakyToy) {...}
play2(doggo2 = myDoggo)
逆コンパイルしたコードはどうなったでしょうか。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ public static final void play(@NotNull Doggo doggo, @NotNull Doggo doggo2, @NotNull Toy toy) {... }// $FF: synthetic methodpublic static void play$default(Doggo var0, Doggo var1, Toy var2, int var3, Object var4) { if ((var3 & 1) != 0) { var0 = goodDoggo; } if ((var3 & 2) != 0) { var1 = veryGoodDoggo; } if ((var3 & 4) != 0) { var2 = SqueakyToy; } play(var0, var1, var2);}public static final void startPlaying() { play2$default((Doggo)null, myDoggo, (Toy)null, 5, (Object)null); }
public static final void play(@NotNull Doggo doggo, @NotNull Doggo doggo2, @NotNull Toy toy) {
public static void play$default(Doggo var0, Doggo var1, Toy var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var0 = goodDoggo;
if ((var3 & 2) != 0) {
var1 = veryGoodDoggo;
if ((var3 & 4) != 0) {
var2 = SqueakyToy;
play(var0, var1, var2);
play2$default((Doggo)null, myDoggo, (Toy)null, 5, (Object)null);
int パラメータが 5 になりました。この計算の仕組みは次のようになります。0 番目と 2 番目のパラメータでデフォルト引数が使われているので、var3 = 2⁰ + 2² = 5 となります。パラメータは、ビット単位の & 演算を使って次のように評価されます。
var3 = 2⁰ + 2² = 5
var3 & 1 != 0
true
var0 = goodDoggo
var3 & 2 != 0
false
var1
var3 & 4 != 0
var2 = SqueakyToy
コンパイラは、var3 に適用されたビットマスクから、どのパラメータをデフォルト値と置き換えるかを計算できます。
var3
上の例で、Object パラメータの値が常に null になっていたことに気づいたかもしれません。実際に、play$default 関数ではこの値が使われることはありません。このパラメータは、オーバーライドする関数でデフォルト値をサポートするために使用されています。
デフォルト引数がある関数をオーバーライドすると、何が起きるでしょうか。
先ほどの例を次のように変更してみましょう。
Doggo
Open
PlayfulDoggo
PlayfulDoggo.play にデフォルト値を設定しようとしても、次のように表示され、許可されません。An overriding function is not allowed to specify default values for its parameters(オーバーライド関数では、パラメータへのデフォルト値の指定は許可されていません)
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */open class Doggo( val name: String, val rating: Int = 11) { open fun play(toy: Toy = SqueakyToy) {...}}class PlayfulDoggo(val playfulness: Int, name: String, rating: Int) : Doggo(name, rating) { // error: An overriding function is not allowed to specify default values for its parameters override fun play(toy: Toy = Stick) { }}
open class Doggo(
) {
open fun play(toy: Toy = SqueakyToy) {...}
class PlayfulDoggo(val playfulness: Int, name: String, rating: Int) : Doggo(name, rating) {
// error: An overriding function is not allowed to specify default values for its parameters
override fun play(toy: Toy = Stick) { }
override を削除して逆コンパイルしたコードを確認すると、PlayfulDoggo.play() は次のようになっています。
override
PlayfulDoggo.play()
public void play(@NotNull Toy toy) {... }// $FF: synthetic methodpublic static void play$default(Doggo var0, Toy var1, int var2, Object var3) { if (var3 != null) { throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: play"); } else { if ((var2 & 1) != 0) { var1 = DoggoKt.getSqueakyToy(); } var0.play(var1); }}
public void play(@NotNull Toy toy) {... }
public static void play$default(Doggo var0, Toy var1, int var2, Object var3) {
if (var3 != null) {
throw new UnsupportedOperationException("Super calls with default arguments not supported in this target, function: play");
} else {
if ((var2 & 1) != 0) {
var1 = DoggoKt.getSqueakyToy();
var0.play(var1);
これは、デフォルト引数を使った super の呼び出しが将来的にサポートされるという意味なのでしょうか。この点については、成り行きを見守るしかありません。
コンストラクタでは、逆コンパイルした Java コードに 1 つだけ違う点があります。以下をご覧ください。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */ // kotlin declarationclass Doggo( val name: String, val rating: Int = 11)// decompiled Java codepublic final class Doggo { ... public Doggo(@NotNull String name, int rating) { Intrinsics.checkNotNullParameter(name, "name"); super(); this.name = name; this.rating = rating; } // $FF: synthetic method public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) { if ((var3 & 2) != 0) { var2 = 11; } this(var1, var2); }
// kotlin declaration
public final class Doggo {
public Doggo(@NotNull String name, int rating) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
this.rating = rating;
public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) {
var2 = 11;
this(var1, var2);
コンストラクタでも合成メソッドが作成されていますが、関数で使われていた Object ではなく、DefaultConstructorMarker が null で呼び出されています。
DefaultConstructorMarker
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */// kotlinval goodDoggo = Doggo("Tofu")// decompiled Java codeDoggo goodDoggo = new Doggo("Tofu", 0, 2, (DefaultConstructorMarker)null);
val goodDoggo = Doggo("Tofu")
Doggo goodDoggo = new Doggo("Tofu", 0, 2, (DefaultConstructorMarker)null);
デフォルト引数があるセカンダリ コンストラクタでも、プライマリ コンストラクタと同じように DefaultConstructorMarker を使った別の合成メソッドが生成されます。
/* Copyright 2020 Google LLC. SPDX-License-Identifier: Apache-2.0 */// kotlinclass Doggo( val name: String, val rating: Int = 11) { constructor(name: String, rating: Int, lazy: Boolean = true) }// decompiled Java codepublic final class Doggo { ... public Doggo(@NotNull String name, int rating) { ... } // $FF: synthetic method public Doggo(String var1, int var2, int var3, DefaultConstructorMarker var4) { if ((var3 & 2) != 0) { var2 = 11; } this(var1, var2); } public Doggo(@NotNull String name, int rating, boolean lazy) { ... } // $FF: synthetic method public Doggo(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) { if ((var4 & 4) != 0) { var3 = true; } this(var1, var2, var3); }}
constructor(name: String, rating: Int, lazy: Boolean = true)
public Doggo(@NotNull String name, int rating, boolean lazy) {
public Doggo(String var1, int var2, boolean var3, int var4, DefaultConstructorMarker var5) {
if ((var4 & 4) != 0) {
var3 = true;
this(var1, var2, var3);
シンプルで美しいデフォルト引数を利用すると、パラメータにデフォルト値を設定できるようになるので、メソッドをオーバーロードする際に書かなければならないボイラープレート コードの量が減ります。多くの Kotlin キーワードに言えることですが、生成されるコードを調べてみれば、そこでどのような魔法が使われているかを理解できます。詳しくは、他の Kotlin Vocabulary の投稿を確認してみてください。
仕事を完了する方法の 1 つは、その仕事を他者に委譲することです。皆さんの仕事を友だちに委譲することを言っているわけではありません。今回のテーマは、あるオブジェクトから別のオブジェクトに委譲することです。
ソフトウェアの世界では、委譲という考え方は新しいものではありません。委譲はデザイン パターンの 1 つで、あるオブジェクトが デリゲートと呼ばれるヘルパー オブジェクトに委譲することでリクエストを処理することを指します。デリゲートの役割は、元のオブジェクトに代わってリクエストを処理し、その結果を元のオブジェクトが利用できるようにすることです。
Kotlin はクラス委譲とプロパティ委譲をサポートしているので、委譲を簡単に扱えます。さらに、いくつかの独自の組み込みデリゲートも提供しています。
最後に削除された項目を復元できる ArrayList を使うとしましょう。基本的に、必要なのは同じ ArrayList の機能だけですが、最後に削除された項目への参照が必要です。
ArrayList
これを実現する方法の 1 つは、ArrayList クラスを拡張することです。この新しいクラスは、MutableList インターフェースの実装ではなく ArrayList の具象クラスを拡張したものなので、ArrayList の具象クラスの実装と強く結合されることになります。
MutableList
MutableList の実装で remove() 関数をオーバーライドし、削除した項目の参照を保持できるようにしたうえで、その他の空の実装を他のオブジェクトに委譲したいと思ったことはありませんか?Kotlin では、これを実現する方法が提供されています。具体的には、内部 ArrayList インスタンスに作業の大半を委譲し、その動作をカスタマイズできます。これを行うため、Kotlin には新しいキーワード by が導入されています。
remove()
by
では、クラス委譲の仕組みを確認してみましょう。by キーワードを使うと、Kotlin は innerList インスタンスをデリゲートとして使用するコードを自動的に生成します。
innerList
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class ListWithTrash <T>( private val innerList: MutableList<T> = ArrayList<T>()) : MutableCollection<T> by innerList { var deletedItem : T? = null override fun remove(element: T): Boolean { deletedItem = element return innerList.remove(element) } fun recover(): T? { return deletedItem }}
class ListWithTrash <T>(
private val innerList: MutableList<T> = ArrayList<T>()
) : MutableCollection<T> by innerList {
var deletedItem : T? = null
override fun remove(element: T): Boolean {
deletedItem = element
return innerList.remove(element)
fun recover(): T? {
return deletedItem
by キーワードは、MutableList インターフェースの機能を innerList という名前の内部 ArrayList インスタンスに委譲するよう Kotlin に伝えます。内部 ArrayList オブジェクトに直接橋渡しするメソッドが提供されるので、ListWithTrash は MutableList インターフェースのすべての機能をサポートします。さらに、独自の動作を追加することもできるようになります。
ListWithTrash
動作の仕組みを確認してみましょう。ListWithTrash のバイトコードを逆コンパイルした Java コードを見ると、Kotlin コンパイラが実際にラッパー関数を作成していることを確認できます。このラッパー関数が、内部 ArrayList オブジェクトの対応する関数を呼び出していることもわかります。
public final class ListWithTrash implements Collection, KMutableCollection { @Nullable private Object deletedItem; private final List innerList; @Nullable public final Object getDeletedItem() { return this.deletedItem; } public final void setDeletedItem(@Nullable Object var1) { this.deletedItem = var1; } public boolean remove(Object element) { this.deletedItem = element; return this.innerList.remove(element); } @Nullable public final Object recover() { return this.deletedItem; } public ListWithTrash() { this((List)null, 1, (DefaultConstructorMarker)null); } public int getSize() { return this.innerList.size(); } // $FF: bridge method public final int size() { return this.getSize(); } //...and so on}
private Object deletedItem;
private final List innerList;
public final Object getDeletedItem() {
return this.deletedItem;
public final void setDeletedItem(@Nullable Object var1) {
this.deletedItem = var1;
public boolean remove(Object element) {
this.deletedItem = element;
return this.innerList.remove(element);
public final Object recover() {
public ListWithTrash() {
this((List)null, 1, (DefaultConstructorMarker)null);
public int getSize() {
return this.innerList.size();
// $FF: bridge method
public final int size() {
return this.getSize();
//...and so on
注: 生成されたコードで、Kotlin コンパイラは Decorator パターンという別のデザイン パターンを使ってクラス委譲をサポートしています。Decorator パターンでは、デコレータ クラスがデコレートされるクラスと同じインターフェースを共有します。デコレータ クラスは、ターゲット クラスの内部参照を保持し、そのインターフェースで提供されるすべてのパブリック メソッドをラップ(デコレート)します。
委譲は、特定のクラスを継承できない場合に特に便利です。クラス委譲を使うと、クラスが他のクラスの階層に含まれることはなくなります。その代わり、同じインターフェースを共有し、元の型の内部オブジェクトをデコレートします。つまり、パブリック API を維持したまま、実装を簡単に入れ替えることができます。
by キーワードを使うと、クラス委譲だけでなく、プロパティを委譲することもできます。プロパティ委譲では、デリゲートはプロパティの get 関数と set 関数の呼び出しを担当します。他のオブジェクトで getter/setter ロジックを再利用しなければならない場合、対応するフィールドだけでなく機能を簡単に拡張することができるので、この機能が非常に便利です。
get
set
次のような定義の Person クラスがあったとしましょう。
Person
class Person(var name:String, var lastname:String)
このクラスの name プロパティには、いくつかのフォーマット要件があります。name を設定するとき、先頭の文字が大文字、他の文字が小文字になるようにします。さらに、 name を更新する場合、updateCount プロパティを自動的にインクリメントします。
name
updateCount
この機能は、次のように実装してもいいかもしれません 。
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, var lastname: String) { var name: String = name set(value) { field = value.toLowerCase().capitalize() updateCount++ } var updateCount = 0}
class Person(name: String, var lastname: String) {
var name: String = name
set(value) {
field = value.toLowerCase().capitalize()
updateCount++
var updateCount = 0
これは動作しますが、要件が変わって lastname が変更されたときも updateCount をインクリメントすることになるとどうでしょうか。ロジックをコピーして貼り付け、カスタムの setter を書いてもいいかもしれませんが、両方のプロパティにまったく同じ setter を書いていることに気づくでしょう。
lastname
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, lastname: String) { var name: String = name set(value) { field = value.toLowerCase().capitalize() updateCount++ } var lastname: String = lastname set(value) { field = value.toLowerCase().capitalize() updateCount++ } var updateCount = 0}
class Person(name: String, lastname: String) {
var lastname: String = lastname
どちらの setter メソッドもほぼ同じということは、どちらかは不要ということです。プロパティ委譲を使うと、getter と setter をプロパティに委譲してコードを再利用できます。
クラス委譲と同じように、by を使ってプロパティを委譲します。すると、プロパティ構文を使ったときに、Kotlin はデリゲートを使うコードを生成します。
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class Person(name: String, lastname: String) { var name: String by FormatDelegate() var lastname: String by FormatDelegate() var updateCount = 0}
var name: String by FormatDelegate()
var lastname: String by FormatDelegate()
この変更を行うと、name プロパティと lastname プロパティが FormatDelegate クラスに委譲されます。FormatDelegate のコードを確認してみましょう。デリゲート クラスは、getter だけを委譲する場合は ReadProperty<Any?, String> を、getter と setter の両方を委譲する場合は ReadWriteProperty<Any?, String> を実装する必要があります。この例の FormatDelegate は、setter が呼び出された場合にフォーマット処理を行うので、ReadWriteProperty<Any?, String> を実装しなければなりません。
FormatDelegate
ReadProperty<Any?, String>
ReadWriteProperty<Any?, String>
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->class FormatDelegate : ReadWriteProperty<Any?, String> { private var formattedString: String = "" override fun getValue( thisRef: Any?, property: KProperty<*> ): String { return formattedString } override fun setValue( thisRef: Any?, property: KProperty<*>, value: String ) { formattedString = value.toLowerCase().capitalize() }}
class FormatDelegate : ReadWriteProperty<Any?, String> {
private var formattedString: String = ""
override fun getValue(
thisRef: Any?,
property: KProperty<*>
): String {
return formattedString
override fun setValue(
property: KProperty<*>,
value: String
formattedString = value.toLowerCase().capitalize()
getter 関数と setter 関数に 2 つの追加パラメータがあることに気づいた方もいらっしゃるでしょう。最初のパラメータ thisRef は、プロパティを含むオブジェクトを表します。これを使うと、オブジェクト自体にアクセスし、他のプロパティを確認したり、他のクラス関数を呼び出したりできます。2 つ目のパラメータは KProperty<*> です。これは、委譲されたプロパティについてのメタデータにアクセスするために使うことができます。
thisRef
KProperty<*>
先ほどの要件を思い出してみてください。thisRef を使って updateCount プロパティにアクセスし、インクリメントしてみましょう。
<!-- Copyright 2019 Google LLC.SPDX-License-Identifier: Apache-2.0 -->override fun setValue( thisRef: Any?, property: KProperty<*>, value: String) { if (thisRef is Person) { thisRef.updateCount++ } formattedString = value.toLowerCase().capitalize()}
if (thisRef is Person) {
thisRef.updateCount++
この仕組みを理解するため、逆コンパイルした Java コードを見てみます。Kotlin コンパイラは、name プロパティと lastname プロパティについての FormatDelegate オブジェクトへのプライベートな参照を保持するためのコードと、追加したロジックを含む getter/setter の両方を生成します。
さらに、委譲されるプロパティを保持する KProperty[] も作成しています。name プロパティに対して生成された getter と setter を見てみると、インスタンスはインデックス 0 に保存されています。一方、lastname プロパティはインデックス 1 に保存されています。
KProperty[]
public final class Person { // $FF: synthetic field static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "lastname", "getlastname()Ljava/lang/String;"))}; @NotNull private final FormatDelegate name$delegate; @NotNull private final FormatDelegate lastname$delegate; private int updateCount; @NotNull public final String getName() { return this.name$delegate.getValue(this, $$delegatedProperties[0]); } public final void setName(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.name$delegate.setValue(this, $$delegatedProperties[0], var1); } //...}
// $FF: synthetic field
static final KProperty[] $$delegatedProperties = new KProperty[]{(KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "name", "getName()Ljava/lang/String;")), (KProperty)Reflection.mutableProperty1(new MutablePropertyReference1Impl(Reflection.getOrCreateKotlinClass(Person.class), "lastname", "getlastname()Ljava/lang/String;"))};
@NotNull
private final FormatDelegate name$delegate;
private final FormatDelegate lastname$delegate;
private int updateCount;
public final String getName() {
return this.name$delegate.getValue(this, $$delegatedProperties[0]);
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name$delegate.setValue(this, $$delegatedProperties[0], var1);
//...
この仕組みによって、通常のプロパティ構文を使って任意の呼び出し元が委譲されるプロパティにアクセスできるようになっています。
person.lastname = “Smith” //
println(“Update count is $person.count”)
Kotlin は単に委譲をサポートしているだけではありません。Kotlin 標準ライブラリで組み込みの委譲も提供していますが、詳しくは別の記事で説明したいと思います。
委譲は他のオブジェクトにタスクを委譲する際に役立ち、コードの再利用性を高めます。Kotlin コンパイラは、委譲をシームレスに使えるようにコードを作成します。Kotlin は、by キーワードを使ったシンプルな構文でプロパティやクラスの委譲を行います。Kotlin コンパイラは、パブリック API を一切変更せず、委譲をサポートするために必要なすべてのコードを内部的に生成します。簡単に言えば、Kotlin は委譲に必要なボイラープレート コードをすべて生成して維持してくれます。つまり、委譲を Kotlin に委譲することができるのです。
Reviewed by Yuichi Araki - Developer Relations Team