Kotlin in action 2/e: 클래스, 객체, 인터페이스(1) 인터페이스, 클래스, 생성자

2026. 2. 9. 16:04·Kotlin

코틀린의 인터페이스

코틀린의 인터페이스는 다른 언어들에 있는 인터페이스와 거의 동일하다

interface Clickable {
    fun click()
}

class Button : Clickable {
    override fun click() = println("I was clicked")
}

 

예제처럼 interface를 사용하여 정의할 수 있으며, 추상 메서드 click()을 정의하였으므로, Clickable을 구현하는 모든 구체적인 클래스는 click에 대한 구현을 해야한다. 

 

예제의 하단처럼 인터페이스를 구현하는 클래스를 정의하고 싶다면, 클래스 명 뒤에 콜론을 붙이고 인터페이스 이름을 붙이면 된다. 일반적인 상속도 위와 같은 방식으로 사용할 수 있다. 

디폴트 구현을 제공하는 인터페이스 메서드

다만, 코틀린의 인터페이스 안에는 추상 메서드 뿐만 아니라 구현이 있는 메서드도 정의할 수 있다.

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

 

위 같이 정의했을 때 Clickable을 구현하는 클래스는 click에 대한 구현은 해야하지만, showOff 메서드에 대한 구현은 선택사항이 된다. 

 

여러 인터페이스를 구현할 때 디폴트 메서드의 다중 상속 문제 

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

interface Focusable {
    fun setFocus(b: Boolean) =
        println("I ${if (b) "got" else "lost"} focus.")

    fun showOff() = println("I'm focusable!")
}

 

다음과 같이 Clickable, Focusable 인터페이스가 있고 각각의 인터페이스에 동일한 이름과 시그니처의 showOff() 함수가 있다고 했을 때 이를 동시에 상속하는 Button을 만들고 싶을 때, 어떤 showOff 메서드가 선택될지 생각해보자.

class Button : Clickable, Focusable 

 

분명 나 또한 어떤 문제가 발생할 것 같다고 생각했고, 결론적으로 다음과 같은 컴파일 오류가 발생한다. 

 

당연한 일이지만, 컴파일러 입장에서는 어떤 디폴트 구현을 선택할지 결정할 수 없다. 때문에 이러한 오류가 발생한다. 

구현하는 클래스 입장에서 이를 해결하려면 디폴트 메서드를 직접 구현해야 한다. 

class Button : Clickable, Focusable {
    override fun click() = println("I was clicked")

    override fun showOff() {
        super<Clickable>.showOff()
        super<Focusable>.showOff()
    }
}

 

위의 예제에서는 상위 인터페이스의 모든 디폴트 구현을 합치는 방식으로 구현했다. super<상위 타입> 을 통해 특정 상위 타입의 메서드를 호출할 수 있다. 

 

디폴트 구현 메서드가 있는 인터페이스를 자바에서 상속하기

자바는 인터페이스의 디폴트 구현이 존재하지 않는다. 그렇다면 디폴트 메서드가 있는 코틀린 코드를 자바에서 상속한다면 어떻게 될까? 

 

자바에서는 당연하게도 디폴트 구현이 존재하지 않으므로 인터페이스에 있는 메서드 구현을 인식할 수 없다. 때문에 디폴트 구현 메서드의 본문을 해당 인터페이스를 구현하는 자바 클래스에서 다시 작성해야 한다. 

 

class JavaButton implements Clickable {
    @Override
    public void click() {
        System.out.println("I was clicked");
    }
    @Override
    public void showOff() {
    	System.out.println("I'm showing off);
    }
}

 

코틀린의 클래스와 메서드는 기본적으로 final 이다. 

처음 봤을 때 의문이 들었던 부분 중 하나이다. 자바의 클래스는 기본적으로 상속 가능한 상태로 알고있었기 때문에, 코틀린의 클래스 또한 기본적으로 상속 가능한 상태일 것이라 생각했다. 

 

그런데 코틀린의 클래스는 기본적으로 상속이 불가능한 final 이었다. 이는 기본적으로 open으로 설정하는 것이 편리하지만 문제가 될 수도 있기 때문이라고 한다. 

 

책에서는 취약한 기반 클래스(fragile base class) 문제를 예를 들어 설명하고 있다. 취약한 기반 클래스 문제는 기반 클래스 구현을 변경함으로써 하위 클래스가 잘못된 동작을 하게 되는 문제이다.

취약한 기반 클래스 문제를 찾아보던 중 이에 대해 정말 잘 나타나 있는 포스팅을 발견했다! 취약한 기반 클래스 문제

 

사실 자바에서도 이러한 문제 때문에 어떤 메서드를 어떻게 오버라이드 해야 하는지의 방법을 제공해야 한다고 조언하며 자바 프로그래밍 책 중 가장 유명한 책 중 하나인 「이펙티브 자바」에서도 상속을 위한 설계와 문서를 갖추지 않으면 상속을 금지하라는 조언을 한다고도 한다.. 

 

이러한 배경 지식을 정리하고 보니, 코틀린의 디폴트 final이 마치 널 안전성을 위한 처리처럼 약간은 번거로울 수 있지만, 프로그램의 안정성을 높이기 위함이었음을 알 수 있었다. 

 

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}

