[Kotlin] 標準入力の値をチェックする – 数値をどう扱うのか?

こちらではKotlinの標準入力を使ってユーザーの入力を受け付ける対話型プログラムを作ります。

特に標準入力から数値を受け取ってそれを処理する場合において、考えられるエラーを極力排除できるよう、入力された値をチェックする方法をまとめていこうと思います。

標準入力を受け付ける方法

ユーザーからの入力を標準入力として受け付けるには2つの方法があります。1つはコマンドライン引数で受け取る、もう1つがコード内の関数で受け取る方法です。

このうちコマンドライン引数については別記事で説明してますので、気になる方は一度ご覧ください。

[Kotlin]バイトコードの逆コンパイルでargs: Array<String>を覗き見る
KotlinにもJavaと同じように定型文としてこのような記述がありました。 f...

今回は関数によって標準入力を受け取り、それを処理していきます。

readLine関数で標準入力

では簡単な標準入力のコードを書いていきましょう。

fun main(){
    print("入力してください:")
    val input = readLine()
    println(input)
}

この中で標準入力を受け付けているのはreadLine()です。readLine関数は標準入力された文字列を受け取り、それを戻り値として返します。

実行すると下の画面のようにまず「入力してください:」という文字が出力されます。ここに何らかの文字を入力すると、

Kotlinからオウム返しの返答が返ってきます。

readLineの戻り値は?

これだけで「ユーザーからの標準入力を受け取る」ということは実現できます。

しかしいつまでもオウム返しでは面白くないので、今度は何かしらの数値をユーザー側から受け取り、それに5をプラスしたものを返してもらうことにしましょう。

fun main(){
    print("入力してください:")     //10と入力
    val input = readLine()
    println(input+5)
}
//105

おっと、これはコードとしては正解ですが、計算としては間違いです。しかしここでKotlinを責めてはいけません。

readLine関数の戻り値は文字列でした。普通に文字列同士をプラスするとこうなるのは当然。

正しく計算するにはこの入力をまず数値に変換しなければなりません。

型の変換(キャスト)に関しては別記事で詳しく説明しますが、ここではとりあえず文字列を整数に変換するにはtoIntというメソッドを使うということだけご紹介しておきます。

それでは入力を数値に変換して試してみましょう。(ここからはmain関数は省いて記述していきます。)

print("入力してください:")
val input = readLine().toInt()
println(input+5)

//エラー:(3, 27) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a nullable receiver of type String?

しかしこのキャストはエラーになります。このエラーは「null許容型(nullable型)に対して通常の方法ではメソッドは使えない」ということです。

readLine関数の戻り値はnull許容型(String?)であるということは、対話型プログラムを作成するのであれば必ず押さえておきましょう。

Nullである可能性がある限りメソッドは呼び出せない

ではこのtoInt()の呼び出しを変更し、Kotlinのエラーにあった通りに?.(セーフコール)演算子で呼び出してみます。

print("入力してください:")
val input = readLine()?.toInt()
println(input+5)

//エラー:(4, 18) Kotlin: Operator call corresponds to a dot-qualified call 'input.plus(5)' which is not allowed on a nullable receiver 'input'.

これでもまだ頑なにKotlinはコンパイルエラーを返します。

この?.演算子では変数inputがnullである可能性が捨てきれず、それによって今度は計算式が通らなくなるからです。

エラー文を見ると算術演算子の+が、実際の処理では.plus(5)とメソッドの呼び出しになっていることが分かると思います。

val input = null?.toInt()
println(input)
//null

nullに対してのセーフコールの結果はnullにしかなりません

つまりなんとかして、この変数inputがnullになり得る可能性を完全に潰してしまわなければ、この後の「+5」を処理できないということになります。

ではエルビス演算子を使ってnullの可能性を潰してしまいましょう。

print("入力してください:")               //10と入力
val input = readLine()?.toInt()?:0
println(input+5)

//15

上のコードでは変数inputがnullであった場合、0を返しています。これでやっと+5される時点でinputがnullである可能性が無くなり、計算結果が正しく表示されました。

まだ起こり得る問題

しかしこのコード、残念ながらプログラムとしては失格です。なぜでしょう?

試しに「入力してください:」が表示された時点で即エンターキーを押すと、

またエラー。しかもこれは実行時エラーです。

コンパイル時エラーはともかく、実行時エラーでプログラムが異常終了することは、なんとかして回避しておかなければなりません。

ここではエラー文の1行目に注目してみましょう。

//NumberFormatException: For input string: ""

数値の書式例外 : 入力されたものはString型の""である (ので、整数に変換できない)

nullチェックだけでは不十分

