티스토리 뷰

Java

[Kotlin] nested class vs. inner class

snail voyager 2026. 1. 11. 00:19
728x90
반응형

nested class

  • 기본값: 아무 키워드 없이 클래스 안에 선언
  • 외부 클래스의 프로퍼티나 메서드에 접근할 수 없음
  • Java로 치면 static class 와 동일
class Outer {
    private val message = "Hello"

    class Nested {
        fun print() {
            // println(message) // ❌ 외부 클래스 접근 불가
            println("I'm a nested class")
        }
    }
}

// 사용
val nested = Outer.Nested()
nested.print()

inner class

  • inner 키워드를 사용해야 함
  • 외부 클래스의 프로퍼티나 메서드에 접근 가능
  • Java의 non-static inner class 와 같음
class Outer {
    private val message = "Hello from Outer"

    inner class Inner {
        fun print() {
            println(message)  // ✅ 외부 클래스 접근 가능
        }
    }
}

// 사용
val outer = Outer()
val inner = outer.Inner()
inner.print()

요약 비교표

항목 nested class inner class
키워드 기본 (class) inner class
외부 클래스 참조 ❌ 없음 (정적) ✅ 있음 (비정적)
바이트코드 변환 static class non-static class
사용 목적 독립적 로직 캡슐화 외부 클래스 상태 활용
메모리 구조 외부 클래스 없이 생성 가능 외부 클래스 인스턴스 필요

protected nested class

  • 외부 클래스(Outer) 를 상속한 하위 클래스에서만 접근할 수 있습니다.
  • 외부 클래스의 필드나 메서드에 접근할 필요 없음
  • 구현 세부를 외부 API로 노출하고 싶지 않음
open class Container {
    protected class Nested {
        fun sayHi() = "Hi"
    }
}

class Child : Container() {
    fun getHi(): String {
        return Nested().sayHi()
    }
}

예제 1: 템플릿 메서드 패턴

  • Context는 상속받은 클래스에서만 의미 있음
  • 외부 인스턴스를 참조할 필요 없음
  • 메모리 누수 위험 없음
  • API 노출 최소화
open class BaseProcessor {

    fun process() {
        val context = Context()
        doProcess(context)
    }

    protected open fun doProcess(context: Context) {
        // 기본 처리
    }

    // 확장 포인트용
    protected class Context {
        var success: Boolean = false
    }
}

class CustomProcessor : BaseProcessor() {

    override fun doProcess(context: Context) {
        context.success = true
    }
}

protected inner class

  • protected inner class는 외부 클래스(Outer) 를 상속한 하위 클래스에서만 접근할 수 있습니다.
  • 외부에서 직접 Outer.Inner()처럼 접근하는 것은 불가능합니다.
  • inner 키워드를 쓰면 외부 클래스의 참조를 가지므로, 일반적으로 외부 클래스와 강하게 연결된 구조입니다.
  • 외부 클래스의 상태를 “강하게” 공유해야 하는 경우
  • 내부 클래스가 외부 클래스의 상태를 직접 읽거나 변경해야 함
open class Outer {
    protected inner class Inner {
        fun hello() = "Hello from Inner"
    }
}

class SubOuter : Outer() {
    fun accessInner(): String {
        val inner = Inner()  // ✅ 접근 가능: 하위 클래스니까
        return inner.hello()
    }
}

fun main() {
    val outer = SubOuter()

    // val inner = outer.Inner() // ❌ 접근 불가: protected는 외부 클래스에서 못 씀
    println(outer.accessInner())  // ✅ 정상 출력
}

예제: 상태 머신(State Machine)

  • State가 isConnected를 직접 변경해야 함
  • 외부 클래스 인스턴스와 1:1 관계
open class Connection {

    protected var isConnected = false

    protected inner class State {
        fun connect() {
            isConnected = true
        }

        fun disconnect() {
            isConnected = false
        }
    }

    protected fun state(): State = State()
}

class SecureConnection : Connection() {

    fun secureConnect() {
        val state = state()
        state.connect()
        println(isConnected) // true
    }
}

메모리 누수 주의점(inner class의 외부 참조)

Kotlin의 inner class는 외부 클래스의 인스턴스에 대한 참조 (this@Outer)를 자동으로 포함합니다.
이 말은 곧, 내부 클래스 객체가 살아 있는 동안 외부 클래스 객체도 GC(Garbage Collection) 되지 않는다는 뜻입니다.

class Activity {
    private val resource = ByteArray(1024 * 1024 * 100) // 100MB 메모리

    inner class LeakyInner {
        fun doSomething() {
            println("Using resource: ${resource.size}")
        }
    }

    fun start() {
        val leak = LeakyInner()
        // leak 객체를 다른 곳에 넘기거나 오래 살게 하면...
    }
}
  • LeakyInner가 다른 스레드나 전역 변수 등에 저장되면 GC 되지 않음
  • LeakyInner가 Activity를 참조하고 있으므로 Activity도 메모리에서 해제되지 않음
  • 100MB짜리 리소스가 메모리에 계속 남아 있음

해결 방법

1. inner class 대신 nested class 사용 (외부 참조 제거)

class Activity {
    private val resource = ByteArray(1024 * 1024 * 100)

    class SafeNested {
        fun doSomething() {
            println("No outer access")
        }
    }
}

 

2. WeakReference 사용

import java.lang.ref.WeakReference

class Activity {
    private val resource = ByteArray(1024 * 1024 * 100)

    class SafeInner(activity: Activity) {
        private val activityRef = WeakReference(activity)

        fun doSomething() {
            val activity = activityRef.get()
            activity?.let {
                println("Using resource: ${it.resource.size}")
            }
        }
    }
}

3. object 또는 companion object 등 사용으로 중첩 클래스 구조 피하기

 

일단 protected nested class로 시작하고,
정말 외부 상태 참조가 필요할 때만 protected inner class를 쓴다.

 

 

728x90
반응형
반응형
300x250