open class RichButton : Clickable {

    fun disable() {}

    open fun animate() {}

    override fun click() {}
}

 

때문에 코틀린에서는 다음과 같이 open을 class 혹은 fun 앞에 붙여 명시적으로 상속 가능함을 지정해야한다. 

open class RichButton : Clickable {
    final override fun click() {}
}

 

다만 주의해야할 점은 override는 기본적으로 open이라는 점이다. 

만약 오버라이드한 메서드를 다른 클래스가 상속하지 못하게 하려면 명시적으로 다시 final을 붙여줘야 한다. 

 

또한 final로 지정해야 스마트 캐스트 기능을 사용할 수 있다. 이유를 찾아보니 다음과 같이 나와있었다. 

 

클래스 프로퍼티는 val이면서 커스텀 접근자가 없어야 스마트 캐스트를 사용할 수 있는데 해당 프로퍼티를 다른 클래스가 상속하면서 커스텀 접근자를 정의할 가능성이 생기기 때문으로 생각된다. 

직접 간단한 코드를 짜봤는데 분명 될 것 같은데 open이기 때문에 프로퍼티를 사용할 수 없다는 오류가 뜬다. 

 

추상 클래스, 멤버 선언하기

abstract class Animated {
    abstract val animationSpeed: Double
    val keyframes: Int = 20
    open val frames: Int = 60
    
    abstract fun animate()
    open fun stopAnimating() {}
    fun animateTwice() {}
}

 

abstract를 붙여 추상 클래스, 메서드, 프로퍼티를 선언할 수 있다. 추상 멤버나 클래스는 인터페이스처럼 구현을 해야하기 때문에 기본적으로 open인 상태이다. 때문에 open을 앞에 명시하지 않아도 된다!

 

 

 

코틀린의 가시성은 기본적으로 공개(public)이다. 

먼저 책에서 흥미로운 이야기를 제공해줘서 이에 대해 먼저 정리해보려 한다. 

라이브러리 개발자를 위한 명시적 API 모드

애플리케이션 개발자에게 있어 기본적으로 공개인 가시성은 편리한 관습이지만,

라이브러리 개발자에게는 자신의 API가 외부에 노출되는 것을 원치 않으므로 약간은 불편한 관습일 것이라고 한다.

 

이를 위해 코틀린에서는 라이브러리 개발자를 위한 명시적 API 모드를 제공한다. 해당 모드에서는 공개 API로 노출시킬 선언의 가시성을 직접 지정해야 한다. 즉 public, protect, private, 어떤 접근 제한자이던 명시해줘야 한다. 

뿐만 아니라 기존에는 자동 타입 추론 기능이 있기 때문에 식 본문 함수나 프로퍼티 등을 사용할 때 타입을 명시하지 않아도 되었으나, 해당 모드를 활성화하면, 반드시 반환 타입을 명시하도록 한다. 

타입 명시에 대한 이유는 좀 더 생각해본 뒤에 정리해보도록 하겠다.

 

 특이하게도, 코틀린에서는 같은 모듈 내에서만의 가시성 internal을 제공한다. 

internal open class TalkativeButton {
    private fun yell() = println("Hey!")
    protected fun whisper() = println("Let's talk")
}

fun TalkativeButton.giveSpeech() { // public 함수 안에서 internal 타입인 TakativeButton을 참조할 수 없다
    yell()
    
    whisper()
}

 

위 예제는 많은 문제가 있는 코드이다. 사실 처음 이를 봤을 때는 어떤 것이 문제인지 알아차리기 힘들었다. 

 

