KotlinにもJavaと同じように定型文としてこのような記述がありました。
fun main(args: Array<String>) {
}
Kotlin1.3以降ではこの()内の記述が不要になっていますが、これはつまり何だったのか?少し掘り下げて考えてみましょう。
今回はターミナルからKotlinファイルをコンパイル、実行する場面があります。WSLのUbuntuへのKotlin、Javaのインストールは前回取り上げていますので、よければそちらもご覧ください。
またエディタはIntelliJ IDEAを使用しています。JVM系の言語を書くには環境構築しやすくオススメです。インストールはこちらを参考にしていただけると簡単にできると思います。
コードを逆コンパイルし、コンパイル作業の裏側を覗いてみる
Kotlinではコードを実行したとき、最初にmain関数を探して走らせます。これはJavaも同じです。ちなみにJavaではこのようにクラス宣言もしなければなりません。
public class Main {
public static void main(String[] args) {
System.out.println("Java");
}
}
Kotlinではクラス宣言は不要です。ただKotlinもJVM上で走る言語である以上、開発者が認識していないところでコードが変換されています。
一度その裏側を見てみましょう。Kotlinコンパイラが裏で何をしているのかが垣間見えると思います。
IntelliJ IDEAでバイトコードを逆コンパイル
それではKotlinファイルをIntelliJで開きます。コードは正しければ何でもかまいませんが、例として文字列を表示させるコードで見ていきましょう。
fun main(args: Array<String>) {
println("Kotlin")
}
このコードを表示させてメニューの「ツール」→「Kotlin」→「Kotlinバイトコードの表示」と選択します。
エディタの右側にバイトコードが表示されます。ここの「逆コンパイル」をクリックすると、
下の画像のようにJavaに逆コンパイルされたコードが表示されます。
この画面下側、再生ボタンの右に書かれているコードはまさしくJavaのコードです。
Kotlinのコードに記述されているのはmain関数とprintln("Kotlin")という命令だけですが、こちらではStartKtというクラス名がファイル名を元に生成され、Javaの文法に沿う形に整形されています。
さらに"Kotlin"はString型として明示的に型指定されて変数に代入されています。これが型推論の裏側です。
main(args :Array<String>)とは?
Javaを知っている方からすれば簡単に推測できると思いますが、Kotlinのmain関数の引数はコマンドライン引数です。
これについてはJavaの文法を用いて一度記事にしてますが、今回はKotlinでこのコマンドライン引数を使ったコードを記述してみたいと思います。
まずこの中で「args」は引数を代入する変数です。実際何でもかまいません。意味は英語のarguments(引数)の略ですので、少しだけコードを変更してみましょう。問題無く動くのが確認できるはずです。
fun main(arguments: Array<String>) {
println("Kotlin")
}
//Kotlin
その次に続くのは型指定です。ここではString型が入った配列を指定しています。
配列というと["英語","数学","現代文"]や[1,2,3]のように複数の値が入ったコンテナ型ですが、ここでは深くは掘り下げません。今回の記事ではこれを実際どう渡してどう使うのか?ということにフォーカスしていきます。
ArrayIndexOutOfBoundsException
以下はコマンドライン引数を使うコードです。ただし、このまま通常の方法で実行してもエラーになります。
fun main(args: Array<String>) {
println("1時間目は${args[0]}")
println("2時間目は${args[1]}")
println("3時間目は${args[2]}")
}
//Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
この例外エラーは他でも度々お目にかかるので、この機会に少し説明しておきます。
ArrayIndexOutOfBoundsException: Index 0 out of bounds for length 0
配列のインデックスが範囲を超えている例外: インデックス0は長さ0の配列の範囲を超えている
この場合コマンドライン引数が渡されていないため、配列の中身はありません。
配列は要素をargs[1]のようにインデックス番号(配列に格納されている順番)を0始まりで指定します(args[1]であれば配列の中で2番目の要素を指定しています)が、配列に無いインデックス番号が指定されたとき、このエラーが出ます。
要するに[1,2,3]のような3つの要素しかない配列で、その要素を指定するのであれば、args[0]からargs[2]までしか指定できないということです。
コマンドライン引数を渡せるのは実行時
このエラーを回避し、コマンドライン引数をこのコードに渡すには、まずコンパイルをしなければなりません。
kotlinc Start.kt
このコマンドをターミナルに打ち込むことによって、StartKt.classファイルが作られます。今回はこれをそのままKotlinで実行しましょう。
コマンドライン引数はこの実行時に渡すことができます。ターミナルに次の1行を入力してみると、
kotlin StartKt 英語 数学 現代文
# 1時間目は英語
# 2時間目は数学
# 3時間目は現代文
このようにコマンドライン引数はKotlin実行コマンドの後に、スペースを空けて複数渡すことができます。これがmain関数の引数が配列であった理由です。
String型だからといってターミナル上ではダブルクオート("")は使わないようにしてください。
argsの記述が不要になった理由
このようにコマンドライン引数を使うのであれば、相変わらずこのargsは記述しなければなりません。しかし、コマンドライン引数を使うコードというのは一般的に、そこまで頻繁に作るものでもありません。
ここにも「必ずしも必要無いものの記述は、できる限り抑えたい」というKotlinの思想が感じられます。
「毎回使うもんでもないし、まぁ使わないなら無くてもいいよね」ということで、Kotlin1.3以降ではこのmain関数の引数は「必要であれば記述する」ということになっています。
それをどうやって実現しているのか。これもKotlinのバイトコードを逆コンパイルしてみると見えてきます。
逆コンパイルする方法は前述の通りですので、ここでは結果だけ表示しておきましょう。
ザックリ言ってしまうとこのように、KotlinコードそのままをJavaの正式なコードでラップした(包み込んだ)構造になっています。Kotlinにコマンドライン引数を明示しなくても、KotlinコンパイラはちゃんとJavaでも実行できるコードに直してくれるわけです。
これでコマンドライン引数を使わない場合、僕たちは非常に簡潔に「main(){}」と心置きなく書けるようになったということで、めでたし、めでたし。
おさらい
今回は
- Kotlinのバイトコードを逆コンパイルし、Javaコードに変換されるとどうなるかを見る方法
- main関数の記述が何を意味するのか
- ArrayIndexOutOfBoundsException
- 現在ではmain関数の引数の記述が不要になった理由
についてお話しました。
次回はまたKotlinの制御構文に戻ります。エレガントなswitch文、whenについて。