Help us understand the problem. What is going on with this article?

Kotlinのcompanion objectとは

普段から当たり前のように使ってはいたものの、あまりきちんと理解していなかったcompanion objectについてざっと調べた内容をまとめました。

companion objectとは

companion objectはクラス内に作成されるSingletonのことです。

Kotlinでは、classの代わりにobjectキーワードを利用するだけでSingletonが作成できます。objectキーワードはパッケージレベルから使用可能です。
クラス内ではobjectキーワードの前にcompanion修飾子を付与することで、クラスに属するSingletonが作成できます。このcompanion objectは1クラス内に1つだけ宣言可能です。また、オブジェクト名は省略可能で、一般的には付与しないことがほとんどです。

Hoge.kt
class Hoge {

  object A {
    val fizz = "fizz"
    fun foo() { ... }
  }

  companion object {
    val buzz = "buzz"
    fun bar() { ... }
  }

}

クラス内で作成されるobjectcompanion objectに機能(?)的な違いはないと思っています(間違っていたらご指摘ください)。
ただ、companion objectはクラスに属することが明確なので、Kotlin側から利用する際は以下のような簡潔な記述になります。

Main.kt
fun main() {
  // 通常のobjectの場合。
  Hoge.A.fizz
  Hoge.A.foo()

  // companion objectの場合。
  Hoge.buzz
  Hoge.bar()
}

Java側からは「Companion」というSingletonに属するgetter/setter、メソッドとして見えます。
Android StudioでKotlinプラグインをインストールした状態で、Tools > Kotlin > Show Kotlin Bytecodeを選択して表示されるペインでDecompileボタンを押すことによってデコンパイルされたコードを確認することができます。

Hoge.decompiled.java
// @Metadataなどは省略しています。 
public final class Hoge {
  @NotNull
  // privateなので、Companionインスタンスのgetterを介してアクセス可能。
  private static final String buzz = "buzz";
  public static final Hoge.Companion Companion = new Hoge.Companion((DefaultConstructorMarker)null);

  public static final class Companion {
    // Companionクラス内に宣言されるので、Companionインスタンスを介してアクセス可能。
    public final void bar() {
      ...
    }

    @NotNull
    public final String getBuzz() {
      return Hoge.buzz;
    }

    private Companion() {
    }

    // $FF: synthetic method
    public Companion(DefaultConstructorMarker $constructor_marker) {
      this();
    }
  }
}

したがって、Java側から利用する際は以下のような記述になります。

Main.java
public class Main {
  public static void main(String[] args) {
    Hoge.A.getFizz()
    Hoge.A.foo()

    Hoge.Companion.getBuzz()
    Hoge.Companion.bar()
  }
}

Kotlinにstatic修飾子がない理由

Kotlinにはstatic修飾子がないので、companion objectはstaticなフィールドやメソッドが必要な際の代替手段として利用されることがほとんどだと思います。

2013年のものですが、「“Static constants” in Kotlin」という記事で言語デザイナーのAndrey BreslavさんがKotlinにおけるstaticに対する考えについて言及していました。

Kotlin is designed so that there’s no such thing as a “static member” in a class.
If you have a function in a class, it can only be called on instances of this class.
If you need something that is not attached to an instance of any class, you define it in a package, outside any class (we call it package-level functions):

But sometimes you need static constants in your class: for example, to comply with requirements of some framework or to use serialization.
How do you do this in Kotlin? There are two things in Kotlin that resemble Java’s statics: aforementioned package-level functions and class objects.

内容的には

  • Javaにおけるstaticな定数(final修飾子の付与されたフィールド)は、Kotlinではパッケージレベルに宣言するかclass objectを利用すればよい
  • Javaにおけるstaticなメソッドは、Kotlinではパッケージレベルに宣言すればよい

このため、Kotlinにおいてstaticは不要であるといった意味だと理解しています。

上記で登場するclass objectcompanion objectのことを意味しています。
私はこの当時まだKotlinを利用したことがなかったので知りませんでしたが、M11で名称やコンセプトに関して変更があったことが公式ブログに記載されていました。

@JvmField/@JvmStaticアノテーション、const修飾子

冒頭で、companion objectがJava側からどのように見えるかについて以下のように書きました。

Java側からは「Companion」というSingletonに属するgetter/setter、メソッドとして見えます。

デコンパイルした先ほどのコードを見ても分かる通り、companion objectも内部的に「Companion」というSingletonを生成しています。つまり、companion object内のプロパティやメソッドは厳密にはstaticではありません。

これらをJava側からstaticなフィールド、メソッドとして扱えるようにする方法が提供されています。それが@JvmField/@JvmStaticアノテーションです。
使い方は以下のようにcompanion object内のプロパティとメソッドに各アノテーションを付与するだけです。

Hoge.kt
class Hoge {

  companion object {
    @JvmField
    val buzz = "buzz"
    @JvmStatic
    fun bar() { ... }
  }

}

その結果、以下のような見え方になります。

Hoge.decompiled.java
// @Metadataなどは省略しています。 
public final class Hoge {
  @JvmField
  @NotNull
  // Companionクラス外にpublicかつstaticで宣言されるので、直接アクセス可能。
  public static final String fizz = "fizz";
  public static final Hoge.Companion Companion = new Hoge.Companion((DefaultConstructorMarker)null);


  @JvmStatic
  // Companionクラス外にpublicかつstaticで宣言されるので、直接アクセス可能。
  public static final void bar() {
    Companion.bar();
  }

  public static final class Companion {
    @JvmStatic
    public final void bar() {
      ...
    }

    private Companion() {
    }

    // $FF: synthetic method
    public Companion(DefaultConstructorMarker $constructor_marker) {
      this();
    }
  }
}

したがって、Java側から利用する際もKotlin側から利用する際と同じように記述可能となります。

Main.java
public class Main {
  public static void main(String[] args) {
    Hoge.buzz
    Hoge.bar()
  }
}

Kotlinのみを利用する場合はこれらアノテーションは必須ではないのですが、このままではimmutableなプロパティはgetterを介して扱うことになります。

Kotlinではパッケージレベルもしくはcompanion object内のimmutableなプロパティにconst修飾子を付与することによって、コンパイル時定数として利用可能になります。

利用する際の記述方法はコンパイル時定数とimmutableなプロパティで同じですが、コンパイル時定数はgetterを介さず直接アクセスできるのでメソッドの数が1つ減るというメリットがあります。
ただし、const修飾子はプリミティブ型もしくはString型で初期化する場合のみ利用可能です
デメリットは特にないので、利用可能な場合はconst修飾子を付ける方がよさそうです。

Javaにおけるstaticをパッケージレベルに宣言する or companion objectを利用するのどちらがいいかについてはよく分かっていないので、そのあたりは別途調べたいと思います。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした