본문 바로가기

공부방/Kotlin

코틀린에서 null을 다루는 방법

1. 자바 코드 예시


아래의 자바 코드를 살펴봅시다.

문제가 없는 코드라고 할 수도 있겠으나, 해당 코드는 null 값이 들어왔을 경우에는 NPE가 발생합니다.

 

public class Lec02Main {

	// str이 null일 수 있기 때문에 NPE 위험성이 존재
	public boolean startsWithA(String str) {
		return str.startsWith("A");
	}
}

 

2. 자바로 문제 해결


NPE를 해결하기 위해서 자바 코드는 아래와 같이 대처할 수 있을겁니다.

  • str 값이 null인 경우 exception 처리
  • str 값이 null인 경우 null return
  • str 값이 null인 경우 false return

다음과 같이 구현할 수 있을 것입니다.

 

public class Lec02Main {
	// 대처1: null인 경우 exception 발생
	public boolean startsWithA1(String str) {
		if (str == null) {
		  throw new IllegalArgumentException("null이 들어왔습니다");
		}
		return str.startsWith("A");
	}

	// 대처2: null인 경우 null return
	// null을 return할 수 있기 때문에 return type은 Boolean입니다.
	public Boolean startsWithA2(String str) {
		if (str == null) {
		  return null;
		}
		return str.startsWith("A");
	}

	// 대처3: null인 경우 false return
	// null은 return하지 않기 때문에 return type은 boolean입니다.
	public boolean startsWithA3(String str) {
		if (str == null) {
		  return false;
		}
		return str.startsWith("A");
	}
}

 

위 코드를 코틀린으로 작성해보면 어떻게 변할까요?

 

3. 코틀린 코드 예시


먼저 아래 코드를 살펴봅시다.

 

 

str 값의 type을 String? 으로 지정해줬기 때문에 null이 들어올 수 있다고 알려줬습니다.

그래서 str.startsWith를 사용하면 문제가 발생할 수 있다고 빨간색 밑줄로 표시해주는 것을 확인할 수 있습니다.

코틀린에서는 null 값에 대한 타입이 있기 떄문에 NPE가 발생하는 문제를 미연에 방지할 수 있다는 장점이 있습니다.

 

4. 코틀린을 이용한 문제 해결


자바와 동일하게 코틀린을 이용해서 문제를 해결해 보겠습니다.

 

 

대처1: null인 경우 exception을 발생시키는 부분은 파라미터 타입 설정에 null이 들어올 수 있다고 체크해주는 부분을 제외하고는 자바 코드와 별다른 점이 없습니다.

 

대처2: null인 경우 null을 return 해주는 경우에는 return type에도 null이 들어갈 수 있다는 것을 명시해줍니다.

 

대처3: null인 경우 false를 return해주는 경우는 null을 return하는 case가 없기 때문에 return type에 ?를 사용하지 않습니다.

 

class Lec01Main {
	// 대처1: null인 경우 exception 발생
	fun startsWithA1(str: String?) :Boolean {
		if (str == null) {
			throw IllegalArgumentException()
		}
		return str.startsWith("A");
	}

	// 대처2: null인 경우 null return
	// null을 return할 수 있기 때문에 return type은 Boolean입니다.
	fun startsWithA2(str: String?) :Boolean? {
		if (str == null) {
			return null
		}
		return str.startsWith("A");
	}

	// 대처3: null인 경우 false return
	// null은 return하지 않기 때문에 return type은 boolean입니다.
	fun startsWithA3(str: String?) :Boolean {
		if (str == null) {
			return false;
		}
		return str.startsWith("A");
	}
}

 

5. null 타입만을 위한 기능


Safe call

 

null이 아니면 실행하고 null이면 실행하지 않는다.

 

fun main() {
	var str: String? = "ABC"
	println(str?.length)
}

 

Elvis 연산자

 

앞의 연산 결과가 null이면 뒤의 값을 사용

 

