자바 코드를 코틀린에서 사용하기
코틀린은 JVM을 주 대상으로 설계됐기 때문에 자바 코드를 코틀린에서 쉽게 사용할 수 있습니다.
자바 메서드와 필드
Unit과 void
자바에서 void는 코틀린에서 Unit으로 변환되서 사용합니다.
연산자 관습
Map.get()과 같은 몇몇 자바 메서드는 코틀린에서 연산자 관습을 만족합니다. 자바 메서드에는 operator가 붙어있지 않지만 코틀린에서는 연산자처럼 사용할 수 있습니다. 그래서 List나 Map의 get을 코틀린에서는 []로 접근 할 수 있습니다.
합성 프로퍼티
자바에는 합성 프로퍼티가 없고 getter와 setter를 사용하는 일이 많습니다. 이로 인해 코틀린 컴파일러는 자바의 getter와 setter를 일반적인 프로퍼티처럼 사용할 수 있게 변형 해줍니다.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
위와 같은 자바 클래스가 있다고 가정하고 아래 코틀린 코드에서 어떻게 사용하는지 확인해봅니다.
fun main() {
val person = Person("석준", 31)
person.name = "Dino"
person.age = 32
println("${person.name}, ${person.age}") // Dino, 32
}
이렇게 자바에서 getter와 setter를 코틀린에서 일반 프로퍼티처럼 접근해서 사용 할 수 있습니다.
플랫폼 타입
자바가 널이 될 수 있는 타입과 그렇지 않는 타입을 구분하지 않기 때문에 일반적으로 코틀린 컴파일러는 자바 코드에서 비롯된 객체가 널인지 여부에 대해 아무런 가정도 할 수 없습니다. 코틀린에서 자바 코드로부터 비롯된 객체는 플랫폼 타입(platform type)이라는 특별한 타입에 속합니다. 플랫폼 타입은 널이 될 수 있는 타입이기도 하고 널이 될 수 없는 타입이기도 하며, 이런 타입에 대한 타입 안전성 보증은 기본적으로 자바와 동일합니다. 플랫폼 타입은 NPE(NullPointerException)가 발생할 수도 있습니다.
fun main() {
val person = Person(null, 25)
println(person.name.length) // NPE
}
널 가능성 Annotation
자바 세계에서 널 안전성을 보장하는 방법은 @NotNull annotation을 사용하는 것 입니다.
위 Person예제에서 name에 @NotNull을 넣어주게 되면 name은 코틀린에서 nonnull 타입으로 사용할 수 있습니다.
import org.jetbrains.annotations.NotNull;
public class Person {
@NotNull
private String name;
private int age;
public Person(@NotNull String name, int age) {
this.name = name;
this.age = age;
}
@NotNull
public String getName() {
return name;
}
public void setName(@NotNull String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
자바/코틀린 타입 매핑
int, boolean같은 원시타입이나 List, Map같은 컬렉션타입이 코틀린에서 맵핑되서 사용할 수 있습니다.
byte/Byte | Byte |
short/Short | Short |
int/Integer | Int |
long/Long | Long |
char/Character | Char |
float/Float | Float |
double/Double | Double |
제네릭 타입의 매핑은 양 언어 제네릭 구문의 차이 때문에 단순하지만은 않은 변환이 필요합니다.
- 자바의 extends 와일드카드는 코틀린 공변 프로젝션으로 변환됩니다. TreeNode<? extends Person>은 TreeNode<out Person>으로 변경됩니다.
- 자바의 super 와일드카드는 코틀린 불공변 프로젝션으로 변환됩니다. TreeNode<? super Person>은 TreeNode<in Person>으로 변경됩니다.
- 자바의 로우 타입(raw type)은 코틀린 스타 프로젝션으로 바뀐다. TreeNode는 TreeNode<*>으로 변경됩니다.
단일 추상 메서드 인터페이스(Single Abstract Method)
추상 메서드가 하나뿐인 자바 인터페이스가 있다면 이 인터페이스는 기본적으로 코틀린 함수 타입처럼 동작합니다.
자동으로 람다와 적절한 SAM 타입 인스턴스 사이의 변환을 지원해주는 자바8+와 비슷합니다.
public interface Runnable {
void run();
}
import java.util.concurrent.ScheduledThreadPoolExecutor
fun main() {
val executor = ScheduledThreadPoolExecutor(5)
executor.execute(object : Runnable {
override fun run() {
println("Working on Asynchronous task...")
}
})
executor.execute { println("Working on Asynchronous task...") }
executor.shutdown()
}
자바를 코틀린으로 변환하는 변환기 사용하기
IntelliJ 에서 자바 소스 파일을 코틀린 코드로 변환해주는 도구가 있습니다. cmd + option + shift + k를 누르면 자바 파일을 코틀린 파일로 변경해줍니다.
코틀린 코드를 자바에서 사용하기
자바에 직접적으로 대응하는 코틀린 기능이 없을 수 있습니다. 코틀린의 기능을 자바에서 사용할 수 있는 방법을 알아보겠습니다.
프로퍼티 접근
자바나 JVM에는 프로퍼티 개념이 없기 때문에 코틀린의 프로퍼티는 메서드로 표현됩니다.
class Person(var name: String, val age: Int)
public class Main {
public static void main(String[] args) {
Person person = new Person("석준", 31);
System.out.println(person.getAge()); // 31
person.setName("Dino");
System.out.println(person.getName()); // Dino
}
}
프로퍼티에 @JvmField annotation을 붙이면 get, set method가 아니라 프로퍼티 처럼 접근 할 수 있습니다.
class Person(@JvmField var name: String, @JvmField val age: Int)
public class Main {
public static void main(String[] args) {
Person person = new Person("석준", 31);
System.out.println(person.age); // 31
person.name = "Dino";
System.out.println(person.name); // Dino
}
}
@JvmField annotation은 커스텀 게터에 사용할 수 없고 final이 아닌 프로퍼티에 사용할 수 없습니다.
open class Person(val firstName: String, val familyName: String) {
// This annotation is not applicable to target 'member property without backing field or delegate'
@JvmField val fullName: String get() = "$firstName $familyName"
// JvmField can only be applied to final property
@JvmField open var description: String = "Hello"
}
object의 프로퍼티에도 @JvmField를 적용할 수 있습니다. const가 붙은 프로퍼티도 정적 필드로 제공 됩니다.
object Application {
@JvmField val name1 = "My Application"
const val name2 = "My Application"
}
public class Main {
public static void main(String[] args) {
System.out.println(Application.name1);
System.out.println(Application.name2);
}
}
lateinit var를 사용하면 두가지 형태로 사용이 가능합니다.
class Person(val firstName: String, val familyName: String) {
lateinit var fullName: String
fun init() {
fullName = "$firstName, $familyName"
}
}
public class Main {
public static void main(String[] args) {
Person person = new Person("석준", "정");
person.init();
// 필드에 직접 접근
System.out.println(person.fullName);
// 접근자 호출
System.out.println(person.getFullName());
}
}
파일 퍼사드와 최상위 선언
코틀린에서는 파일에 바로 선언하는 최상위 선언을 자주 사용합니다. 이런 프로퍼티나 함수를 자바에서 접근하기 위해서는 파일 이름 뒤에 Kt를 덧붙인 클래스가 생성되는데 이것을 파일 퍼사드라고 부릅니다.
// Util.kt
class Person(val firstName: String, val familyName: String)
val Person.fulName
get() = "$firstName $familyName"
fun readPerson(): Person? {
val fullName = readLine() ?: return null
val p = fullName.indexOf(' ')
return if (p >= 0) {
Person(fullName.substring(0, p), fullName.substring(p + 1))
} else {
Person(fullName, "")
}
}
public class Main {
public static void main(String[] args) {
Person person = UtilKt.readPerson();
if (person == null) return;
System.out.println(UtilKt.getFulName(person));
}
}
@JvmName annotation을 사용하면 퍼사드 클래스 이름을 지정할 수 있습니다.
@file:JvmName(name = "MyUtils")
class Person(val firstName: String, val familyName: String)
val Person.fullName: String
get() = "$firstName $familyName"
public class Main {
public static void main(String[] args) {
Person person = new Person("석준", "정");
System.out.println(MyUtils.getFullName(person));
}
}
@JvmMultifileClass과 @JvmName을 같이 사용하게 되면 파일이 달라도 하나의 퍼사드 클래스로 지정할 수 있습니다.
객체와 정적 멤버
object에 val를 선언 해서 사용할 때 INSTANCE필드를 접근해서 사용해야만 합니다. 하지만 @JvmStatic annotation을 사용하게 되면 INSTANCE필드를 사용하지 않고 접근 할 수 있습니다.
import java.io.InputStream
object Application {
@JvmStatic var stdin: InputStream = System.`in`
@JvmStatic fun exit() {}
}
import java.io.ByteArrayInputStream;
public class Main {
public static void main(String[] args) {
Application.setStdin(new ByteArrayInputStream("Hello".getBytes()));
Application.exit();
}
}
노출된 선언 이름 변경하기
@JvmName을 사용하면 퍼사드 클래스 이름을 변경할 수 있지만 함수나 프로퍼티에 사용하면 함수나 프로퍼티의 이름도 변경 할 수 있습니다. 이 방식은 코틀린에서는 허용하는 예약어를 자바에서 허용하지 않을 때 종종 사용합니다.
class Person(val firstName: String, val familyName: String)
val Person.fullName: String
get() = "$firstName $familyName"
@JvmName("getFullNameFamilyFirst")
fun getFullName(person: Person): String {
return "${person.familyName} ${person.firstName}"
}
오버로딩한 메서드 생성하기
코틀린 함수는 디폴트 값을 가질 수 있지만 이 함수를 자바에서 그대로 사용하는 경우 모든 파라미터를 전달 해줘야 합니다. 이런 경우에는 @JvmOverloads annotation을 사용하면 디폴트 값을 가진 함수를 사용 할 수 있습니다.
import kotlin.math.max
import kotlin.math.min
@JvmOverloads
fun restrictToRange(
what: Int,
from: Int = Int.MIN_VALUE,
to: Int = Int.MAX_VALUE,
): Int {
return max(from, min(to, what))
}
예외 선언하기
코틀린에서는 명시적으로 함수가 예외를 던진다고 선언할 필요가 없지만 자바는 명시적으로 선언해야합니다. 이때 @Throws annotation을 사용해서 처리 합니다.
import java.io.File
import java.io.IOException
@Throws(IOException::class)
fun loadData() = File("data.txt").readLines()
import java.io.IOException;
public class Main {
public static void main(String[] args) {
try {
for (String line : UtilKt.loadData()) {
System.out.println(line);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
인라인 함수
자바에는 인라인 함수가 없기 대문에 코틀린에서는 inline 변경자가 붙은 함수는 자바에서 일반 메서드로 호출 할 수 있지만 인라인되지는 않습니다.
타입 별명
코틀린 타입 별명은 자바 코드에서 쓸 수 없습니다.
'개발 > 코틀린' 카테고리의 다른 글
(코틀린) Testing with Kotest (0) | 2022.12.18 |
---|---|
(코틀린) 동시성 (0) | 2022.12.11 |
(코틀린) 도메인 특화 언어(DSL) (0) | 2022.11.27 |
(코틀린) 제네릭스 (0) | 2022.11.12 |
(코틀린) 클래스 계층 이해하기 (0) | 2022.11.06 |