Kotlin でリストを使用する

1. 始める前に

すべきことのリスト、イベントのゲストのリスト、ウィッシュ リスト、食料品リストなど、日常のあらゆる状況でリストを作成することがよくあります。プログラミングでも、リストは非常に便利です。たとえば、ニュース記事、曲、カレンダー イベント、ソーシャル メディア投稿のリストをアプリ内に含めることができます。

リストを作成し使用する方法を学ぶことは、ツールボックスに追加すべき重要なプログラミング概念であり、より高度なアプリの作成を可能にします。

この Codelab では、Kotlin プレイグラウンドを使用して Kotlin のリストについて理解を深め、各種のヌードルスープを注文するためのプログラムを作成します。お腹が空きましたか?

前提条件

  • Kotlin プレイグラウンドを使用した Kotlin プログラムの作成と編集に慣れている。
  • Kotlin での Android の基礎コースのユニット 1 での基本的な Kotlin プログラミングの概念(main() 関数、関数の引数と戻り値、変数、データ型、オペレーション、制御フロー文)に精通している。
  • Kotlin クラスを定義し、そこからオブジェクト インスタンスを作成してプロパティとメソッドにアクセスできる。
  • サブクラスを作成でき、それらが互いにどのように継承しているかを理解できる。

学習内容

  • Kotlin でリストを作成して使用する方法
  • ListMutableList の違いと、それぞれを使用するタイミング
  • リストのすべてのアイテムを反復処理し、各アイテムに対してアクションを実行する方法

作成するアプリの概要

  • Kotlin プレイグラウンドでリストとリスト オペレーションを試します。
  • Kotlin プレイグラウンドでリストを使用する料理注文プログラムを作成します。
  • プログラムでは注文を作成し、麺と野菜を追加して、注文の合計金額を計算できます。

必要なもの

2. リストの概要

前の Codelab では、Kotlin の基本的なデータ型(IntDoubleBooleanString など)について学習しました。変数内に特定の型の値を格納できます。しかし、複数の値を格納する場合はどうでしょうか。そこで役立つのが、List データ型です。

リストとは、特定の順序で並んだアイテムの集合です。Kotlin には 2 種類のリストがあります。

  • 読み取り専用リスト: 作成後に List を変更することはできません。
  • 可変リスト: MutableList は作成後に変更できます。つまり、要素を追加、削除、更新できます。

List または MutableList を使用する場合は、含むことができる要素の型を指定する必要があります。たとえば、List<Int> は整数のリストを保持し、List<String> は文字列のリストを保持します。プログラムで Car クラスを定義すると、Car オブジェクト インスタンスのリストを保持する List<Car> を持つことができます。

リストを理解する最善の方法は、実際に試してみることです。

リストを作成する

  1. Kotlin プレイグラウンドを開き、提示された既存のコードを削除します。
  2. 空の main() 関数を追加します。次のコードステップはすべて、この main() 関数の中に入ります。
fun main() {

}
  1. main() 内に、List<Int> 型の numbers という変数を作成します。これは整数の読み取り専用リストが含まれるためです。Kotlin 標準ライブラリ関数 listOf() を使用して新しい List を作成し、リストの各要素を引数としてカンマで区切って渡します。listOf(1, 2, 3, 4, 5, 6) は、1~6 の整数の読み取り専用リストを返します。
val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6)
  1. 代入演算子(=)の右側にある値に基づいて変数の型を推測(推定)できる場合は、変数のデータ型を省略できます。したがって、このコード行を次のように短縮できます。
val numbers = listOf(1, 2, 3, 4, 5, 6)
  1. println() を使用して numbers リストを出力します。
println("List: $numbers")

文字列に「$」を含めるということは、その後に続くものが、評価されてこの文字列に追加される式であることを意味します(文字列テンプレートをご覧ください)。このコード行は println("List: " + numbers). と記述することもできます。

  1. numbers.size プロパティを使用してリストのサイズを取得し、それも出力します。
println("Size: ${numbers.size}")
  1. プログラムを実行します。出力は、リストのすべての要素とリストのサイズのリストです。かっこ [] は、これが List であることを示します。かっこ内には、カンマで区切った numbers の要素が入ります。また、要素が作成したときと同じ順序であることにも注意してください。
List: [1, 2, 3, 4, 5, 6]
Size: 6

リスト要素にアクセスする

リストに固有の機能は、位置を表す整数であるインデックスでリストの各要素にアクセスできることです。各要素とそれに対応するインデックスを示す、作成した numbers リストの図を次に示します。

cb6924554804458d.png

実際には、インデックスは最初の要素からのオフセットです。たとえば list[2] と指定した場合、リストの 2 番目の要素を求めるのではなく、最初の要素から 2 位置オフセットした要素を求めることになります。したがって、list[0] は最初の要素(オフセット 0)、list[1] は 2 番目の要素(オフセット 1)、list[2] は 3 番目の要素(オフセット 2)、というようになります。

main() 関数の既存のコードの後に次のコードを追加します。各ステップの後にコードを実行して、出力が予想どおりであることを確認します。

  1. インデックス 0 でリストの最初の要素を出力します。目的のインデックスで get() 関数を numbers.get(0) として呼び出すことができます。または、インデックスを角かっこで囲んで numbers[0] として省略した構文を使用することもできます。
