Kotlin - objectとclassキーワードの違い

鼻間違ったクラスを定義するキーワードは classです。 たまに objectキーワードでクラスを定義する場合を見ることができます。 objectでクラスを定義すると、シンクルトン(Singleton)パターンが適用され、オブジェクトが一度だけ生成されるようにします。 Javaではシングルトンパターンを適用するために、かなり多くのコードを記述する必要がしたんです、鼻間違ったでは objectを使用すると、このような形式的なコード(boilerplate)を作成しなくてもされます。 シングルトンとして使用する方法に加えて、objectは匿名のオブジェクトを作成するときにも使用されます。

objectの使用する理由を調べ、それぞれについてサンプルコードを見てみましょう。

シングルトンクラスを定義するためのobjectの例

objectでシングルトンクラスを定義することができます。 次のコードでCarFactoryクラスを定義するときに classがあるべき位置にobjectを入力してくれれば、このクラスはシングルトンで動作するようになります。

object CarFactory {
    val cars = mutableListOf<Car>()

    fun makeCar(horsepowers: Int): Car {
        val car = Car(horsepowers)
        cars.add(car)
        return car
    }
}

class Car(power: Int) {
}

次のコードのように CarFactory.makeCarようメソッドにアクセスしてCarオブジェクトを生成することができます。また、 CarFactory.carsのように直接変数にアクセスすることができます。 CarFactoryオブジェクトはシングルトンで実装がされているので、何度も呼び出してもCarFactoryオブジェクトは一度だけ生成がされます。

val car = CarFactory.makeCar(150)
println(CarFactory.cars.size)

上記のコードを見ると、CarFactory.makeCar()は、staticメソッドを呼び出しているように見えます。 Javaでどのように変換がされるか見ればシングルトンが内部的にどのように実装されるかを理解することができます。

public final class CarFactory {
   private static final List cars;
   public static final CarFactory INSTANCE;

   public final List getCars() {
      return cars;
   }

   public final Car makeCar(int horsepowers) {
      Car car = new Car(horsepowers);
      cars.add(car);
      return car;
   }

   static {
      CarFactory var0 = new CarFactory();
      INSTANCE = var0;
      cars = (List)(new ArrayList());
   }
}

public static final void main(@NotNull String[] args) {
   Intrinsics.checkParameterIsNotNull(args, "args");
   Car car = CarFactory.INSTANCE.makeCar(150);
   int var2 = CarFactory.INSTANCE.getCars().size();
   System.out.println(var2);
}

上記のJavaで変換されたコードを見ると、CarFactoryオブジェクトはINSTANCEというstaticオブジェクトを生成します。 そして、このオブジェクトにアクセスするとき CarFactory.INSTANCEを介してアクセスされます。 INSTANCEはstaticで生成されるため、プログラムがロードされるときに生成されます。だからスレッド安全性(thread-safety)が保証されますが、内部的に共有リソースを使用する場合、スレッドの安全性が保証されていないため、同期(synchronization)コードを作成する必要があります。

シングルトンクラスを定義するためcompanion object例

上記の例では、CarFactoryオブジェクトがCarオブジェクトを生成してくれる実装でした。ここファクトリーパターンと、シングルトンパターンが一緒に適用置く。 Carクラスの中Factoryパターンを定義したいことができます。 Car.makeCarよう呼び出しをすることが直感的に見栄えからです。こういうときは、companion objectとして宣言してくれればされます。

このようにCar中companion objectでFactoryを定義してくれれば Car.makeCar()のように呼び出すことができます。 事実 Car.Factory.makeCar()の呼び出してくれることが明示的に正確な表現ですが、鼻間違っは便宜のためにFactoryを省略することができるようにしてくれました。

class Car(val horsepowers: Int) {
    companion object Factory {
        val cars = mutableListOf<Car>()

        fun makeCar(horsepowers: Int): Car {
            val car = Car(horsepowers)
            cars.add(car)
            return car
        }
    }
}

fun main(args: Array<String>) {
    val car = Car.makeCar(150)
    val car2 = Car.Factory.makeCar(150)
    println(Car.Factory.cars.size)
}

変換されたJavaコードを見れば、Carクラスの中に入れ子になったクラス(nested class)でFactoryクラスが定義されています。 また、 Car.FactoryクラスはFactoryという名前のstaticオブジェクトとして宣言しました。 外部から Car.Factory.makeCarのように使用することができているが、ここでFactoryは、クラス名ではなく、static変数の名前でした。 鼻間違っからmakeCarを二つの方法で呼び出しましたが、Javaでは同じコードで呼び出していることを知ることができます。

public final class Car {
   private final int horsepowers;
   private static final List cars = (List)(new ArrayList());
   public static final Car.Factory Factory = new Car.Factory((DefaultConstructorMarker)null);

   public final int getHorsepowers() {
      return this.horsepowers;
   }

   public Car(int horsepowers) {
      this.horsepowers = horsepowers;
   }

   public static final class Factory {
      @NotNull
      public final List getCars() {
         return Car.cars;
      }

      @NotNull
      public final Car makeCar(int horsepowers) {
         Car car = new Car(horsepowers);
         ((Car.Factory)this).getCars().add(car);
         return car;
      }

      private Factory() {
      }

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

public static final void main(@NotNull String[] args) {
   Intrinsics.checkParameterIsNotNull(args, "args");
   Car car = Car.Factory.makeCar(150);
   Car car2 = Car.Factory.makeCar(150);
   int var3 = Car.Factory.getCars().size();
   System.out.println(var3);
}

objectを匿名オブジェクトとして使用した例

objectは、匿名のオブジェクトを定義する際にも使用されます。匿名オブジェクトは名前がないオブジェクトで、一度だけ使用されて再利用されない場合に使用します。 再利用されないので、気にクラス名をつけてくれないのです。

たとえば、以下のようにVehicleインタフェース、start()メソッドが定義されています。 start()は、Vehicleオブジェクトを引数として渡されます。

interface Vehicle {
    fun drive(): String
}

fun start(vehicle: Vehicle) = println(vehicle.drive())

次のコードでは、start()の引数として渡される object : Vehicle{...}は、匿名オブジェクトです。 この匿名オブジェクトはVehicleインタフェースを継承したクラスをオブジェクトとして生成されたことを意味します。匿名オブジェクトであるため、クラス名はなく、実装部 {...}の中に定義する必要があります。

start(object : Vehicle {
    override fun drive() = "Driving really fast"
})

上記のコードでも、Javaに変換してみましょう。 Javaのも鼻間違ったようVehicleを継承した匿名のオブジェクトを作成しました。 (参考までに、鼻間違っから fun start()はファイルのTop-levelに定義されているため、Javaでstaticメソッドで生成がされました。)

public interface Vehicle {
   @NotNull
   String drive();
}

public final class KotlinKt {
   public static final void start(@NotNull Vehicle vehicle) {
      String var1 = vehicle.drive();
      System.out.println(var1);
   }

   public static final void main(@NotNull String[] args) {
      start((Vehicle)(new Vehicle() {
         @NotNull
         public String drive() {
            return "Driving really fast";
         }
      }));
   }
}

まとめ

objectの使用方法について説明しました。 クラスを定義する際にobjectを使用すると、シングルトンパターンが適用され、objectを使用して、匿名のオブジェクトを作成することもできました。 もしJava開発者であれば、objectコードがどのようにJavaに変換されるか確認することをお勧めします。鼻間違ったコードの理解度を高めることができます。

参考

codechachaCopyright ©2019 codechacha