Backend

사람들은 왜 자바가 아닌 코틀린에 열광할까?

Danna 다나 2025. 1. 5. 21:41
728x90
반응형

저는 회사에서 백엔드 개발을 할 때 코틀린 스프링을 씁니다.
자바를 썼던 기억은 학부 때 객체지향 수업을 들었을 때와 스프링에 입문하며 김영한 강사님의 스프링 강의를 들을 때뿐이었습니다. 그 이후엔 줄곧 코틀린을 써왔고, 주변 소위 '잘한다'는 한국의 백엔드 개발자들은 대부분 코틀린을 쓰고 있었습니다.

그래서 자바로 개발한다는 얘기를 들으면 저도 모르게 아직 그 팀의 기술스택이 충분히 힙하지 않구나 하고 넘겨 짚어버렸던 것 같습니다.
자바 하면 왠지 모르게 이클립스 뷰가 떠오르고 굉장히 오래된 이미지가 연상되기도 했습니다.

그러다 문득 궁금해졌습니다.
대체 무엇이 내 머릿속에 자바에 대한 이런 인식을 만들어냈을까.
그리고 왜 사람들은 코틀린을 쓸까.

코틀린이 만들어진 이유에서부터 그 힌트를 찾을 수 있었습니다.

코틀린은 젯브레인에서 자바와의 호환성, 더 나아가 많은 모던어의 특징인 편의성을 개선해 개발한 언어입니다.

즉, 태생부터가 사용성과 가독성을 위한 거였다는 것입니다.

그래서 코틀린 공식문서에는 자바보다 나은 점, 자바와 다른 점을 정리한 문서가 있습니다.

 

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은 inout 키워드로 이를 더 명확하게 표현합니다.

// 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을 도입할 수 있으며, 기존 스프링의 모든 기능과 라이브러리를 그대로 사용할 수 있습니다. 이는 리스크를 최소화하면서 새로운 기술을 도입할 수 있게 해줍니다. 이 점이 코틀린으로의 진입장벽을 낮췄고, 점점 더 많은 개발자가 더 편하고, 더 깔끔하며, 더 안전한 코틀린으로의 전환을 무리 없이 시도하는 결정적 계기가 되고 있는 게 아닌가 합니다.

728x90
반응형