Kotlin のクラスとオブジェクト インスタンス

1. 始める前に

この Codelab では、Dice Roller Android アプリを作成します。ユーザーがサイコロを振る(rolls the dice)と、ランダムな結果が生成されます。結果ではサイコロの面数が考慮されます。たとえば、6 面のサイコロを振ると 1~6 の値のみが出ます。

完成したアプリの外観は次のようになります。

8299aaca25c93863.png

このアプリで新しいプログラミングの概念に集中できるように、ブラウザベースの Kotlin プログラミング ツールを使用してアプリの主要な機能を作成します。プログラムによって、結果がコンソールに出力されます。後で、Android Studio にてユーザー インターフェースを実装します。

この最初の Codelab では、サイコロ投げをシミュレートして、現実のサイコロと同じように乱数を出力する Kotlin プログラムを作成します。

前提条件

  • https://developer.android.com/training/kotlinplayground でコードを開き、編集し、実行できる。
  • 変数と関数を使用し、結果をコンソールに出力する Kotlin プログラムを作成、実行できる。
  • ${variable} 表記の文字列テンプレートを使用して、テキスト内の数字をフォーマットできる。

学習内容

  • プログラムで乱数を生成し、サイコロ投げをシミュレートする方法。
  • 変数とメソッドを含む Dice クラスを作成してコードを構築する方法。
  • クラスのオブジェクト インスタンスを作成し、その変数を変更し、メソッドを呼び出す方法。

作成するアプリの概要

  • ブラウザベースの Kotlin プログラミング ツールを使用して、ランダムなサイコロ投げを実行できる Kotlin プログラムを作成します。

必要なもの

  • インターネットに接続されているパソコン

2. サイコロを振って乱数を出す

多くのゲームには、ランダムな要素が含まれています。たとえば、ランダムな賞金を獲得したり、ゲームボードでランダムな数だけコマを進めたりする場合などです。日常生活でも、ランダムな数字と文字を使うことで、より安全なパスワードを設定できます。

ここでは現実のサイコロを振るのではなく、サイコロ投げをシミュレートするプログラムを作成します。サイコロを振るたびに、可能な値の範囲内で数字がランダムに出るようにします。幸いなことに、このようなプログラムのために独自の乱数ジェネレータを構築する必要はありません。Kotlin を含むほとんどのプログラミング言語には、乱数を生成するための機能が用意されています。このタスクでは、Kotlin コードを使用して乱数を生成します。

スターター コードを設定する

  1. ブラウザで、ウェブサイト https://developer.android.com/training/kotlinplayground にアクセスします。
  2. コードエディタ内の既存のコードをすべて削除し、次のコードに置き換えます。これは前の Codelab で使用した main() 関数です。
fun main() {

}

ランダム関数を使用する

サイコロを振るには、すべての有効な出目の値を表すための方法が必要です。通常の 6 面サイコロの場合、有効な値は 1、2、3、4、5、6 です。

以前の Codelab で、整数の Int やテキストの String などのデータ型があることを学びました。IntRange もデータ型であり、これは始点から終点までの整数の範囲を表します。IntRange は、サイコロ投げで出る可能性のある目の値を表すのに適切なデータ型です。

  1. main() 関数内で、変数を diceRange という val として定義します。この変数に、1 から 6 までの IntRange を代入します。これは、6 面のサイコロを振って出る可能性のある整数の範囲を表します。
val diceRange = 1..6

1..6 は Kotlin の範囲です。これは、始点の数字、2 つのドット、終点の数字で構成され、間にスペースがないことから判断できます。整数の範囲の他の例として、2~5 の範囲は 2..5、100~200 の範囲は 100..200 となります。

println() を呼び出すことで特定のテキストを出力できるように、random() という関数を使用して、特定の範囲の乱数を生成して返すことができます。前と同様に、結果を変数に格納できます。

  1. main() 内で、変数を randomNumber という val として定義します。
  2. 以下に示すように、diceRange の範囲で random() を呼び出した結果の値が randomNumber に格納されるようにします。
 val randomNumber = diceRange.random()

変数 diceRange と関数 random() の間にピリオド(ドット)を使用して呼び出していることに注意してください。これは、「diceRange から乱数を生成する」と読むことができます。呼び出しの結果は randomNumber 変数に格納されます。

  1. ランダムに生成された数字を表示するには、文字列の形式表記(「文字列テンプレート」とも呼ばれます)である ${randomNumber} を使用して出力します。
println("Random number: ${randomNumber}")

完成したコードは次のようになります。

fun main() {
    val diceRange = 1..6
    val randomNumber = diceRange.random()
    println("Random number: ${randomNumber}")
}
  1. コードを複数回実行してみてください。出力は次のようになり、実行ごとにランダムな数字が表示されます。
Random number: 4

