この Codelab について
1. ようこそ!
この Codelab では、Java から Kotlin にコードを変換する方法を学びます。また、Kotlin 言語の慣例と、それに沿うようにコードを記述する方法についても学習します。
この Codelab は、現在 Java を使用し、Kotlin へのプロジェクトの移行を検討しているデベロッパーに適しています。最初に、IDE を使用していくつかの Java クラスを Kotlin に変換します。次に、変換後のコードをより慣用的なものに改善する方法と、よくある問題を回避する方法を学びます。
学習内容
Java を Kotlin に変換する方法を学びます。その過程で、以下の Kotlin 言語の機能とコンセプトについて学習します。
- null 値許容の処理
- シングルトンの実装
- データクラス
- 文字列の処理
- Elvis 演算子
- 分解
- プロパティとバッキング プロパティ
- デフォルトの引数と名前付きパラメータ
- コレクションの操作
- 拡張関数
- 最上位の関数とパラメータ
let
、apply
、with
、run
の各キーワード
前提条件
Java に精通している必要があります。
必要なもの
2. 設定方法
新しいプロジェクトの作成
IntelliJ IDEA を使用している場合は、Kotlin/JVM で新しい Java プロジェクトを作成します。
Android Studio を使用している場合は、Activity なしで新しいプロジェクトを作成します。
コード
ここでは、User
モデル オブジェクトと、User
オブジェクトと連携してユーザーのリストとフォーマットされたユーザー名を公開する Repository
シングルトン クラスを作成します。
User.java
という新しいファイルを app/java/<パッケージ名> に作成し、次のコードを貼り付けます。
public class User {
@Nullable
private String firstName;
@Nullable
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;
}
}
プロジェクトの種類に応じて、Android プロジェクトを使用する場合は androidx.annotation.Nullable
を、それ以外の場合は org.jetbrains.annotations.Nullable
をインポートします。
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. null 値許容、val、var、データクラスの宣言
IDE では Java コードから Kotlin コードへのリファクタリングをほぼ自動的に行えますが、一部で手動操作が必要になる場合もあります。まずこれを行ってから、リファクタリングされたコードを確認して、そのようにリファクタリングされた方法と理由を理解します。
User.java
ファイルに移動し、メニューバー -&g; Code -> Convert Java File to Kotlin File を選択して Kotlin に変換します。
IDE で変換後に修正するよう求められた場合は、[Yes] を押します。
次の Kotlin コードが表示されます。
class User(var firstName: String?, var lastName: String?)
User.java
の名前が User.kt
に変更されたことを確認しますKotlin ファイルの拡張子は .kt です。
Java の User
クラスでは、firstName
と lastName
の 2 つのプロパティがありました。各プロパティにゲッター メソッドとセッター メソッドがあるため、値は可変となっていました。Kotlin での可変変数のキーワードは var
であるため、コンバータはこれらのプロパティに var
を使用します。仮に Java プロパティにゲッターのみがあったとすると、これらのプロパティは不変となり、val
変数として宣言されていたはずです。val
は、Java の final
キーワードと似ています。
Kotlin と Java の主な違いの 1 つは、Kotlin では、変数が null 値を受け入れるかどうかを明示的に指定することです。これを行うには、型宣言に「?
」を追加します。
firstName
と lastName
を null 値許容としてマークしていたため、自動コンバータによってこれらのプロパティが null 値許容として String?
で自動的にマークされています。Java メンバーに(org.jetbrains.annotations.NotNull
または androidx.annotation.NonNull
を使用して) 非 null のアノテーションを付けると、コンバータによって認識され、Kotlin でもそのフィールドが非 null になります。
基本的なリファクタリングはすでに完了しています。このコードはより慣用的な方法で記述できます。その方法を見てみましょう。
Data クラス
User
クラスは、データのみを保持します。Kotlin には、このロールを持つクラスに使用する data
というキーワードがあります。このクラスを data
クラスとしてマークすると、コンパイラによって自動的にゲッターとセッターが作成されます。また、equals()
、hashCode()
、toString()
関数も取得されます。
data
キーワードを User
クラスに追加しましょう。
data class User(var firstName: String, var lastName: String)
Java と同様に、Kotlin では 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
であることを指定できます。これを行うには、null
を lastName
に割り当てます。
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 に変換しましょう。自動変換の結果は次のようになります。
import java.util.*
class Repository private constructor() {
private var users: MutableList<User?>? = null
fun getUsers(): List<User?>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
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
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
自動コンバータによって行われた処理を見てみましょう。
init
ブロックが追加されました(Repository.kt#L50)static
フィールドがcompanion object
ブロックの一部になりました(Repository.kt#L33)- 宣言時にオブジェクトがインスタンス化されなかったため、
users
のリストは null 値許容になります(Repository.kt#L7) getFormattedUserNames()
メソッドがformattedUserNames
というプロパティになりました(Repository.kt#L11)- ユーザーリストの反復処理(当初は
getFormattedUserNames(
の一部であったもの)の構文は、Java のもの(Repository.kt#L15)とは異なる
先に進む前に、コードを少しクリーンアップしましょう。コンバータによって、users
リストが、null 値許容オブジェクトを保持する可変リストになりました。実際にはリストは null にできますが、null ユーザーを保持できないとしましょう。そのため、次のことを行います。
users
型宣言内のUser?
から?
を削除します。getUsers
はList<User>?
を返す必要があります。
また、自動コンバータはユーザー変数と init ブロックで定義された変数の変数宣言を不必要に 2 行に分割しています。変数宣言を 1 行で記述します。コードは次のようになります。
class Repository private constructor() {
private var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
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
}
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
init ブロック
Kotlin ではプライマリ コンストラクタにコードを含めることができないため、初期化コードは init
ブロックに配置されますが、機能としては同じです。
class Repository private constructor() {
...
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
init
コードの多くは、プロパティの初期化を処理します。これは、プロパティの宣言で行うこともできます。たとえば、Repository
クラスの Kotlin バージョンでは、宣言で users プロパティが初期化されています。
private var 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 var users: MutableList<User>? = null
fun getUsers(): List<User>? {
return users
}
val formattedUserNames: List<String>
get() {
val userNames: MutableList<String> =
ArrayList(users!!.size)
for (user in users) {
var name: String
name = if (user!!.lastName != null) {
if (user!!.firstName != null) {
user!!.firstName + " " + user!!.lastName
} else {
user!!.lastName
}
} else if (user!!.firstName != null) {
user!!.firstName
} else {
"Unknown"
}
userNames.add(name)
}
return userNames
}
// keeping the constructor private to enforce the usage of getInstance
init {
val user1 = User("Jane", "")
val user2 = User("John", null)
val user3 = User("Anne", "Doe")
users = ArrayList<Any?>()
users.add(user1)
users.add(user2)
users.add(user3)
}
}
object
クラスを使用する場合、次のように関数とプロパティをオブジェクトで直接呼び出すことができます。
val users = Repository.users
分解
Kotlin では、分解宣言という構文を使用して、オブジェクトを複数の変数に分解できます。複数の変数を作成し、個別に使用することが可能です。
たとえば、データクラスは分解をサポートしているため、for
ループ内の User
オブジェクトを (firstName, lastName)
に分解できます。これにより、firstName
と lastName
の値を直接処理できます。次のように for
ループを更新してみましょう。
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)
}
6. null 値許容の処理
Repository
クラスを Kotlin に変換した際、自動コンバータによってユーザーのリストが null 値許容になりました。これは、宣言時にオブジェクトに初期化されなかったためです。users
オブジェクトを使用する場合は、必ず非 null アサーション演算子 !!
が使用されます。任意の変数を非 null 型に変換し、値が null の場合は例外をスローします。!!
を使用すると、実行時に例外がスローされるリスクがあります。
代わりに、次のいずれかの方法で null 値許容を処理することをおすすめします。
- null チェックを行う(
if (users != null) {...}
) - Elvis 演算子
?:
を使用する(この Codelab で後述します) - Kotlin の標準関数を使用する(この Codelab で後述します)
この例では、オブジェクトの作成直後にユーザーのリストが初期化されるため、ユーザーのリストは null 値許容にする必要はありません。宣言したときにオブジェクトを直接インスタンス化できます。
コレクション型のインスタンスを作成する際に、Kotlin で提供されているヘルパー関数を使用することで、コードを読みやすくして柔軟性を高めることができます。ここでは、users
に MutableList
を使用しています。
private var users: MutableList<User>? = null
簡素化するには、mutableListOf()
関数を使用し、リスト要素型を指定して、init
ブロックから ArrayList
コンストラクタ呼び出しを削除し、users
プロパティの明示的な型宣言を削除します。
private val users = mutableListOf<User>()
また、users にはユーザーのリストへの不変参照が含まれるため、var を val に変更しました。参照は不変ですが、リスト自体は可変です(要素の追加や削除が可能です)。
上記の変更により users
プロパティが非 null になったため、不要な !!
演算子をすべて削除できます。
val userNames: MutableList<String?> = ArrayList(users.size)
for ((firstName, lastName) in users) {
...
}
users 変数はすでに初期化されているため、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 値許容を処理する必要があります。どちらの名前もない場合は "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 演算子は、左辺の式が null でない場合は左辺の式を返し、左辺の式が null の場合は右辺の式を返します。
したがって、次のコードでは、null でない場合は user.firstName
が返されます。user.firstName
が null の場合、右辺の値である "Unknown"
が返されます。
if (lastName != null) {
...
} else {
name = firstName ?: "Unknown"
}
7. 文字列テンプレートと if 式
Kotlin では、文字列テンプレート を使用してString
簡単に作業できます。文字列テンプレートを使用すると、文字列宣言内で変数を参照できます。
自動コンバータは、姓と連結を連結して、文字列内で変数名を直接参照するように $
記号を使用し、{ }
の間に式を配置しました。
// Java
name = user.getFirstName() + " " + user.getLastName();
// Kotlin
name = "${user.firstName} ${user.lastName}"
コードで、文字列の連結を次のように置き換えます。
name = "$firstName $lastName"
Kotlin では、if
、when
、for
、while
は式を返します。これらは値を返します。また、割り当てを解除する必要があるという警告が if
に表示されます。
IDE の提案に従って、両方の if
ステートメントの割り当てを解除します。if ステートメントの最後の行が割り当てられます。このように、このブロックの唯一の目的は名前の値の初期化です。
name = if (lastName != null) {
if (firstName != null) {
"$firstName $lastName"
} else {
lastName
}
} else {
firstName ?: "Unknown"
}
次に、name
宣言を割り当てに結合できるという警告が表示されます。これも同様に適用します。name 変数の型は推測できるため、明示的な型宣言を削除できます。formattedUserNames
は次のようになります。
val formattedUserNames: List<String?>
get() {
val userNames: MutableList<String?> = ArrayList(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
}
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
関数です。この関数は、特定の変換関数を元のリストの各要素に適用した結果を含む、新しいリストを返します。したがって、新しいリストを作成して、ユーザーのリストの反復処理を手動で行う代わりに、map
関数を使って、for
ループ内のロジックを map
本文内に移動させることができます。デフォルトでは、map
で使用されている現在のリスト項目の名前は it
ですが、読みやすくするために 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"
}
}
}
9. プロパティとバッキング プロパティ
自動コンバータが getFormattedUserNames()
関数をカスタムのゲッターを持つ formattedUserNames
というプロパティに置き換えたことを確認しました。内部では、Kotlin は引き続き List
を返す getFormattedUserNames()
メソッドを生成します。
Java では、ゲッター関数とセッター関数を介してクラス プロパティを公開します。Kotlin を使用すると、フィールドによって表現されるクラスのプロパティと、関数によって表現され、クラスが実行できる機能やアクションを、より適切に区別できるようになります。この例では、Repository
クラスは非常にシンプルでアクションを行わないため、フィールドだけが含まれます。
Java の getFormattedUserNames()
関数でトリガーされたロジックが、formattedUserNames
Kotlin プロパティのゲッターを呼び出したときにトリガーされるようになりました。
formattedUserNames
プロパティに対応するフィールドを明示的に含んではいませんが、Kotlin には、必要に応じてカスタム ゲッターとセッターからアクセスできる field
という名前の自動バッキング フィールドが用意されています。
ただし、自動バッキング フィールドでは提供されない追加機能が必要な場合があります。次の例で説明します。
Repository
クラス内には、Java コードから生成された関数 getUsers()
で公開されているユーザーの可変リストがあります。
fun getUsers(): List<User>? {
return users
}
ここでの問題は、users
を返すことで Repository クラスのすべてのユーザーがユーザーのリストを変更できてしまう点であり、あまりよくありません。バッキング プロパティを使用して、これを修正します。
まず、users
の名前を _users
に変更します。次に、ユーザーのリストを返す公開不変プロパティを追加します。users
とします。
private val _users = mutableListOf<User>()
val users: List<User>
get() = _users
この変更により、非公開 _users
プロパティは公開 users
プロパティのバッキング プロパティになります。Repository
クラスの外部では、クラスのユーザーは users
を介さないとリストにアクセスできないため、_users
リストを変更できません。
完全なコード:
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 には、クラス、オブジェクト、インターフェースの外部で関数やプロパティを宣言する機能が用意されています。たとえば、List
の新しいインスタンスを作成するために使用した mutableListOf()
関数は、標準ライブラリから直接 Collections.kt
で定義されます。
Java では、ユーティリティ機能が必要な場合は常に Util
クラスを作成し、その機能を静的関数として宣言する必要があります。Kotlin では、クラスを使用せずにトップレベル関数を宣言できます。ただし、Kotlin には拡張関数を作成する機能もあります。特定の型を拡張するものの、その型の外部で宣言される関数です。そのため、ゲームには高い親和性があります。
拡張関数と拡張プロパティの可視性を制限するには、可視性修飾子を使用します。これにより、拡張機能が必要なクラスだけに使用が限定されるため、名前空間が汚染されません。
User
クラスの場合、フォーマットされた名前を計算する拡張関数を追加するか、フォーマットされた名前を拡張プロパティで保持できます。次のように Repository
クラスの外部で、同じファイル内に追加できます。
// 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
の機能の多くが拡張関数として実装されています。たとえば、前のステップで使用した map
関数は Iterable
の拡張関数です。
11. スコープ関数: let、apply、with、run、also
Repository
クラスのコードで、複数のユーザー オブジェクトを _users
リストに追加しています。これらの呼び出しは、スコープ関数を利用することでより慣用的に記述することができます。
名前に基づいてオブジェクトにアクセスすることなく、特定のオブジェクトのコンテキストでのみコードを実行するために、Kotlin では 5 つのスコープ関数(let
、apply
、with
、run
、also
)を作成しました。簡潔で強力なこれらの関数はすべて、レシーバー(this
)を持ち、引数(it
)を持ち、値を返すことができます。使用する目標に応じてどちらを使用するかを決めます。
その覚え方に役立つ便利なクイック リファレンスをご紹介します。
ここでは _users
オブジェクトを Repository
内で設定しているため、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. まとめ
この Codelab では、Java から Kotlin へのコードのリファクタリングを開始するために必要な基本事項について説明しました。このリファクタリングは開発プラットフォームから独立しており、記述されたコードが慣用的であることを確認できます。
慣用的な Kotlin により、コードを簡潔でわかりやすく記述することができます。Kotlin が提供するすべての機能を使用して、コードの安全性、簡潔性、読みやすさを向上させる方法は多数あります。たとえば、次のようにユーザーが含まれる _users
リストを宣言で直接インスタンス化し、init
ブロックを取り除くことで、Repository
クラスを最適化することもできます。
private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
null 値許容、シングルトン、文字列、コレクションの処理から、拡張関数、トップレベル関数、プロパティ、スコープ関数に至るまで、幅広い内容を取り上げました。最終的には、2 つの Java クラスを次のような 2 つの Kotlin クラスに変換しました。
User.kt
class User(var firstName: String?, var lastName: String?)
Repository.kt
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 の各機能と、それらの Kotlin へのマッピングの要約を次に示します。
Java | Kotlin |
|
|
|
|
|
|
データのみを保持するクラス |
|
コンストラクタでの初期化 |
|
|
|
シングルトン クラス |
|
Kotlin の詳細と、プラットフォームでの Kotlin の使用方法について詳しくは、以下のリソースをご覧ください。
- Kotlin Koans
- Kotlin のチュートリアル
- Kotlin による Android アプリの開発 - 無料コース
- プログラマー向け Kotlin ブートキャンプ
- Java デベロッパーのための Kotlin - 監査モードでの無料コース