이 문제점을 파악하기 위해 다음의 규칙 숙지가 필요하다,

 

어떤 클래스의 기반 타입 목록에 들어있는 타입이나 제네릭 클래스의 타입 파라미터에 들어있는 타입의 가시성은 그 클래스 자신의 가시성과 같거나 더 높아야 하며, 

메서드의 시그니처에 사용된 모든 타입의 가시성은 그 메서드의 가시성과 같거나 더 높아야 한다.

 

이는 쉽게 말해 public으로 공개된 가시성을 가지고 있는 녀석이 더 좁은 가시성을 가진 녀석을 드러내면 안된다, 라고 할 수 있겠다. 

 

다시 예제를 살펴보면 다음과 같은 문제가 있음을 확인할 수 있다.

 

giveSpeech는 public이지만, 수신객체 타입이 internal이므로, 위 규칙에 위배된다. 

 

위와 비슷한 결의 문제는 아니지만 다음과 같은 문제점들도 찾아볼 수 있다. 

확장 함수는 클래스의 멤버로 들어가지 못하므로, yell 함수나 whisper 함수는 호출할 수 없다(public이므로 외부에서 호출할 수 없다)

코틀린의 확장 함수에 대해서는  다음의 글에 정리해 두었다. 

https://hynwlee.tistory.com/36

 

Kotlin in action 2/e: 함수 정의와 호출(2) 로컬 함수, 코드를 확장 함수로 추출하기

이번 포스팅에서는 로컬 함수에 대해 정리해보려고 한다, 책에서는 다음과 같이 언급하고 있다. 많은 개발자가 좋은 코드의 중요한 특징 중 하나가 중복이 없는 것이라 믿는다. 심지어 이런 원

hynwlee.tistory.com

 

클래스 안에 클래스 선언하기

자바처럼 코틀린도 클래스 안에 다른 클래스를 선언할 수 있다고 한다. 

사실 나는 지금까지 특정한 경우를 제외하고, 클래스 안에 클래스를 한번도 선언해본적이 없는 것 같다. 이번 챕터를 학습하면서 이를 왜 사용하는지에 대해 자세히 알아보고자 한다. 

 

우선 일반적으로 내부 클래스를 선언하는 이유는 다음과 같아보인다. 

 

도우미 클래스를 캡슐화하거나 코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때, 

코드 정의를 그 코드를 사용하는 곳 가까이에 두고 싶을 때, 

 

개인적으로는 함수 안에 함수를 선언하는 로컬 함수의 취지와 비슷해 보인다는 생각이 들었다. 하지만 코틀린에는 바깥쪽 클래스에 접근할 수 없는 클래스도 있었다...

 

내포 클래스(Nested Class)

내포 클래스는 명시적으로 요청하지 않는 한 바깥쪽 클래스 인스턴스에 대한 접근 권한이 없다. 그렇다면 바깥쪽 클래스와 연관이 없다는 것인데 언제 이를 사용할지 정말 의문이 들었다.

 

책에서는 다음과 같이 설명하고 있다.

interface State: Serializable

interface View {
    fun getCurrentState(): State
    fun restoreState(state: State) {}
}

 

이러한 View 요소를 하나 만든다고 가정 했을 때, 그 View의 상태를 직렬화 해야 하는 상황이 있을 수 있다. 

 

책에서는 이러한 상황에 State 인터페이스를 선언하고, Serializable을 구현하는 예제와

interface State: Serializable

 

추가적으로 뷰의 상태를 가져와 저장할 때 사용할 getCurrentState, restoreState 메서드를 선언해둔 View 인터페이스 예제를 제시한다. 

interface View {
    fun getCurrentState(): State
    fun restoreState(state: State) {}
}

 

이런 상황에서 View 클래스를 구현하는 Button 클래스를 만든다고 했을 때, 자바로는 다음과 같이 짜볼 수도 있을 것이다.

public class Button implements View {
    @Override
    public State getCurrentState() {
        return new ButtonState();
    }
    
    @Override
    public void restoreState(State state) { /*...*/ }
    
    public class ButtonState implements State { /*...*/ }
}

 

하지만 이는 문제가 발생하는 코드이다. 선언한 버튼의 상태를 직렬화 하면 java.io.NotSerializableException: Button 이라는 오류가 발생하게 된다.

 

