코틀린과 자바를 한 프로젝트에서 섞어 쓰는 규칙.
코틀린 → 자바 (쓰는 쪽)
// 자바의 모든 타입이 플랫폼 타입으로 넘어온다 → null 검사가 생략된다
val name: String = javaRepo.findName(1) // 컴파일 통과, 런타임 NPE 가능
val safe: String? = javaRepo.findName(1) // 경계에서 명시하는 습관
// 자바의 게터/세터가 프로퍼티처럼 보인다
javaObj.name = "a" // setName("a")
val n = javaObj.name // getName()
자바 → 코틀린 (호출당하는 쪽)
class Utils {
companion object {
@JvmStatic fun parse(s: String) = ... // 자바에서 Utils.parse(s) 로 부를 수 있다
// 없으면 Utils.Companion.parse(s) 가 된다
}
}
// 기본 인자는 자바에 안 보인다
@JvmOverloads
fun greet(name: String, greeting: String = "안녕") = "$greeting, $name"
// → 자바용 오버로드가 자동 생성된다
@JvmField val CONSTANT = 42 // 게터 대신 필드로 노출
@file:JvmName("StringUtils") // 파일 이름 대신 이 이름으로 클래스를 만든다
@Throws(IOException::class) // 자바가 검사 예외로 인식하게 한다
fun read() { ... }
@JvmOverloads를 빠뜨리면 자바 쪽에서 기본 인자를 못 쓰고 전부 넘겨야 한다 — 라이브러리를 만들 때 반드시 챙긴다.
SAM 변환
// 자바의 단일 추상 메서드 인터페이스는 람다로 넘길 수 있다
button.setOnClickListener { view -> ... }
// 코틀린에서 정의한 인터페이스는 fun interface 여야 한다
fun interface Handler { fun handle(x: Int) }
register { x -> println(x) }
흔한 함정
-
① 검사 예외
- 코틀린에는 검사 예외가 없다
- 자바에서 코틀린 함수를 부를 때 try-catch 를 요구하지 않는다
- @Throws 를 붙여야 자바가 인식한다
-
② 배열의 변성
- 코틀린 Array<String> 은 자바 String[] 과 매핑되지만
- Array<out String> 같은 표기가 자바에서는 다르게 보인다
-
③ 원시 타입
- Int → int, Int? → Integer
- nullable 여부가 박싱 여부를 바꾼다
-
④ 프로퍼티와 필드
- 코틀린 프로퍼티는 자바에서 getX()/setX() 로 보인다
- 필드로 접근하려면 @JvmField
자주 쓰는 코틀린 관용구
require(age >= 0) { "나이는 음수일 수 없다" } // 인자 검증 → IllegalArgumentException
check(state == READY) { "준비되지 않았다" } // 상태 검증 → IllegalStateException
repeat(3) { println(it) }
val (a, b) = pair
list.forEachIndexed { i, v -> ... }
String.format() 대신 "$a - $b"
면접 함정
- ❌ "코틀린과 자바는 그냥 섞으면 된다" → 플랫폼 타입·기본 인자·검사 예외 경계에서 문제가 생긴다.
- ❌ "@JvmStatic은 성능 최적화" → 자바에서의 호출 형태를 바꾸는 것뿐이다.