今回からはクラスというテーマでお送りしていきます。今回メインとなるのはその始めとして、クラスの作成からプロパティ、コンストラクタの定義方法についてです。
クラスファイルを作成して、クラスを定義する
それではクラスを定義してみますがその前に、今まで使ってきたmain関数を含んだファイルとは別に、クラス定義用のファイルを新たに作成しておきましょう。
IntelliJで新たにファイルを作成するには、画面左側のプロジェクト・ビューを開きます。
表示されていない場合はAlt+1か、左端の「プロジェクト」をクリックしてください。
このプロジェクトフォルダの中で、一番上に表示されているフォルダにクラスなどが収まります。
一番上の「+」をクリックして展開し、その中にある「src」フォルダを右クリック→「新規」→「kotlinファイル/クラス」と進んでクリック。
ファイルの名前を決めます。ここでは「Player」としておきます。
OKをクリックすると、作成したファイルが新しいタブで開くはずです。
とりあえず、この一行だけ入力しておきましょう。
class Player
これだけでPlayerというクラスが使用可能になります。
main関数内でインスタンスを作成してみましょう。
fun main() {
val player = Player()
}
エラーが出なければ成功です。おめでとうございます。
新たなクラスを定義した別ファイルを作成し、それをmain関数内にインスタンスとして実体化することができました。これがクラスを定義して使用する第一歩です。
プロパティを設定する
作成した「Player」というクラスは、一見して「人」であることがイメージできるはずですが、まだその中身がありません。
人というと、どんなプロパティ(属性や特徴、ステータスと言い換えてもかまいません)があるでしょう?
名前、性別、年齢、国籍、職業、髪の色、目の大きさ、背の高さ、腕力、足の速さ、記憶力etc…
数え上げたらキリがありませんが、これらのプロパティによって、人は個人を特定することができます。
ではクラス定義の中に、Playerの個人としての特徴を入力してみましょう。
class Player{
val name = "坂本勇人"
var age = 30
}
プロパティは通常の変数と同じようにval(リードオンリー)やvar(変更可能)キーワードを使って設定します。クラスに属する変数=クラス変数とも呼ばれます。
なお下のように初期値が無い定義は、この方法ではエラーになるので注意してください。
class Player{
val name: String
var age: Int
}
ここで設定したプロパティは、インスタンスから呼び出すことで値を得ることができます。main関数に戻ってプロパティを呼び出してみましょう。
fun main() {
val sakamoto = Player()
println(sakamoto.name+"は"+sakamoto.age+"歳")
}
//坂本勇人は30歳
インスタンスのプロパティにはインスタンス名.プロパティ名でアクセスできます。
プライマリコンストラクタを設定する
上の例では坂本勇人という個人をクラスとして表現していますが、この方法ではPlayerクラス=坂本勇人という個人しか表現できず、汎用性がありません。
クラスを使うことの利点として、同じカテゴリに属する複数のインスタンスをクラスとしてまとめ、再利用性を高めるという点があります。
そこでクラス定義の抽象度を高め、インスタンス生成時に個人を特定するようにしてみましょう。
クラス定義ファイルの記述をこのように変更します。
class Player(val name: String, var age: Int){
val job = "プロ野球選手"
}
このカッコ内をKotlinではプライマリコンストラクタと呼びます。
コンストラクタとは?
constructorを直訳すると、【建設(建築)する者】となります。
プログラミングに当てはめると、クラスを設計図として、その実体(インスタンス)を生成するときに働くメソッドです。
ここで設定している引数のnameやageはその建設の材料となります。
コンストラクタはここで指定された材料を使って、新たなPlayerインスタンスを生成するわけです。
そのため初期値が設定されていない場合、インスタンス生成時にこの材料が与えられないとエラーになります。
コンストラクタの引数は関数のパラメータと同様、イコール(=)を使うことで初期値を設定することも可能です。
class Player(val name: String = "name", var age: Int = 0){
val job = "プロ野球選手"
}
コンストラクタを設定することによって、このクラスは「プロ野球選手」というカテゴリをまとめるクラスとなり、個人の名前や年齢といったプロパティはインスタンス生成時に設定するようになりました。
それでは坂本勇人をプロ野球選手としてインスタンス化してみましょう。
fun main() {
val sakamoto = Player("坂本勇人",30)
println(sakamoto.name+"の職業は"+sakamoto.job)
}
//坂本勇人の職業はプロ野球選手
セカンダリコンストラクタを設定する
野球選手であるからには、打数やヒット数といったプロパティも必要な要素になってきますが、全てのインスタンス生成時に多くの情報を必ず入力しなければならないとなると、それも骨が折れる作業です。
あるプロパティをあくまでオプションとして考えるのであれば、セカンダリコンストラクタを使うのも一つの手と言えます。
ここでは打数、ヒット数をオプションの引数として、セカンダリコンストラクタで初期化してみたいと思います。
class Player(val name: String, var age: Int){
val job = "プロ野球選手"
var batting: Int? = null
var hits: Int? = null
constructor(name: String,
age: Int,
batting: Int,
hits: Int):this(name,age){
this.batting = batting
this.hits = hits
}
}
上のコードでは、まず3行目と4行目でbatting(打数)とhits(ヒット数)という変数を初期化しています。
セカンダリコンストラクタではプライマリコンストラクタのように、引数でval、varキーワードを使用することができないため、あらかじめクラス変数を初期化しておき、その変数にセカンダリコンストラクタの引数を割り当てるという手順をとっています。
セカンダリコンストラクタの引数
次の行から下の部分はセカンダリコンストラクタです。セカンダリコンストラクタは「constructor」に続けて引数を取っています。
constructor(name: String,
age: Int,
batting: Int,
hits: Int):this(name,age){...}
引数の前半2つはプライマリコンストラクタと同じものです。
プライマリコンストラクタはそのクラスのインスタンスを生成する際、セカンダリコンストラクタがあったとしても必ず呼び出されるメソッドです。
そのため、プライマリコンストラクタの引数は省略できません。val、var以外はそのまま記述しましょう。
その後にbattingとhitsという2つのInt型を引数に取っています。こちらはオプションの引数であり、インスタンス生成の際、省略することが可能な引数です。
この2つの引数を省略してインスタンスを生成した場合、プライマリコンストラクタのみが呼び出され、オプションの変数には初期値nullが代入されます。
プライマリコンストラクタの呼び出し
引数の()の後に「:this(name,age)」となっているのがプライマリコンストラクタの呼び出しになっています。この「this」はこの場合、クラス名「Player」と同じ意味になります。
セカンダリコンストラクタを使用する場合にも、プライマリコンストラクタは呼び出されるというのが、これで分かるのではないでしょうか。こちらも省略不可です。
セカンダリコンストラクタはあくまで、プライマリコンストラクタに付け加えられるコンストラクタであるということを覚えておきましょう。
処理内で使用するthis
最後に波括弧{}内を見ていきましょう。こちらはセカンダリコンストラクタの処理内容になります。
this.batting = batting
this.hits = hits
この「this」はインスタンスを表します。main関数内で
val sakamoto = Player(...)
とした場合、変数sakamotoを指すthisであり、つまりはクラス定義内で最初に定義された
var batting: Int? = null
var hits: Int? = null
と同じものです。
右辺の「batting」や「hits」はセカンダリコンストラクタの引数を指しています。ということでこの処理を和訳すると、
セカンダリコンストラクタを使用する場合、その引数battingとhitsで与えられた値を、(プライマリコンストラクタでは初期値nullで初期化される)battingプロパティとhitsプロパティに代入します。
となります。
画像で軽くまとめ
セカンダリコンストラクタについては若干ややこしいので、画像でもまとめておきます。
- 引数にはプライマリの引数も含まれる
- セカンダリのみに設定される引数は、あらかじめクラス内で初期化しておく必要がある
- 引数の後にプライマリを「this()」で呼び出す必要がある
- 処理内の「this」はインスタンスを指す
2種類のコンストラクタでインスタンスを生成する
セカンダリコンストラクタを定義することによって、インスタンスの生成をより柔軟に行えるようになりました。
それを確認するため、同じクラスでありながら別々のコンストラクタを使用した、2種類のインスタンスを生成してみましょう。以下はmain関数内のコードです。
val sakamoto = Player("坂本勇人",30,320,97)
val tsutsugoh = Player("筒香嘉智",27)
println(sakamoto.hits) //97
println(tsutsugoh.hits) //null
今回のように単純なプロパティであれば、プライマリコンストラクタに初期値を定義すれば事足りますが、何らかの処理をコンストラクタの中で行うような場合には、セカンダリコンストラクタを使用するのがベストな選択肢になり得ます。
おさらい
今回はクラス定義の初回として、
- クラスの定義方法
- プロパティ(クラス変数)の定義方法
- プライマリコンストラクタの記述
- セカンダリコンストラクタの記述
についてお話しました。
次回はこのクラスを使用し、メソッドを定義していきます。