자바에서 다른 클래스 안에 정의한 클래스는 자동으로 내부(inner) 클래스가 된다. 

자바에서는 다른 클래스 안에 정의한 클래스는 자동으로 내부 클래스가 된다. 그러면 ButtonState 클래스는 바깥쪽의 Button 클래스에 대한 참조를 암시적으로 가지게 된다.

class ButtonState {
    View Button; // 자동으로 생김 (암시적 참조)
    // ...
}

 

그런데 View는 직렬화 할 수 없다.(Serializable을 구현하지 않았기 때문에) 

결론적으로 View를 ButtonState에서 직렬화 불가한 요소를 참조하게 되므로 ButtonState가 직렬화 될 수 없는 것이다.

중첩된 클래스 중에 왜 바깥 참조를 가지지 않는 클래스가 등장했는지에 대한 의문이 어느정도 해소되었다!

 

이를 해결하기 위해서는 ButtonState를 static 클래스로 선언해야 한다. 자바에서는 static으로 내포(nested) 클래스를 선언할 수 있다고 한다.   

 

코틀린에서 다른 클래스 안에 정의한 클래스는 자동으로 내포(nested) 클래스가 된다.

코틀린은 위에서 설명한 자바와 정반대의 기본값을 가지고 있다. 기본적으로 내포(nested) 클래스로 생성된다, 아까도 언급했지만, 로컬 함수는 외부 함수를 참조 가능한 것이 디폴트이지만, 클래스는 기본적으로 외부를 못보는 내포 상태인 것에 대한 이유가 궁금했다.

이에 대한 이유는 아무래도 직렬화와도 관련이 있을 것이라 생각하지만, 추후 좀 더 자세히 조사하여 정리해보려고 한다. 

 

다시 돌아와서 자바에서 static을 붙여서 내포 클래스로 만드는 것처럼 코틀린에서도 내포 클래스에 inner를 붙이면 바깥을 볼 수 있는 내부 클래스가 된다.

내포 클래스와 내부 클래스

접근은 위와 같이 this@클래스명으로 접근 가능하다. 

 

 

확장이 제한된 클래스 계층 정의

우테코 프리코스를 하면서도, when을 사용할 때, 특히 이부분에 대해 의아했던 기억이 있다. 이 책에 2장에서도 내가 맞닥뜨렸던 부분과 동일한 부분이 나온다. 

interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.right) + eval(e.left)
        else -> throw IllegalArgumentException("Unknown expr")
    }

 

Expr을 분명 Num, Sum으로만 구현했으므로 다른 구현체가 없음은 컴파일러가 판단할 수 있지 않을까? 하는 생각이 들었다. 하지만 when에서는 else를 추가하지 않으면 에러가 뜨기에 의문이 들었던 적이 있다. 

 

그렇지만, 프리코스를 하면서는 Enum을 쓰면 When에서 else를 쓰지 않아도 되는구나! 까지만 알게되니, 정작 클래스 레벨에서는 else를 어떻게 제외하고 쓸 수 있었는지에 대해 자세히 찾아보지 않았던 것 같다. 

 

else를 왜 생략하려고 할까?

 

그렇다면 왜 else를 생략하고자 할까? 책에서는 다음과 같은 문제점이 발생할 수 있다고 설명한다. 

 

디폴트 분기, 즉 else가 존재하게 되면 컴파일러가 when이 모든 경우를 제대로 처리하는 지 제대로 검사할 수 없다.

 

만약 실수로 새로운 클래스에 대한 처리를 잊어버리더라도, else 분기가 불리기 때문에 심각한 버그가 발생할 가능성이 생긴다. 

왜 else를 지양해야 하는지에 대해서는 좀 더 조사하여 정리하려고 한다. 

 

sealed를 붙여 봉인 클래스, 인터페이스 선언하기

다행히 책에서 위와 같은 내용을 다루고 있다. 바로 봉인된 클래스(sealed class) 이다. 상위 클래스에 sealed 변경자를 붙이면 그 상위 클래스를 상속한 클래스의 가능성을 제한할 수 있다. 

 

위의 예제에 대입하여 생각해 봤을 때, Expr 인터페이스를 seaded class로 만들고 Num과 Sum을 Expr의 하위 클래스로 만들면 될 것이다. 

sealed class Expr
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()

fun eval(e: Expr): Int =
    when (e) {
        is Num -> e.value
        is Sum -> eval(e.right) + eval(e.left)
    }

 

