아이템 3: 최대한 플랫폼 타입을 사용하지 말라


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는 적어도 mapfilterNotNull 등의 메서드를 제공하지, 다른 제네릭 타입이라면 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?

이런 코드를 사용할 수 있으므로 이전의 문제는 사라진다.