Chapter 3: 객체지향의 본질 - 불변식(Invariant)과 캡슐화
Class Invariants, Heap Layout, Object Header, Effective Java Item 15/16
1. Class vs Object: Engineering View
입문서에서는 클래스를 '붕어빵 틀'이라고 설명하지만, 엔지니어링 관점에서는 "데이터의 구조(Layout)와 불변식(Invariant)의 집합" 입니다.
1.1 Memory Layout (Deep Dive)
객체는 메모리(Heap) 상에서 어떻게 존재할까요? 단순히 필드 값만 저장되는 것이 아닙니다.
Architecture Visualization
- Mark Word: 객체의 주민등록증 같은 것입니다. 이 객체가 현재 Locking 상태인지, GC 대상인지, HashCode는 무엇인지 기록합니다.
- Klass Pointer: 이 객체가 어떤 클래스(Class Meta)의 인스턴스인지 가리킵니다.
2. 불변식(Invariant)과 생성자
핵심 개념 (Effective Java): 생성자는 단순히 값을 넣는 메서드가 아닙니다. "객체가 태어날 때부터 유효한(Valid) 상태임" 을 보장하는 관문입니다.
2.1 불변식이란?
프로그램이 실행되는 동안 "반드시 참이어야 하는 조건" 입니다.
- 예: "나이는 음수일 수 없다", "은행 잔고는 마이너스 통장이 아닌 한 0 이상이어야 한다".
java
1 public class User {2 private int age;3 4 // 생성자: 불변식 수립 (Establish Invariants)5 public User(int age) {6 if (age < 0) {7 throw new IllegalArgumentException("나이는 음수일 수 없습니다.");8 }9 this.age = age;10 }11 }
생성자에서 이 검증을 하지 않는다면, User 객체는 태어날 때부터 '잘못된 상태(Corrupted State)'를 가질 수 있습니다.
3. 정보 은닉(Information Hiding)의 진짜 이유
Effective Java Item 15: 클래스와 멤버의 접근 권한을 최소화하라
private을 쓰는 이유는 "부끄러워서"가 아닙니다. 시스템의 복잡성(Complexity)을 낮추기 위해서입니다.
3.1 엔지니어링 관점의 이득
- API와 구현의 분리: 내부 필드(
private list)를ArrayList에서LinkedList로 바꿔도, 외부 코드는 전혀 수정할 필요가 없습니다. - 불변식 보호:
public필드는 외부에서 언제든user.age = -500이라고 조작할 수 있습니다.private으로 잠그고Setter를 통해 검증 로직을 거치게 해야만 데이터 무결성이 지켜집니다.
4. 변경 가능성(Mutability) 최소화
Effective Java Item 17: 변경 가능성을 최소화하라
가능하다면 모든 필드를 final로 선언하십시오.
- Thread Safety: 불변 객체(Immutable Object)는 동기화 없이도 스레드로부터 안전합니다.
- Simplicity: 상태가 변하지 않으므로, 객체를 생성한 시점의 상태가 파괴될 때까지 유지됩니다.
java
1 // Best Practice: Value Object (Immutable)2 public final class Money {3 private final long amount; // 한 번 정해지면 절대 안 바뀜4 5 public Money(long amount) {6 this.amount = amount;7 }8 9 public Money plus(Money other) {10 // 내 값을 바꾸는 게 아니라(Mutate), 새로운 객체를 만듦(Return New)11 return new Money(this.amount + other.amount);12 }13 }
핵심 요약
- 1객체는 메모리(Heap) 상에서 Header와 Body로 구성되며, Header에는 GC/Lock 정보가 담깁니다.
- 2생성자의 존재 목적은 '초기화'가 아니라 '불변식(Invariant) 수립'입니다.
- 3가능한 한 모든 필드를 private final로 만들어 불변 객체(Immutable)를 지향하십시오. (Effective Java Item 17)
실전 코드 예제
| 1 | public class BankAccount { |
| 2 | // 1. 모든 필드는 private |
| 3 | private long balance; |
| 4 | |
| 5 | public BankAccount(long initialBalance) { |
| 6 | if (initialBalance < 0) throw new IllegalArgumentException(); |
| 7 | this.balance = initialBalance; |
| 8 | } |
| 9 | |
| 10 | // 2. Setter보다는 명확한 '행위'를 나타내는 메서드 제공 |
| 11 | public void deposit(long amount) { |
| 12 | if (amount <= 0) throw new IllegalArgumentException("입금액 오류"); |
| 13 | this.balance += amount; |
| 14 | // Invariant 유지됨: balance는 절대 음수가 될 수 없음 |
| 15 | } |
| 16 | |
| 17 | // 3. Getter는 필요한 경우에만 (방어적 복사 고려) |
| 18 | public long getBalance() { |
| 19 | return balance; |
| 20 | } |
| 21 | } |
Tech Interview Challenge
현업 면접관이 실제로 던지는 질문입니다. 먼저 스스로 답해보세요.
Hard자바의 객체 헤더(Object Header)인 Mark Word에는 어떤 정보가 저장되나요?
자바의 객체 헤더(Object Header)인 Mark Word에는 어떤 정보가 저장되나요?
Mark Word(64bit JVM 기준 8바이트)에는 객체의 HashCode, GC 세대 나이(Age), 그리고 동기화를 위한 Lock 상태(Biased Lock, Lightweight Lock 등) 정보가 비트 단위로 패킹되어 저장됩니다.
Medium왜 Setter를 무분별하게 만들면 안 되나요?
왜 Setter를 무분별하게 만들면 안 되나요?
Setter가 있으면 객체는 언제든 상태가 변할 수 있는 '가변(Mutable)' 상태가 됩니다. 이는 스레드 안전성을 해치고, 객체의 불변식이 언제 어디서 깨졌는지 추적하기 어렵게 만듭니다. 따라서 상태 변경이 꼭 필요한 경우에만, 의도가 명확한 메서드(예: changePassword)를 제공해야 합니다.