else 분기가 없어졌다! 때문에 이제 새로운 클래스를 만들고 이를 분기에 추가하지 않으면 자동으로 오류가 발생할 것을 예상해 볼 수 있을 것이다. 

예상대로 Mul 클래스를 추가하고 when에 분기를 추가하지 않았더니 IDE에서 에러를 발견하여 알려주는 것을 확인할 수 있다. 

뿐만 아니라 sealed를 interface 앞에 붙여 봉인된 인터페이스도 선언할 수 있으므로 상황에 맞게 잘 사용하면 될 것 같다!

 

뻔하지 않은 생성자나 프로퍼티를 갖는 클래스 선언

책에서는 객체지향 언어에서 클래스는 보통 생성자를 하나 이상 선언할 수 있다고 소개한다. 사실 나는 지금까지 하나의 객체에 생성자는 하나면 될 것이라는 생각을 가지고 있었다.

 

특히 코틀린의 생성자에서도 디폴트 파라미터를 지원하기 때문에 이를 사용하는 것 이상의 기능이 필요한가? 라는 생각이 들었다. 

이에 대한 설명을 하기 전에 코틀린에서 클래스를 편리하게선언할 때 사용할 수 있는 몇몇 기술을 정리해보려고 한다. 

 

init 블록, constructor 사용하기

class User constructuor(_nickname: String) {
    val nickname: String
    
    init {
        nickname = _nickname
    }
]

 

constructor 키워드는 주 생성자나 부 생성자, 즉 생성자의 정의를 시작할 때 사용한다.

 

주 생성자에서는 별도의 코드를 포함할 수 없기 때문에, init 키워드를 사용하여 클래스의 객체가 만들어질 때 실행되는 블록을 정의할 수 있다. 

 

"_" 기호는 프로퍼티와 생성자 파라미터의 nickname을 서로 구별해주기 위해 명시적으로 붙인 기호이다. Java나 C++에서 사용하던 this를 생각하면 될 것이다, 때문에 _대신 this를 사용하여 구별해줘도 무방하다. 

 

하지만 지금까지의 코틀린의 철학을 생가했을 때 , 위 코드는 조금 복잡해보인다. 결론적으로 아래 코드는 위의 코드와 동일한 동작을 수행한다. 

class User(val nickname: String)

 

코틀린에서는 별다른 어노테이션, 가시성 변경자가 없다면 constructor를 생략해도 주 생성자로 인식해준다, 

뿐만 아니라 생성자의 파라미터 앞에 val, var 등을 지정해주면 자동으로 프로퍼티를 생성하고 대입해준다. 그러므로, 위 코드에서 수행했던 클래스 내 프로퍼티 선언이나, init 블록에서의 프로퍼티 대입 등의 코드를 생략할 수 있다. 

class User(val nickname: String,
           val isSubscribed: Boolean = true)

 

추가적으로 위에서 잠깐 언급했듯이, 함수의 파라미터에 디폴트 값을 채울 수 있는 것처럼 클래스 생성자 파라미터에도 디폴트 값을 지정해줄 수 있다. 

 

심지어 만약 모든 파라미터에 디폴트 값을 지정해 준다면, 컴파일러가 자동으로 파라미터가 없는 생성자를 만들어 준다고 한다. 

코틀린의 주 생성자와 부 생성자

코틀린 주 생성자와 부 생성자를 구분한다. 주 생성자는 우리가 익히 알던 클래스의 생성자이다. 문법적으로 말하면, 클래스 이름 뒤에 오는 괄호로 둘러싸인 코드를 주 생성자라고 할 수 있겠다. 

class User(val nickname: String)

 

User 뒤에 오는 코드가 주 생성자이다. 

그렇다면 주 생성자의 역할은 무엇일까? 책에서는 다음과 같은 두 역할을 주 생성자가 수행한다고 한다. 

 

1. 생성자 파라미터 지정

 

2. 생성자 파라미터에 의해 초기화되는 프로퍼티를 정의

 

부모 클래스의 생성자 호출

또한 어떤 클래스의 부모 클래스의 생성자가 인자를 받아야 한다면 클래스의 주 생성자에서 기반 생성자를 호출하여야 한다. 

open class User(val nickname: String) { /*...*/ }

class SocialUser(nickname: String): User(nickname) { /*...*/ } // 기반 클래스의 생성자를 호출해야 한다.

 