println("First element: ${numbers[0]}")
  1. 次に、インデックス 1 でリストの 2 番目の要素を出力します。
println("Second element: ${numbers[1]}")

リストの有効なインデックス値は、0 から最後のインデックス(リストのサイズから 1 を引いた値)までです。つまり、numbers リストの場合、インデックスは 0~5 です。

  1. numbers.size - 1 を使用してインデックスを計算し、リストの最後の要素を出力します(5 になるはずです)。5 番目のインデックスの要素にアクセスすると、出力として 6 が返されます。
println("Last index: ${numbers.size - 1}")
println("Last element: ${numbers[numbers.size - 1]}")
  1. Kotlin では、リストの first() オペレーションと last() オペレーションもサポートしています。numbers.first()numbers.last() を呼び出して、出力を確認してみます。
println("First: ${numbers.first()}")
println("Last: ${numbers.last()}")

numbers.first() はリストの最初の要素を返し、numbers.last() はリストの最後の要素を返します。

  1. もう 1 つの便利なリスト オペレーションは contains() メソッドです。特定の要素がリスト内にあるかどうかを確認します。たとえば、会社の従業員名のリストがある場合、contains() メソッドを使用して、特定の名前がリスト内にあるかどうかを確認できます。

numbers リストで、リスト内の整数のいずれかを指定して contains() メソッドを呼び出します。numbers.contains(4) は値 true を返します。次に、リストにない整数を指定して contains() メソッドを呼び出します。numbers.contains(7)false を返します。

println("Contains 4? ${numbers.contains(4)}")
println("Contains 7? ${numbers.contains(7)}")
  1. コードは次のようになります。コメントは任意です。
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5, 6)
    println("List: $numbers")
    println("Size: ${numbers.size}")

    // Access elements of the list
    println("First element: ${numbers[0]}")
    println("Second element: ${numbers[1]}")
    println("Last index: ${numbers.size - 1}")
    println("Last element: ${numbers[numbers.size - 1]}")
    println("First: ${numbers.first()}")
    println("Last: ${numbers.last()}")

    // Use the contains() method
    println("Contains 4? ${numbers.contains(4)}")
    println("Contains 7? ${numbers.contains(7)}")
}
  1. コードを実行します。出力は次のとおりです。
List: [1, 2, 3, 4, 5, 6]
Size: 6
First element: 1
Second element: 2
Last index: 5
Last element: 6
First: 1
Last: 6
Contains 4? true
Contains 7? false

リストは読み取り専用

  1. Kotlin プレイグラウンド内のコードを削除して、次のコードに置き換えます。colors リストは、Strings として表される 3 つの色のリストに初期化されます。
fun main() {
    val colors = listOf("green", "orange", "blue")
}
  1. 読み取り専用 List の要素を追加または変更することはできません。リストにアイテムを追加しようとした場合や、新しい値に設定してリスト内の要素を変更しようとした場合の動作を確認します。
colors.add("purple")
colors[0] = "yellow"
  1. コードを実行すると、エラー メッセージがいくつか表示されます。エラーは基本的に、add() メソッドが List のためには存在しないこと、要素の値を変更できないことを示しています。

dd21aaccdf3528c6.png

  1. 誤ったコードを削除します。

読み取り専用リストは変更できないことを実際に確認しました。ただし、リストを変更せずに新しいリストを返すオペレーションは複数あります。そのうちの 2 つは reversed()sorted() です。reversed() 関数は要素を逆順に並べた新しいリストを返し、sorted() は要素を昇順に並べ替えた新しいリストを返します。

  1. colors リストを反転するコードを追加します。出力します。これは、逆順にした colors の要素を含む新しいリストです。
  2. 元のリストが変更されていないことを確認できるように、コードの 2 行目を追加して元の list を出力します。
println("Reversed list: ${colors.reversed()}")
println("List: $colors")
  1. 出力される 2 つのリストは次のとおりです。
Reversed list: [blue, orange, green]
List: [green, orange, blue]
  1. sorted() 関数を使用して、並べ替えた List を返すコードを追加します。
println("Sorted list: ${colors.sorted()}")

出力は、アルファベット順に並べ替えられた新しい色リストです。すばらしいですね!

Sorted list: [blue, green, orange]
  1. 並べ替えられていない番号のリストで sorted() 関数を試すこともできます。
val oddNumbers = listOf(5, 3, 7, 1)
println("List: $oddNumbers")
println("Sorted list: ${oddNumbers.sorted()}")
List: [5, 3, 7, 1]
Sorted list: [1, 3, 5, 7]

これで、リストを作成できることの便利さがわかりました。ただし、作成後にリストを変更できると有用であるため、次は可変リストを見てみましょう。

3.可変リストの概要

可変リストは、作成後に変更できるリストです。アイテムを追加、削除、変更できます。読み取り専用リストでできることはすべて、こちらでもできます。可変リストは MutableList 型です。mutableListOf() を呼び出してリストを作成できます。

