Kotlin in Action 2/e: 코틀린 기초(1): 함수, 변수, 클래스, 프로퍼티, 이넘, when()

2026. 2. 5. 18:04·Kotlin

함수, 변수, 클래스, 이넘, 프로퍼티

Hello, world를 출력하는 프로그램을 통해 알 수 있는 점

fun main() {
	println("Hello, world")
}

위 코드는 전형적인 예제인 “Hello, world”를 출력하는 프로그램을 코틀린을 작성한 것이다. 정말 간단한 프로그램이지만 어떤 언어든 이 프로그램 만으로도 그 언어의 많은 부분을 보여준다.

 

책에서도 비슷한 맥락으로 위 코드를 이용해 다양한 부분을 설명하고 있다. 정리하면 다음과 같다.

 

함수를 선언할 때, fun 키워드를  사용한다.

 

함수를 모든 코틀린 파일의 최상위 수준에 정의할 수 있으므로 클래스 안에 함수를 넣어야 할 필요가 없다

 

최상위에 있는 main 함수를 애플리케이션의 진입점으로 지정할 수 있다, 이때 main에 인자가 없어도 된다.

 

최신 프로그래밍 언어 경향과 마찬가지로 줄 끝에 세미콜론을 붙이지 않아도 좋다.

 

위 부분들 중에서 특히 main에 인자가 없어도 된다는 점에 주목해볼 수 있을 것 같다는 생각이 들었다.

 

사실 JVM 위에서 실행되는 대표적인 언어인 자바만 하더라도 public static void main(String[] args)처럼 외부에서 들어오는 인자가 없더라도 규격을 지켜야 프로그램이 실행될 수 있다고 알고있었는데 코틀린은 이러한 부분을 생략할 수 있도록 했다는 부분이 인상깊었다. 

 

또한 비교적 최신 언어가 줄 끝에 세미콜론을 붙이지 않는다는 체감은 하고 있었으나, 이게 최신 프로그래밍 언어 경향인지는 이번에 처음 생각해보게 되었는데, 조사해본 바로는 다음과 같은 이유로 이런 경향이 발생한 것으로 보인다. 

 

이전의 컴파일러에서는 세미콜론을 이용하여 문장이 끝났는지 여부를 컴파일러가 알 수 있게 하였으나, 프로그래머들이 세미콜론을 빠뜨려 오류가 발생되거나, 굳이 세미콜론이 아니라 개행을 통해 컴파일러가 문장의 끝을 추론하도록 바뀌었다. 

아래는 코틀린 2.0 깃허브에서 찾아본 문장의 끝을 판단하는 함수이다. https://github.com/JetBrains/kotlin/blob/v2.0.0/compiler/psi/src/org/jetbrains/kotlin/parsing/AbstractKotlinParsing.java#L178-L186

private boolean tokenMatches(IElementType token, IElementType expectation) {
    if (token == expectation) return true; // 기대한 토큰과 일치하면?
  
    if (expectation == EOL_OR_SEMICOLON) { // 만약 파서가 기대하는 것이 문장의 끝이라면
        if (eof()) return true;            // 파일이 끝이어도 ok
        if (token == SEMICOLON) return true; // 세미콜론이어도 ok
        if (myBuilder.newlineBeforeCurrentToken()) return true; // 세미콜론이 없고, 줄바꿈이어도 ok
    }
    return false;
}

파일의 끝이거나 세미콜론, 개행이면 true, 즉 문장의 끝이라고 판단하는 것으로 보인다. 특이한 점은 그냥 기댓값이 토큰과 일치하는지에 대한 판단도 수행하는 것으로 보인다. 단일 책임 원칙에 위배되는 것이 아닌가 생각했지만,

 

일반적인 프로그래밍이 아닌 컴파일러 등에 사용될 로우레벨 프로그래밍임을 감안한다면, 어떤 효율적인 의도가 있을 것이라 생각된다. 

이에 대해서는 나중에 추가로 조사하여 정리하려고 한다. 

코틀린의 함수

책에서는 간단한 함수를 선언하는 방법을 들어 함수에 대해 설명하고 있다. 

위 함수는 이름에서도 알 수 있듯이 Int형 값 2개를 인자로 받아 더 큰 값을 반환하는 함수이다. 책에서는 다음과 같은 내용을 알 수 있다고 설명하고 있다. 

 

함수 선언은 fun 키워드로 시작한다. 

 

fun 다음에는 함수 이름이 온다. 

 

함수 이름 뒤에는 괄호 안에 파라미터 목록이 온다.

 

