2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

KotlinAdvent Calendar 2021

Day 3

Kotlinのエントリポイントの話

Last updated at Posted at 2021-12-02

はじめに

この記事はKotlinアドベントカレンダーの参加記事です。

皆さん、Kotlin使っていますか?

コアの設計はJavaに大きな影響を受けたKotlinですが、その言語仕様はJavaと大きく異なる部分が少なくありません。
今回はそんなKotlinの特徴の一つ、エントリポイントについてお話しようと思います。

TL;DR

  • Kotlinのmainメソッドはコンパイル時特殊クラスに突っ込まれる
  • mainメソッドをそのままクラスの静的メソッドとして書くことはできない
  • companion objectはそれ自体が独立クラスとしてコンパイルされる
  • @JvmStaticでクラスの静的メソッドを登録する

前提知識

  1. KotlinはJava同様にJVM用Javaバイトコード(.classファイル)にコンパイルされる。
  • JavaもKotlinもmain()というメソッドをエントリポイントとして起動する。
  • Javaは全てのメソッドや変数が必ず何かしらのクラスに属している必要がある。無論mainメソッドも同様
  • Kotlinのメソッド・変数はクラスに属している必要がない。特に**mainメソッドはトップレベルに記述されるのが慣例である**。
ちなみにもう一つ慣例として、エントリポイントを含むファイルの名称をKotlinは`main`、Javaは`メインクラス名`とするものがあります。今回あまり重要ではありません。
main.kt
fun main(args:Array<String>){
    println("Hello Kotlin")
}
HelloWorld.java
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としてクラス内にシングルトンを作り、その中に記述したメソッド・プロパティを静的メソッド・プロパティとして使うことになります。

main.kt
class HelloWorld{
    companion object{
        fun main(args:Array<String>){
            println("Hello Kotlin")
        }
    }
}

このときmainHelloWorldクラスの静的メソッドになる予定なので、ビルドスクリプトも修正しておきましょう。

build.gradle.kts
//前略
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を抱えるクラスのメソッドとして登録してくれるのです。

main.kt
class HelloWorld{
    companion object{
        @JvmStatic
        fun main(args:Array<String>){
            println("Hello Kotlin")
        }
    }
}
Hello Kotlin

できました。

感想

KotlinはJavaに比べ構文も設計も洗練されていて大好きなのですが、staticの書き方に限ってはJavaのほうが完結で好きです。

物は試しでmain in classやってみましたが、冗長になる上メリットも(私の調査の範囲では)無いので、やる必要はおそらく皆無でしょう。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?