MutableList を作成する

  1. main() の既存のコードを削除します。
  2. main() 関数内で、空の可変リストを作成し、entrees という val 変数に割り当てます。
val entrees = mutableListOf()

コードを実行しようとすると、次のエラーが発生します。

Not enough information to infer type variable T

前述のように、MutableList または List を作成すると、Kotlin は渡された引数からリストに含まれている要素の型を推測しようとします。たとえば、listOf("noodles") と記述すると、Kotlin は String のリストを作成すると推測します。要素のない空のリストを初期化すると、Kotlin は要素の型を推測できないため、型を明示的に記述する必要があります。これを行うには、mutableListOf または listOf の直後に山かっこで囲まれた型を追加します(ドキュメントでは、<T> として表示されます。ここで T は型パラメータを表します)。

  1. String 型の可変リストを作成することを指定するように、変数宣言を修正します。
val entrees = mutableListOf<String>()

エラーを修正するもう 1 つの方法は、変数のデータ型を事前に指定することです。

val entrees: MutableList<String> = mutableListOf()
  1. リストを出力します。
println("Entrees: $entrees")
  1. 出力には、空のリストについて [] が表示されます。
Entrees: []

リストに要素を追加する

可変リストは、要素を追加、削除、更新すると面白くなります。

  1. entrees.add("noodles")."noodles" をリストに追加します。add() 関数は、要素が正常にリストに追加されると true を返します。それ以外の場合は false を返します。
  2. リストを出力して、"noodles" が実際に追加されたことを確認します。
println("Add noodles: ${entrees.add("noodles")}")
println("Entrees: $entrees")

出力は次のとおりです。

Add noodles: true
Entrees: [noodles]
  1. 別のアイテム "spaghetti" をリストに追加します。
println("Add spaghetti: ${entrees.add("spaghetti")}")
println("Entrees: $entrees")

結果の entrees リストに 2 つのアイテムが含まれるようになりました。

Add spaghetti: true
Entrees: [noodles, spaghetti]

add() を使用して要素を 1 つずつ追加するのではなく、addAll() を使用して一度に複数の要素を追加し、リストに渡すことができます。

  1. moreItems のリストを作成します。変更する必要はないため、val とし、不変にします。
val moreItems = listOf("ravioli", "lasagna", "fettuccine")
  1. addAll() を使用して、新しいリストの全アイテムを entrees に追加します。結果のリストを出力します。
println("Add list: ${entrees.addAll(moreItems)}")
println("Entrees: $entrees")

リストが正常に追加されたことを示す出力が表示されます。これで、entrees リストのアイテムが合計 5 つになりました。

Add list: true
Entrees: [noodles, spaghetti, ravioli, lasagna, fettuccine]
  1. このリストに番号を追加してみましょう。
entrees.add(10)

これはエラーを伴って失敗します。

The integer literal does not conform to the expected type String

これは、entrees リストが String 型の要素を想定しており、Int を追加しようとしているためです。リストには正しいデータ型の要素だけを追加するようにしてください。そうしないと、コンパイル エラーが発生します。これは、Kotlin が型安全性でコードの安全性を確保する方法のひとつです。

  1. 正しくないコード行を削除して、コードをコンパイルできるようにします。

リストから要素を削除する

  1. remove() を呼び出して、リストから "spaghetti" を削除します。リストをもう一度出力します。
println("Remove spaghetti: ${entrees.remove("spaghetti")}")
println("Entrees: $entrees")
  1. "spaghetti" を削除すると、要素がリストに存在し、正常に削除できるため、true が返されます。リストにはアイテムが 4 つだけ残っています。
Remove spaghetti: true
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. リストに存在しないアイテムを削除しようとするとどうなるでしょうか。entrees.remove("rice") でリストから "rice" を削除してみてください。
println("Remove item that doesn't exist: ${entrees.remove("rice")}")
println("Entrees: $entrees")

remove() メソッドは、要素が存在せず削除できないため、false を返します。リストは変更されず、アイテムは 4 つのままです。出力は次のとおりです。

Remove item that doesn't exist: false
Entrees: [noodles, ravioli, lasagna, fettuccine]
  1. 削除する要素のインデックスを指定することもできます。removeAt() を使用してインデックス 0 のアイテムを削除します。
println("Remove first element: ${entrees.removeAt(0)}")
println("Entrees: $entrees")

removeAt(0) の戻り値は、リストから削除された最初の要素("noodles")です。entrees リストのアイテムは残り 3 つです。

Remove first element: noodles
Entrees: [ravioli, lasagna, fettuccine]
  1. リスト全体を消去する場合は、clear() を呼び出します。
entrees.clear()
println("Entrees: $entrees")

出力に空のリストが表示されます。

Entrees: []
  1. Kotlin では、isEmpty() 関数を使用してリストが空であるかどうかを確認できます。entrees.isEmpty(). を出力してみてください。
println("Empty? ${entrees.isEmpty()}")

現在リストは空であり、要素は 0 であるため、出力は true になるはずです。

Empty? true

isEmpty() メソッドは、リストに対してオペレーションを行うときや、特定の要素にアクセスするとき、まずリストが空でないことを確認する場合に便利です。

