코틀린을 배우기 위해서 인프런에서 강의를 구매하고 코틀린과 친해지고 기본기를 다지기 위해서 공부하는 중이다. 글 내용은 null을 다루는 방법이고 최태현님의 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide) 강의에 소금을 조금 친 내용이다
Person class
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
@Nullable
public String getName() {
return name;
}
}
Java에서의 null
public class JavaMain {
public static void main(String[] args) {
// startsWithA(null); NullPointerException 발생
}
public static boolean startsWithA(String str) {
return str.startsWith("A");
}
// 예외를 던지는 메서드 & return null을 허용하지 않는 메서드
public static boolean startsWithException(String str) {
if (str == null) {
throw new IllegalArgumentException("null이 들어왔습니다");
}
return str.startsWith("A");
}
// return null을 허용하는 메서드
public static Boolean startsWithReturnNull(String str) {
if (str == null) {
return null;
}
return str.startsWith("A");
}
public static boolean startsWithBoolean(String str) {
if (str == null) {
return false;
}
return str.startsWith("A");
}
}
null checking
- Kotlin에서는 null을 허용하는 타입과 허용하지 않는 타입으로 구분되기 때문에, null을 체크하는 방법이 다르다
- null을 허용하는 타입은 ?을 붙여서 선언하고, null을 체크하는 방법은 if문을 사용한다
- null을 허용하지 않는 타입은 ?를 붙이지 않고 선언하고, null을 체크하는 방법은 !!를 사용한다
safe call
fun main() {
val str: String? = "ABC"
println(str.length) // 컴파일 에러 발생
println(str?.length) // 3
val str2: String? = null
println(str2?.length) // null
}
- ?가 safe call이며 ?.에서 ?는 null 체크를 의미한다
- sase call은 ?.을 사용하여 null을 체크하고, null이 아닐 경우에만 메서드를 호출하는 방법이다
Elvis operator
fun main() {
val str3: String? = null
println(str3?.length ?: 0) // 0
}
- 앞의 연산자가 null일 경우 뒤의 값을 반환하는 연산자이다
- Java에서는 삼항 연산자(?:)를 사용하지만, Kotlin에서는 Elvis operator(?:)를 사용한다
- 90도 회전하면 엘비스 프레슬리를 닮았다고 해서 Elvis operator라고 불린다 (서양유머???? 이모티콘을 많이 사용해서 그런가보다)
Kotlin
val str: String? = "ABC"
str?.length ?: 0
// str이 null이면 0을 반환, null이 아니면 length를 반환
Java
String str = "ABC";
int length = (str != null) ? str.length() : 0;
// str이 null이면 0을 반환, null이 아니면 length를 반환
- Elvis operator은 Java에서 early return을 사용하는 것과 유사하다
Kotlin
fun calculate(number: Long?): Long {
number ?: return 0
// 다음 로직
}
Java
public long calculate(Long number) {
if (number == null) return 0;
}
함수로 정의해서 알아보기
다양한 null 처리 방법 비교를 위한 변수 정의
val testStr: String? = "Apple"
val nullStr: String? = null
return null을 허용하지 않는 메서드 Boolean
fun main() {
try {
startsWithException(nullStr)
} catch (e: IllegalArgumentException) {
println("에러: ${e.message}")
// 에러: null이 들어왔습니다
}
}
fun startsWithException(str: String?): Boolean {
if (str == null) {
throw IllegalArgumentException("null이 들어왔습니다")
}
return str.startsWith("A")
}
return null을 허용하는 메서드 Boolean?
fun main() {
println("${startsWithReturnNull(testStr)}") // true
println("${startsWithReturnNull(nullStr)}") // null
}
fun startsWithReturnNull(str: String?): Boolean? {
if (str == null) {
return null
}
return str.startsWith("A")
}
return Boolean을 반환하는 메서드
fun main() {
println("${startsWithBoolean(testStr)}") // true
println("${startsWithBoolean(nullStr)}") // false
}
fun startsWithBoolean(str: String?): Boolean {
if (str == null) {
return false;
}
return str.startsWith("A")
}
startsWithException 메서드와 동일한 기능을 하는 메서드 – Safe call + Elvis 활용
func main() {
println("${startsWithSafeCallElvisException(testStr)}") // true
try {
startsWithSafeCallElvisException(nullStr)
} catch (e: IllegalArgumentException) {
println("에러: ${e.message}")
에러: null이 들어왔습니다
}
}
fun startsWithSafeCallElvisException(str: String?): Boolean {
return str?.startsWith("A")
?: throw IllegalArgumentException("null이 들어왔습니다")
}
fun startsWithException(str: String?): Boolean {
if (str == null) {
throw IllegalArgumentException("null이 들어왔습니다")
}
return str.startsWith("A")
}
startsWithReturnNull 메서드와 동일한 기능을 하는 메서드 – Safe Call + null 반환
fun main() {
println("${startWithSafeCallNull(testStr)}") // true
println("${startWithSafeCallNull(nullStr)}") // null
}
fun startWithSafeCallNull(str: String?): Boolean? {
return str?.startsWith("A")
}
fun startsWithReturnNull(str: String?): Boolean? {
if (str == null) {
return null
}
return str.startsWith("A")
}
startsWithBoolean 메서드와 동일한 기능을 하는 메서드 – Safe Call + Elvis false
fun main() {
println("${startWithSafeCallElvisBoolean(testStr)}") // true
println("${startWithSafeCallElvisBoolean(nullStr)}") // false
}
fun startWithSafeCallElvisBoolean(str: String?): Boolean {
return str?.startsWith("A") ?: false
}
fun startsWithBoolean(str: String?): Boolean {
if (str == null) {
return false;
}
return str.startsWith("A")
}
null 아님 단언 – Not-null assertion (!!)
fun main() {
println("${startWithNeverNull(testStr)}") // true
try {
startWithNeverNull(nullStr)
} catch (e: IllegalArgumentException) {
println("에러: ${e.message}")
// 에러: NullPointerException 발생
}
}
// 만약 null이 들어오면 RuntimeException이 발생하는 메서드
fun startWithNeverNull(str: String?): Boolean {
return str!!.startsWith("A")
}
- nullable type이지만 아무리 생각해도 null이 될 수 없는 경우에는 !!를 사용하여 null이 아님은 단언할 수 있다
- 생성일(createAt)을 코드 레벨이나 DB 레벨에서 100% 입력 하는 경우, null이 될 수 없다고 확실할 수 있다면 !!를 사용하여 null이 아님을 단언할 수 있다
- 이럴 경우 Safe call을 매번 사용하는 것보다 코드가 더 간결해진다
- null이 들어오면 NPE(Null Pointer Exception)가 발생할 수 있으므로 주의해야 한다
플랫폼 타입- Java에서 @Nullable 메서드
fun main() {
val person = Person("혁")
println("${person.name}") // Java @Nullable이므로 String? 타입
println("${startsWithJavaNullable(person.name)}")
// Java의 @Nullable 때문에 person.name이 String? 타입이므로 컴파일 에러 발생
// null 체크 후 호출
person.name?.let { name ->
println("${startsWithJavaNullable(name)}")
// startsWithJavaNullable(name)?.let = false
}
}
fun startsWithJavaNullable(str: String): Boolean {
return str.startsWith("A")
}
let은 무엇일까?
- let은 nullable 값이 null이 아닐 때 만 코드 블록을 실행하는 scope 함수
- person.name을 호출하면 String? 타입 반환
- ?. (Safe call) → null이면 전체 체인 중단, 아니면 계속
- let { name -> …} → null이 아닌 값을 name 매개변수로 전달하여 블록 실행한다
- let의 역할
- null 안정성: null이 아닐 때만 실행한다
- Smart Cast: 블록 내에서 non-null 타입으로 자동 변환한다
- 가독성: 조건부 실행을 간결하게 표현한다
- 체이닝: 다른 함수들과 연결하여 사용 가능하다
- let은 “null이 아니면 이 작업을 해줘”라는 의미로, nullable 값을 안전하고 간결하게 처리하는 Kotlin의 핵심 도구이다
출처 – 인프런 강의 중 자바 개발자를 위한 코틀린 입문(Java to Kotlin Starter Guide)