코틀린 언어 용어 사전
널 안전String! · @Nullable

플랫폼 타입

자바에서 온 타입은 null 여부를 알 수 없어 컴파일러가 검사를 포기한 상태. 코틀린 NPE의 주 통로다.

자바 코드에서 넘어온 타입. 코틀린 컴파일러가 null 여부를 알 수 없어 검사를 유보한 상태다.

코틀린 타입 표기
  String    not-null
  String?   nullable
  String!   플랫폼 타입 ← 우리가 직접 쓸 수는 없고, IDE 가 보여 주는 표기

왜 위험한가

// 자바
public class UserRepo {
    public String findName(long id) { return null; }   // null 을 반환할 수 있다
}

// 코틀린
val name: String = repo.findName(1)   // 컴파일 통과! (플랫폼 타입이라 검사를 안 한다)
println(name.length)                  // 런타임 NPE

코틀린을 쓰는데도 NPE가 나는 거의 유일한 실무 경로다.

왜 이렇게 설계했나

모든 자바 타입을 nullable 로 취급하면

  • 기존 자바 라이브러리를 쓸 때마다 ?. 와 !! 가 도배된다
  • 실용성이 무너진다

모든 자바 타입을 not-null 로 취급하면

  • 위험을 감춘다

타협: "모르니 개발자에게 맡긴다" — 대신 그 지점을 알고 있어야 한다

방어하는 법

// ① 경계에서 명시적으로 타입을 붙인다
val name: String? = repo.findName(1)   // 이제 컴파일러가 검사한다

// ② 검증 함수로 의도를 남긴다
val name = requireNotNull(repo.findName(1)) { "이름이 없다" }
// ③ 자바 쪽에 애노테이션을 단다 — 코틀린이 이를 읽는다
@Nullable public String findName(long id) { ... }   // → String? 로 인식
@NonNull  public String getName() { ... }           // → String 으로 인식

JSpecify·JetBrains·Android의 애노테이션을 코틀린 컴파일러가 인식한다 — 자바 라이브러리를 감쌀 때 이걸 다는 것이 근본 해법이다.

안드로이드에서 특히 자주 만난다

// 프레임워크 API 상당수가 플랫폼 타입으로 넘어온다
val extras = intent.extras            // Bundle! → 사실 null 일 수 있다
val s = savedInstanceState?.getString("key")   // 안전 호출을 습관화한다

면접 함정

  • "코틀린만 쓰면 NPE가 없다" → 자바 라이브러리 경계가 남아 있다.
  • "플랫폼 타입은 nullable이다"알 수 없음이다. 검사를 안 할 뿐이다.

어디서 새는지 찾아내기

// IDE 에서 타입을 확인하면 ! 가 보인다
val name = repo.findName(1)     // 추론 타입: String!  ← 플랫폼 타입

// 컴파일러 옵션으로 경고를 켤 수 있다
// kotlinc -Xjsr305=strict  (JSR-305 애노테이션을 엄격하게 해석)
// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.addAll("-Xjsr305=strict")
    }
}

strict로 두면 애노테이션이 붙은 자바 API가 진짜 nullable/not-null로 해석되어 컴파일 시점에 잡힌다.

어디를 경계로 삼을 것인가

자바 라이브러리를 직접 여기저기서 부르지 않는다

  • 얇은 래퍼(코틀린 함수)를 하나 두고 거기서 타입을 명시한다
  • 플랫폼 타입이 코드베이스 전체로 퍼지는 것을 막는다

class UserGateway(private val java: JavaUserRepo) {

  • fun findName(id: Long): String? = java.findName(id) // 여기서 한 번만 판단한다 }

안드로이드 프레임워크의 흔한 지점

requireContext()        // Context! 가 아니라 Context 를 보장한다 (없으면 예외)
requireArguments()
arguments?.getString("k")    // Bundle? · String? — 안전 호출을 습관화
findViewById<TextView>(R.id.x)   // 없으면 null 이 아니라 NPE 로 터진다

함께 보면 좋은 용어

노트에서 맥락과 함께 보기 — 널 안전 — ?·!!·Elvis·플랫폼 타입·스마트 캐스트