可変リスト用に記述したコードを次に示します。コメントは任意です。

fun main() {
    val entrees = mutableListOf<String>()
    println("Entrees: $entrees")

    // Add individual items using add()
    println("Add noodles: ${entrees.add("noodles")}")
    println("Entrees: $entrees")
    println("Add spaghetti: ${entrees.add("spaghetti")}")
    println("Entrees: $entrees")

    // Add a list of items using addAll()
    val moreItems = listOf("ravioli", "lasagna", "fettuccine")
    println("Add list: ${entrees.addAll(moreItems)}")
    println("Entrees: $entrees")

    // Remove an item using remove()
    println("Remove spaghetti: ${entrees.remove("spaghetti")}")
    println("Entrees: $entrees")
    println("Remove item that doesn't exist: ${entrees.remove("rice")}")
    println("Entrees: $entrees")

    // Remove an item using removeAt() with an index
    println("Remove first element: ${entrees.removeAt(0)}")
    println("Entrees: $entrees")

    // Clear out the list
    entrees.clear()
    println("Entrees: $entrees")

    // Check if the list is empty
    println("Empty? ${entrees.isEmpty()}")
}

4. リストをループする

リスト内の各アイテムに対してオペレーションを行うには、リストをループします(リストの反復処理ともいいます)。ループは ListsMutableLists で使用できます。

while ループ

ループの種類のひとつとして、while ループがあります。Kotlin で、while ループは while キーワードで始まります。これには、かっこ内の式が true である限り繰り返し実行されるコードブロック(中かっこ内)が含まれます。コードが永久に実行されること(無限ループ)を避けるには、式の値を変更するロジックをコードブロックに含める必要があります。これにより、式が最終的に false となり、ループの実行が停止します。この時点で while ループを終了し、ループの後にあるコードの実行を進めます。

while (expression) {
    // While the expression is true, execute this code block
}

while ループを使用してリストを反復処理します。リストで現在参照している index をトラッキングする変数を作成します。この index 変数は、リストの最後のインデックスに到達するまで毎回 1 ずつ増加します。その後、ループを終了します。

  1. Kotlin プレイグラウンドの既存のコードを削除し、main() 関数を空にします。
  2. パーティーを主催するとします。各家庭から参加を申し込んだゲストの数を各要素が表すリストを作成します。最初の家庭は 2 人が参加すると回答しました。2 番目の家庭は 4 人が参加すると回答しました(以下同様)。
val guestsPerFamily = listOf(2, 4, 1, 3)
  1. ゲストの総数を計算します。ループを記述して答えを出します。ゲストの総数について var を作成し、0 に初期化します。
var totalGuests = 0
  1. 前述のように、index 変数の var を初期化します。
var index = 0
  1. while ループを記述してリストを反復処理します。条件は、index の値がリストのサイズより小さい限り、コードブロックの実行を続けることです。
while (index < guestsPerFamily.size) {

}
  1. ループ内で、現在の index にあるリストの要素を取得し、ゲスト総数の変数に追加します。totalGuests += guestsPerFamily[index]totalGuests = totalGuests + guestsPerFamily[index]. と同じです。

ループの最後の行は index++ を使用して index 変数を 1 ずつ増加させるため、ループの次の反復処理でリストの次の家庭が参照されます。

while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
  1. while ループの後、結果を出力できます。
while ... {
    ...
}
println("Total Guest Count: $totalGuests")
  1. プログラムを実行すると、次のように出力されます。リスト内の数字を手動で合計して、出力が正しい答えであることを確認します。
Total Guest Count: 10

完全なコード スニペットは次のとおりです。

val guestsPerFamily = listOf(2, 4, 1, 3)
var totalGuests = 0
var index = 0
while (index < guestsPerFamily.size) {
    totalGuests += guestsPerFamily[index]
    index++
}
println("Total Guest Count: $totalGuests")

while ループでは、インデックスをトラッキングする変数を作成し、リスト内のインデックスの要素を取得して、そのインデックス変数を更新するコードを記述する必要がありました。リストを反復処理するための、迅速かつ簡潔な方法は for ループを使用することです。

for ループ

for ループは別の種類のループです。リストを簡単にループさせることができます。Kotlin で、これは for キーワードで始まり、コードブロックは中かっこで囲まれています。コードブロックの実行条件はかっこ内に記載されています。

for (number in numberList) {
   // For each element in the list, execute this code block
}

この例では、変数 numbernumberList の最初の要素に等しく設定され、コードブロックが実行されます。次に、number 変数が自動的に numberList の次の要素に更新され、コードブロックが再度実行されます。この処理は、numberList の末尾に達するまで、リストの各要素に対して繰り返されます。

  1. Kotlin プレイグラウンドの既存のコードを削除し、次のコードに置き換えます。
fun main() {
    val names = listOf("Jessica", "Henry", "Alicia", "Jose")
}
  1. for ループを追加して names リストの全アイテムを出力します。
for (name in names) {
    println(name)
}

これは while ループとして記述するよりもはるかに簡単です。

  1. 出力は次のとおりです。
