독서

[코틀린 인 액션] 4장. 클래스, 객체, 인터페이스

오렌지색 귤 2024. 4. 7. 21:23
반응형

찾아본 내용

 

1. 중첩 클래스는 기본적으로는 내부 클래스가 아니다. 즉, 코틀린 중첩 클래스에는 외부 클래스에 대한 참조가 없다. (p. 142)

 

중첩 클래스 (Nested Class)

  • 중첩 클래스는 기본적으로 static 클래스와 유사하게 동작합니다. 이는 중첩 클래스가 외부 클래스의 인스턴스 없이도 생성될 수 있다는 것을 의미합니다.
  • 중첩 클래스는 외부 클래스의 인스턴스에 대한 참조를 자동으로 가지고 있지 않습니다. 즉, 외부 클래스의 속성이나 메소드에 직접 접근할 수 없습니다.
  • 코틀린에서 중첩 클래스는 특별한 키워드 없이 클래스 내부에 다른 클래스를 선언함으로써 생성됩니다.

 

내부 클래스 (Inner Class)

  • 내부 클래스는 inner 키워드를 사용하여 선언됩니다. 이 키워드는 클래스가 외부 클래스의 인스턴스에 대한 참조를 유지해야 함을 나타냅니다.
  • 내부 클래스는 외부 클래스의 인스턴스 멤버에 접근할 수 있습니다. 즉, 외부 클래스의 속성과 메소드를 마치 자신의 것처럼 사용할 수 있습니다.
  • 내부 클래스는 외부 클래스의 인스턴스가 생성되어야만 사용할 수 있습니다.

 

class Outer {
    private val bar: Int = 1

    // 중첩 클래스
    class Nested {
        fun foo() = 2
    }

    // 내부 클래스
    inner class Inner {
        fun foo() = bar
    }
}

fun main() {
    // 중첩 클래스 인스턴스 생성 및 사용
    val nested = Outer.Nested().foo() // 2

    // 내부 클래스 인스턴스 생성 및 사용
    val outer = Outer()
    val inner = outer.Inner().foo() // 1, 외부 클래스의 private 멤버에 접근 가능
}

 

 

 

2. "클래스를 확장한 함수는 그 클래스의 private이나 protected 멤버에 접근할 수 없다는 사실.." 이라는 문장에서 함수가 클래스를 확장할 수 있다는 말이 무슨 말인가? (p.152)

 

코틀린에서 제공하는 "확장 함수(Extension Functions)" 기능을 언급하는 것이다.

 

 

 

 

3. 프로퍼티가 val인 경우에는 게터에 field가 없으면 되지만, var인 경우에는 게터나 세터 모두에 field가 없어야 한다. (p.169)

 

이 문장은 코틀린에서 프로퍼티의 백킹 필드(backing field)와 관련된 규칙을 설명하고 있습니다. 코틀린에서는 프로퍼티를 정의할 때 자동으로 게터(getter)와 세터(setter)를 제공합니다. 이때, 프로퍼티의 값은 백킹 필드라고 하는 숨겨진 필드에 저장됩니다. 그러나 개발자는 게터와 세터를 직접 정의할 수도 있습니다.

 

 

val 프로퍼티의 경우

  • val로 선언된 프로퍼티는 읽기 전용입니다. 즉, 값을 한 번 할당하면 변경할 수 없습니다. 따라서 val 프로퍼티는 기본적으로 게터만 가집니다.
  • 게터에서 field를 사용하지 않으면, 코틀린 컴파일러는 백킹 필드를 생성하지 않습니다. 여기서 field는 현재 프로퍼티의 값을 저장하거나 검색하는 데 사용되는 특별한 식별자입니다.
  • val 프로퍼티의 경우 게터에서 field를 사용하지 않아도 됩니다. 예를 들어, 계산된 값을 반환하는 게터를 정의할 수 있습니다.

 

var 프로퍼티의 경우

  • var로 선언된 프로퍼티는 가변입니다. 즉, 값을 할당한 후에도 변경할 수 있습니다. 따라서 var 프로퍼티는 게터와 세터 모두를 가집니다.
  • var 프로퍼티의 경우, 커스텀 게터나 세터 중 하나라도 field를 사용하지 않는다면, 컴파일러는 백킹 필드를 생성하지 않습니다. 그러나 이는 특별한 경우입니다. 일반적으로 var 프로퍼티의 세터에서는 값을 설정할 때 field를 사용해야 하며, 게터에서는 field를 통해 현재 값을 읽습니다.
  • 게터와 세터 모두에서 field를 사용하지 않으면, 프로퍼티가 백킹 필드 없이 동작해야 함을 의미합니다. 이는 보통 프로퍼티가 단순한 값이 아니라 어떤 계산을 통해 얻어지거나 다른 방식으로 관리되어야 할 때 유용합니다.

 

