普段から当たり前のように使ってはいたものの、あまりきちんと理解していなかったcompanion object
についてざっと調べた内容をまとめました。
companion object
とは
companion object
はクラス内に作成されるSingletonのことです。
companion object
の宣言方法
Kotlinでは、class
の代わりに object
キーワードを使用するだけでSingletonが作成できます。object
キーワードはパッケージレベルから使用可能です。
詳しくは公式ドキュメントをご参照ください。
クラス内では、object
キーワードの前にcompanion
修飾子を付与することでクラスに属するSingletonが作成できます。これをcompanion object
と呼びます。
companion object
は1クラス内に1つだけ宣言可能です。また、オブジェクト名は省略可能です。
なお、companion
修飾子を付与しないobject
は1クラス内に複数宣言可能です。
class Hoge {
object A {
val fizz = "fizz"
fun foo() { ... }
}
companion object {
val buzz = "buzz"
fun bar() { ... }
}
}
クラス内で作成されるcompanion object
とobject
に機能的な違いはないと思っています(間違っていたらご指摘ください)。
companion object
の使用方法
Kotlinから使用するとき
companion object
はクラスに属することが明白なので、Kotlin側から使用するときは以下のような簡潔な記述になります。
fun main() {
// objectの場合。
val fizz = Hoge.A.fizz
Hoge.A.foo()
// companion objectの場合。
val buzz = Hoge.buzz
Hoge.bar()
}
Javaから使用するとき
companion object
内に宣言したプロパティ・メソッドは、Java側からは「Companion」というSingletonに属するフィールドのgetter/setter・メソッドとして見えます。
また、object
内に宣言したプロパティ・メソッドは、Java側からは「INSTANCE」というSingletonに属するフィールドのgetter/setter・メソッドとして見えます。
したがって、Java側から使用するときは以下のような記述になります。
public class Main {
public static void main(String[] args) {
// objectの場合。
String fizz = Hoge.A.INSTANCE.getFizz();
Hoge.A.INSTANCE.foo();
// companion objectの場合。
String buzz = Hoge.Companion.getBuzz();
Hoge.Companion.bar();
}
}
Android Studioでは、Kotlinプラグインをインストールした状態でTools
> Kotlin
> Show Kotlin Bytecode
を選択して表示されるペインでDecompile
ボタンを押すことによって、デコンパイルされたコードを確認することができます。
// @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 A {
@NotNull
// privateなので、INSTANCEインスタンスのgetterを介してアクセス可能。
private static final String fizz = "fizz";
public static final Hoge.A INSTANCE;
// Aクラス内に宣言されるので、INSTANCEインスタンスを介してアクセス可能。
@NotNull
public final String getFizz() {
return fizz;
}
public final void foo() {
...
}
private A() {
}
static {
Hoge.A var0 = new Hoge.A();
INSTANCE = var0;
fizz = "fizz";
}
}
public static final class Companion {
// Hogeクラス内に宣言されるので、Companionインスタンスを介してアクセス可能。
@NotNull
public final String getBuzz() {
return Hoge.buzz;
}
public final void bar() {
...
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
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 object
はcompanion object
のことを意味しています。
私はこの当時まだKotlinを使用したことがなかったので知りませんでしたが、M11で名称やコンセプトに関して変更があったことが公式ブログに記載されていました。
companion object
のプロパティ・メソッドにJava側から直接アクセスする方法
@JvmField
/@JvmStatic
アノテーション
companion object
がJava側からどのように見えるかについて以下のように書きました。
companion object
内に宣言したプロパティ・メソッドは、Java側からは「Companion」というSingletonに属するフィールドのgetter/setter・メソッドとして見えます。
これらをJava側からstaticなフィールド・メソッドとして扱えるようにする方法が提供されています。それが@JvmField
/@JvmStatic
アノテーションです。
詳しくは公式ドキュメントをご参照ください。
使い方は以下のようにcompanion object
内のプロパティとメソッドに各アノテーションを付与するだけです。
class Hoge {
companion object {
@JvmField
val buzz = "buzz"
@JvmStatic
fun bar() { ... }
}
}
その結果、以下のような見え方になります。
// @Metadataなどは省略しています。
public final class Hoge {
@JvmField
@NotNull
// Companionクラス外にpublicかつstaticで宣言されるので、直接アクセス可能。
public static final String buzz = "buzz";
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側から使用するときと同じように記述可能となります。
public class Main {
public static void main(String[] args) {
String buzz = Hoge.buzz;
Hoge.bar();
}
}