Jessica
Henry
Alicia
Jose

リストに対する一般的なオペレーションは、リストの各要素に対してなんらかの処理を行います。

  1. 人物名の文字数も出力するようにループを変更します。ヒント: Stringlength プロパティを使用すると、その String の文字数を確認できます。
val names = listOf("Jessica", "Henry", "Alicia", "Jose")
for (name in names) {
    println("$name - Number of characters: ${name.length}")
}

出力は次のとおりです。

Jessica - Number of characters: 7
Henry - Number of characters: 5
Alicia - Number of characters: 6
Jose - Number of characters: 4

ループ内のコードは、元の List を変更しませんでした。出力内容にのみ影響します。

1 つのリストアイテムに対して発生すべきことに関する指示を記述でき、コードはリストアイテムごとに実行されます。ループを使用すると、コードを何度も繰り返しタイプせずに済みます。

リストと可変リストの両方を作成して使用しながら、ループについて学びました。次は、この知識をサンプル ユースケースで応用してみましょう。

5. すべてをまとめる

地元のレストランで料理を注文するとき、通常は 1 回で複数のアイテムを注文します。注文に関する情報を保存する場合、リストを使用すると便利です。また、コードのすべてを main() 関数内に置くのではなく、クラスと継承に関する知識を活かして、堅牢でスケーラブルな Kotlin プログラムを作成します。

次の一連のタスクでは、さまざまな食品の組み合わせを注文できる Kotlin プログラムを作成します。

まず、最終的なコードの出力例を見てみましょう。このデータをすべて整理するために、どのようなクラスを作成する必要があるのか、ブレインストーミングしましょう。

Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

出力から、次のことがわかります。

  • 注文のリストがある
  • 各注文に番号がある
  • 各注文に、Noodles や Vgetables といったアイテムのリストが含まれることがある
  • 各アイテムに価格がある
  • 各注文に合計額(個々のアイテムの価格を合計したもの)がある

Order を表すクラスと、NoodlesVegetables などの各食品を表すクラスを作成できます。さらに、NoodlesVegetables はどちらも食品であり、それぞれに価格があるため、似ていることがわかります。Noodle クラスと Vegetable クラスの両方が継承できる共有プロパティを持つ Item クラスを作成することを検討します。そうすると、Noodle クラスと Vegetable クラスの両方でロジックを重複させる必要がなくなります。

  1. 次のスターター コードが提供されます。新しいプロジェクトに参加する場合や他の人が作成した機能に追加する場合など、プロの開発者は他の人のコードを読まなければならないことがよくあります。コードを読んで理解できることは重要なスキルです。

時間をかけてこのコードに目を通し、何が起こっているのかを把握してください。このコードをコピーして Kotlin プレイグラウンドに貼り付け、実行します。この新しいコードを貼り付ける前に、Kotlin プレイグラウンドの既存のコードをすべて削除してください。出力を見て、コードに対する理解が深まるかどうかを確認してください。

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10)

class Vegetables : Item("Vegetables", 5)

fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables()
    println(noodles)
    println(vegetables)
}
  1. 次のような出力が表示されます。
Noodles@5451c3a8
Vegetables@76ed5528

次に、コードについて詳しく説明します。まず Item というクラスがあり、コンストラクタはアイテムの name(文字列)と price(整数)という 2 つのパラメータを受け取ります。どちらのプロパティも、渡された後に変更されないため、val とマークされます。Item は親クラスであり、そこからサブクラスが拡張されているため、クラスは open キーワードでマークされます。

Noodles クラスのコンストラクタはパラメータを受け取りませんが、Item から拡張され、name と price を 10 として "Noodles" を渡すことでスーパークラス コンストラクタを呼び出します。Vegetables クラスも同様ですが、"Vegetables" と price 5 でスーパークラス コンストラクタを呼び出します。

main() 関数は、Noodles クラスと Vegetables クラスの新しいオブジェクト インスタンスを初期化し、出力します。

toString() メソッドをオーバーライドする

オブジェクト インスタンスを出力すると、オブジェクトの toString() メソッドが呼び出されます。Kotlin では、すべてのクラスが自動的に toString() メソッドを継承します。このメソッドのデフォルト実装では、単にインスタンスのメモリアドレスを伴ったオブジェクト型が返されます。toString() をオーバーライドして、Noodles@5451c3a8Vegetables@76ed5528 よりも有意義でユーザー フレンドリーなもの返す必要があります。

  1. Noodles クラス内で、toString() メソッドをオーバーライドして name を返すようにします。Noodles は親クラス Item から name プロパティを継承します。
class Noodles : Item("Noodles", 10) {
   override fun toString(): String {
       return name
   }
}
  1. Vegetables クラスについても同じ手順を繰り返します。
class Vegetables() : Item("Vegetables", 5) {
   override fun toString(): String {
       return name
   }
}
  1. コードを実行します。出力がわかりやすくなりました。
Noodles
Vegetables

次のステップでは、一部のパラメータを取り込むように Vegetables クラスのコンストラクタを変更し、その追加情報を反映するように toString() メソッドを更新します。

注文の vegetables をカスタマイズする