주의해야 할 점은 기반 클래스에서 생성자를 정의하지 않은 경우이다. 이 경우에도 컴파일러가 자동으로 아무것도 하지 않는 디폴트 생성자를 만들기 때문에 하위 클래스에서 빈 생성자를 호출해야 한다.

open class User() { /*...*/ }

class SocialUser(nickname: String): User() { /*...*/ } // 기반 클래스의 생성자를 호출해야 한다.

 

이와 비슷한 경우로 인터페이스의 경우를 생각해볼 수 있을 것이다. 하지만 인터페이스는 생성자가 없기 때문에 이를 구현하는 클래스는 생성자를 호출하지 않아도 된다!

생성자에 private 붙이기

생성자에 private을 붙이면 문법 그대로 외부에서 생성자를 호출할 수 없다. 사실 이러면 외부에서 객체를 생성할 수 없다. 내 기억상 싱글턴 패턴의 동작 방식이 생성자를 private으로 구현해두고, init 블록에서 객체를 하나 만들고 마는, 그런 방식으로 동작하는 것으로 기억나긴 한다.

생성자에 private을 붙이는 것에 대한 자세한 용도는 추후 더 정리하도록 하겠다. 

 

부 생성자는 왜 필요할까?

책에서는 다음과 같은 내용을 언급한다. 

실제로 대부분의 경우 클래스의 생성자는 아주 단순하다. 생성자에 아무 파라미터도 없는 클래스도 많고 생성자 코드 안에서 생성자가 인자로 받은 값을 프로퍼티에 설정하기만 하는 생성자도 많다. 

 

나 또한 위와 같은 내용에 공감이 되었다. 그렇다면, 클래스의 주 생성자가 아니라 왜 부 생성자를 만들어놓은걸까? 책에는 다음과 같은 부 생성자의 역할이 소개된다. 

 

자바와의 상호 운용성

 

일반적으로 코틀린에서는 자바에 비해 생성자가 여러개 있는 경우는 훨씬 드물다고 한다. 이는 역시 이전에 설명했듯이 디폴트 파라미터를 지원하기 때문에, 하나의 생성자에서도 여러 버전의 호출이 가능하기 때문이다. 

 

때문에 자바에서 코틀린의 생성자를 봤을 때는 디폴트 파라미터 값이 보이지 않기 때문에 @JvmOverloads 어노테이션을 붙여주거나, 직접 부 생성자를 선언해둬야 한다. 

 

생성할 때 아예 다른 파라미터 목록이 필요할 때

 

이러한 경우에는 디폴트 파라미터 만으로는 커버할 수 없기 때문에, 부득이 하게 부 생성자가 필요한 경우가 있을 것이다. 

 

 

 

 

'Kotlin' 카테고리의 다른 글

Kotlin in action 2/e: 람다를 사용한 프로그래밍(1) 코틀린의 람다  (0) 2026.02.14
Kotlin in action 2/e: 클래스, 객체, 인터페이스(2) 기본 구현 메서드, by, object  (0) 2026.02.13
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' 카테고리의 다른 글
  • Kotlin in action 2/e: 람다를 사용한 프로그래밍(1) 코틀린의 람다
  • Kotlin in action 2/e: 클래스, 객체, 인터페이스(2) 기본 구현 메서드, by, object
  • Kotlin in action 2/e: 함수 정의와 호출(2) 로컬 함수, 코드를 확장 함수로 추출하기
  • Kotlin in action 2/e: 함수 정의와 호출(1) joinToString() 함수 직접 구현
Kirbyyy
Kirbyyy
개인적인 일상과 회고를 기록하는 블로그입니다.
  • Kirbyyy
    커브볼의 생존일지
    Kirbyyy
  • 전체
    오늘
    어제
    • 분류 전체보기 (53)
      • 우아한테크코스 (8)
      • 프로덕트 빌드 (0)
      • Problem Solving (20)
      • C++ (0)
      • Kotlin (19)
      • Java (3)
      • CS (2)
        • AI (2)
      • 취미생활 (0)
        • 서평 (0)
        • 프라모델 (0)
  • 블로그 메뉴

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

  • 공지사항

  • 인기 글

  • 태그

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

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
Kirbyyy
Kotlin in action 2/e: 클래스, 객체, 인터페이스(1) 인터페이스, 클래스, 생성자
상단으로

티스토리툴바