코틀린의 ?, !!, ?:는 왜 필요할까?
자바와 비교해 보는 Null 안정성 설계
1. 코틀린의 ?, !!, ?: 연산자는 무엇이고 왜 쓰는가?
코틀린은 NullPointerException(NPE)을 방지하기 위해 설계 단계에서부터 null 처리를 강제한다.
이를 위해 대표적으로 다음 세 가지 연산자를 제공한다.
?: null이 될 수 있는 타입을 선언할 때 사용한다. 예:String?!!: null이 아님을 단언할 때 사용한다. null이면 런타임 예외가 발생한다.?:: null일 경우 대체 값을 제공할 때 사용한다. 엘비스 연산자라고도 불린다.
2. 실제 예시로 이해하기
var name: String? = null
println(name?.length) // null 안전 접근 → 출력: null
val length = name?.length ?: 0 // null이면 0 반환
val error = name!!.length // null 단언 → NullPointerException 발생
3. 자바에서는 어떻게 처리할까?
자바는 모든 참조형 변수가 null이 될 수 있다. 그래서 다음과 같은 코드가 런타임 오류를 발생시킨다.
String name = null;
int len = name.length(); // NullPointerException 발생
자바 8 이후 Optional이 등장했지만, 여전히 코드가 길고 사용이 제한적이라는 평가도 있다.
4. 자바 스타일을 코틀린에서는 어떻게 표현할까?
예시로 비교해보자.
자바:
Optional.ofNullable(name).orElse("Guest");
코틀린:
name ?: "Guest"
또 다른 예시:
자바:
if (user != null && user.getName() != null) {
return user.getName();
} else {
return "Guest";
}
코틀린:
return user?.name ?: "Guest"
코틀린은 null-safe 연산자를 통해 더 짧고 읽기 쉬운 코드를 작성할 수 있다.
5. 스프링에서의 적용 사례
요청 파라미터 처리
자바:
@GetMapping("/greet")
public String greet(@RequestParam(required = false) String name) {
return name != null ? name : "Guest";
}
코틀린:
@GetMapping("/greet")
fun greet(@RequestParam name: String?): String {
return name ?: "Guest"
}
엔티티에서 null 허용 필드
@Entity
class Member(
@Column(nullable = false)
val name: String,
val nickname: String? // null 허용 필드
)
Kotlin에서는 타입 수준에서 null 가능성을 명시하므로 의도를 더 명확하게 표현할 수 있다.
6. 서비스 계층에서 Optional 처리 방식
자바에서는 Optional을 그대로 반환하거나, map/ifPresent 등으로 처리한다.
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
public ResponseEntity<UserDto> getUser(Long id) {
return userService.findById(id)
.map(user -> ResponseEntity.ok(new UserDto(user)))
.orElse(ResponseEntity.notFound().build());
}
코틀린에서는 nullable 타입을 사용하고 let, ?: 등의 연산자로 처리한다.
fun findById(id: Long): User? {
return userRepository.findById(id).orElse(null)
}
fun getUser(id: Long): ResponseEntity<UserDto> {
val user = userService.findById(id)
return user?.let { ResponseEntity.ok(UserDto(it)) }
?: ResponseEntity.notFound().build()
}
7. 예외를 안전하게 처리하는 방식
코틀린에서는 예외 발생 가능성이 있는 코드를 다음처럼 처리할 수 있다.
fun findUserName(id: Long): String {
return runCatching {
userRepository.findById(id).orElseThrow().name
}.getOrElse { "UNKNOWN" }
}
예외를 try-catch로 감싸는 대신 runCatching 블록을 통해 처리하면 코드가 깔끔해지고, 테스트도 쉬워진다.
8. 요약
- 코틀린은 null 안정성을 언어 차원에서 제공하며,
?,!!,?:등의 연산자로 이를 처리한다. - 자바의
Optional은 코틀린에서는 nullable 타입과 엘비스 연산자로 자연스럽게 대체된다. - 컨트롤러나 서비스 계층에서도
?.let,?:,runCatching등을 통해 안정적인 코드를 작성할 수 있다.
참고자료
- Kotlin 공식 문서: https://kotlinlang.org/docs/null-safety.html
- Java Optional: https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
- Spring + Kotlin 가이드: https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#features.kotlin
- Kotlin in Action (Manning)
'Kotlin' 카테고리의 다른 글
| [Kotlin] lateinit vs lazy 정리 (0) | 2025.05.12 |
|---|---|
| [Kotlin] for문 안에서 인덱스 조정 에러, 해결법 (0) | 2023.03.22 |
댓글