ヌードルスープを充実させるために、注文でさまざまな野菜を追加できます。

  1. main() 関数では、入力引数なしで Vegetables インスタンスを初期化するのではなく、お客様が希望する特定の種類の vegetables を渡します。
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}

ここでコードをコンパイルしようとすると、次のようなエラーが発生します。

Too many arguments for public constructor Vegetables() defined in Vegetables

3 つの文字列引数を Vegetables クラス コンストラクタに渡しているため、Vegetables クラスを変更する必要があります。

  1. 次のコードに示すように、3 つの文字列パラメータを取り込むように Vegetables クラスヘッダーを更新します。
class Vegetables(val topping1: String,
                 val topping2: String,
                 val topping3: String) : Item ("Vegetables", 5) {
  1. これで、コードがもう一度コンパイルされます。ただしこのソリューションは、お客様が必ずちょうど 3 つの野菜を注文する場合にしか機能しません。お客様が野菜を 1 つや 5 つ注文しようと思っていても、対応できません。
  2. 野菜ごとにプロパティを使用するのではなく、Vegetables クラスのコンストラクタで野菜のリスト(長さは任意)を受け入れることで、問題を解決できます。List には Strings しか含まれないため、入力パラメータの型は List<String> です。
class Vegetables(val toppings: List<String>) : Item("Vegetables", 5) {

main() で、トッピングのリストを作成するには、Vegetables コンストラクタに渡す前にまずコードを変更する必要があるため、これは最善のソリューションとは言えません。

Vegetables(listOf("Cabbage", "Sprouts", "Onion"))

もっと良い方法があります。

  1. Kotlin では、vararg 修飾子を使用して、同じ型の可変長引数を関数またはコンストラクタに渡すことができます。こうして、リストではなく個々の文字列として、さまざまな野菜を提供できます。

String 型の vararg toppings を取るように Vegetables のクラス定義を変更します。

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
  1. main() 関数のこのコードが機能するようになりました。任意の数のトッピング文字列を渡すことで、Vegetables インスタンスを作成できます。
fun main() {
    ...
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    ...
}
  1. 次に、トッピングを Vegetables Cabbage, Sprouts, Onion の形式で表した String を返すように、Vegetables クラスの toString() メソッドを変更します。

アイテムの名前(Vegetables)から始めます。次に、joinToString() メソッドを使用して、すべてのトッピングを 1 つの文字列に結合します。この 2 つの部分を、間にスペースを入れて + 演算子で足し合わせます。

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        return name + " " + toppings.joinToString()
    }
}
  1. プログラムを実行します。出力は次のようになります。
Noodles
Vegetables Cabbage, Sprouts, Onion
  1. プログラムを記述するときは、あり得るすべての入力を考慮する必要があります。Vegetables コンストラクタに入力引数がない場合は、toString() メソッドを扱いやすい方法で処理します。

お客様は野菜を求めていますが、どれかは言わなかったため、シェフの選んだ野菜をデフォルトで提供するということもソリューションのひとつです。

トッピングが渡されない場合は Vegetables Chef's Choice を返すように toString() メソッドを更新します。先ほど学習した isEmpty() メソッドを使用します。

override fun toString(): String {
    if (toppings.isEmpty()) {
        return "$name Chef's Choice"
    } else {
        return name + " " + toppings.joinToString()
    }
}
  1. コンストラクタ引数がない場合と複数の引数がある場合の両方で Vegetables インスタンスを作成できるかどうかをテストするように main() 関数を更新します。
fun main() {
    val noodles = Noodles()
    val vegetables = Vegetables("Cabbage", "Sprouts", "Onion")
    val vegetables2 = Vegetables()
    println(noodles)
    println(vegetables)
    println(vegetables2)
}
  1. 出力が予想どおりであることを確認します。
Noodles
Vegetables Cabbage, Sprouts, Onion
Vegetables Chef's Choice

注文を作成する

食品が揃ったので、注文を作成できます。プログラムの Order クラス内の注文ロジックをカプセル化します。

  1. Order クラスに適したプロパティとメソッドについて考えてみましょう。参考までに、最終的なコードの出力例をもう一度示します。
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25
  1. 次のように出力されたとします。

Order クラス

プロパティ: 注文番号、アイテムのリスト

メソッド: アイテムの追加、複数のアイテムの追加、注文の概要の出力(価格を含む)

  1. まずプロパティに着目し、各プロパティのデータ型について考えます。クラスに対しパブリックにするか、プライベートにするか。引数として渡すか、クラス内で定義するか。
  2. これを実装する方法は複数ありますが、ここではソリューションを 1 つ紹介します。整数の orderNumber コンストラクタ パラメータを持つ class Order を作成します。
class Order(val orderNumber: Int)
  1. 注文のすべてのアイテムを事前に把握することは難しい場合があるため、アイテムのリストを引数として渡す必要はありません。代わりに、最上位のクラス変数として宣言し、Item 型の要素を保持できる空の MutableList として初期化できます。このクラスだけがアイテムのリストを直接変更できるように、変数を private にマークします。こうすることで、このクラスの外部のコードによってリストが予期せず変更されることを防止できます。