코틀린에서는 파라미터 이름이 먼저 오고 그 뒤에 그 파라미터의 타입을 지정하며, 타입과 이름을 콜론으로 구분한다. 

 

함수의 반환 타입은 파라미터 목록을 닫는 괄호 다음에 오며, 콜론으로 구분된다. 

 

사실 위의 내용들은 다른 프로그래밍 언어와 거의 공통된 부분이기 때문에 한번에 보고 이해될 만한 내용일 것이다. 함수라는 내용에 벗어나긴 하지만, 위의 내용에서 강조하고 싶은 부분은 바로 return 문에 if문이 있다는 점일 것이다.

 

사실 이는 다른 언어에서 쉽게 볼 수 있는 삼항 연산자를 좀 더 가독성 좋게 if문을 사용하여 표현한 것이다. 위 return 문을 삼항 연산자를 사용해 나타내면 다음과 같다. 

(a > b) ? a : b

 

삼항 연산자에 익숙한 사람들이라면 이 식이 쉽게 읽힐 것이다.

 

나 또한 그렇지만, 이 문법을 처음 봤을 때를 생각하면 절대 쉽게 읽히는 문법은 아니었던 것 같다. 그런 측면에서 코틀린의 if문의 이러한 확장은 다음과 같이 읽을 수 있다.

 

"만약 a가 b보다 크면 a, 아니면 b"

 

이런 식으로 그대로 자연어로 읽히기 때문에 좀 더 가독성이 좋지 않나 생각이 든다.

실제로 반환값이 있는 if문을 사용하면서 삼항 연산자의 사용보다 좀 더 사용하기 쉽다고 느꼈던 것 같다. 

 

책에서는 이러한 차이를 문(statement), 식의 차이라고 추가적으로 설명하고 있다. 

 

문(statement)은 자신을 둘러싸고 있는 가장 안쪽 블록의 최상위 요소로  존재하며 값을 만들어 내지 않으며

식은 값을 만들어내며 다른 식의 하위요소로 계산에 참여할 수 있다. 

 

때문에 코틀린의 if는 값을 만들어내므로 문이 아니라 식이라고 할 수 있으며, 미리 설명하자면, 코틀린은 루프(for, while, do/while)을 제외한 대부분의 제어 구조가 식이다. 즉 값을 만들어 낼 수 있다. 

 

식 본문을 이용해 함수를 더 간결하게 정의 

코틀린의 함수는 다음과 같은 두 가지의 종류로 나눌 수 있다. 

블록 본문 함수

식 본문 함수

블록 본문 함수는 위에서 정의했던 max 함수를 예로 들을 수 있다. 중괄호로 묶여있는 하나의 블록이 함수의 본문을 이룬다. 

 

식 본문 함수는 이름에서도 알 수 있듯이 식 자체가 함수의 본문이 될 수 있는 경우이다. 이해를 위해 max 함수를 식 본문 함수로 바꿔보면 다음과 같다. 

fun max (a: Int, b: Int): Int = if(a > b) a else b

 

if문은 아까 설명했듯 반환값을 가지는 식임을 생각하면 더 이해가 쉬울 것이다. 

결론적으로 이와 같은 변환을 통해 함수를 좀 더 간결하게 나타낼 수 있으며, 심지어는 식 본문 함수에 한하여  반환 타입도 생략 가능하다. 

fun max (a: Int, b: Int) = if(a > b) a else b

 

코틀린의 변수

val question: String = 
    "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer: Int = 42

val question1 = 
    "삶, 우주, 그리고 모든 것에 대한 궁극적인 질문"
val answer1 = 42

 

책에서는 다음과 같은 예제를 통해 변수의 선언에 대해 설명한다. 위 예제를 통해 눈여겨 볼 수 있는 점은 타입 추론이 가능하기에 마치 식 본문 함수처럼 타입을 지정해줄수도, 혹은 생략할 수도 있다는 점이다.

 

하지만 다음과 같이 변수의 값을 나중에 초기화 할 때는 반드시 타입을 지정해야 한다

val answer: Int
answer = 42

 

또한 코틀린의 변수는 val 혹은 var로 선언할 수 있는데, val은 읽기 전용 변수이고, var은 재대입 가능한 변수이다. 초반에 val과 var이 굉장히 헷갈렸는데, value(중요하기 때문에 못바꿈), varable(다양하다는 뜻이니까 바꿀 수 있음)으로 외웠던 기억이 있는데 책에도 실제로 그런 유래에서 따왔다는 사실에 놀랐다. 

 

