본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
7장 : 클래스와 객체

생성자와 불변식

생성자는 객체가 사용되기 전에 필요한 상태를 한 번에 준비합니다.

필수 값이 빠진 반쯤 완성된 객체를 만든 뒤 setter로 채우는 방식은 그 사이 잘못된 상태를 허용합니다.

생성자 본문보다 먼저 필드 초기화와 상위 생성자 호출이 이루어지는 순서를 이해해야 합니다.

여러 생성자는 가장 완전한 생성자로 위임해 검증을 한곳에 모을 수 있습니다.

이 절에서는 객체가 태어나는 순간부터 지켜야 할 불변식을 정합니다.


필수 상태를 생성자 매개변수로 받는다

객체가 동작하는 데 반드시 필요한 값은 생성 시점에 요구합니다.

null이나 범위를 검사하고 잘못된 값이면 즉시 예외를 냅니다.


this는 현재 객체를 가리킨다

매개변수와 필드 이름이 같을 때 this.field로 필드를 구분합니다.

this(...)는 같은 클래스의 다른 생성자를 첫 문장에서 호출합니다.

ConstructorRules.java는 편의 생성자가 기본 레벨을 채워 대표 생성자로 위임합니다. 두 호출 모두 같은 검증 코드를 거친다는 점이 새 생성자를 추가할 때 중요한 기준입니다.

ConstructorRules.java
public class ConstructorRules {
    static final class User {
        private final String name;
        private final int level;

        User(String name) {
            this(name, 1);
        }

        User(String name, int level) {
            if (name == null || name.isBlank()) throw new IllegalArgumentException("name");
            if (level < 1) throw new IllegalArgumentException("level");
            this.name = name;
            this.level = level;
        }

        String label() { return name + "@" + level; }
    }

    public static void main(String[] args) {
        System.out.println(new User("Ada").label());
        System.out.println(new User("Linus", 3).label());
        // new User("", 1); // 실행 시 IllegalArgumentException
    }
}

매개변수와 필드 이름이 같을 때 this.name은 현재 객체의 필드, name은 생성자 매개변수입니다. this를 빼면 매개변수를 자기 자신에게 대입해 필드가 초기화되지 않는 실수를 할 수 있습니다.

Ada@1
Linus@3

한 인자 생성자는 기본 level을 채워 주 생성자에 위임하므로 검증 경로가 하나입니다.


생성자 위임은 초기화 경로를 하나로 모은다

편의 생성자가 기본값을 채워 주 생성자를 호출하면 검증과 필드 대입이 중복되지 않습니다.

위임이 순환하지 않게 합니다.

여러 생성자가 this(...)로 대표 생성자에 모이면 검증과 필드 대입이 한 경로에서만 일어납니다. 새 필드를 추가했을 때 모든 생성자를 따로 고치는 대신 대표 생성자 하나를 수정하면 불변식이 갈라지지 않습니다.

class Point {
    final int x;
    final int y;

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
}

// 생성자가 끝났다면 모든 final 필드는 반드시 초기화돼 있어야 한다.

생성 중 this 노출을 피한다

생성자에서 콜백을 등록하거나 재정의 가능한 메서드를 호출하면 초기화가 끝나기 전에 외부가 객체를 볼 수 있습니다.

생성 완료 뒤 별도 팩터리나 시작 메서드를 사용합니다.

생성자 안에서 재정의 가능한 메서드를 호출하거나 this를 외부 저장소에 넘기면 초기화가 끝나기 전에 다른 코드가 객체를 관찰할 수 있습니다. 객체 공개는 모든 필드 검증과 대입이 끝난 뒤 팩터리나 호출부에서 하도록 둡니다.


좋은 생성자는 필드를 채우는 함수가 아니라 객체가 유효하다는 사실을 보장하는 입구입니다.