class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()
}
  1. 続いて、クラス定義にもメソッドを追加します。各メソッドに対し相応の名前を自由に選択します。今のところ、各メソッドの実装ロジックは空白のままで構いません。また、必要な関数の引数と戻り値を決定します。
class Order(val orderNumber: Int) {
   private val itemList = mutableListOf<Item>()

   fun addItem(newItem: Item) {
   }

   fun addAll(newItems: List<Item>) {
   }

   fun print() {
   }
}
  1. addItem() メソッドは最も単純であるため、まずその関数を実装します。新しい Item を取り込み、メソッドがそれを itemList に追加します。
fun addItem(newItem: Item) {
    itemList.add(newItem)
}
  1. 次に、addAll() メソッドを実装します。アイテムの読み取り専用リストを取り込みます。これらのアイテムすべてを、アイテムの内部リストに追加します。
fun addAll(newItems: List<Item>) {
    itemList.addAll(newItems)
}
  1. 次に、すべてのアイテムとその価格の概要や、注文の合計額を出力する print() メソッドを実装します。

まず注文番号を出力します。次にループを使用して、注文リスト内の全アイテムを反復処理します。各アイテムとその価格を出力します。また、ここまでの合計額を保持し、リストの反復処理で追加し続けます。最後に合計額を出力します。このロジックをご自身で実装してみてください。サポートが必要な場合は、以下のソリューションをご確認ください。

出力を読みやすくするために、通貨記号を含めることをおすすめします。ソリューションを実装する方法のひとつを以下に示します。このコードは $ 通貨記号を使用していますが、現地通貨の記号に変更することもできます。

fun print() {
    println("Order #${orderNumber}")
    var total = 0
    for (item in itemList) {
        println("${item}: $${item.price}")
        total += item.price
    }
    println("Total: $${total}")
}

itemList 内の各 item について、itemitem に対して呼び出される toString() をトリガーします)とそのアイテムの price を出力します。また、ループの前に、total 整数変数を 0 に初期化します。次に現在のアイテムの価格を total に加えて、合計額に加算します。

注文を作成する

  1. main() 関数内に Order インスタンスを作成してコードをテストします。まず main() 関数の現在の内容を削除します。
  2. こうした注文例を使用することも、独自の注文を作成することもできます。注文内のアイテムのさまざまな組み合わせを試し、コード内のすべてのコードパスをテストしましょう。たとえば、addItem() メソッドと addAll() メソッドを Order クラス内でテストし、引数なしと引数ありで Vegetables インスタンスを作成するなどします。
fun main() {
    val order1 = Order(1)
    order1.addItem(Noodles())
    order1.print()

    println()

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    order2.print()

    println()

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    order3.print()
}
  1. 上のコードの出力は次のようになります。合計額の計算が合っていることを確認します。
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

よくできました。料理の注文らしくなりました。

6. コードを改善する

注文のリストを保持する

実際にヌードル ショップで使用するプログラムを作成する場合は、すべてのお客様の注文リストを追跡すると良いでしょう。

  1. すべての注文を保存するためのリストを作成します。これは読み取り専用リストでしょうか。可変リストでしょうか。
  2. このコードを main() 関数に追加します。まずリストを初期化して空にします。次に、各注文が作成されたら、その注文をリストに追加します。
fun main() {
    val ordersList = mutableListOf<Order>()

    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)
}

注文は経時的に追加されるため、リストは Order 型の MutableList である必要があります。次に、MutableListadd() メソッドを使用して、各注文を追加します。

  1. 注文のリストを作成したら、ループを使用して各注文を出力できます。出力を読みやすくするために、注文間に空白行を出力します。
fun main() {
    val ordersList = mutableListOf<Order>()

    ...

    for (order in ordersList) {
        order.print()
        println()
    }
}

これにより、main() 関数内の重複コードが削除され、コードが読みやすくなります。出力は前と同じです。

注文のビルダー パターンを実装する

Kotlin コードを簡潔にするには、ビルダー パターンを使用して注文を作成します。ビルダー パターンは、複雑なオブジェクトをステップ バイ ステップで作成できる、プログラミングの設計パターンです。

  1. Order クラスの addItem() メソッドと addAll() メソッドから Unit を返す(または何も返さない)のではなく、変更された Order を返します。Kotlin には、現在のオブジェクト インスタンスを参照するキーワード this が用意されています。addItem() メソッドと addAll() メソッドの中で、this を返すことで現在の Order を返します。
fun addItem(newItem: Item): Order {
    itemList.add(newItem)
    return this
}

fun addAll(newItems: List<Item>): Order {
    itemList.addAll(newItems)
    return this
}
  1. main() 関数では、次のコードに示すように、呼び出しをチェーンできるようになりました。このコードは新しい Order を作成し、ビルダー パターンを利用します。
val order4 = Order(4).addItem(Noodles()).addItem(Vegetables("Cabbage", "Onion"))
ordersList.add(order4)

Order(4)Order インスタンスを返します。このインスタンスで addItem(Noodles()) を呼び出すことができます。addItem() メソッドは同じ Order インスタンス(新しい状態)を返すため、vegetables を指定して再度 addItem() を呼び出すことができます。返された Order の結果は order4 変数に格納できます。