3.Dice クラスを作成する

実際にサイコロを振るときには、サイコロは手の中に現実の物体(オブジェクト)として存在します。さきほど作成したコードは問題なく動作しますが、これが現実のサイコロの動作を表現したコードであるとは想像しにくいでしょう。表現する対象に似せてプログラムを構成することで、プログラムを理解しやすくなります。そこで、ここではプログラムでサイコロを作成し、実際にサイコロ投げができるようにします。

すべてのサイコロは、基本的に同じように機能します。「面がある」という特性(プロパティ)と、「振ることができる」という動作はいずれも共通しています。この「サイコロには面があり、振ると乱数の目が出る」というサイコロの設計図を、Kotlin でプログラムを使って作成できます。この設計図がクラスと呼ばれるものです。

そのクラスから、実際のサイコロのオブジェクトを作成できます。これをオブジェクト インスタンスと呼びます。たとえば、12 面のサイコロや 4 面のサイコロを作成することが可能です。

Dice クラスを定義する

次の手順では、Dice という名前の新しいクラスを定義して、振ることのできるサイコロを表現します。

  1. 新しいコードを作成するには、main() 関数のコードを削除して、最終的にコードを次のようにします。
fun main() {

}
  1. この main() 関数の下に空白行を追加し、Dice クラスを作成するためのコードを追加します。次に示すように、キーワード class、クラス名、左中かっこ、右中かっこの順に記述します。中かっこの間には、クラスのコードを記述するためのスペースを残しておきます。
class Dice {

}

クラスの定義内で、変数を使用してクラスのプロパティを 1 つまたは複数指定できます。現実のサイコロには複数の面があり、色や重量もあります。このタスクでは、サイコロの面数というプロパティに焦点を当てます。

  1. Dice クラス内に、サイコロの面数を示す sides という var を追加します。sides を 6 に設定します。
class Dice {
    var sides = 6
}

これで、サイコロを表す非常にシンプルなクラスを作成できました。

Dice クラスのインスタンスを作成する

この Dice クラスを、サイコロの設計図として使用できます。プログラムで実際にサイコロを使うには、Dice のオブジェクト インスタンスを作成する必要があります(3 つのサイコロが必要な場合は、3 つのオブジェクト インスタンスを作成します)。

ba2038022410942c.jpeg

  1. Dice のオブジェクト インスタンスを作成するには、main() 関数内で myFirstDice という val を作成し、Dice クラスのインスタンスとして初期化します。クラス名の後のかっこは、クラスから新しいオブジェクト インスタンスを作成することを示しています。
fun main() {
    val myFirstDice = Dice()
}

設計図から作成した myFirstDice オブジェクトができたら、そのプロパティにアクセスできます。Dice の唯一のプロパティは sides です。プロパティにアクセスするには「ドット表記法」を使用します。したがって、myFirstDicesides プロパティにアクセスするには、myFirstDice.sides(「myFirstDice ドット sides」と読みます)を呼び出します。

  1. myFirstDice の宣言の下に println() ステートメントを追加して、myFirstDice.sides の数を出力します。
println(myFirstDice.sides)

コードの内容は次のようになります。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
}

class Dice {
    var sides = 6
}
  1. プログラムを実行すると、Dice クラスで定義された sides の数が出力されます。
6

これで、Dice クラスと、6 つの sides を持つ実際のサイコロ myFirstDice が作成されました。

それではサイコロを振ってみましょう。

サイコロを振る

以前、関数を使用して、ケーキレイヤを出力するアクションを実行しました。サイコロ投げも同様に、関数として実装できるアクションです。また、すべてのサイコロは振ることができるため、Dice クラス内にその関数を追加できます。クラス内で定義される関数はメソッドとも呼ばれます。

  1. Dice クラス内で、sides 変数の下に空白行を挿入し、サイコロを振るための新しい関数を作成します。Kotlin キーワード fun、メソッド名、かっこ ()、左中かっこと右かっこ {} の順に記述します。以下に示すように、中かっこの間に空白行を入れておくと、追加のコード用のスペースを確保できます。クラスは次のようになります。
class Dice {
    var sides = 6

    fun roll() {

    }
}

6 面のサイコロを振ると、1~6 の乱数が生成されます。

  1. roll() メソッド内で val randomNumber を作成し、1..6 の範囲の乱数を割り当てます。ドット表記を使用して、この範囲で random() を呼び出します。
val randomNumber = (1..6).random()
  1. 乱数を生成したら、コンソールに出力します。完成した roll() メソッドは次のようになります。
fun roll() {
     val randomNumber = (1..6).random()
     println(randomNumber)
}
  1. 実際に myFirstDice を振るには、main() で、myFirstDiceroll() メソッドを呼び出します。メソッドを呼び出す際は「ドット表記法」を使用するため、myFirstDiceroll() メソッドを呼び出すには、myFirstDice.roll()(「myFirstDice ドット roll()」と読みます)と記述します。