class Sample {
    val simpleVal: Int = 10
        get() = field // 여기서 field 사용

    val calculatedVal: Int
        get() = (Math.random() * 100).toInt() // 여기서 field 사용하지 않음

    var simpleVar: Int = 10
        get() = field // 여기서 field 사용
        set(value) {
            field = value // 여기서 field 사용
        }

    var noBackingFieldVar: Int
        get() = (Math.random() * 100).toInt() // 여기서 field 사용하지 않음
        set(value) {
            // 여기서 field 사용하지 않음. 실제 값을 저장하려면 다른 방법을 사용해야 함.
        }
}

 

위 예제에서 simpleValsimpleVar는 백킹 필드를 사용하는 기본적인 방법을 보여줍니다. 반면, calculatedValnoBackingFieldVar는 게터(및 noBackingFieldVar의 세터)에서 field를 사용하지 않으므로 백킹 필드를 사용하지 않습니다. noBackingFieldVar의 경우, var 프로퍼티임에도 불구하고 백킹 필드가 없으므로, 실제로 값을 저장하거나 변경하기 위한 다른 메커니즘이 필요합니다.

 

 

궁금한 내용

 

1. 코틀린 object와 스프링 빈으로 띄워진 코틀린 class는 어떤 차이가 있을까? (p.184)

 

코틀린의 object 키워드와 스프링 프레임워크에서 관리하는 빈(Bean)으로 선언된 클래스는 서로 다른 목적과 사용 사례를 가지고 있으며, 내부적으로 관리되는 방식도 다릅니다. 이들의 주요 차이점을 이해하려면 각각의 개념과 특성을 살펴보는 것이 중요합니다.

 

코틀린 object

  • 싱글턴 패턴 구현: 코틀린의 object 키워드는 싱글턴 패턴을 쉽게 구현하도록 도와줍니다. object로 선언된 클래스는 애플리케이션 전체에서 단 하나의 인스턴스만을 가지게 됩니다. 코틀린 컴파일러가 이를 보장합니다.
  • 컴파일 타임에 결정: object의 인스턴스는 프로그램 실행 시 자동으로 생성되며, 이 인스턴스에 접근하기 위한 전역 접근 지점을 제공합니다. 이는 컴파일 타임에 결정되며, 런타임에 추가적인 인스턴스가 생성되지 않습니다.
  • 스태틱 멤버 사용: object 내에 정의된 모든 프로퍼티와 함수는 자바의 static 멤버와 유사하게 작동합니다. 이는 object 인스턴스 없이도 접근할 수 있음을 의미합니다.

 

스프링 빈으로 선언된 코틀린 클래스

  • 스프링 IoC 컨테이너 관리: 스프링 빈으로 선언된 클래스는 스프링의 제어 역전(IoC) 컨테이너에 의해 관리됩니다. 이는 클래스의 인스턴스 생명주기를 스프링 컨테이너가 관리함을 의미합니다.
  • 런타임에 결정: 스프링 빈의 생성과 의존성 주입은 런타임에 이루어집니다. 개발자는 빈의 생성 방법, 스코프(scope), 의존성 등을 설정할 수 있으며, 이를 통해 더 유연한 구성이 가능합니다.
  • 스코프에 따른 인스턴스 관리: 스프링 빈은 싱글턴, 프로토타입 등 다양한 스코프를 가질 수 있습니다. 기본적으로 싱글턴 스코프로 관리되어 애플리케이션 내에서 단 하나의 인스턴스만 존재하지만, 필요에 따라 스코프를 변경하여 사용할 수 있습니다.

 

주요 차이점 요약

  • 관리 방법: object는 코틀린 언어 차원에서 제공하는 싱글턴 패턴 구현체이며, 컴파일 타임에 그 특성이 결정됩니다. 반면, 스프링 빈은 스프링 프레임워크의 IoC 컨테이너에 의해 관리되며, 런타임에 그 특성이 결정됩니다.
  • 유연성과 설정: 스프링 빈은 생성 방법, 생명주기, 의존성 주입 방식 등을 유연하게 설정할 수 있으며, 다양한 스코프를 지원합니다. 반면, object는 싱글턴 인스턴스에 접근하는 것이 주 목적이며, 이에 대한 설정이나 변형이 제한적입니다.
  • 사용 사례: object는 애플리케이션 내에서 단일 인스턴스가 필요한 경우 간단하게 사용할 수 있습니다. 예를 들어, 유틸리티 함수나 애플리케이션의 전역 상태 관리에 적합합니다. 스프링 빈은 복잡한 애플리케이션에서 의존성 주입을 통한 모듈 관리, 생명주기 관리, 다양한 스코프를 필요로 하는 경우에 사용됩니다.

 

 

