저는 회사에서 백엔드 개발을 할 때 코틀린 스프링을 씁니다.
자바를 썼던 기억은 학부 때 객체지향 수업을 들었을 때와 스프링에 입문하며 김영한 강사님의 스프링 강의를 들을 때뿐이었습니다. 그 이후엔 줄곧 코틀린을 써왔고, 주변 소위 '잘한다'는 한국의 백엔드 개발자들은 대부분 코틀린을 쓰고 있었습니다.
그래서 자바로 개발한다는 얘기를 들으면 저도 모르게 아직 그 팀의 기술스택이 충분히 힙하지 않구나 하고 넘겨 짚어버렸던 것 같습니다.
자바 하면 왠지 모르게 이클립스 뷰가 떠오르고 굉장히 오래된 이미지가 연상되기도 했습니다.
그러다 문득 궁금해졌습니다.
대체 무엇이 내 머릿속에 자바에 대한 이런 인식을 만들어냈을까.
그리고 왜 사람들은 코틀린을 쓸까.
코틀린이 만들어진 이유에서부터 그 힌트를 찾을 수 있었습니다.
코틀린은 젯브레인에서 자바와의 호환성, 더 나아가 많은 모던어의 특징인 편의성을 개선해 개발한 언어입니다.
즉, 태생부터가 사용성과 가독성을 위한 거였다는 것입니다.
그래서 코틀린 공식문서에는 자바보다 나은 점, 자바와 다른 점을 정리한 문서가 있습니다.
Comparison to Java | Kotlin
kotlinlang.org
Kotlin이 해결한 Java의 7가지 문제점
Kotlin은 Java에서 오랫동안 해결되지 않고 있던 고질적인 문제를 해결했습니다.
1. Null의 안전한 처리
Java 개발자라면 누구나 한 번쯤은 겪어봤을 NullPointerException
, Java에서는 컴파일 타임에 검사되지 않고, 런타임에 검사되도록해 빌드와 실행 전에는 알기 어렵다는 문제가 있었습니다.
// Java에서는 이런 코드가 문제 없이 컴파일됩니다
String str = null;
str.length(); // 런타임에 NPE 발생!
Kotlin은 이 문제를 타입 시스템 차원에서 해결했습니다.
// Kotlin의 null 처리
var nonNullString: String = "Hello" // null 불가능
var nullableString: String? = null // null 가능
// 컴파일러가 null 체크를 강제합니다
nullableString?.length // 안전한 호출
nullableString!!.length // non-null 단언
2. Raw 타입의 제거
Java는 하위 호환성을 위해 제네릭의 raw 타입을 허용합니다. 하지만 이는 타입 안전성을 해치는 원인이 됩니다.
// Java의 raw 타입 - 권장되지 않지만 가능
List list = new ArrayList(); // 경고는 발생하지만 컴파일됨
list.add("string");
list.add(1); // 어떤 타입이든 추가 가능
Kotlin에서는 이런 위험한 상황을 원천 차단합니다.
// Kotlin에서는 반드시 타입 파라미터를 명시해야 합니다
val list: List<String> = listOf("a", "b", "c")
// val list = List // 컴파일 에러!
3. 배열의 타입 안전성
Java에서 배열은 공변성(covariant)을 가집니다. 이는 예상치 못한 런타임 에러의 원인이 될 수 있습니다.
// Java의 위험한 배열 동작
Object[] objects = new String[1];
objects[0] = 1; // 런타임 에러!
Kotlin은 배열을 불변(invariant)으로 처리하여 이러한 문제를 방지합니다.
// Kotlin의 안전한 배열 처리
val strings: Array<String> = arrayOf("a", "b", "c")
val objects: Array<Any> = strings // 컴파일 에러!
4. 진정한 함수 타입
Java에서는 함수형 프로그래밍을 위해 SAM(Single Abstract Method) 변환을 사용합니다. 이는 다소 장황한 코드를 만들어내 코드 가독성을 해칩니다.
// Java의 SAM 변환
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// 처리
}
});
Kotlin은 자체적인 함수 타입을 제공하여 더 간결하고 명확한 코드를 작성할 수 있습니다.
// Kotlin의 함수 타입
val onClick: (View) -> Unit = { view ->
// 처리
}
button.setOnClickListener(onClick)
5. 더 나은 제네릭 변성 처리
Java의 와일드카드 문법은 복잡하고 이해하기 어렵습니다.
// Java의 와일드카드
void copyNumbers(List<? extends Number> from, List<? super Number> to)
Kotlin은 in
과 out
키워드로 이를 더 명확하게 표현합니다.
// Kotlin의 변성 선언
fun copyNumbers(from: List<out Number>, to: List<in Number>)
6. 체크 예외의 제거
Java의 체크 예외는 가끔 불필요한 코드 역시 적게끔 강제합니다.
// Java의 체크 예외 처리
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 대부분의 경우 빈 catch 블록
}
Kotlin은 모든 예외를 언체크 예외로 처리하여 코드를 간소화했습니다.
// Kotlin의 예외 처리
Thread.sleep(1000) // try-catch가 필요 없음
7. 명확한 컬렉션 구분
Java의 컬렉션은 읽기 전용과 변경 가능을 명확히 구분하지 않습니다. Kotlin은 이를 타입 시스템 차원에서 구분합니다.
// 읽기 전용 컬렉션
val readOnly: List<String> = listOf("a", "b", "c")
// readOnly.add("d") // 컴파일 에러!
// 변경 가능 컬렉션
val mutable: MutableList<String> = mutableListOf("a", "b", "c")
mutable.add("d") // OK
Kotlin에만 있는 기능들
그리고 자바에는 없고 코틀린에만 있는 기능들도 있습니다.
1. 인라인 함수와 람다 표현식
inline fun transaction(block: () -> Unit) {
try {
block()
} catch (e: Exception) {
rollback()
}
}
2. 확장 함수
fun String.addExclamation() = "$this!"
"Hello".addExclamation() // "Hello!"
3. null 안전성
var name: String? = null
println(name?.length) // null이면 안전하게 null 반환
4. 스마트한 type cast
if (x is String) {
print(x.length) // x를 String으로 자동 캐스트
}
5. 문자열 템플릿
val name = "Kotlin"
println("Hello, $name!") // 문자열 내 변수 사용
6. property
class Person {
var name: String = ""
get() = field.capitalize()
set(value) {
field = value.trim()
}
}
7. 주 생성자
class Person(val name: String, var age: Int)
8. 위임
class DelegatingCollection<T>(
private val innerList: MutableCollection<T> = ArrayList()
) : Collection<T> by innerList
9. 타입 추론
val numbers = listOf(1, 2, 3) // List<Int>로 자동 추론
10. 싱글톤
object DatabaseConfig {
val url = "jdbc:mysql://localhost:3306/db"
}
11. data class
data class User(val name: String, val age: Int)
12. 코루틴
suspend fun fetchData() = coroutineScope {
val result = async { api.getData() }
result.await()
}
13. 최상위 함수
// 파일: utils.kt
fun utility() {
// 클래스 없이 직접 함수 정의 가능
}
14. default 인자
fun greet(name: String = "Guest") {
println("Hello, $name!")
}
15. named parameter
fun createUser(name: String, age: Int) { }
createUser(age = 25, name = "John") // 순서 상관없이 호출 가능
16. 중위 함수
infix fun Int.times(str: String) = str.repeat(this)
2 times "Hello " // "Hello Hello "
Java에만 있는 기능들
반면, Kotlin에는 없지만 Java에만 있는 기능도 존재합니다.
1. 체크 예외 (Checked Exceptions)
// Java
public void readFile() throws IOException { // 반드시 예외 선언 필요
// 파일 읽기 코드
}
2. 원시 타입 (Primitive Types)
int number = 42; // 원시 타입
Integer boxed = 42; // 래퍼 클래스
3. static 멤버
public class Example {
public static final int CONSTANT = 42;
public static void doSomething() { }
}
4. 와일드카드 타입
List<? extends Number> numbers;
5. 삼항 연산자
int result = condition ? value1 : value2;
6. 레코드 (Java 14+)
public record Person(String name, int age) { }
7. 패턴 매칭
// Java 17+
if (obj instanceof String s) {
System.out.println(s.toLowerCase());
}
8. package-private 가시성
class InternalClass { // package-private
// 같은 패키지 내에서만 접근 가능
}
java와 kotlin은 이렇듯, 각각의 강점이 있습니다.
- Java는 안정성과 성숙도가 높고, 레코드나 패턴 매칭 같은 최신 기능들을 계속 추가하고 있습니다.
- Kotlin은 현대적인 프로그래밍 패러다임을 적극 수용하여, 더 안전하고 생산적인 코드 작성을 가능하게 합니다.
그렇다면 다시 질문으로 돌아가, 왜 자바가 아닌 코틀린일까요?
사람들은 왜 코틀린 + Spring Boot로 점점 옮겨갈까요?
위에 정리한 문서로 미루어 보았을 때, 가장 큰 이유는 단연 생산성일 것입니다.
Kotlin은 보일러플레이트 코드를 획기적으로 줄여줍니다. 데이터 클래스 하나로 Java에서 수십 줄이 필요한 코드를 한 줄로 작성할 수 있죠. 특히 스프링에서 자주 사용되는 DTO, Entity 클래스 작성이 훨씬 간편해집니다.
두 번째로는 안전성입니다.
Kotlin의 널 안전성은 개발자들을 NullPointerException의 공포에서 해방시켜줍니다. 컴파일 시점에서 널 관련 오류를 잡아낼 수 있어, 런타임 에러를 크게 줄일 수 있습니다. 그 외에도 다양한 배열의 불변성 등 코틀린은 다양한 안전장치를 제공하고 있습니다.
또, 유지보수성이 좋습니다.
Kotlin은 함수형 프로그래밍의 장점을 가져오면서도, 객체지향 프로그래밍의 특성을 잘 살리고 있습니다. 확장 함수, 고차 함수, 불변성 강조 등 현대적인 프로그래밍 패러다임을 자연스럽게 적용할 수 있어, 더 유지보수하기 좋은 코드를 작성할 수 있습니다.
더 나아가 보자면, 비동기 프로그래밍에서의 이점도 가지고 있습니다.
스프링의 웹플럭스와 같은 리액티브 프로그래밍에서 Kotlin의 코루틴은 큰 강점을 발휘합니다. 복잡한 비동기 코드를 마치 동기 코드처럼 간단하게 작성할 수 있어, 개발자의 부담을 크게 줄여줍니다.
하지만, 가장 중요한 점은 기존 Java 코드와의 완벽한 호환성입니다.
점진적으로 Kotlin을 도입할 수 있으며, 기존 스프링의 모든 기능과 라이브러리를 그대로 사용할 수 있습니다. 이는 리스크를 최소화하면서 새로운 기술을 도입할 수 있게 해줍니다. 이 점이 코틀린으로의 진입장벽을 낮췄고, 점점 더 많은 개발자가 더 편하고, 더 깔끔하며, 더 안전한 코틀린으로의 전환을 무리 없이 시도하는 결정적 계기가 되고 있는 게 아닌가 합니다.
'Backend' 카테고리의 다른 글
스프링부트, 빈의 Lazy 로딩 (@Lazy) (0) | 2025.02.16 |
---|---|
DB Connection Pool에 대해 (0) | 2025.01.31 |
Nest.js에서 prisma exception 데코레이터로 깔끔하게 핸들링하기 (0) | 2024.11.24 |
WebFlux에서 chunked 스트리밍 request 받기 (3) | 2024.10.27 |
Next.js 14와 Firebase로 간단하게 백엔드 API 만들기 (1) | 2023.12.22 |