null-safety 매커니즘이 없는 자바, C 등의 프로그래밍 언어와 코틀린을 연결해서 사용할 때는 NPE가 발생할 수 있다.
만약 자바에서 String 타입을 리턴하는 메서드가 있다고 했을 때 @Nullable
어노테이션이 붙어있다면 nullable로 추정하고 String?
으로 변경, @NotNull
어노테이션이 붙어있다면 String
으로 변경한다.
만약 아무 어노테이션이 붙어 있지 않다면 자바에서는 모든 것이 nullable 일 수 있으므로 최대한 안전하게 접근한다면 nullable로 가정하고 다루어야 한다. 하지만 어떤 메서드는 null
을 리턴하지 않을 것이 확실할 수 있다. 이런 경우 not-null 단정을 나타내는 !!
을 붙인다.
nullable과 관련하여 문제가 되는 부분은 자바의 제네릭 타입이다. 자바 API 에서 List<User>
를 리턴하고, 어노테이션이 따로 붙어 있지 않은 경우, 코틀린이 모든 타입을 nullable로 다룬다면, 우리는 이를 사용할 때 이러한 리스트와 리스트 내부의 User
객체들이 null이 아니라는 것을 알아야 한다. 따라서 리스트 자체만 null인지 확인해서는 안 되고, 그 내부에 있는 것들도 null인지 확인해야 한다.
// 자바
public class UserRepo {
public List<User> getUsers() {
// ***
}
}
// 코틀린
val users: List<User> = UserRepo().users!!.filterNotNull()
만약 함수가 List<List<User>>
를 리턴한다면 훨씬 복잡해질 것이다.
val users: List<List<User>> = UserRepo().groupedUsers!!.map { it!!.filterNotNull() }
List는 적어도 map
과 filterNotNull
등의 메서드를 제공하지, 다른 제네릭 타입이라면 null을 확인하는 것 자체가 정말 복잡한 일이 된다.
그래서 코틀린은 자바 등의 다른 프로그램이 언어에서 넘어온 타입들을 특수하게 다룬다. 이러한 타입을 플랫폼 타입이라고 부른다.
플랫폼 타입은 String!
처럼 타입 이름 뒤에 !
기호를 붙여서 표기한다. 물론 이러한 어노테이션이 직접적으로 코드에 나타나지는 않는다. 대신 다음 코드와 같은 형태로 이를 선택적으로 사용한다.
// 자바
public class UserRepo {
public User getUsers() {
// ...
}
}
// 코틀린
val repo = UserRepo()
val user1 = repo.user // user1의 타입은 User!
val user2: User = repo.user // user2의 타입은 User
val user3: User? = repo.user // user3의 타입은 User?
이런 코드를 사용할 수 있으므로 이전의 문제는 사라진다.