devseop08 님의 블로그
[OOP] 불변 클래스 본문
- 한 번 생성된 객체의 상태(필드 값)가 절대 변하지 않는 클래스를 의미
- 즉, 생성 시점에 정의된 값이 프로그램 종료까지 절대 변경되지 않음을 보장
불변 클래스 특징
- 장점
- 스레드 안정성(Thread-Safety): 상태가 변하지 않으므로 여러 스레드가 동시에 접근해도 동기화 불필요
- 안정성 & 단순성: 외부 코드에서 객체 내부 상태를 바꿀 수 없으므로 예측 가능한 동작 보장
- 함수형 프로그래밍 스타일과 궁합: 데이터가 변경 불가능하기 때문에 변경 대신 새 객체를 생성 -> side effect 최소화
- 다른 클래스에서의 방어적 복사 불필요
- Hash 기반 컬렉션에 적합:
HashMap,HashSet등에서 key로 사용 시 값이 변하지 않으므로 안전.
- 단점
- 객체가 갖는 값마다 매번 새로운 객체가 필요하기 때문에 메모리 누수와 성능 저하 발생
불변 클래스 예시
대표적인 자바 불변 클래스
StringInteger,Long,BigDecimal등 Wrapper 클래스들LocalDate,LocalDateTime
직접 구현
public final class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// getter만 제공, setter 미제공
public String getName(){ return name; }
public int getAge() { return age; }
}불변 클래스 설계 원칙
final클래스로 선언 => 상속 금지(상태 변경 위험 방지)- 모든 필드를
private final로 선언 - setter 메서드(접근 메서드) 제공 금지
- 변경 가능한 객체(Mutable object)를 필드로 가질 경우 해당 필드에 대한 방어적 복사 필요
public final class Student {
private final String name;
private final Date birth; // Date는 mutable 클래스
public Student(String name, Date birth) {
this.name = name;
this.birth = new Date(birth.getTime()); // 방어적 복사
}
public Date getBirth() {
return new Date(birth.getName()); // 방어적 복사본 반환
}
}equals,hashCode,toString메서드 오버라이딩 고려
불변 클래스 vs 가변 클래스
| 구분 | 불변 클래스 | 가변 클래스 |
|---|---|---|
| 상태 변경 | 불가능 | 가능 |
| 스레드 안전 | 기본적으로 안전 | 별도 동기화 필요 |
| 메모리 사용 | 새로운 객체 계속 생성 | 같은 객체 재사용 |
| 예시 | String, Integer, LocalDate |
StringBuilder, ArrayList |
record
record는 JDK 16부터 정식으로 도입된 새로운 타입의 클래스으로, 불변 객체를 간단하게 정의할 수 있도록 설계된 문법적 편의 구문
- 데이터 전용 클래스를 쉽게 작성할 수 있도록 지원
record키워드로 선언- 자동 생성:
private final필드, 생성자,equals(),hashCode(),toString()메서드 - 보일러 플레이트 코드, 즉 불필요하게 반복 작성되는 코드를 줄여준다
record 사용 예시
public record Point(int x, int y) {}
class Main {
public static void main(String[] args) {
Point p1 = new Point(10, 20);
Point p2 = new Point(10, 20);
System.out.println(p1);
System.out.println(p1.equals(p2));
System.out.println(p1.hashCode());
}
}record 특징
- 불변성: 필드가
private final로 고정되므로 객체의 상태가 변경되지 않는다. - 상속 제한: 다른 클래스를 상속할 수도 없고, 다른 클래스에 상속될 수도 없다.
- 인터페이스 구현 가능: 클래스처럼 인터페이스는 구현 가능하다.
- 필드 추가 불가: 자동 생성된 필드 외엔 인스턴스 필드 직접 선언 불가능, static 필드는 가능
- 명시적 선언 시에도
equals,hashCode,toString기본적으로 생성
sealed class: 봉인된 클래스
sealed는 어떤 클래스나 인터페이스를 상속/구현할 수 있는 타입을 제한한다.- 상속 가능한 "하위 클래스 목록"을 명시적으로 선언해야 한다.
- 계층 구조를 명확히 제한하고, 유지 보수성과 안전성을 확보할 수 있다.
public sealed class Shape permits Circle, Rectangle, Square {
...
}sealed class의 하위 클래스 선택지
- sealed 클래스를 상속하거나 sealed 인터페이스를 구현하는 클래스는 반드시 다음 세 가지 중 하나를 선택해야 한다.
final: 더 이상 상속할 수 없음(계층 닫음)
public final class Circle extends Shape {}sealed: 다시 제한을 걸어 특정 클래스에만 상속 허용
public sealed class Rectangle extends Shape permits FilledReatangle {}non-sealed: 상속 제한 없음 => 일반 클래스처럼 누구나 상속 가능
public non-sealed class Square extends Shape {}sealed class 사용 예시
public sealed interface Payment permits CardPayment, CashPayment { }
public final class CardPayment implements Payment {}
public non-sealed class CashPayment implements Payment {}sealed class 장점
- 패턴 매칭과 결합해 사용하면 컴파일러가 타입 체계의 완전성을 보장
static String describe(Shape s) {
return switch(s) {
case Circle c -> "Circle";
case Rectangle r -> "Rectangle";
case Square s1 -> "Square";
}; // switch 구문에 모든 하위 클래스가 다 나열 되어야 컴파일 가능
}- 유지 보수성 향상: 새로운 하위 클래스를 추가할 때, 상위 클래스의 permits 목록을 반드시 수정해야 한다. => 계층 구조가 명확해진다.
- 안전한 상속 모델 제공: 불필요한 상속 구조의 확산을 방지
sealed 인터페이스와 record 조합
public sealed interface Shape permits Circle, Rectangle, Square {
double area();
}
public record Circle(double radius) implements Shape {
@Override
public double area() {
return Math.PI * radius * radius;
}
}
public record Rectangle(double width, double height) implements Shape {
@Override
public double area() {
return width * height;
}
}
public record Square(double side) implements Shape {
@Override
public double area() {
return side * side;
}
}
보일러 플레이트 제거:
record가 getter/생성자/equals/hashCode/toString 자동 생성계층 안전성 확보:
sealed가 상속 가능한 하위 타입을 고정패턴 매칭 완전성:
switch문에서 누락된 타입이 있으면 컴파일 에러 발생
'Language > Java' 카테고리의 다른 글
| [API] StringBuffer, StringBuilder, StringJoiner (0) | 2025.09.15 |
|---|---|
| [API] String (0) | 2025.09.06 |
| [API] Object 클래스와 Class 클래스 (3) | 2025.08.05 |
| [API] 람다와 스트림: Mordern Java - 12. 새로운 날짜와 시간 API (2) | 2025.07.30 |
| [API] 람다와 스트림: Mordern Java - 11. null 대신 Optional 클래스 (0) | 2025.07.21 |