티스토리 뷰
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
반응형
'Java' 카테고리의 다른 글
| [Kotlin] JPA Entity를 data class 로 사용할 경우 문제 (0) | 2026.01.11 |
|---|---|
| [Kotlin] JPA Entity @Id 필드 선언 방식 (0) | 2026.01.11 |
| [Java] openjdk:11-jdk-slim 이미지 사용 시 POI 엑셀 기능 불가 (0) | 2026.01.10 |
| [Java] Jackson @JsonAnySetter, @JsonAnyGetter (2) | 2025.07.08 |
| [Java] Jackson readValue(), convertValue() (0) | 2025.04.20 |
반응형
300x250