1.ようこそ!
[このコードラボは、中国語とブラジル系ポルトガル語でも利用可能です]
このコードラボでは、JavaからKotlinにリファクタリングする方法を学びます。また、Kotlin言語の規則とあなたが書いているコードがそれらに従うことを保証する方法についても学びます。
このコードラボは、プロジェクトをKotlinに移行することを検討しているJavaを使用している開発者に適しています。IDEを使用してKotlinに変換する事をいくつかのJavaクラスから始めます。次に、変換されたコードを見て、より慣用的にし、一般的な落とし穴を避けることでコードを改善する方法を確認します。
学ぶこと
JavaをKotlinにリファクタリングする方法を学習します。 そうすることで、次のKotlin言語の機能と概念を学習します。
- nullabilityの処理
- シングルトンの実装
- データクラス
- 文字列の処理
- エルビス演算子
- Destructuring
- プロパティとバッキングプロパティ
- デフォルトの引数と名前付きパラメーター
- コレクションを操作する
- 拡張関数
- ret, apply, with, run キーワード
想定
すでにJavaに精通している必要があります。
必要なもの
Android Studio 3.5またはIntelliJ IDEA
Android Studio 3.6または4.0自動コンバーターは、異なる結果を作成する場合があります。そのため、このコードラボの目的でAndroid Studio 3.5を使用していることを確認してください。
2.セットアップ
新しいプロジェクトを作成する
IntelliJ IDEAを使用している場合は、Kotlin / JVMで新しいJavaプロジェクトを作成します。
Android Studioを使用している場合は、アクティビティのない新しいプロジェクトを作成します。
コード
User
モデルオブジェクトと、User
オブジェクトと連携してユーザーおよびフォーマットされたユーザー名のリストを公開するRepository
シングルトンクラスを作成します。
app.java/ の下にUser.java
という新しいファイルを作成し、次のコードを貼り付けます。
public class User {
private String firstName;
private String lastName;
public User(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
Repository.java
という新しいファイルを作成し、次のコードを貼り付けます。
import java.util.ArrayList;
import java.util.List;
public class Repository {
private static Repository INSTANCE = null;
private List<User> users = null;
public static Repository getInstance() {
if (INSTANCE == null) {
synchronized (Repository.class) {
if (INSTANCE == null) {
INSTANCE = new Repository();
}
}
}
return INSTANCE;
}
// keeping the constructor private to enforce the usage of getInstance
private Repository() {
User user1 = new User("Jane", "");
User user2 = new User("John", null);
User user3 = new User("Anne", "Doe");
users = new ArrayList();
users.add(user1);
users.add(user2);
users.add(user3);
}
public List<User> getUsers() {
return users;
}
public List<String> getFormattedUserNames() {
List<String> userNames = new ArrayList<>(users.size());
for (User user : users) {
String name;
if (user.getLastName() != null) {
if (user.getFirstName() != null) {
name = user.getFirstName() + " " + user.getLastName();
} else {
name = user.getLastName();
}
} else if (user.getFirstName() != null) {
name = user.getFirstName();
} else {
name = "Unknown";
}
userNames.add(name);
}
return userNames;
}
}
3.nullability、val、var、およびdataクラスの宣言
IDEは、Javaコードを自動的にKotlinコードにリファクタリングするという非常に良い仕事をすることができますが、時には少し助けが必要です。最初にこれを行い、次にリファクタリングされたコードを調べて、この方法でリファクタリングされた方法と理由を理解します。
Android Studio 3.6または4.0自動コンバーターは、異なる結果を作成する場合があります。そのため、このコードラボの目的でAndroid Studio 3.5を使用していることを確認してください。
User.java
ファイルに移動して、それをKotlinに変換します。Menu bar -> Code -> Convert Java File to Kotlin File.
IDEが変換後に修正を求めるプロンプトを表示した場合、はいを押します。
次のKotlinコードが表示されます。
class User(var firstName: String?, var lastName: String?)
User.java
がUser.kt
に名前が変更されたことに注意してください。 Kotlinファイルの拡張子は.ktです。
プロのヒント:JavaコードをKotlinファイルに貼り付けると、IDEは貼り付けられたコードを自動的にKotlinに変換します。
Java User
クラスには、firstName
とlastNameの2つのプロパティがありました。それぞれにゲッターメソッドとセッターメソッドがあり、その値を可変にします。可変変数に対するKotlinのキーワードは
varです。そのため、コンバーターはこれらの各プロパティに
varを使用します。Javaプロパティにゲッターのみがある場合、それらは不変であり、
val変数として宣言されていました。
valは、Javaの
final`キーワードに似ています。
KotlinとJavaの主な違いの1つは、変数がnull値を受け入れることができるかどうかをKotlinが明示的に指定することです。これを行うには、型宣言に「?」
を追加します。
Java User
プロパティはnull値を受け入れることができるため、それぞれString?
でnull可能としてマークされます。Javaメンバーにnon-nullアノテーションを付ける場合(org.jetbrains.annotations.NotNull
またはandroidx.annotation.NonNull
)、コンバーターはこれを認識し、Kotlinのフィールドもnon-nullにします。
プロのヒント:Kotlinでは、可能な限り不変オブジェクトを使用する(つまり、varの代わりにvalを使用する)ことをお勧めします。ヌルアビリティを意味のあるものにし、具体的に処理したいものに努力する必要があります。
基本的なリファクタリングはすでに行われています。 しかし、これをもっと慣用的な方法で書くことができます。方法を見てみましょう。
データクラス
User
クラスはデータのみを保持します。 Kotlinには、このロールを持つクラスのキーワードであるdata
があります。このクラスをdata
クラスとしてマークすることにより、コンパイラは自動的にゲッターとセッターを作成します。また、equals()
、hashCode()
、およびtoString()
関数も派生します。
User
クラスにdata
キーワードを追加しましょう:
data class User(var firstName: String?, var lastName: String?)
Kotlinは、Javaと同様に、プライマリコンストラクターと1つ以上のセカンダリコンストラクターを持つことができます。上記の例の1つは、Userクラスのプライマリコンストラクターです。複数のコンストラクターを持つJavaクラスを変換する場合、コンバーターはKotlinでも複数のコンストラクターを自動的に作成します。それらはconstructor
キーワードを使用して定義されます。
コンストラクターの詳細については、公式ドキュメントをご覧ください。
このクラスのインスタンスを作成する場合は、次のようにします。
val user1 = User("Jane", "Doe")
等しいこと
Kotlinには2種類の平等があります。
- 構造的平等では
==
演算子を使用し、equals()
を呼び出して2つのインスタンスが等しいかどうかを判断します。 - 参照等価では、
===
演算子を使用して、2つの参照が同じオブジェクトを指しているかどうかを確認します。
データクラスのプライマリコンストラクターで定義されたプロパティは、構造的な同等性チェックに使用されます。
val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false
データクラスの詳細については、公式ドキュメントをご覧ください。
4.デフォルト引数、名前付き引数
Kotlinでは、関数呼び出しの引数にデフォルト値を割り当てることができます。引数が省略された場合、デフォルト値が使用されます。Kotlinでは、コンストラクターも関数なので、デフォルトの引数を使用して、lastName
のデフォルト値がnull
であることを指定できます。これを行うには、lastName
にnull
を割り当てます。
data class User(var firstName: String?, var lastName: String? = null)
// usage
val jane = User ("Jane") // same as User("Jane", null)
val joe = User ("John", "Doe")
関数を呼び出すときに、関数パラメーターに名前を付けることができます。
val john = User (firstName = "John", lastName = "Doe")
別のユースケースとして、firstName
にはデフォルト値としてnull
があり、lastName
にはないとしましょう。この場合、デフォルトパラメータは、デフォルト値のないパラメータの前にあるため、名前付き引数で関数を呼び出す必要があります:
data class User(var firstName: String? = null, var lastName: String?)
// usage
val jane = User (lastName = "Doe") // same as User(null, "Doe")
val john = User ("John", "Doe")
関数に複数のパラメーターがある場合は、名前付き引数を使用するとコードが読みやすくなるため、使用を検討してください。
5.オブジェクトの初期化、コンパニオンオブジェクト、およびシングルトン
先に進む前に、Userクラスがdataクラスであることを確認してください。Repository
クラスをKotlinに変換しましょう。自動変換の結果は次のようになります。
class Repository
// keeping the constructor private to enforce the usage of getInstance
private constructor() {
private val users: MutableList<User>? = null
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users!!.size)
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
return userNames
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList()
users!!.add(user1)
users!!.add(user2)
users!!.add(user3)
}
fun getUsers(): List<User>? {
return users
}
companion object {
private var INSTANCE: Repository? = null
val instance: Repository
get() {
if (INSTANCE == null) {
synchronized(Repository::class.java) {
if (INSTANCE == null) {
INSTANCE = Repository()
}
}
}
return INSTANCE
}
}
}
自動コンバーターが何をしたのか見てみましょう:
-
init
ブロックが追加されました(Repository.kt#L33) -
static
フィールドは、companion object
ブロックの一部になりました(Repository.kt#L51) - オブジェクトは宣言時にインスタンス化されなかったため、
users
のリストはNULL可能です(Repository.kt#L9) -
getFormattedUserNames()
メソッドは、formattedUserNames
(Repository.kt#L11)というプロパティになりました。 - ユーザーのリストに対する反復の構文は、Javaの構文とは異なります(Repository.kt#L14)
注:生成されたコードはコンパイルされません。 心配する必要はありません。次のステップで変更します。
初期化ブロック
Kotlinでは、プライマリコンストラクターにコードを含めることはできないため、初期化コードはinitブロックに配置されます。 機能は同じです。
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList()
users!!.add(user1)
users!!.add(user2)
users!!.add(user3)
}
}
init
コードの多くは、プロパティの初期化を処理します。これは、プロパティの宣言でも実行できます。たとえば、Repository
クラスのKotlinバージョンでは、usersプロパティが宣言で初期化されたことがわかります。
private val users: MutableList<User>? = null
公式ドキュメントから初期化ブロックの詳細をご覧ください。
Kotlinのstatic
プロパティとメソッド
Javaでは、フィールドまたは関数にstatic
キーワードを使用して、クラスのインスタンスではなくクラスに属していることを示します。これが、Repository
クラスにINSTANCE
静的フィールドを作成した理由です。これに対応するKotlinは、companion object
ブロックです。 ここでは、静的フィールドと静的関数も宣言します。コンバーターは、INSTANCE
フィールドを作成して、ここに移動しました。
シングルトンの処理
Repository
クラスのインスタンスは1つしか必要ないため、Javaでシングルトンパターンを使用しました。Kotlinを使用すると、class
キーワードをobject
に置き換えることで、コンパイラレベルでこのパターンを適用できます。
プライベートコンストラクターとコンパニオンオブジェクトを削除し、クラス定義をobject Repository
に置き換えます。
object Repository {
private val users: MutableList<User>? = null
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users!!.size)
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
userNames.add(name)
}
return userNames
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList()
users!!.add(user1)
users!!.add(user2)
users!!.add(user3)
}
fun getUsers(): List<User>? {
return users
}
}
object
クラスを使用する場合、次のように、オブジェクトに対して関数とプロパティを直接呼び出します。
val users = Repository.users
オブジェクトとコンパニオンオブジェクトの詳細については、公式ドキュメントをご覧ください。
Destructuring
Kotlinでは、構造化宣言と呼ばれる構文を使用して、オブジェクトをいくつかの変数に構造化できます。複数の変数を作成し、それらを個別に使用できます。
たとえば、dataクラスは構造化をサポートしているため、自動コンバーターはfor
ループでUser
オブジェクトを構造化しました。これにより、firstName
およびlastName
の値を直接操作できます。
for ((firstName, lastName) in users!!) {
val name: String?
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
}
...
公式ドキュメントで宣言のdestructuringについて詳しく読んでください。
6. nullabilityの処理
Repository
クラスをKotlinに変換する場合、自動コンバータは、宣言時にオブジェクトに初期化されなかったため、ユーザーのリストをnull可能にしました。users
オブジェクトのすべての使用法について、not-null アサーション演算子!!
が使用されている。変数をnull以外の型に変換し、値がnullの場合は例外をスローします。 !!
を使用すると、実行時に例外がスローされる危険があります。
代わりに、次の方法のいずれかを使用してヌル可能性を処理することを好みます。
- nullチェックを行う(if(users!= null){...})
-
elvis演算子の使用
?:
(コードラボで後述) - Kotlin標準関数の一部を使用(コードラボで後ほど説明)
公式ドキュメントからヌルの安全性に関する詳細をご覧ください。
この場合、ユーザーのリストはNULL値を許可する必要はありませんが、オブジェクトが構築された直後に初期化されるため、宣言時にオブジェクトを直接インスタンス化できます。
コレクションタイプのインスタンスを作成する場合、Kotlinはコードをより読みやすく柔軟にするためのいくつかのヘルパー関数を提供します。ここでは、users
にMutableList
を使用しています。
private val users: MutableList<User>? = null
簡単にするために、mutableListOf()
関数を使用できます。リスト要素タイプを提供し、init
ブロックからArrayList
コンストラクター呼び出しを削除し、usersプロパティの明示的な型宣言を削除します。
private val users = mutableListOf<User>()
この変更により、users
プロパティはnullでなくなり、!!
演算子の出現で不要なものをすべて削除できます。また、ユーザー変数は既に初期化されているため、init
ブロックから初期化を削除する必要があります。
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users.add(user1)
users.add(user2)
users.add(user3)
}
lastName
とfirstName
の両方がnull
になる可能性があるため、フォーマットされたユーザー名のリストを作成するときにnullを処理する必要があります。自動コンバーターは名前変数をnull可能にしましたが、いずれかの名前が欠落している場合は"Unknown"
を表示したいので、型宣言から?
を削除することで名前をnull以外にできます。
val name: String
lastName
がnullの場合、name
はfirstName
または"Unknown"
のいずれかです。
if (lastName != null) {
if (firstName != null) {
name = "$firstName $lastName"
} else {
name = lastName
}
} else if (firstName != null) {
name = firstName
} else {
name = "Unknown"
}
これは、elvis演算子?:
を使用して、より慣用的に記述することができます。elvis演算子は、nullでない場合は左辺の式を返し、左辺がnullの場合は右辺の式を返します。
したがって、次のコードでは、user.firstName
がnullでない場合に返されます。user.firstName
がnullの場合、式は右側の値"Unknown"
を返します。
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
公式ドキュメントでelvisオペレーターの詳細をお読みください。
7.文字列テンプレートとif式
Kotlinでは、String templatesを使用して文字列を簡単に操作できます。文字列テンプレートを使用すると、文字列宣言内の変数を参照できます。
自動コンバーターは、$
記号を使用してストリング内の変数名を直接参照し、式を{}
の間に置くことで、姓と名の連結を更新しました。
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
コードで、文字列連結を次のように置き換えます。
name = "$firstName $lastName"
Kotlinでは、if
、when
、for
、while
が式であり、値を返します。IDEは、割り当てをif
から外す必要があるという警告も表示します。
IDEの提案に従って、両方のif
ステートメントの割り当てを解除しましょう。if
ステートメントの最後の行が割り当てられます。このように、このブロックの唯一の目的が名前の値を初期化することであることはより明確です。
name = if (firstName != null) {
// do something
firstName
}
// name = firstName
if
ステートメントから割り当てを解除すると、簡単に悪用される可能性があります。if
ブロックに1つの役割のみがあり、他の副作用がないことを確認してください。
次に、name
宣言を割り当てと結合できるという警告が表示されます。 これも適用してみましょう。名前変数の型を推測できるため、明示的な型宣言を削除できます。次に、formattedUserNames
は次のようになります。
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
公式ドキュメントで
if
、when
、for
、while
の詳細を読んでください。
8.コレクションの操作
formattedUserNames
ゲッターを詳しく見て、それをもっと慣用的にする方法を見てみましょう。現在、コードは次のことを行います。
- 文字列の新しいリストを作成します
- ユーザーのリストを反復処理します
- ユーザーの名と姓に基づいて、各ユーザーの書式設定された名前を作成します
- 新しく作成されたリストを返します
val formattedUserNames: List<String>
get() {
val userNames = ArrayList<String>(users.size)
for ((firstName, lastName) in users) {
val name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
userNames.add(name)
}
return userNames
}
Kotlinは、Java Collections APIの機能を拡張することにより、開発をより迅速かつ安全にするコレクション変換の広範なリストを提供します。それらの1つはマップ機能です。この関数は、指定された変換関数を元の配列の各要素に適用した結果を含む新しいリストを返します。そのため、新しいリストを作成してユーザーのリストを手動で繰り返す代わりに、 map
関数を使用して、forループ内のロジックを map
本体内に移動できます。デフォルトでは、 map
で使用される現在のリスト項目の名前は it
ですが、読みやすくするために、独自の変数名に置き換えることができます。この例では、user
に名前を付けましょう。
val formattedUserNames: List<String>
get() {
return users.map { user ->
val name = if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
name
}
}
これをさらに単純化するために、 name
変数を完全に削除できます:
val formattedUserNames: List<String>
get() {
return users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
コレクションの変換だけでなく、Kotlin Standard Libraryはコレクションを管理するための幅広いツールを提供します。さまざまなコレクションタイプから、一般的な
Collection
タイプまたはList
やSet
などのさまざまなサブタイプに固有のさまざまな操作まで。一般に、コレクションを操作するときは、Kotlinでの書き込みを計画している機能が標準ライブラリによって既に実装されているかどうかを確認し、独自の実装でその機能を使用することを選択してください。
9.プロパティとバッキングプロパティ
自動コンバーターがgetFormattedUserNames()
関数を、カスタムgetterを持つformattedUserNames
と呼ばれるプロパティに置き換えていることがわかりました。 内部では、KotlinはList
を返すgetFormattedUserNames()
メソッドを生成します。
Javaでは、getterおよびsetter関数を介してクラスプロパティを公開します。
Kotlinを使用すると、フィールドで表現されたクラスのプロパティと、機能で表現された機能、クラスで実行可能なアクションをより適切に区別できます。この場合、Repositoryクラスは非常にシンプルで、アクションを実行しないため、フィールドのみがあります。
formattedUserNames
Kotlinプロパティのgetterを呼び出すときにJavaでトリガーされたロジックのgetFormattedUserNames()
関数がトリガーされるようになりました。
formattedUserNames
プロパティに対応するフィールドは明示的にありませんが、
Kotlinは、必要に応じてカスタムgetterおよびsetterからアクセスできるfieldという名前の自動バッキングフィールドを提供します。
ただし、自動バッキングフィールドにはない追加機能が必要な場合があります。 以下の例を見てみましょう。
Repositoryクラス内には、Javaコードから生成されたgetUsers関数で公開されているユーザーの可変リストがあります。
fun getUsers(): List<User>? {
return users
}
ここでの問題は、ユーザーを返すことにより、Repositoryクラスのすべてのコンシューマーがユーザーのリストを変更できることです。 バッキングプロパティを使用してこれを修正しましょう。
まず、ユーザーの名前を_usersに変更しましょう。
次に、ユーザーのリストを返すパブリックの不変のプロパティを追加します。 それをユーザーと呼びましょう:
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
この変更により、プライベート_users
プロパティがパブリックusers
プロパティのバッキングプロパティになります。Repository
クラスの外部では、_users
リストは変更できません。クラスのコンシューマーはusers
を介してのみリストにアクセスできるためです。
バッキングプロパティの規則では、先頭にアンダースコアを使用します。
公式ドキュメントからプロパティの詳細をご覧ください。
Full code:
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user ->
if (user.lastName != null) {
if (user.firstName != null) {
"${user.firstName} ${user.lastName}"
} else {
user.lastName ?: "Unknown"
}
} else {
user.firstName ?: "Unknown"
}
}
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
10.トップレベルおよび拡張機能とプロパティ
現在、Repository
クラスは、User
オブジェクトのフォーマットされたユーザー名を計算する方法を知っています。ただし、他のクラスで同じフォーマットロジックを再利用する場合は、コピーして貼り付けるか、User
クラスに移動する必要があります。
Kotlinは、クラス、オブジェクト、またはインターフェースの外部で関数とプロパティを宣言する機能を提供します。たとえば、リストの新しいインスタンスを作成するために使用したmutableListOf()
関数は、標準ライブラリのCollections.ktで直接定義されます。
Javaでは、ユーティリティ機能が必要な場合はいつでも、Util
クラスを作成し、その機能を静的関数として宣言する可能性が高くなります。Kotlinでは、クラスを持たずにトップレベルの関数を宣言できます。Kotlinはまた、拡張機能を作成する機能も提供します。これらは特定の型を拡張する関数ですが、型の外部で宣言されています。そのため、それらはそのタイプに類似しています。
クラスを所有していないか、継承に対して開かれていないため、クラスの機能を拡張するには、Kotlinは、拡張機能と呼ばれる特別な宣言を作成します。Kotlinは、拡張機能と拡張プロパティをサポートしています。
拡張機能とプロパティの可視性は、可視性修飾子を使用して制限できます。これらは、拡張を必要とするクラスにのみ使用を制限し、名前空間を汚染しません。
Userクラスの場合、フォーマットされた名前を計算する拡張関数を追加するか、フォーマットされた名前を拡張プロパティに保持できます。リポジトリクラスの外部の同じファイルに追加できます。
// extension function
fun User.getFormattedName(): String {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// extension property
val User.userFormattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName
その後、拡張機能とプロパティをUser
クラスの一部であるかのように使用できます。
フォーマットされた名前はユーザーのプロパティであり、Repository
クラスの機能ではないため、拡張プロパティを使用しましょう。Repository
ファイルは次のようになります。
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() {
return _users.map { user -> user.formattedName }
}
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.add(user1)
_users.add(user2)
_users.add(user3)
}
}
Kotlin標準ライブラリは、拡張関数を使用していくつかのJava APIの機能を拡張します。Iterable
およびCollection
の多くの機能は、拡張機能として実装されています。たとえば、前の手順で使用したマップ関数は、Iterable
の拡張関数です。
11.スコープ関数:let、apply、with、run、also
Repository
クラスコードでは、いくつかのユーザーオブジェクトを_users
リストに追加しています。これらの呼び出しは、スコープ関数の助けを借りてより慣用的にすることができます。
名前に基づいてオブジェクトにアクセスする必要なく、特定のオブジェクトのコンテキストでのみコードを実行するために、Kotlinは5つのスコープ関数let
、apply
、with
、run
、also
を作成しました。短く強力なこれらの関数はすべて、レシーバー(this
)を持ち、引数(it
)を持ち、値を返すことがあります。達成したい内容に応じて、どれを使用するかを決定します。
ここからスコープ機能のチートシートをダウンロードしてください。
Repository
で_users
オブジェクトを構成しているため、apply
関数を使用してコードをより慣用的にすることができます。
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
_users.apply {
// this == _users
add(user1)
add(user2)
add(user3)
}
公式ドキュメントからスコープ関数の詳細をご覧ください。
12.まとめ
このコードラボでは、JavaからKotlinへのコードのリファクタリングを開始するために必要な基本について説明しました。このリファクタリングは開発プラットフォームに依存せず、記述するコードが慣用的であることを保証するのに役立ちます。
慣用的なKotlinを使用すると、コードを短く簡潔に書くことができます。Kotlinが提供するすべての機能を使用すると、コードをより安全で簡潔で読みやすくするための方法がたくさんあります。たとえば、_users
リストを宣言で直接ユーザーでインスタンス化し、init
ブロックを削除することで、Repository
クラスを最適化することもできます。
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
nullability、シングルトン、文字列、コレクションの処理から、拡張機能、トップレベル機能、プロパティ、スコープ機能などのトピックまで、幅広いトピックを取り上げました。2つのJavaクラスから、次のような2つのKotlinクラスに移行しました。
class User(var firstName: String?, var lastName: String?)
val User.formattedName: String
get() {
return if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName ?: "Unknown"
}
} else {
firstName ?: "Unknown"
}
}
object Repository {
private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
val users: List<User>
get() = _users
val formattedUserNames: List<String>
get() = _users.map { user -> user.formattedName }
}
Java機能のTL; DRと、Kotlinへのマッピングを次に示します。
Java | Kotlin |
---|---|
final object | val object |
equals() | == |
== | === |
Class that just holds data | data class |
Initialization in the constructor | Initialization in the init block |
static fields and functions | fields and functions declared in a companion object |
Singleton class | object |
Kotlinの詳細とプラットフォームでのKotlinの使用方法については、次のリソースをご覧ください。
- Kotlin Koans
- Kotlin Tutorials
- Developing Android apps with Kotlin - free course
- Kotlin Bootcamp for Programmers
- https://www.coursera.org/learn/kotlin-for-java-developers - free course in Audit mode