ツアー副読本:クラス入門
クラス(ツアー) - Kotlin 日本語リファレンスを読んでいく時の補助教材です。
クラス、最初の一歩
クラスとオブジェクトというのがある。
オブジェクト
- 変数と関数を持つ
- クラスから作られる
クラス
- オブジェクトの設計図である
- コードで書くのはこちら
- 変数と関数を書く(けれど実際はオブジェクトの変数と関数となる)
例:
class Hoge {
var name = "はろー"
fun fuga() {
println("うがうが")
}
}
変数や関数は複数持てる。
コンストラクタ
クラスからオブジェクトを作るのは、コンストラクタと呼ばれる特別な関数を使う。
コンストラクタは、クラスを書くと自動で作られる。
以下のように書くと、
class Hoge {
// ...
}
Hogeという名前の関数が作られる。 これを呼び出すとオブジェクトが作られる。
val a = Hoge()
val b = Hoge()
オブジェクトの関数は同じオブジェクトの変数を触れる
オブジェクトの関数は、同じオブジェクトに属している変数を触ることが出来ます。 コード上はクラスの変数を触っているように見えるのだけれど、 クラスでは無くオブジェクトの変数を触っている、というのが重要です。
オブジェクトの関数や変数の触り方とか呼び方とかは後で説明するけれど、とりあえずaとbという別のオブジェクトの関数を呼んでいて、
a.fuga()
とb.fuga()
の結果が違うことに注目してください。
用語いろいろ
クラスには大量の名前が出てきて初心者を威圧してきます。 全部一気に覚えるのは大変なので出てくる都度徐々に覚えていきましょう。
オブジェクト
幾つか名前があるが同じ意味です。
- オブジェクト
- インスタンス
オブジェクトの関数
幾つかの名前がある。
- メンバ関数
- メソッド
どちらも同じ意味です。
オブジェクトの変数
- プロパティ
- フィールド
- メンバ変数
プロパティは微妙に意味が違う(メンバ変数と後に出てくるcomputedプロパティというものの両方を含んだ言葉)のですが、 それはおいおい説明していくのでしばらくはオブジェクトの変数のことと思っておいてください。 しばらくはこの3つは同じ意味と思ってもらって良いです。
コンストラクタの引数とプロパティ
この動画では以下の話をしています。
- プロパティの初期化はコンストラクタが呼ばれる時である
- コンストラクタに引数を追加出来る
- コンストラクタの引数にvalを追加すると、結構いろいろな事が起こる
プロパティの初期化のタイミング
以下のようなコードがあった時に、
class Hoge {
val name = "ハロー"
}
このnameを初期化する行は、このコードが評価される時には実行されないで、コンストラクタが呼ばれる問に実行される。
class Hoge {
val name = "ハロー"
}
// ここまではまだ実行されない
// ここで実行される
val a = Hoge()
// ここでも実行される(別のオブジェクトに対して)
val b = Hoge()
この事を関数を使って試してみよう。
コンストラクタへの引数の追加
コンストラクタに引数を追加する事が出来る。 これは関数に引数を追加するのに似ている。
// 関数
fun Ika(name: String) {
println(name)
}
// コンストラクタ
class Hoge(name: String) {
val title = name
}
コンストラクタに引数を追加するには、クラスの名前の後にカッコで引数の名前をカンマ区切りで書く。また、型をコロンの後につける。
この追加した引数は、プロパティの初期化に使える。
コンストラクタの引数にvalを追加
一見一つ前と同じように見えるが全然違うのがこのval。 コンストラクタの引数にvalというのをつけると、それがプロパティになる。 valをつけるといろいろ省略出来る便利機能です。
以下のようにnameの方にだけvalをつけると、
class Hoge(val name: String, b: Int) {
val id = b
}
valの無い以下のコードと一緒だ。
class Hoge(tmp0: String, b: Int) {
val name = tmp0
val id = b
}
ここはややこしいのでもう少し詳しく見てみる。
以下のようなコンストラクタにvalの無いコードの場合、aとbにはさわれません。
class Hoge(a: String, b: Int) {
val name = a
val id = b
}
実際に試してみましょう。
でもvalをつけると、これがプロパティになって触る事が出来るようになります。
触る事が出来るかどうかってだけだと思っても良いのだけれど、 最初のうちはちゃんと中括弧の中のプロパティに変換される、と思う方が良いです。
dataクラスの解説を読み直してみよう
この辺まで理解した後に、 以前やったdata class入門を読むと、この機能を使っている事がわかります。 dataクラスとclassは似ている、というかほとんど同じもので、もっと言うとclassに追加で幾つか自動でメンバ関数が生成されるのがdataクラスです(dataクラスはclassに機能を追加したもの)。
以下のような例がありました。
このTwoIntは、省略したvalの記法を使っています。
data class TwoInt(val v1: Int, val v2: Int)
省略記法を使わないと以下のようになります。
data class TwoInt(tmp1: Int, tmp2: Int) {
val v1 = tmp1
val v2 = tmp2
}
data classなのかただのclassなのかは、実はこのくらいの例では違いは無いので、 dataはなくしてしまっても構いません。
class TwoInt(tmp1: Int, tmp2: Int) {
val v1 = tmp1
val v2 = tmp2
}
先程のコードで試してみましょう。
v1だけ省略記法にしてみる。
両方省略記法にしてみる。
最後の空の中括弧を省略してみる
これでdata class入門で書いたのとほぼ同じ記法になりました。
少し自分でもやってみましょう。
課題: 短縮記法を使わずに同じクラスを書け
class TwoInt(val v1: Int, val v2: Int)
を、コンストラクタの中にvalを書く短縮記法無しで、
中括弧の中にプロパティのv1とv2を書く書き方で書け。
既に解説の中で全く同じ事をやりましたが、自分でも書いてみましょう。
解答例
// TODO: 以下と同じ内容のTwoIntを、短縮記法無しで書け
// class TwoInt(val v1: Int, val v2: Int)
class TwoInt(tmp0: Int, tmp1: Int {
val v1 = tmp0
val v2 = tmp1
})
fun main() {
val ti1 = TwoInt(1, 2)
println(ti1.v1)
println(ti1.v2)
}
プロパティとメソッドのアクセス方法
オブジェクトはプロパティとメソッド(メンバ関数)を持つものだ、 という話をしました。 ここではその使い方の話をします。
オブジェクトの後に.
をつけて、その後にプロパティ名やメソッド名を書いて呼べば、使う事が出来ます。
やってみましょう。
このように、オブジェクトに.
をつけて、その後にsum()
という風に普通の関数を呼ぶように呼べば呼ぶ事が出来ます。
そしてこのsumという関数では、v1とv2というプロパティにアクセスしている事に注目してください。
fun sum() : Int {
return v1+v2
}
このv1とv2は、一見するとTwoIntという、この関数の外側に定義されている変数にアクセスしているだけに見えますが、
class TwoInt(tmp1: Int, tmp2: Int) {
val v1 = tmp1
val v2 = tmp2
fun sum() : Int {
return v1+v2
}
}
実際はti1とti2の結果が違う事から分かるように、 オブジェクトのv1とv2に触っています。 ti1というオブジェクトのv1とv2の値は、ti2というオブジェクトのv1とv2の値と違う訳です。
じゃんけんゲームにクラスを使ってみよう
実際に使ってみないと分からない事も多いので、 無理やりにでも何か使ってみるのが勉強には良いです。
という事で以前に作ったじゃんけんゲームで、 以下のようなクラスを作ってみましょう。
まず、クラスの名前はJankenとします。 これはJanken.ktという別ファイルにします。
作り方はActivityの追加と同じ感じで右クリックで New>Kotlin Class/File を選び、Classを選んでNameの所にJankenと書きます。
そして、以下のようなプロパティを持つとします。
val GOO = 0
val CHOKI = 1
val PAA = 2
そして、ランダムにどれかを返す、ponというメソッドを作るとします。
fun pon() : Int { ... }
さらにこれらの値から、対応するR.drawable.goo
などを返す、toResIdというメソッドも作りましょう。
グーチョキパーを頭文字をとってgcpと書くと、
fun toResId(gcp : Int) : Int {...}
これはgcpがGOOならR.drawable.goo
を、CHOKIならR.drawable.choki
を返す感じです。
せっかくなのでwhenを使うといいかもしれません。
以下解答例も書いておきますが、自力で頑張れる気もするので頑張ってみて欲しい所。 なお以下の解答例はテストしてないので動かないかも(間違ってたら間違ってる場所教えて、直すから)
解答例
class Janken {
val GOO = 0
val CHOKI = 1
val PAA = 2
fun pon() : Int {
return Random.nextInt(0, 3)
}
fun toResId(gcp: Int) : Int {
return when(gcp) {
GOO -> R.drawable.goo
CHOKI -> R.drawable.choki
else -> R.drawable.paa
}
}
}
最後に、MainActivityでこのクラスのインスタンスを作り、それを使うようにします。
val janken = Janken()
このjankenの、janken.pon()
とか、janken.toResId(gcp)
とかを使ってじゃんけんゲームを書いてみてください。