fun main() {
	var str2: String? = "ABC"
	println(str2?.length) ?: 0
}

 

6. Safecall과 Elvis 연산자를 이용하여 코드 개선


변경 전 코드와, 변경 후 코드를 같이 작성하였습니다.

Safe call과 Elvis 연산자를 사용하니 코드가 간결해진 것을 확인할 수 있습니다.

 

// 대처1: null인 경우 exception 발생
fun startsWithA1(str: String?) :Boolean {
	if (str == null) {
		throw IllegalArgumentException()
	}
	return str.startsWith("A");
}


fun startsWithB1(str: String?): Boolean {
	return str?.startsWith("A")
		?: throw IllegalArgumentException()
}


// 대처2: null인 경우 null return
// null을 return할 수 있기 때문에 return type은 Boolean입니다.
fun startsWithA2(str: String?) :Boolean? {
	if (str == null) {
		return null
	}
	return str.startsWith("A");
}

// str이 null이라면 null이 그대로 반환됨
fun startsWithB2(str: String?) :Boolean? {
	return str?.startsWith("A")		
}


// 대처3 null인 경우 false return
// null은 return하지 않기 때문에 return type은 boolean입니다.
fun startsWithA3(str: String?) :Boolean {
	if (str == null) {
		return false;
	}
	return str.startsWith("A");
}

// str이 null이라면 null이 그대로 반환됨
fun startsWithB3(str: String?) :Boolean? {
	return str?.startsWith("A") ?: false
}

 

7. null 아님 단언 (!!)


특이하게도 타입은 null일 수 있다고 선언하였지만, 확실하게 null이 아니라고 판단 될 경우에 사용할 수 있는 문법이 있습니다. "!!" 를 사용하게 되면 이 변수는 null이 정말 아니야! 라고 선언하는 것과 동일하다고 보면 됩니다.

 

fun startsWithC(str: String?) {
	str!!.startsWith("A")
}

 

8. 자바 프로젝트에서의 코틀린


자바 프로젝트에서 코틀린을 사용하는 경우가 있습니다.

이러한 경우 null은 어떻게 처리되는지를 확인해 봅시다.

 

코틀린은 null과 관련된 annotation을 이해합니다.


null과 관련된 annotation 패키지는 다음과 같은 것들이 있습니다.

  • javax.annotation 패키지
  • android.support.annotation 패키지
  • org.jetbrains.annotation 패키지

 

코드를 통한 예시를 살펴보겠습니다.

 

1. @Nullable에 대한 처리

 

public class Person {

  private final String name;

  public Person(String name) {
    this.name = name;
  }

  @Nullable
  public String getName() {
    return name;
  }
}

 

person.name은 null 일 수 있습니다.

startsWithC 라는 메서드는 null이 들어올 수 없기 떄문에 아래와 같이 에러가 발생하는 것을 확인할 수 있습니다.

 

 

2. @NotNull

 

@NotNull이라는 annotation을 사용하게 되면 name이라는 값은 null일 수가 없기 때문에 아래와 같이 발생하던 문제가 사라진 것을 확인할 수 있습니다.

 

 

3. annotation이 없는 경우 (플랫폼 타입)

 

annotation이 없는 경우 null인지 아닌지를 판단할 수 없는 상태이고 이를 플랫폼 타입이라고 합니다.

이럴때는 기존 자바코드와 동일하게 런타임 시점에 NPE가 발생하는 것은 동일합니다.

 

public class Person {

  private final String name;

  public Person(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }
}

 

아래 코드를 실행하면 NPE가 발생하는 것을 확인할 수 있습니다.

 

fun main() {
	val person : Person = Person(null)
	startsWithC(person.name);
}

fun startsWithC(str: String) {
	str.startsWith("A")
}

 

코틀린에서 자바코드를 사용할 떄는 null을 꼼꼼하게 처리가 필요

라이브러리를 사용하게 되면, wrapping 해서 처리가 가능.