Kotlinで標準入力あれこれ

Kotlinでコマンドラインから標準入力をする方法をいくつかまとめたのでご紹介します。

# 準備
まずはKotlinのコンパイラがインストールされている必要があります。
以下からコンパイラのzipをダウンロードし、インストールしてください。
[最新版のコンパイラ](https://github.com/JetBrains/kotlin/releases/latest)

macOSの場合はHomebrewで入れることができます。

```bash
$ brew install kotlin
```

以下のコマンドでバージョンが表示されれば準備完了です。

```bash
$ kotlinc -version
info: kotlinc-jvm 1.1.51 (JRE 1.8.0_152)
```

# コンパイルと実行方法
StandardInput.ktをStandardInput.jarへとコンパイルするコマンドは以下の通りです。

```bash
$ kotlinc StandardInput.kt -include-runtime -d StandardInput.jar
```
生成されたファイルは通常のjarと同様に実行することができます。

```bash
$ java -jar StandardInput.jar
```

それではいよいよ標準入力の方法を紹介していきます。

# main関数の引数に渡す
以下のようなmain関数を作成します。

```kotlin:StandardInput1.kt
fun main(args: Array<String>) {
    for (input in args) {
        println(input)
    }
}
```
引数を与えて実行してみると以下のようになります。

```bash
$ kotlinc StandardInput1.kt -include-runtime -d StandardInput1.jar
$ java -jar StandardInput1.jar a
a
$ java -jar StandardInput1.jar one two three
one
two
three
```
やっていることは単純で、main関数でargsとして受け取った引数を要素ごとに表示しています。
この方法ではシンプルな記述で入力を受け付けることができました。
しかし実行時に引数を渡すため、処理の途中で入力を受け付けることができません。

# readLine関数を使う
この関数はKotlin標準ライブラリの中に含まれています。
そのため何もimportすることなく使用することができます。
ではreadLine関数を用いた例を以下に示します。

```kotlin:StandardInput2.kt
fun main(args: Array<String>) {
    println("> Please input your name.")
    val name = readLine()

    println("> Please input your age.")
    val age = readLine()

    println("> $name is $age years old.")
}
```
上記のファイルをコンパイルし、実行してみると以下のようになります。

```bash
$ kotlinc StandardInput2.kt -include-runtime -d StandardInput2.jar
$ java -jar StandardInput2.jar
> Please input your name.
ki
> Please input your age.
17
> ki is 17 years old.
```

上記の4, 6行目が標準入力の部分となります。
このようにreadLine関数では1行ごとに入力を取得することができました！

## readLine関数の注意点
しかし、readLine関数はnullや型に気をつけて使用する必要があります。
例えば以下のようなコードをコンパイルしてみます。

```kotlin:StandardInput3.kt
fun main(args: Array<String>) {
    println("> Please input your age.")
    val age = readLine()
    
    if (age >= 18) {
      println("> You are an adult.")
    } else {
      println("> You are a child.")
    }
}
```
すると以下のようなエラーが表示されます。

```bash
StandardInput3.kt:5:13: error: operator call corresponds to a dot-qualified call 'age.compareTo(18)' which is not allowed on a nullable receiver 'age'.
StandardInput3.kt:5:16: error: the integer literal does not conform to the expected type String
```
1行目はnullableな型は比較できないよー、2行目はString型とInt型を比較できないよーという旨の内容です。

ここでreadLine関数の内部実装を示します。

```kotlin:readLine()
/**
 * Reads a line of input from the standard input stream.
 *
 * @return the line read or `null` if the input stream is redirected to a file and the end of file has been reached.
 */
public fun readLine(): String? = stdin.readLine()

private val stdin: BufferedReader by lazy { BufferedReader(InputStreamReader(object : InputStream() {
    // 略
}))}
```
readLine関数はJavaのBufferedReaderを内部的に使用し、String?型を返すということが上記からわかります。
そのためreadLine関数を呼ぶとString?型が返ってくるということに留意して使用しなくてはなりません。

# まとめ
Kotlinの標準入力の方法を2つご紹介しました。
1つ目はmain関数の引数に渡す方法、
2つ目はreadLine関数を使う方法でした。

readLine関数はJavaのBufferedReaderを内部的に使用しており、
String?型が返ってくることを想定して入力のnull判定やキャストをする必要があります。

最後に感想ですが、なんだかんだKotlinでよくあるnull判定やキャストをいかに華麗に行うか問題に帰着するんだなぁと思いました。

本記事へのご指摘やアドバイスがございましたら、ぜひ教えて頂けるとうれしいです！