[Kotlin]バイトコードの逆コンパイルでargs: Array<String>を覗き見る

KotlinにもJavaと同じように定型文としてこのような記述がありました。

fun main(args: Array<String>) {
}

Kotlin1.3以降ではこの()内の記述が不要になっていますが、これはつまり何だったのか?少し掘り下げて考えてみましょう。

今回はターミナルからKotlinファイルをコンパイル、実行する場面があります。WSLのUbuntuへのKotlin、Javaのインストールは前回取り上げていますので、よければそちらもご覧ください。

JavaもKotlinもWSLのUbuntuで動かす! SDKMANでインストール
今回はWSLを使ってインストールしたUbuntuにKotlinとJavaをインス...

またエディタはIntelliJ IDEAを使用しています。JVM系の言語を書くには環境構築しやすくオススメです。インストールはこちらを参考にしていただけると簡単にできると思います。

[Kotlin] 10分で環境構築!IntelliJ IDEAとJDKをインストール (Windows)
KotlinはJavaで書いていたものをより簡潔に書けるモダンな文法を持ち、それ...

コードを逆コンパイルし、コンパイル作業の裏側を覗いてみる

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について。

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