2. 동반 객체 선언과 팩토리 메서드로 만들어진 객체는 일반 인스턴스인가 혹은 싱글턴인가? (p. 187)

 

동반 객체(Companion Object)

  • 동반 객체는 클래스 내에 정의된 companion object 키워드를 사용하여 선언된 싱글턴 객체입니다. 즉, 각 클래스당 하나의 인스턴스만 존재합니다.
  • 동반 객체의 주된 용도는, 클래스와 연관된, 인스턴스에 속하지 않는 메서드나 프로퍼티를 보유하는 것입니다. 이를 통해 자바의 정적 메소드(static methods)나 프로퍼티(static properties)와 유사한 기능을 코틀린에서 구현할 수 있습니다.
  • 동반 객체는 해당 클래스 타입의 인스턴스가 아니라, 독립된 싱글턴 객체입니다. 클래스의 인스턴스와는 별개로 존재합니다.

 

팩토리 메서드로 만들어진 객체

  • 팩토리 메서드는 객체를 생성하는 메서드로, 객체의 생성 과정을 추상화하고, 호출하는 측에서는 구체적인 클래스 타입을 몰라도 객체를 생성할 수 있도록 합니다.
  • 팩토리 메서드는 동반 객체 내에 정의될 수도 있고, 다른 객체나 함수로 정의될 수도 있습니다.
  • 팩토리 메서드로 생성된 객체는 일반적으로 일반 인스턴스입니다. 즉, 팩토리 메서드를 호출할 때마다 새로운 인스턴스가 생성될 수 있습니다. 그러나 팩토리 메서드의 구현 방식에 따라 싱글턴 객체를 반환하도록 설계할 수도 있습니다. 예를 들어, 팩토리 메서드 내에서 싱글턴 패턴을 적용하여 항상 동일한 인스턴스를 반환하도록 할 수 있습니다.

 

결론

  • 동반 객체는 싱글턴입니다: 각 클래스에 대해 단 하나의 인스턴스만 존재합니다.
  • 팩토리 메서드로 만들어진 객체는 일반적으로 일반 인스턴스입니다: 팩토리 메서드를 호출할 때마다 새로운 객체 인스턴스가 생성될 수 있습니다. 그러나 구현에 따라 싱글턴 객체를 반환하도록 설계할 수도 있습니다.

 

 

 

3. 코틀린에서는 메서드가 끝나면서 변수가 초기화되지 않는것인가? 자바와는 다르게 람다식이나 익명 클래스에서 외부 로컬 변수를 사용할 수 있는 이유는 무엇인가? (p.194)

 

코틀린과 자바에서 무명 객체(익명 클래스) 또는 람다식 내부에서 외부 로컬 변수를 사용하는 방식은 다르게 동작하는데요, 코틀린의 경우 이러한 차이가 나타나는 주된 이유는 코틀린이 자바에 비해 람다식과 클로저에 있어서 더 유연한 규칙을 적용하기 때문입니다.

 

자바의 경우

자바에서는 람다식 또는 익명 클래스 내부에서 사용되는 외부 로컬 변수가 effectively final이어야 합니다. 즉, 해당 변수는 명시적으로 final로 선언되지 않았더라도, 람다식이나 익명 클래스가 생성되는 시점 이후로 값이 변경되어서는 안 됩니다. 이러한 제약은 람다식 또는 익명 클래스가 생성될 때 캡처된 변수의 값을 유지하기 위해 필요합니다. 변수의 값이 변경될 경우, 람다식이나 익명 클래스 내부에서 참조하는 값과 실제 변수의 값 사이에 불일치가 발생할 수 있기 때문입니다.

 

 

코틀린의 경우

코틀린에서는 이러한 제약이 더 유연합니다. 코틀린의 람다식 또는 무명 객체 내에서 외부 로컬 변수의 값을 변경할 수 있습니다. 이는 코틀린이 람다식이나 무명 객체를 포함하는 함수의 로컬 변수를 "파이널(final)"로 취급하지 않기 때문입니다. 대신, 코틀린은 이러한 변수를 "효과적으로 final"로 취급하지 않고, 해당 변수를 람다식이나 무명 객체에서 변경할 수 있도록 허용합니다.

 

이러한 유연성 뒤에는 코틀린 컴파일러가 변수를 적절히 처리하고, 필요한 경우 래핑(wrapper) 객체를 사용하여 변수가 변경 가능하도록 만드는 추가적인 작업을 수행한다는 점이 있습니다. 코틀린에서는 변경 가능한 외부 로컬 변수를 캡처하는 람다식이나 무명 객체 내부에서도 해당 변수를 마치 최종(final) 상태처럼 안전하게 참조할 수 있도록 컴파일 타임에 필요한 처리를 합니다.

반응형