myFirstDice.roll()

コードは次のようになります。

fun main() {
    val myFirstDice = Dice()
    println(myFirstDice.sides)
    myFirstDice.roll()
}

class Dice {
    var sides = 6

    fun roll() {
        val randomNumber = (1..6).random()
        println(randomNumber)
    }
}
  1. コードを実行してみましょう。面数の下にランダムな出目の結果が表示されます。コードを複数回実行すると、面数がそのままで、出目の値が変わることがわかります。
6
4

これで、sides 変数と roll() 関数を持つ Dice クラスを定義できました。main() 関数で新しい Dice オブジェクト インスタンスを作成し、その roll() メソッドを呼び出して乱数を生成しました。

4. サイコロの出目の値を返す

この時点で、roll() 関数で randomNumber の値を問題なく出力できていますが、関数の呼び出し元に関数の結果を返す方が便利な場合もあります。たとえば、roll() メソッドの結果を変数に割り当てて、出目の数だけプレーヤーを移動させるといった場合などです。その方法を確認してみましょう。

  1. main() で、myFirstDice.roll() と記述されている行を変更し、diceRoll という val を作成します。roll() メソッドで返される値を割り当てます。
val diceRoll = myFirstDice.roll()

roll() はまだ何も返していないため、このように設定しても何も起こりません。このコードが意図したとおりに動作するには、roll() がなんらかの値を返す必要があります。

前の Codelab では、関数に渡す入力引数のデータ型を指定する必要があることを学びました。同様に、関数が返すデータのデータ型を指定する必要があります。

  1. roll() 関数を変更して、返されるデータのデータ型を指定します。この場合、乱数は Int であるため、戻り値の型は Int になります。戻り値の型を指定する構文は、関数名の後のかっこの後に、コロン、スペースと続けて、関数の戻り値の型として Int キーワードを追加します。関数定義は次のコードのようになります。
fun roll(): Int {
  1. このコードを実行します。[Problems View] に次のエラーが表示されます。
A ‘return' expression required in a function with a block body (‘{...}')

Int を返すように関数定義を変更しましたが、コードが実際には Int を返さないため、エラーとなっています。「ブロック本体(block body)」または「関数本体(function body)」とは、関数における中かっこの間のコードを指します。このエラーを修正するには、関数本体の末尾で return ステートメントを使用して関数から値を返します。

  1. roll()println() ステートメントを削除し、randomNumberreturn ステートメントに置き換えます。roll() 関数は次のコードのようになります。
fun roll(): Int {
     val randomNumber = (1..6).random()
     return randomNumber
}
  1. main() で、サイコロの面数を出力する print ステートメントを削除します。
  2. わかりやすい文で sidesdiceRoll の値を出力するステートメントを追加します。完成した main() 関数は次のコードのようになります。
fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}
  1. コードを実行すると、次のように出力されます。
Your 6 sided dice rolled 4!

ここまでで、コードは次のようになりました。

fun main() {
    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..6).random()
        return randomNumber
    }
}

5. サイコロの面数を変更する

サイコロは 6 面とは限りません。4 面や 8 面、最大で 120 面と、さまざまな形状とサイズのサイコロがあります。

  1. Dice クラスの roll() メソッドで、ハードコードされた 1..6 を、sides を使用するように変更します。これにより、面数に応じた範囲が正しく設定され、適切な乱数が出るようになります。
val randomNumber = (1..sides).random()
  1. main() 関数の、出目を出力するコードの下で、myFirstDicesides を 20 に変更します。
myFirstDice.sides = 20
  1. さきほど面数を変更したコードの下に、以下の既存の print ステートメントをコピーして貼り付けます。
  2. 出力対象の diceRoll を、myFirstDiceroll() メソッドの呼び出し結果に置き換えます。
println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")

プログラムは次のようになります。

fun main() {

    val myFirstDice = Dice()
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.sides} sided dice rolled ${diceRoll}!")

    myFirstDice.sides = 20
    println("Your ${myFirstDice.sides} sided dice rolled ${myFirstDice.roll()}!")
}

class Dice {
    var sides = 6

    fun roll(): Int {
        val randomNumber = (1..sides).random()
        return randomNumber
    }
}
  1. プログラムを実行すると、6 面のサイコロに関するメッセージと、20 面のサイコロに関するメッセージが表示されます。
Your 6 sided dice rolled 3!
Your 20 sided dice rolled 15!

6. サイコロをカスタマイズする