중요한 사실은 코틀린에서는 기본적으로 val 키워드를 사용해 선언하고 필요할 때만 var를 사용하는 것을 권장한다는 것이다. 이는 IDE에서도 지원하고 있어 이미 알고 있던 사실이었다. 그만큼 코틀린은 val 사용을 적극 권장한다.

인텔리제이에서 var를 사용했을 때.

실제로 var로 변수를 선언하고 이를 수정하는 코드를 발견하지 않으면 경고를 보내고, val로 자동으로 바꿔주는 것까지도 지원해주는 것을 확인할 수 있다. 

코틀린의 클래스 

코틀린의 간결성을 가장 잘 나타낼 수 있는 부분이지 않나 생각한다. 책에서는 자바 클래스를 코틀린 클래스와 비교하며 설명하고 있다. 

public class Person {
	private final String name;
    
    public Person(String name) {
    	this.name = name;
    }
    
    public String getName() {
    	return name;
    }
}

 

위 코드는 우선 자바의 간단한 클래스 코드이다. 굉장히 정석적인 객체지향의 형태를 띄고 있다. 그런데 name이라는 프로퍼티를 갖는 클래스를 만드는데 굉장히 많은 타이핑을 해야한다, 멤버변수 선언, 생성자, 게터 등..

 

앞선 글에서 소개했듯이 코틀린은 '간결성'을 상당히 중요시한다. 그렇지만 솔직히 코틀린을 배우기 전의 나에게 이 코드를 줄일 수 있냐고 물어보면,  각각의 코드가 서로 다른 분명한 일을 하고 있다고 생각하기에 게터함수 자동생성? 정도를 생각했을 것 같다. 

 

하지만 코틀린은 이를 획기적으로 줄여냈다. 

class Person(val name: String)

 

이 코틀린 코드는 위의 자바 코드와 동일한 클래스 선언이다. 특히 책에서는 위 Person 클래스를 사용하는 자바, 코틀린 코드 예제를 보여주곤, 자바, 코틀린 어느 클래스 코드를 사용해도 각각의 코드를 바꿀 필요가 없다고 설명했다. 자바와 코틀린의 상호 운용성에 대해 직접 체감할 수 있는 좋은 예제였던 것 같다.  

 

여기서 코틀린 클래스의 다음과 같은 특징을 알 수 있다. 

 

코틀린의 클래스는 기본적으로 public 클래스이므로 생략해도 된다. 

 

클래스 생성자를 클래스 이름 뒤에 바로 붙일 수 있다. 

 

클래스 본문을 생략 가능하다. 

 

자동으로 게터 세터를 생성해준다

 

생성자에서 val 혹은 var를 붙여 프로퍼티를 선언할 수 있다. 

 

여기서 좀 더 자세히 들어다보면, var로 선언한 프로퍼티는 수정 가능하므로 게터, 세터 둘다 만들어지지만, val로 선언한 프로퍼티는 읽기 전용이기 때문에 세터가 생성되지 않는다. 

그렇다면 자동으로 구현되는 게터, 세터는 어떻게 구현될까? 당연하게도 게터는 해당 필드 값 반환, 세터는 해당 필드 값 변경이라고 할 수 있겠다. 

 

프로퍼티 접근자의 커스텀 구현 작성하기

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean 
        get() {
            return height == width
        }
}

 

위의 코드는 책에서 소개하는 코틀린의 커스텀 게터 구현의 예제 코드이다. 결론적으로 Rectangle 객체에서 rectangle.isSquare 이런식으로 isSquare 프로퍼티에 접근하게 되면 위의 연산이 수행되고 결과가 반환된다. 

프로퍼티에 접근할 때만 계산이 수행되며, 따로 값을 저장할 필요가 없는데, 책에서는 이를 온더고(On The Go) 프로퍼티라고 소개하고 있다. 

이에 대해서는 나중에 추가로 조사하여 정리하려고 한다. 

 

사실 돌아보면 지금까지 코틀린 프로그래밍을 하면서 커스텀 게터를 구현하기 보다는 거의 멤버 함수를 사용했었다. 책에서는 마침 이와 관련된 내용을 설명하고 있는데, 커스텀 게터, 멤버함수 사이에 성능과 구현 차이는 없지만, 가독성의 차이가 있다고 말하고 있다. 

 

원래 일반적으로으로 클래스의 특성(프로퍼티)를 나타내고 싶을 때는 프로퍼티로 이를 정의해야 하고, 클래스의 행동을 기술하고 싶다면 멤버함수를 선택해야 한다고 한다. 실제로 공식 문서에도 이와 관련된 내용이 나와있다.

https://kotlinlang.org/docs/coding-conventions.html#functions-vs-properties

