코틀린에서 클래스를 정의하는 키워드는 class인데, object로 클래스를 정의하는 경우도 있다.
2가지 경우에서인데,
싱글톤
익명 클래스 객체 생성
먼저 싱글톤에 대해 알아보자면, 인스턴스를 단 하나만 생성하는 방법이다.
자바에서는 static을 이용하여 단 하나의 인스턴스를 만들어낸다. 이유는 메모리 낭비를 방지하는 데 있다.
익명 클래스 객체를 생성하는 이유는 한번만 사용되고 재사용되지 않을 때 사용한다.
코틀린으로 싱글톤 클래스를 정의하면 아래와 같다.
object CarFactory {
val cars = mutableListOf<Car>()
fun makeCar(horsepowers: Int): Car {
val car = Car(horsepowers)
cars.add(car)
return car
}
}
class Car(power: Int) {
}
val car = CarFactory.makeCar(150)
println(CarFactory.cars.size)
자바로 변환을 해보면 아래와 같다.
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);
}
위의 자바로 변환된 코드를 보면 CarFactory 객체는 INSTANCE라는 static 객체를 생성합니다. 그리고 이 객체에 접근할 때 CarFactory.INSTANCE를 통해서 접근하게 됩니다. INSTANCE는 static으로 생성되기 때문에 프로그램이 로딩될 때 생성됩니다. 그래서 쓰레드 안전성(thread-safety)이 보장됩니다만, 내부적으로 공유자원을 사용하는 경우 쓰레드 안전성이 보장되지 않기 때문에 동기화(synchronization) 코드를 작성해야 합니다.
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)
}
변환된 자바 코드를 보시면 Car 클래스 안에 중첩클래스(nested class)로 Factory 클래스가 정의되어있습니다. 또한 Car.Factory 클래스는 Factory라는 이름의 static 객체로 선언하였습니다. 외부에서 Car.Factory.makeCar처럼 사용할 수 있는데 여기서 Factory는 클래스 이름이 아니라 static 변수의 이름이었습니다. 코틀린에서 makeCar를 두가지 방식으로 호출했는데요, 자바에서는 동일한 코드로 호출한다는 것을 알 수 있습니다.
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는 익명객체를 정의할 때도 사용됩니다. 익명객체는 이름이 없는 객체로, 한번만 사용되고 재사용되지 않을 때 사용합니다. 재사용되지 않기 때문에 귀찮게 클래스 이름을 지어주지 않는 것이죠.
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"
})
위 코드도 자바로 변환해서 살펴보겠습니다. 자바도 코틀린처럼 Vehicle를 상속한 익명객체를 만들었습니다. (참고로, 코틀린에서 fun start()는 파일의 Top-level에 정의되었기 때문에 자바에서 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";
}
}));
}
}