Orders を作成する既存のコードは引き続き機能するため、変更せずそのままにしておけます。こうした呼び出しをチェーンさせることは必須ではありませんが、関数の戻り値を利用することは、一般的かつおすすめの方法です。

  1. この時点では、注文を変数に格納する必要すらありません。main() 関数(注文を出力する最後のループの前)で、Order を直接作成して orderList に追加します。また、各メソッド呼び出しをそれぞれの行に配置すると、コードが読みやすくなります。
ordersList.add(
    Order(5)
        .addItem(Noodles())
        .addItem(Noodles())
        .addItem(Vegetables("Spinach")))
  1. コードを実行すると、次のように出力されます。
Order #1
Noodles: $10
Total: $10

Order #2
Noodles: $10
Vegetables Chef's Choice: $5
Total: $15

Order #3
Noodles: $10
Vegetables Carrots, Beans, Celery: $5
Total: $15

Order #4
Noodles: $10
Vegetables Cabbage, Onion: $5
Total: $15

Order #5
Noodles: $10
Noodles: $10
Vegetables Spinach: $5
Total: $25

これでこの Codelab は完了です。

ここでは、データをリストに保存すること、リストを変更すること、リストをループすることが、いかに便利であるかということを学びました。次の Codelab では、この知識を Android アプリのコンテキストで活用し、データのリストを画面に表示してみましょう。

7. 解答コード

ItemNoodlesVegetablesOrder クラスの解答コードを次に示します。main() 関数は、これらのクラスの使用方法も示しています。このプログラムの実装方法は複数あるため、コードが多少異なる場合があります。

open class Item(val name: String, val price: Int)

class Noodles : Item("Noodles", 10) {
    override fun toString(): String {
        return name
    }
}

class Vegetables(vararg val toppings: String) : Item("Vegetables", 5) {
    override fun toString(): String {
        if (toppings.isEmpty()) {
            return "$name Chef's Choice"
        } else {
            return name + " " + toppings.joinToString()
        }
    }
}

class Order(val orderNumber: Int) {
    private val itemList = mutableListOf<Item>()

    fun addItem(newItem: Item): Order {
        itemList.add(newItem)
        return this
    }

    fun addAll(newItems: List<Item>): Order {
        itemList.addAll(newItems)
        return this
    }

    fun print() {
        println("Order #${orderNumber}")
        var total = 0
        for (item in itemList) {
            println("${item}: $${item.price}")
            total += item.price
        }
        println("Total: $${total}")
    }
}

fun main() {
    val ordersList = mutableListOf<Order>()

    // Add an item to an order
    val order1 = Order(1)
    order1.addItem(Noodles())
    ordersList.add(order1)

    // Add multiple items individually
    val order2 = Order(2)
    order2.addItem(Noodles())
    order2.addItem(Vegetables())
    ordersList.add(order2)

    // Add a list of items at one time
    val order3 = Order(3)
    val items = listOf(Noodles(), Vegetables("Carrots", "Beans", "Celery"))
    order3.addAll(items)
    ordersList.add(order3)

    // Use builder pattern
    val order4 = Order(4)
        .addItem(Noodles())
        .addItem(Vegetables("Cabbage", "Onion"))
    ordersList.add(order4)

    // Create and add order directly
    ordersList.add(
        Order(5)
            .addItem(Noodles())
            .addItem(Noodles())
            .addItem(Vegetables("Spinach"))
    )

    // Print out each order
    for (order in ordersList) {
        order.print()
        println()
    }
}

8. まとめ

Kotlin には、Kotlin 標準ライブラリを通じてデータのコレクションを簡単に管理し操作する機能が用意されています。コレクションは、同じデータ型の複数のオブジェクトとして定義できます。Kotlin には、リスト、セット、マップという基本的なコレクション型があります。この Codelab では特にリストに注目していますが、今後の Codelab ではセットとマップについて詳しく取り上げます。

  • リストは、特定の型の要素を順序立てて集めたものです(Strings. のリストなど)。
  • インデックスは、要素の位置を反映する整数の位置です(myList[2] など)。
  • リストでは、最初の要素はインデックス 0 にあり(myList[0] など)、最後の要素は myList.size-1 にあります(myList[myList.size-1]myList.last() など)。
  • リストには、ListMutableList. の 2 種類があります。
  • List は読み取り専用であり、初期化後は変更できません。ただし、sorted()reversed() などのオペレーションを適用すると、元のリストを変更せずに新しいリストを返すことができます。
  • MutableList は、要素の追加、削除、変更など、作成後に変更できます。
  • addAll() を使用して、可変リストにアイテムのリストを追加できます。
  • while ループを使用して、式が false と評価されループを終了するまで、コードのブロックを実行します。

while (expression) {

// While the expression is true, execute this code block

}

  • for ループを使用して、リストの全アイテムを反復処理します。

for (item in myList) {

// Execute this code block for each element of the list

}

  • vararg 修飾子を使用すると、可変長引数を関数またはコンストラクタに渡すことができます。

9. 詳細