Search for a command to run...

안녕하세요, 개발자 여러분! 많은 분들이 안드로이드 개발이나 백엔드 개발에서 Kotlin을 주력 언어로 사용하고 계실 겁니다. 코틀린은 간결한 문법과 강력한 기능으로 많은 사랑을 받고 있지만, 현실적으로 모든 프로젝트를 100% 코틀린으로만 작성하기는 어렵습니다. 기존에 잘 만들어진 방대한 Java 라이브러리를 활용해야 할 때도 있고, 점진적으로 코틀린으로 마이그레이션하는 프로젝트의 경우 Java와 Kotlin 코드가 공존하는 기간이 반드시 필요하죠.
다행히도 코틀린은 JVM(Java Virtual Machine) 위에서 동작하며, 처음부터 Java와의 100% 상호운용성을 목표로 설계되었습니다. 덕분에 우리는 코틀린 프로젝트에서 아무런 장벽 없이 Java 코드를 호출하고, 반대로 Java에서도 코틀린 코드를 사용할 수 있습니다. 오늘은 이렇게 두 언어를 함께 사용할 때 알아두면 정말 유용한 꿀팁 5가지를 공유해보고자 합니다.
) 활용가끔 Java 라이브러리 중 코틀린에서는 예약어(keyword)인 단어를 메소드 이름으로 사용하는 경우가 있습니다. 예를 들어, Mockito 같은 테스팅 프레임워크는 when이라는 메소드를 사용하죠. 코틀린에서 when은 조건 분기문으로 사용되는 키워드라 그냥 호출할 수 없습니다.
이럴 때 **백틱(Backtick, )**으로 메소드 이름을 감싸주면 문제를 간단히 해결할 수 있습니다.
// Java 코드 예시 (Mockito)
// when(mockObject.someMethod()).thenReturn(true);
// 코틀린에서 호출할 때
import org.mockito.Mockito.`when`
`when`(mockObject.someMethod()).thenReturn(true)
이렇게 하면 코틀린 컴파일러는 when을 키워드가 아닌 일반 메소드 이름으로 인식하게 됩니다.
코틀린의 가장 큰 장점 중 하나는 컴파일 시점에 Null Pointer Exception (NPE)을 방지해주는 Null 안정성입니다. 하지만 Java 코드에는 이런 개념이 없죠. 코틀린에서 Nullable/Non-Null 정보가 없는 Java 타입을 가져와 사용할 때, 이를 **플랫폼 타입(Platform Type)**이라고 부릅니다. 이는 String!처럼 타입 뒤에 !이 붙는 형태로 표현되며, 개발자가 Null 처리를 어떻게 할지 선택권을 주는 타입입니다.
하지만 이는 곧 런타임에 NPE가 발생할 수 있다는 의미이기도 합니다. 이를 방지하기 위해 Java 코드에 어노테이션을 추가하여 Nullability 정보를 코틀린 컴파일러에게 알려줄 수 있습니다.
@Nullable: 이 변수나 메소드 리턴 값은 null이 될 수 있음을 의미합니다.@NonNull: null이 아님을 보장합니다.// Java 코드
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class JavaUser {
@NotNull
public String getName() {
return "pixelwave88";
}
@Nullable
public String getNickname() {
return null;
}
}
이제 코틀린에서 이 코드를 사용하면 컴파일러가 각 타입의 Nullability를 정확히 추론하게 됩니다.
val javaUser = JavaUser()
val name: String = javaUser.name // NotNull로 추론됨
val nickname: String? = javaUser.nickname // Nullable로 추론됨
println(name.length) // 안전하게 호출 가능
println(nickname?.length) // Safe call(?.)을 사용해야 함
Java의 static 키워드로 선언된 변수나 메소드는 코틀린에서 어떻게 접근할까요? 코틀린에는 static 키워드가 없지만, **동반 객체(Companion Object)**가 비슷한 역할을 합니다. Java의 static 멤버는 마치 그 클래스의 동반 객체에 선언된 것처럼 취급되어, 클래스 이름을 통해 바로 접근할 수 있습니다.
// Java 코드
public class JavaUtils {
public static final String VERSION = "1.0";
public static int doubleValue(int value) {
return value * 2;
}
}
// 코틀린에서 접근
fun main() {
println("Version: ${JavaUtils.VERSION}")
val result = JavaUtils.doubleValue(10)
println("Result: $result")
}
별도의 import나 특별한 문법 없이 아주 자연스럽게 사용할 수 있습니다.
Java에서는 이벤트 리스너처럼 단 하나의 추상 메소드만 가진 인터페이스(SAM 인터페이스)를 람다식으로 간결하게 표현할 수 있습니다. 코틀린은 이를 완벽하게 지원합니다. 코틀린의 람다식을 Java의 SAM 인터페이스가 필요한 곳에 바로 전달할 수 있습니다.
// Java 코드 (SAM 인터페이스)
public interface OnClickListener {
void onClick();
}
public class Button {
private OnClickListener listener;
public void setOnClickListener(OnClickListener listener) {
this.listener = listener;
}
public void click() {
if (listener != null) {
listener.onClick();
}
}
}
// 코틀린에서 람다로 처리
val button = Button()
// SAM 변환을 통해 람다식을 바로 전달
button.setOnClickListener {
println("Button clicked!")
}
button.click()
덕분에 코틀린에서는 장황한 익명 객체 구현 없이 코드를 깔끔하게 유지할 수 있습니다.
코틀린의 강력한 기능인 **확장 함수(Extension Function)**는 사실 컴파일 시점에 해당 클래스를 받는 static 메소드로 변환됩니다. 따라서 Java에서는 확장 함수를 호출할 때, static 메소드를 호출하는 방식으로 사용해야 합니다.
// 코틀린 확장 함수 (FileName: StringExtensions.kt)
package com.example
fun String.lastChar(): Char = this.last()
// Java에서 호출
import com.example.StringExtensionsKt; // 파일 이름에 "Kt" 접미사가 붙음
char last = StringExtensionsKt.lastChar("hello"); // "h"가 아닌 "o"가 반환됨
System.out.println(last);
확장 함수가 정의된 파일 이름(StringExtensions.kt)에 Kt 접미사가 붙은 클래스 이름으로 접근하고, 첫 번째 인자로 수신 객체("hello")를 넘겨주는 형태가 됩니다.
오늘은 코틀린과 자바를 함께 사용할 때 마주칠 수 있는 상황들과 이를 해결하는 팁들을 알아보았습니다. 코틀린의 뛰어난 상호운용성 덕분에 우리는 두 언어의 장점을 모두 취하며 더 나은 코드를 작성할 수 있습니다. Java의 방대한 생태계를 적극적으로 활용하면서, 코틀린의 현대적인 언어 기능으로 생산성을 높여보세요! 여러분의 프로젝트에 두 언어가 아름답게 공존하기를 바랍니다.
로그인 후 댓글을 작성할 수 있습니다.