앞으로 클래스를 만들 때, 멤버함수, 프로퍼티를 적절히 사용할 수 있도록 해야겠다는 생각이 들었다. 

 

 

코틀린의 이넘과 when

이제 코틀린의 이넘과 when에 대해 설명해보려고 한다. 

이넘(enum)은 enumerated type의 줄임말로 한글로 "열거형" 이라는 말로 불리기도 한다. 책에서는 이넘으로 설명하기 때문에 나 또한 이넘으로 설명하려고 한다. 

 

이전에는 이넘을 단순히 가독성을 위해 요일문자를 숫자로 치환시키는 등의 기능만 사용했었다. 물론 이 기능도 이넘의 핵심적인 기능이라고 할 수 있겠지만, 우테코에서 코틀린을 사용하면서 이넘, 특히 when과 함께 사용하는 방법을 많이 배웠었다. 책의 예제를 바탕으로 이에 대해 설명하도록 하겠다. 

 

enum class Color(
    val r: Int,
    val g: Int,
    val b: Int
) {
    RED(255, 0, 0),
    ORANGE(255, 165, 0),
    YELLOW(255, 255, 0),
    GREEN(0, 255, 0),
    BLUE(0, 0, 255),
    INDIGO(75, 0, 130),
    VIOLET(238, 130, 238); // 프로퍼티, 멤버함수, 동반 객체 등을 선언하려면 반드시 세미콜론으로 구분한다.

    val rgb: Int
        get() = (r * 256 + g) * 256 + b
    fun printColor() = println("$this is $rgb")
}

fun main() {
    println(Color.BLUE.rgb)
    //255
    Color.GREEN.printColor()
    // GREEN is 65280
}

 

이넘 클래스 Color를 만들어서 RED, ORANGE... 등등 각 색깔에 대한 상수를 만들고 있다. 눈여겨볼 점은 이넘 클래스이기 때문에 생성자를 선언할 수 있으며, 각 이넘 상수 또한 마치 이넘 클래스의 객체인 것처럼 선언되어있다는 것이다. 

또한 앞서 언급했던 프로퍼티, 멤버 함수 또한 가질 수 있다.  코드에서는 숫자로 된 rgb값을 돌려주는 rgb 프로퍼티와 색상을 출력하는 printColor 멤버함수가 정의된 것을 알 수 있다. 

또한 상수 선언이 끝난 후, 메서드나 프로퍼티 혹은 동반객체(companion object)를 선언하려면, 상수 선언을 끝낸다는 뜻으로 세미콜론을 반드시 붙여야한다. 

 

이제 이 이넘 클래스를 when으로 다루는 방법에 대해 알아보도록 하겠다. 

when으로 이넘 클래스 다루기

when은 사실 다른 언어에 있는 switch와 비슷하면서도 좀 더 강력한 기능을 제공해주는 문이라고 생각하면 될 것 같다. when또한 문이 아니라 식이다. 때문에 책에서도 이를 이용하여 when을 이용한 식 본문 함수를 예제로 제공하고 있다.

fun getMnemonic(color: Color) =
    when(color) {
        RED -> "빨강"
        ORANGE -> "주황"
        YELLOW -> "노랑"
        GREEN -> "초록"
        BLUE -> "파랑"
        INDIGO -> "남색"
        VIOLET -> "보라"
    }

fun main() {
    println(getMnemonic(Color.BLUE))
    // 파랑
}

다른 언어의 switch를 사용해보거나, 이넘과 함께 코틀린의 when을 사용해보지 않은 사람들은 다른 점을 눈치 챘을 수도 있겠지만, else를 사용하지 않아도 된다. 이는 color가 Color 이넘 클래스 타입이고, 가능한 모든 분기에 대해 기재하였으므로 else를 사용하지 않아도 된다. 

 

번외로, 이렇게 when 안에 값이 어떤 것일지 자명할때는, IDE에서는  alt+enter를 누르면 다음과 같이 모든 분기를 완성해주는 기능도 제공한다. 

철저한 when

앞서 경우에서는 when안에 이넘 상수 타입이 들어가기도 했고, when의 본문에서 모든 이넘 상수를 처리한다. 책에서는 이러한 모든 가능한 경로에서 값을 만들어내는 when에 대해 철저하다, 라고 표현하고 있다. 그렇다면 철저한 when은 왜 필요할까? 

 

모든 가능한 경로에서 값을 만들어내지 않게 된다면, 그 로직은 결과를 예측할 수 없게될 것이다. 때문에 when에서 모든 이넘 상수를 처리하지 않거나 다른 임의의 객체가 온 경우, else 키워드를 사용해 디폴트 케이스를 제공해야 한다. 다행히 컴파일러는 이를 감지할 수 있으며, IDE단에서도 자동으로 모든 분기를 추적하여 빈 분기가 있으면 에러를 띄워준다.

 

