코틀린 클래스 생성자와 기본값
코틀린에서는 클래스의 생성자에 기본값을 설정하는 것이 허용된다. 예를 들어 우리가 일반적인 메모장을 위한 클래스를 만든다고 해보자. 이 메모장에는 제목(title), 부제목(subTitle), 내용(content)이 들어가며 이를 표현하는 Memo 클래스는 다음과 같이 만들 수 있다.
class Memo(val title: String, val subTitle: String = "", val content: String = "")
이렇게 만들어진 Memo 클래스는 다음과 같이 초기화가 가능하다.
fun main() {
val memo = Memo("dummy title")
}
그러면 이 메모 객체는 title 필드에만 dummy title이라는 값이 들어가고 나머지 필드에는 빈 값이 들어간 객체가 된다. 여기까지는 아무 문제가 없다.
Memo 객체를 자바에서 사용할 때의 문제
문제는 바로 자바와 코틀린을 함께 사용할 때 생긴다. 자바 파일에서 위의 Memo 객체를 같은 방식으로 초기화 해보자.
public class Main {
public static void main(String[] args) {
Memo memo = new Memo("dummy title");
}
}
그러면 다음과 같은 오류가 생기는 것을 볼 수 있다.
Expected 3 arguments but found1
세 개의 인자가 입력돼야 하지만 한 개만 입력됨.
왜 이런 문제가 생길까? 바로 코틀린 생성자의 기본 값은 코틀린 코드에서만 사용이 가능하기 때문이다. 때문에, 자바에서는 코틀린의 기본 값을 사용할 수 없다.
그렇다면, 자바에서 생성자의 기본 값을 사용하려면 어떻게 해야할까? 바로 여러 생성자를 만들고, 하나의 생성자가 다른 생성자의 일부 값을 채워 호출하는 방식으로 만들어야 한다. 우리는 이를 생성자 오버로딩(Overloading) 이라고도 부른다. 위의 Memo 클래스와 완전히 같은 역할을 하는 Memo 클래스를 자바를 사용해 만들어보면 다음과 같은 모양이 된다.
public class Memo {
private final String title;
private final String subTitle;
private final String content;
public Memo(String title, String subTitle, String content) {
this.title = title;
this.subTitle = subTitle;
this.content = content;
}
public Memo(String title, String subTitle) {
this(title, subTitle, "");
}
public Memo(String title) {
this(title, "", "");
}
public String getTitle() {
return title;
}
public String getSubTitle() {
return subTitle;
}
public String getContent() {
return content;
}
}
그렇다면, 자바에서 기본값을 사용하기 위해 매번 코틀린의 객체를 위와 같이 바꿔야 할까? 그렇지 않다. 여기서 바로 @JvmOverloads 어노테이션이 등장한다.
@JvmOverloads 사용해 코틀린 클래스의 기본 값을 자바에서 사용할 수 있도록 만들기
위와 같이 복잡한 코드를 만드는 대신 코틀린에서는 같은 역할을 하는 코드를 @JvmOverloads를 사용해 만들 수 있다. 코틀린의 Memo 객체를 자바로 작성된 Memo 객체와 같은 역할을 하도록 만들려면 코틀린의 Memo 객체의 생성자(constructor) 앞에 @JvmOverloads 만 붙이면 된다.
class Memo @JvmOverloads constructor(val title: String, val subTitle: String = "", val content: String = "")
그러면 이 어노테이션은 코틀린 코드가 바이트 코드로 컴파일 될 때 위와 같은 역할을 하도록 만들며, 자바에서도 기본 생성자를 사용할 수 있게 된다.
@JvmOverloads의 한계
코틀린에서는 생성자를 호출 할 때 앞에 파라미터의 이름만 붙이면 생성자에 인자를 넣는 순서를 얼마든지 바꿀 수 있으며, 중간에 있는 인자의 기본 생성자만 사용할 수도 있다.
예를 들어, subTitle의 값만 기본 값을 사용하고 title과 content의 값을 설정하려면 다음과 같이 만들 수 있다.
fun main() {
val memo1 = Memo(
title = "dummy title",
content = "dummy content"
)
val memo2 = Memo(
content = "dummy content",
title = "dummy title"
)
}
하지만, @JvmOverloads를 사용한 상태로 자바에서 Memo 클래스를 사용하면, 중간에 있는 변수를 설정하지 않는 것이 불가능하다. 즉, 자바에서 Memo 객체를 초기화 할 때는 다음 예시와 같이 순차성이 유지돼야 한다.
public class Main {
public static void main(String[] args) {
Memo memo1 = new Memo("dummy title", "dummy subtitle", "dummy content");
Memo memo2 = new Memo("dummy title", "dummy content");
Memo memo3 = new Memo("dummy title");
}
}