ユーザーが何の文字も入力せずにエンターキーを押して入力を終了すると、readLine関数の戻り値は""になります。

これは空の文字列であってnullではありません。よって先ほど記述したnullチェックに引っかかることは無く、単純にtoIntできないというエラーが起こります。

nullではないけれど不正な値が入る可能性がまだあるということです。

入力された値をチェックする方法

ではこの標準入力された値をもっと厳しくチェックするにはどうすればいいのか?

この場合、代表的な対策として挙げられるのは、

  1. 正規表現を使ってがっつりチェックする
  2. try-catch構文を使って例外エラーを補足し、不正終了を防止する
  3. チェック用メソッド(関数)を使う

このうち1と2は、本格的なプログラムになってくれば正しい回答です。

しかし、正直この程度のプログラムでは大げさかもしれません。ここは便利なメソッドを使ってやってみましょう。

nullと空文字列をチェックする – isNullOrEmpty

まずご紹介するのは、与えられた値がnullか空文字列であればtrueを返す、isNullOrEmptyです。

nullであるかもしれないことが前提のメソッドなので、null許容型からでも通常の呼び出しが可能になっています。

var check :String? = ""
println(check.isNullOrEmpty())
//true

空文字列が補足されてtrueが返りました。同じようにnullでもtrueが返ります。

null、空文字列、スペースをチェックする – isNullOrBlank

上でご紹介したisNullOrEmptyの条件に加え、スペースのみしか入っていない文字列も検出するのであれば、こちらのisNullOrBlankを使いましょう。

var check :String? = "   "
println(check.isNullOrEmpty())
//false

isNullOrEmptyではfalseが返ってくるスペースのみの文字列が、

var check :String? = "   "
println(check.isNullOrBlank())
//true

isNullOrBlankメソッドではtrueと判定されます。

このようなメソッドを使って入力文字列をチェックすれば、値がnullであったり空文字列、スペースのみであった場合でも、エラーでプログラムが異常終了することは避けられます。

以下はnull、空文字列、スペースのみの文字列に対応させた標準入力プログラムのコードです。

print("入力してください:")        //エンターキーを押す
val input = readLine()
if(!input.isNullOrBlank()){   //結果のBoolean型を!(NOT)で反転させる
    println(input.toInt()*3)
}else{
    println("数値を入力してください")
}
//数値を入力してください

ちなみにif(!input…)のようにエクスクラメーションを付けているのは、メソッドの結果のBoolean型を反転させ、正規の入力があった場合の処理をifブロックに持ってくるためです。

やっと正常終了することができました。

toIntを試してInt型かnullを返す – toIntOrNull

上の2つとは若干毛色が違いますが、今回のようなプログラムの場合にはこのメソッドが最も簡潔に問題を解決してくれそうなので、ここでご紹介しておきます。

ユーザーからの数値の入力を受け付ける場合、懸念すべきはnullや空文字列、スペースのみの文字列だけではありません。

toIntできないアルファベットなどの文字が入力された場合もエラーになります。その場合、どうやって対処するのが簡単でしょうか。

そんなとき使えるのがこのtoIntOrNullメソッドです。

文字通りですが、toIntができなかった場合、nullを返します。ここではwhen式で入力された値を3倍するコードを書いてみましょう。

print("入力してください:")            //58
val input = readLine()
val ans = when(input?.toIntOrNull()){    //Int型にキャストするか、nullにする
    is Int->input.toInt()*3          //その結果の型がIntであれば3倍する
    else->"数値を入力してください"        //そうでなければ(nullであれば)メッセージを表示する
}
println(ans)

//174

こちらの関数はnull許容型そのままでは呼び出せないので、セーフコール演算子で呼び出しています。

関数の結果はInt型かnullかのどちらかです。そこでisキーワードを使ってその型を判定し、Int型かそれ以外かで処理を分けています。

このコードそのままでは警告が出ますが、変数ansの型が結果によって違うという警告なので、ansに対して関数を使ったりしないのであれば今は無視してもかまいません。

色々と入力を変えて試してみてください。

おさらい

いかがだったでしょうか?今回は標準入力を使ったプログラムを作りながら、起こり得るエラーを回避する方法についてお話しました。

  • readLine関数の戻り値はnull許容型である
  • String型をInt型に変換するtoIntメソッド
  • null+空文字列をチェックするメソッド
  • null+空文字列+空白文字列をチェックするメソッド
  • 数値に変換するtoIntをtryするメソッド

以上のポイントですが、大事なのは「ユーザーの入力を受け付けるのであれば多種多様な入力に対応する必要がある」ということです。

次回からは関数について取り上げていこうと思います。

タイトルとURLをコピーしました