はじめに
この記事はKotlinアドベントカレンダーの参加記事です。
皆さん、Kotlin使っていますか?
コアの設計はJavaに大きな影響を受けたKotlinですが、その言語仕様はJavaと大きく異なる部分が少なくありません。
今回はそんなKotlinの特徴の一つ、エントリポイントについてお話しようと思います。
TL;DR
- Kotlinの
main
メソッドはコンパイル時特殊クラスに突っ込まれる -
main
メソッドをそのままクラスの静的メソッドとして書くことはできない -
companion object
はそれ自体が独立クラスとしてコンパイルされる -
@JvmStatic
でクラスの静的メソッドを登録する
前提知識
- KotlinはJava同様にJVM用Javaバイトコード(.classファイル)にコンパイルされる。
- JavaもKotlinも
main()
というメソッドをエントリポイントとして起動する。 - Javaは全てのメソッドや変数が必ず何かしらのクラスに属している必要がある。無論
main
メソッドも同様 - Kotlinのメソッド・変数はクラスに属している必要がない。特に**
main
メソッドはトップレベルに記述されるのが慣例である**。
ちなみに
もう一つ慣例として、エントリポイントを含むファイルの名称をKotlinは`main`、Javaは`メインクラス名`とするものがあります。今回あまり重要ではありません。fun main(args:Array<String>){
println("Hello Kotlin")
}
public class HelloWorld{
public static void main(String[] args){
System.out.println("Hello Java");
}
}
この3と4、互換性のあるバイトコードにコンパイルされたとき矛盾が生じますね。Kotlinでトップレベルに書かれるmain
メソッドはそのままではJavaに受け入れられません。
これはどういうことでしょう。
Kotlinのmain
メソッドもコンパイル時にクラスに収められる
実はKotlinのmain
メソッドもコンパイル時にクラスの中に放り込まれるんです。
Kotlinソースコードをコンパイルすると、[mainメソッドがあるファイル名の先頭を大文字にした文字列]Kt
という特殊なクラスが作られ、main
メソッドはその静的メソッドとして登録されます。
慣例的にKotlinのmain
メソッドを含むファイルはmain.kt
とされるため、出来るクラス名は**MainKt
**となります。
Kotlinのmain
メソッドをクラスメソッドとして書いてみる
さて、Kotlinをコンパイルすると特殊クラスが作られてJavaバイトコードにしたとき矛盾が発生しない仕組みになっていることはわかりました。
これで枕を高くして寝られる方は帰って大丈夫です。
ここからは、Kotlinのmain
をJavaのそれのようにクラス内に記述できないかを試みていきます。
環境
- Kotlin JVM 1.5.21
- gradle 7.3
- shadowJar 7.1.0
- IntelliJ IDEA 2021.1.1(Community)
書いたコードをshadowJarにて単一jarファイル化したものをコンソールから実行し、jarのエントリポイントmain()
が実行されることをゴールとします。
まずは素直にJavaライクに書く
Javaではmain
メソッドはクラスの静的メソッドです。Kotlinにはstatic
宣言がないので、代わりにcompanion object
としてクラス内にシングルトンを作り、その中に記述したメソッド・プロパティを静的メソッド・プロパティとして使うことになります。
class HelloWorld{
companion object{
fun main(args:Array<String>){
println("Hello Kotlin")
}
}
}
このときmain
はHelloWorld
クラスの静的メソッドになる予定なので、ビルドスクリプトも修正しておきましょう。
//前略
application {
mainClass.set("HelloWorld") //mainメソッドを含むクラスの設定を変更しておく
}
エラー: メイン・メソッドがクラスHelloWorldで見つかりません。次のようにメイン・メソッドを定義してください。
public static void main(String[] args)
HelloWorld
クラスに定義したはずのmain
メソッドが読み込まれません。これはなぜでしょう。
companion objectはコンパイル時別クラス扱いされる
staticの代用として使われるcompanion object
の実態はシングルトンクラス。この中に書かれたメソッドはそれを持つクラスのメソッドではなく、独立した別クラスのメソッド扱いなのです。
もう少し詳しく言うと、Kotlinコンパイル時ソースにcompanion object
を含むクラスが見つかると、MainKt
と同じように、[クラス名]$Companion
というクラスが作られ、クラス内のcompanion object
内のメソッドはこちらに収められるのです。今回であればmain
メソッドはHelloWorld$Companion
クラスのメソッドとして登録されたのです。
JvmStatic
これを解決するために、@JvmStatic
というアノテーションをメソッドに付与します。
こうすることで付与されたメソッドを[クラス名]$Companion
のとは別に、companion object
を抱えるクラスのメソッドとして登録してくれるのです。
class HelloWorld{
companion object{
@JvmStatic
fun main(args:Array<String>){
println("Hello Kotlin")
}
}
}
Hello Kotlin
できました。
感想
KotlinはJavaに比べ構文も設計も洗練されていて大好きなのですが、static
の書き方に限ってはJavaのほうが完結で好きです。
物は試しでmain in classやってみましたが、冗長になる上メリットも(私の調査の範囲では)無いので、やる必要はおそらく皆無でしょう。