クラスの概念は、もの(多くの場合、現実世界に物理的に存在するもの)を表すために使用されます。このプログラムの場合、Dice クラスは物理的なサイコロを表しています。現実の世界では、サイコロの面数を変えることはできません。面数を変えるには、別のサイコロを入手する必要があります。プログラムでも同様に、既存の Dice オブジェクト インスタンスの面のプロパティを変更するのではなく、必要な面数を持つ dice オブジェクト インスタンスを新しく作成する必要があります。

このタスクでは、新しいインスタンスの作成時に面数を指定できるように Dice クラスを変更します。Dice クラスの定義を変更して、面数を指定できるようにします。これは、関数が入力引数を受け入れるようにするための方法と似ています。

  1. numSides という整数を受け入れるように Dice クラスの定義を変更します。クラス内のコードは変更されません。
class Dice(val numSides: Int) {
   // Code inside does not change.
}
  1. numSides を使用できるようになったため、Dice クラス内の sides 変数を削除します。
  2. また、範囲も numSides を使用するように修正します。

Dice クラスは次のようになります。

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}

このコードを実行すると多数のエラーが表示されます。これは、Dice クラスの変更に対応するように main() を更新する必要があるためです。

  1. main() で 6 面の myFirstDice を作成するには、次のように Dice クラスに対する引数として面数を指定する必要があります。
    val myFirstDice = Dice(6)
  1. print ステートメントの sidesnumSides に変更します。
  2. その下にある、sides を 20 に変更するコードを削除します。この変数がもはや存在しないためです。
  3. その下の println ステートメントも削除します。

main() 関数は次のようなコードになります。実行してもエラーにならないはずです。

fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")
}
  1. 最初の出目の出力の後に、mySecondDice という 2 つ目の Dice オブジェクト(20 面)を作成および出力するコードを追加します。
val mySecondDice = Dice(20)
  1. サイコロを振って返された値を出力する print ステートメントを追加します。
println("Your ${mySecondDice.numSides} sided dice rolled  ${mySecondDice.roll()}!")
  1. 完成した main() 関数は次のようになります。
fun main() {
    val myFirstDice = Dice(6)
    val diceRoll = myFirstDice.roll()
    println("Your ${myFirstDice.numSides} sided dice rolled ${diceRoll}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        val randomNumber = (1..numSides).random()
        return randomNumber
    }
}
  1. 完成したプログラムを実行すると、出力は次のようになります。
Your 6 sided dice rolled 5!
Your 20 sided dice rolled 7!

7. 適切なコーディング慣習を採用する

コードを記述する際には、できるだけ簡潔にすることをおすすめします。randomNumber 変数を削除して、乱数を直接返すことができます。

  1. return ステートメントを変更して、乱数を直接返すようにします。
fun roll(): Int {
    return (1..numSides).random()
}

2 つ目の print ステートメントでは、文字列テンプレートに乱数を取り込むための呼び出しを記述しています。1 つ目の print ステートメントでも同じようにすることで、diceRoll 変数を削除できます。

  1. 文字列テンプレートで myFirstDice.roll() を呼び出し、diceRoll 変数を削除します。main() コードの最初の 2 行は次のようになります。
val myFirstDice = Dice(6)
println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")
  1. コードを実行し、出力結果に違いが生じないことを確認します。

リファクタリング後の最終的なコードは次のようになります。

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

8. 解答コード

fun main() {
    val myFirstDice = Dice(6)
    println("Your ${myFirstDice.numSides} sided dice rolled ${myFirstDice.roll()}!")

    val mySecondDice = Dice(20)
    println("Your ${mySecondDice.numSides} sided dice rolled ${mySecondDice.roll()}!")
}

class Dice (val numSides: Int) {

    fun roll(): Int {
        return (1..numSides).random()
    }
}

9. 概要

  • IntRangerandom() 関数を呼び出して、乱数を生成します((1..6).random())。
  • クラスはオブジェクトの設計図のようなものです。オブジェクトの特性(プロパティ)と動作は、変数や関数として実装できます。
  • クラスのインスタンスはオブジェクト(多くの場合、サイコロなどの物体)を表します。オブジェクトのアクションを呼び出して、属性を変更できます。
  • インスタンスを作成するときに、クラスに値を指定できます。たとえば、class Dice(val numSides: Int) のように指定してから、Dice(6) でインスタンスを作成します。
  • 関数は、なんらかの値を返すことができます。関数定義で返されるデータ型を指定し、関数本体で return ステートメントを使用してなんらかの値を返します(例: fun example(): Int { return 5 })。

10. 詳細

11. 自習用練習問題

次のことを行います。

  • Dice クラスにさらに色(color)の属性を定義して、面数と色が異なる複数のサイコロのインスタンスを作成します。
  • Coin クラスを作成して、コイントスができるようにします。クラスのインスタンスを作成し、実際にコイントスをやってみましょう。一定の範囲を持つ random() 関数をどのように使用すればコイントスを実現できるか考えてみてください。