WEB BE Repository/JAVA & Kotlin

Kotlin 문법 - Java와의 차이점

조금씩 차근차근 2026. 1. 6. 02:00

코틀린을 사용하면서, 자바랑 확연히 달라 적응하기 어려웠던 부분들을 기록해둔다.

read-only vs mutable 관리

read-only vs mutable: 객체 내부 프로퍼티 접근제어

코틀린에서 val/var 차이와, 프로퍼티의 getter/setter를 정의하는 방식(특히 private set)을 정리한다.

1. val vs var

  • val: 재할당 불가(읽기 전용 참조). 프로퍼티 관점에서는 setter가 없음.
  • var: 재할당 가능. 프로퍼티 관점에서는 getter + setter가 있음.
val a = 10
// a = 20 // 컴파일 에러

var b = 10
b = 20 // 가능

2. 기본 getter/setter

코틀린 프로퍼티는 기본적으로 접근자(accessor)가 자동 생성된다.

class User {
    var name: String = "kim" // 기본 getter/setter 생성
    val id: Long = 1L        // 기본 getter만 생성
}

3. 커스텀 getter

get() 블록에서 반환 값을 정의한다.

class Rectangle(val width: Int, val height: Int) {
    val area: Int
        get() = width * height
}
  • 이런 프로퍼티는 보통 계산 프로퍼티로 backing field 없이 동작한다.

4. 커스텀 setter + field(backing field)

var에서만 setter 커스터마이징이 가능하며, 내부 필드에 접근하려면 field를 쓴다.

class Account {
    var balance: Long = 0
        set(value) {
            field = if (value < 0) 0 else value
        }
}
  • field는 “이 프로퍼티의 실제 저장 공간”을 의미한다.
  • setter에서 balance = ...로 다시 대입하면 재귀 호출이 되므로 field를 사용한다.

5. private set (외부 수정 차단)

외부에는 읽기만 허용하고, 클래스 내부에서만 변경 가능하게 만든다.

class Order {
    var status: String = "CREATED"
        private set

    fun ship() {
        status = "SHIPPED" // 클래스 내부에서는 변경 가능
    }
}

이 패턴은 “상태는 노출하되 변경은 메서드로만” 같은 캡슐화에 자주 쓴다.

private set + 커스텀 setter도 가능

class Person {
    var age: Int = 0
        private set(value) {
            field = value.coerceAtLeast(0)
        }

    fun grow() { age += 1 }
}

6. getter만 private로 할 수도 있음

읽기를 막고 내부에서만 읽게 하는 패턴이다.

class Secret {
    val token: String = "t"
        private get
}
  • 클래스 밖에서는 token 접근 자체가 불가능하다.

7. 프로퍼티의 가시성(visibility)을 프로퍼티 자체에 주는 방식

프로퍼티 선언에 붙이면, getter/setter 모두에 영향을 준다.

class A {
    private var x: Int = 0  // getter/setter 모두 private
}

반면 아래는 “읽기는 public, 쓰기만 private”이다.

class A {
    var x: Int = 0
        private set
}

8. lateinit var (지연 초기화)

  • var + non-null 타입에만 가능
  • DI/테스트에서 종종 사용
lateinit var service: MyService

read-only vs mutable: 컬렉션 인터페이스

코틀린 표준 라이브러리는 컬렉션을 두 계열로 나눈다.

  • 읽기 전용(read-only): List, Set, Map<K,V>
    • 수정 메서드가 인터페이스에 없음
  • 가변(mutable): MutableList, MutableSet, MutableMap<K,V>
    • add/remove/put 등 제공

활용: 외부에는 read-only로 노출, 내부에서만 수정

class Cart {
    private val _items = mutableListOf<String>()
    val items: List<String> get() = _items  // 외부 read-only 뷰

    fun add(item: String) { _items.add(item) }
}

 


코틀린의 스코프 함수

특정 객체를 일시적인 “수신 객체(receiver)” 또는 인자로 두고, 그 블록 안에서 간결하게 다루게 해주는 유틸리티 함수이다.

대표적으로

  • let: it 사용, 결과 반환
  • run: this 사용, 결과 반환
  • apply: this 사용, 자기 자신 반환(초기화에 자주 사용)
  • also: it 사용, 자기 자신 반환(부수효과/로깅에 자주 사용)
  • with: (obj) { ... } 형태, this 사용, 결과 반환

가 있다.

예시 코드

val len = "abc".let { it.length }     // 3
val len2 = "abc".run { length }       // 3

val sb = StringBuilder().apply {
    append("a")
    append("b")
} // sb 자신이 반환됨

프로퍼티 문법(getter/setter)과 backing field

필드 직접 접근처럼 보이지만 실제로는 접근자를 사용하는 형태이다.
field 키워드가 backing field를 참조한다.

var x: Int = 0
    set(value) { field = value.coerceAtLeast(0) }

코틀린의 {}

코틀린에서 {}로 만드는 “코드 블록”은 보통 람다 식(lambda expression) 또는 익명 함수/함수 리터럴(function literal) 이다.

코틀린은 { ... } 블록 자체가 값(객체) 이 될 수 있고, 그래서 함수 인자로 넘기거나 변수에 담을 수 있다.

1. 람다 식 (lambda expression)

가장 흔한 형태이다.

val f: (Int) -> Int = { x -> x + 1 }
println(f(10)) // 11
  • { x -> x + 1 } 가 람다
  • -> 왼쪽은 파라미터, 오른쪽은 본문
  • 마지막 식의 값이 반환값(return 없이)

파라미터가 1개면 it으로 생략 가능하다.

val f: (Int) -> Int = { it + 1 }

2. “트레일링 람다” (trailing lambda)

함수의 마지막 인자가 함수 타입이면, 괄호 밖으로 {}를 뺄 수 있다.

listOf(1, 2, 3).map { it * 2 }

원래는

listOf(1, 2, 3).map({ it * 2 })

이 문법 때문에 “중괄호로 코드블럭 만든다”는 느낌이 강하게 나게 된다.

3. 스코프 함수(let/run/apply/also/with)에서의 {}도 람다

val result = "abc".run {
    length // 마지막 식이 반환
}

여기서 { ... }run에 전달되는 람다이다.

4. 함수 본문 블록 {} (일반 블록)

함수/조건문/반복문에서의 {}는 “값”이라기보다 문(statement) 블록이다.

fun foo() {
    val x = 1
    println(x)
}

if (a > 0) {
    println("positive")
}

다만 코틀린에선 if/when식(expression) 이라서, 블록 안의 마지막 식이 값이 되는 패턴이 섞여 보일 수 있다.

val v = if (a > 0) {
    1
} else {
    0
}