when의 분기 조건에 임의의 객체 사용하기, 인자 없는 when 사용하기

이전에 언급했듯이 코틀린의 when은 다른 언어의 비슷한 역할인 switch보다 훨씬 유연하다, 특히 when()의 분기 조건에서 임의의 객체를 사용하거나, 혹은 when에 인자를 던져주지 않을 수도 있다. 

fun mix(c1: Color, c2: Color) =
    when (setOf(c1, c2)) {
        setOf(RED, YELLOW) -> ORANGE
        setOf(YELLOW, BLUE) -> GREEN
        setOf(BLUE, VIOLET) -> INDIGO
        else -> throw Exception("Dirty Color")
    }

fun main() {
    println(mix(BLUE, YELLOW))
    // GREEN
}

 

책에서는 위 예제에서 when 분기 조건을 비교하기 위해 setOf()가 계속 호출되므로 여러 Set인스턴스가 생성된다고 지적하고 있다. 때문에 인자 없는 when을 사용해 가독성을 조금 희생하고, 성능을 더 향상시킨 버전도 제안하고 있다. 

fun mixOptimized(c1: Color, c2: Color) =
    when {
        (c1 == RED && c2 == YELLOW) ||
        (c1 == YELLOW && c2 == RED) ->
            ORANGE

        (c1 == YELLOW && c2 == BLUE) ||
        (c1 == BLUE && c2 == YELLOW) ->
            GREEN

        (c1 == BLUE && c2 == VIOLET) ||
        (c1 == VIOLET && c2 == BLUE) ->
            INDIGO

        else -> throw Exception("Dirty Color")
    }

개인적인 생각으로는 물론 해당 함수 호출이 비약적으로 늘어날 경우에는 성능이 좀 더 개선된 버전이 알맞겠지만, 일반적인 상황에서는 가독성의 이점이 있는 setOf()를 사용하는 버전을 사용하는게 좋지 않을까 생각이 들었다.

물론, 성능과 가독성의 트레이드 오프 관계를 잘 고려하여 상황에 맞는 코드를 설계하는 것이 가장 최선이겠다는 생각도 들게 되었다. 

'Kotlin' 카테고리의 다른 글

Kotlin in action 2/e: 함수 정의와 호출(2) 로컬 함수, 코드를 확장 함수로 추출하기  (0) 2026.02.09
Kotlin in action 2/e: 함수 정의와 호출(1) joinToString() 함수 직접 구현  (0) 2026.02.07
Kotlin in Action 2/e: 코틀린 기초(2): 스마트 캐스트, if를 when으로 리팩터링, while, for, 예외처리  (0) 2026.02.06
Kotlin in Action 2/e: 코틀린 코드의 컴파일  (0) 2026.02.05
Kotlin in Action 2/e: 책 소개 및 서론  (0) 2026.02.04
'Kotlin' 카테고리의 다른 글
  • Kotlin in action 2/e: 함수 정의와 호출(1) joinToString() 함수 직접 구현
  • Kotlin in Action 2/e: 코틀린 기초(2): 스마트 캐스트, if를 when으로 리팩터링, while, for, 예외처리
  • Kotlin in Action 2/e: 코틀린 코드의 컴파일
  • Kotlin in Action 2/e: 책 소개 및 서론
Kirbyyy
Kirbyyy
개인적인 일상과 회고를 기록하는 블로그입니다.
  • Kirbyyy
    커브볼의 생존일지
    Kirbyyy
  • 전체
    오늘
    어제
    • 분류 전체보기 (53)
      • 우아한테크코스 (8)
      • 프로덕트 빌드 (0)
      • Problem Solving (20)
      • C++ (0)
      • Kotlin (19)
      • Java (3)
      • CS (2)
        • AI (2)
      • 취미생활 (0)
        • 서평 (0)
        • 프라모델 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    C++
    백준 11123
    다이나믹 프로그래밍
    백준 파도반 수열
    백준 31575
    백준 16173
    BFS
    분할 정복
    백준 RGB 거리
    Problem Solving
    백준 연속 합
    백준 16174
    백준 알고리즘
    너비 우선 탐색
    백준 1356
    우테코 8기
    그리디 알고리즘
    백준
    ProblemSolving
    백준 33272
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Kirbyyy
Kotlin in Action 2/e: 코틀린 기초(1): 함수, 변수, 클래스, 프로퍼티, 이넘, when()
상단으로

티스토리툴바