今回はクラス第2回ということで、初期化処理とメソッド定義について解説していこうと思います。
プロパティやコンストラクタについては前回の記事でご紹介していますので、よければそちらも合わせてご覧ください。
初期化処理
「処理」はプライマリコンストラクタの管轄外
前回使用したコンストラクタはインスタンス生成時に、引数に取った値をプロパティに代入するというのが主な仕事でした。
class Player(val name: String, var age: Int){
val job = "プロ野球選手"
}
上記のクラスをインスタンス化するとこのようになります。
val sakamoto = Player("坂本勇人",30)
"坂本勇人"と30という2つの値がコンストラクタによって設定され、それにクラスのプロパティ"プロ野球選手"が加わった、「3つの値を持つインスタンス」が生成されます。
しかしプライマリコンストラクタでは、値の代入以外の「処理」を行うことができません。
例えばインスタンスを生成したタイミングでインフォメーションが欲しいとしましょう。
「〇〇インスタンスが生成されました」
このような文字列を表示するなどの処理は、プライマリコンストラクタの管轄外です。
インスタンス生成時に値の代入以外の処理を行わせたい場合は、初期化処理ブロックを記述しましょう。
initで初期化処理
初期化処理を記述するには「init」に波括弧{}を続け、その中で処理を定義します。
class Player(val name: String, var age: Int) {
val job = "プロ野球選手"
init{
println("${name}インスタンスが生成されました")
}
}
main関数内で実際にインスタンスを生成してみると、
val sakamoto = Player("坂本勇人",30)
//坂本勇人インスタンスが生成されました
新たに定義した処理が実行されました。
initブロックの中で定義された処理内容は、そのクラスが構築されるときに実行されます。
初期化処理で引数の妥当性チェック
initブロックを利用することで、例えばコンストラクタに渡される引数をチェックすることも可能になります。
上記のクラス定義ではインスタンス生成時に空文字列、空白文字が許されている第一引数のnameですが、これをinitブロックでチェックするように修正してみましょう。
class Player(val name: String, var age: Int){
val job = "プロ野球選手"
init{
require(name.isNotEmpty()) {"名前を入力してください"}
}
}
4行目がinitの処理内容です。ここではrequire関数を使って、nameの値が空文字列や空白文字であった場合、例外を送出するよう設定しています。
val x = Player("",30)
//...java.lang.IllegalArgumentException: 名前を入力してください
関数: requireとcheck
引数の値をチェックする、あるいは「あるメソッドが呼び出せる状況かどうか」チェックするにはこの2つの関数が便利です。
check(条件式){ラムダ式}
どちらも条件式の結果がfalseのとき、例外を送出します。ラムダ式の中身は例外のメッセージになりますが、省略してもかまいません。
この2種類の関数、違いは送出される例外の種類です。
関数 | 送出される例外 | 意味 |
---|---|---|
require | IllegalArgumentException | 引数が不正な値である例外 |
check | IllegalStateException | 不適切なときにメソッドが呼び出された例外 |
引数のチェックであればrequire関数、その他外部の要因チェックであればcheck関数が適任でしょう。
他にも渡された引数がnullであれば例外を送出するrequireNotNull、checkNotNullといった関数もあります。
ここまでのコード
前回の記事からここまでの作業で、野球選手を表すクラスにプロパティ、2種類のコンストラクタ、初期化処理を実装することができました。作成したクラスがこちらです。
class Player(val name: String, var age: Int){
val job = "プロ野球選手"
var batting: Int? = null
var hits: Int? = null
init{
require(name.isNotEmpty()) {"名前を入力してください"}
}
constructor(name: String,
age: Int,
batting: Int,
hits: Int):this(name,age){
this.batting = batting
this.hits = hits
}
}
メソッド定義
ここからはバッターの打率を算出するメソッドを定義していこうと思います。
打率はヒット数を打数で割れば求めることができるので、計算としては簡単です。こちらがサンプルのメソッドになります。クラス定義内に追加しましょう。
fun calcAvg():String{
val a = requireNotNull(batting)
val b = requireNotNull(hits)
val ans = "%.3f".format(b.toDouble() / a.toDouble())
return "${ans[2]}割${ans[3]}分${ans[4]}厘"
}
メソッドの記述方法は通常の関数と変わりません。クラス定義の内部に記述することで、その関数はクラス(この場合はインスタンス)に紐付けられたメソッドとなります。
定義したメソッドが正しく機能するか試してみましょう。今回は2018年シーズン終了時のデータを使用してインスタンスを生成し、実際にメソッドを使って打率を表示させてみます。
fun main() {
val sakamoto = Player("坂本勇人",30,441,152)
println(sakamoto.calcAvg())
}
//3割4分5厘
以下はこのメソッドの処理内容の解説になります。どれも初歩的ながら使用頻度の高い処理なので、不明な点があれば確認してみてください。
null許容型を扱うrequireNotNull
変数batting(打数)とhits(ヒット数)はクラス定義の中で、どちらもnull許容型として初期化されています。
そのため計算などの処理をする場合、まず値がnullである可能性を潰しておかなければなりません。
ここではrequireNotNull関数を使用しています。関数に渡される引数がnullであれば例外が送出されるため、この関数を通った変数aとbは、その後のコードではnull非許容型として扱うことができます。
答えが少数なら少数で計算
メソッドの3行目では
val ans = "%.3f".format(b.toDouble() / a.toDouble())
となっています。少々ややこしいので、まずは右側のb.toDouble() / a.toDouble()を見てみましょう。
変数aはbatting、bはhitsをnull非許容型にしたものです。当然この時点では441と152という整数が代入されています。
これをそのまま152÷441とするとどうなるでしょう?
println(152 / 441) //0
答えは0です。整数同士の割り算は、答えもまた整数しか出てこないということに注意してください。ちなみに四捨五入ではなく、1未満は切り捨てになります。
小数点以下の答えが欲しい場合、ここに小数点以下の数字を付け加えます。
println(152.0 / 441.0) //0.34467120181405897
サンプルコードではこれと同じことをtoDouble()で実行し、少数同士の計算として成立させています。
formatメソッドの引数に入る数字は上記の答えと同じものです。
String.format()
formatについては次回詳しくお話しますが、ここでも簡単に触れておきます。
もう一度この部分のコードを見てみましょう。分かりやすくするために引数部分は数値に置き換えます。
val ans ="%.3f".format(0.34467)
このコードを和訳してみると、
「小数点第3位までの少数」という規則(%.3f)に従った文字列にフォーマット(変換)したもの
となります。
この規則によって、数値は小数点第3位までに収められ、それより下の桁は四捨五入されて、結果は"0.345"という文字列になります。
ちなみにString.format()と静的メソッドとしても使うことができます。以下のコードは先程のコードと同じ意味です。
val ans =String.format("%.3f",0.34467)
この場合は第一引数に書式指定、第二引数に値を入れてください。
メソッド最後の行ではformatメソッドの結果を、打率として分かりやすいように整形してreturnしています。
おさらい
今回はクラス定義内部の初期化処理とメソッド定義についてお話しました。
- プライマリコンストラクタでは処理を行えないため、処理を行うにはinitを使う
- initを利用すると、プライマリコンストラクタの引数の妥当性チェックもできる
- requireやcheck関数
- メソッド定義の方法
次回はformatメソッドについて、もう少し詳しく見ていきます。