
Null 처리 방식의 차이: 런타임 방어 vs 컴파일 타임 강제
Java에서 NullPointerException은 “조심하면 피할 수 있는” 문제가 아니라, 구조적으로 발생하기 쉬운 런타임 리스크다. 결국 방어 코드가 늘어나고, 리뷰 포인트도 null 체크로 채워진다.
Java
public String getUserEmail(User user) {
if (user != null && user.getProfile() != null) {
return user.getProfile().getEmail();
}
return "unknown";
}
Kotlin
fun getUserEmail(user: User?): String =
user?.profile?.email ?: "unknown"
차이는 문법이 아니라 책임의 위치다.
Java는 “개발자가 잘 처리하길 기대”하지만, Kotlin은 nullable 여부를 타입으로 고정해 컴파일 단계에서 강제한다.
실무에서는 이 차이가 장애율과 코드 리뷰 피로도로 직결된다.
DTO 작성 비용: 보일러플레이트 vs 의도 중심 모델링
Java에서 DTO 하나는 생각보다 많은 코드를 요구한다. 생성자, getter, equals/hashCode, toString까지 포함하면 핵심 필드는 몇 줄 안 되는데 파일은 금방 비대해진다.
Java
public class Product {
private final String name;
private final int price;
public Product(String name, int price) {
this.name = name;
this.price = price;
}
public String getName() { return name; }
public int getPrice() { return price; }
@Override public boolean equals(Object o) { /* ... */ }
@Override public int hashCode() { /* ... */ }
@Override public String toString() { /* ... */ }
}
Kotlin
data class Product(val name: String, val price: Int)
data class는 단순한 문법 설탕이 아니다.
“이 클래스는 데이터를 담는 용도다”라는 의도를 명확히 드러내고, 불필요한 코드 작성과 유지보수를 제거한다.
특히 API DTO, 이벤트 메시지, 캐시 객체에서 효과가 크다.
컬렉션 처리: API 사용 vs 언어 차원의 일관성
Java Stream은 강력하지만, 컬렉션과 스트림을 오가야 하는 구조적 불편함이 있다. Kotlin은 컬렉션 자체가 연산의 중심이다.
Java
List<String> activeUserNames = users.stream()
.filter(user -> user.isActive())
.map(User::getName)
.collect(Collectors.toList());
Kotlin
val activeUserNames = users
.filter { it.isActive }
.map { it.name }
Kotlin의 강점은 짧은 문법이 아니라 확장 함수 기반의 일관된 체이닝이다.
여기에 let, apply, also 같은 스코프 함수가 더해지면, 객체 생성·검증·후처리 흐름을 분리하지 않고 표현할 수 있다.
결과적으로 “읽히는 코드”가 된다.
언어 차원에서 제거된 패턴들: 싱글톤과 분기 처리
실무에서 자주 쓰지만 매번 귀찮은 패턴들이 Kotlin에서는 언어 기능으로 흡수됐다.
1. 싱글톤
Java
public class ConfigManager {
private static final ConfigManager INSTANCE = new ConfigManager();
private ConfigManager() {}
public static ConfigManager getInstance() {
return INSTANCE;
}
}
Kotlin
object ConfigManager
동시성, 초기화 타이밍을 고민할 필요가 없다. 의도가 코드에 그대로 드러난다.
2. 분기 처리
Java
String message;
switch (status) {
case SUCCESS:
message = "완료";
break;
case FAILED:
message = "실패";
break;
default:
message = "대기";
}
Kotlin
val message = when (status) {
SUCCESS -> "완료"
FAILED -> "실패"
else -> "대기"
}
when은 표현식이다.
값을 반환하므로 임시 변수가 사라지고, 분기 누락도 컴파일러가 잡아낸다.
핵심 정리: Kotlin은 “덜 쓰는 언어”가 아니다
- Kotlin의 장점은 짧음이 아니라 실수 여지를 줄이는 설계다
- null, 데이터 객체, 분기 처리에서 컴파일러가 개발자의 동료가 된다
- Java 개발 경험이 길수록 차이는 더 체감된다
Kotlin을 선택하기 좋은 상황
- 신규 Spring 프로젝트 또는 신규 모듈
- null 안정성이 중요한 도메인
- DTO·이벤트·API 모델이 많은 구조
- 팀 생산성과 코드 리뷰 비용이 병목인 경우
Kotlin은 Java와 100% 상호운용된다.
모든 것을 한 번에 바꿀 필요는 없다. 새로운 코드부터 더 안전한 언어를 쓰는 것, 그게 실무적인 도입 전략이다.
'Backend > Java, Kotlin' 카테고리의 다른 글
| [Java] try-with-resources: 예외 처리와 자원 관리를 깔끔하게 해결하는 방법 (0) | 2025.04.04 |
|---|---|
| Java의 강한 참조, 약한 참조, 부드러운 참조 완벽 정리 (0) | 2025.03.27 |
| [JAVA] List 출력 "System.out::println" (0) | 2022.05.20 |
| [JDBC] DB 연동 클래스 생성 (0) | 2021.11.22 |
| [JAVA] 컬렉션 클래스 정리 & 요약 (0) | 2021.10.06 |