본문 바로가기

생성자와 this

초기화가 누락된 객체의 실패를 재현하고 생성자와 this로 유효한 Member의 시작 상태를 보장합니다.

객체가 생성된 뒤 필드를 하나씩 대입하면 “만들어졌지만 아직 쓸 수 없는” 시간이 생깁니다.

다른 메서드가 그 중간 상태를 먼저 사용하거나 한 필드 대입을 빼먹을 수 있습니다.

생성자는 객체 생성과 필수 초기화를 하나의 연산으로 묶어 처음부터 유효한 상태를 만들게 합니다.


초기화 누락

lab/MissingInitialization.java
public final class MissingInitialization {
    public static void main(String[] args) {
        Member member = new Member();
        member.age = 40;

        System.out.println(member.email.toUpperCase());
    }

    private static final class Member {
        String email;
        int age;
    }
}
실패 관찰
Exception in thread "main" java.lang.NullPointerException

new Member()는 필드를 기본값으로 초기화합니다.

참조형 email은 null, int age는 0입니다.

호출자가 age만 대입했으므로 email의 메서드를 호출할 때 실패했습니다.

초기화 누락 예제에서 필드 값과 실패 시점

new의 email=null/age 0에서 age만 40으로 바꾸고 email 값 읽기 성공 뒤 toUpperCase 호출에서 실패합니다. 초기화 누락과 역참조 실패의 다른 시점을 보여 줄 수 있습니다.

초기화 누락 예제에서 필드 값과 실패 시점

MissingInitialization에서 email은 null로 남고 age만 40으로 바뀐다. null 값 읽기는 가능하지만 그 값으로 toUpperCase를 호출하면 예외가 발생한다.

정상 생성 뒤에도 누락된 email은 null로 남는다

age만 대입한 객체에서 이메일을 읽는 것과 메서드를 호출하는 것을 구분합니다.

MissingInitialization의 같은 객체를 실행 순서대로 추적합니다.

실행 문장별 email과 age 상태 및 결과
실행 시점객체 필드관찰
생성 직후email = null

age = 0

new Member()가 정상 참조를 반환합니다. 필드는 언어 기본값입니다.
나이 대입 후email = null

age = 40

member.age = 40은 이메일을 초기화하지 않습니다.
이메일 사용email = null

age = 40

member.email 읽기는 성공합니다. 이어지는 toUpperCase() 호출에서 NullPointerException이 발생합니다.
정상 생성 뒤에도 누락된 email은 null로 남는다 newMember기본null0→age40,emailnull유지→email필드읽기결과null·age40유지→null.toUpperCase에서NPE. 필드읽기와메서드호출구분,예외뒤null40유지. new Member() email=null · age=0 필드는 Java 기본값 age = 40 email=null 유지 email 초기화는 없음 email 필드 읽기 결과 null · 읽기는 성공 필드: null / 40 toUpperCase() 호출 null 대상 메서드 호출 NullPointerException null 필드 읽기는 성공 · null 대상 메서드 호출에서 예외 · email은 계속 null
정상 생성 뒤에도 누락된 email은 null로 남는다 newMember기본null0→age40,emailnull유지→email필드읽기결과null·age40유지→null.toUpperCase에서NPE. 필드읽기와메서드호출구분,예외뒤null40유지. new Member() email=null · age=0 필드는 Java 기본값 age = 40 email=null 유지 email 초기화는 없음 email 필드 읽기 결과 null · 읽기는 성공 필드: null / 40 toUpperCase() 호출 null 대상 메서드 호출 NullPointerException 예외 뒤 필드값은 null / 40 기본값은 업무상 유효값과 다름
  1. 생성 직후

    new Member()는 정상 참조를 반환합니다. 필드는 email = null, age = 0입니다.

  2. 나이 대입 후

    member.age = 40 이후에도 email = null이며, age = 40만 달라집니다.

  3. 이메일 사용

    member.email 읽기는 성공하지만, 그 값으로 toUpperCase()를 호출하면 NullPointerException이 발생합니다. 필드 값은 그대로입니다.

이 예제의 기본 생성자는 이메일 필수 조건을 검사하지 않습니다. 정상 생성과 업무상 유효한 초기 상태는 같은 뜻이 아닙니다.

이 예의 조건

  • email 초기화가 누락된 원 순서입니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2new Member의 기본 null 0→age 40, email null 유지→email 필드 읽기 결과 null·age 40 유지→null.toUpperCase에서 NPE가 발생한다. 필드 읽기와 메서드 호출을 구분하며 예외 뒤에도 null 40을 유지한다.
11필드: null
1240
원 도표의 설명과 주석
구분내용
설명new Member의 기본 null 0→age 40, email null 유지→email 필드 읽기 결과 null·age 40 유지→null.toUpperCase에서 NPE가 발생한다. 필드 읽기와 메서드 호출을 구분하며 예외 뒤에도 null 40을 유지한다.
전체 읽기 정보초기화 누락 예제에서 필드 값과 실패 시점 초기화 누락 예제에서 필드 값과 실패 시점 MissingInitialization에서 email은 null로 남고 age만 40으로 바뀐다. null 값 읽기는 가능하지만 그 값으로 toUpperCase를 호출하면 예외가 발생한다. 정상 생성 뒤에도 누락된 email은 null로 남는다 age만 대입한 객체에서 이메일을 읽는 것과 메서드를 호출하는 것을 구분합니다. MissingInitialization 의 같은 객체를 실행 순서대로 추적합니다. 실행 문장별 email과 age 상태 및 결과 실행 시점 객체 필드 관찰 생성 직후 email = null age = 0 new Member() 가 정상 참조를 반환합니다. 필드는 언어 기본값입니다. 나이 대입 후 email = null age = 40 member.age = 40 은 이메일을 초기화하지 않습니다. 이메일 사용 email = null age = 40 member.email 읽기는 성공합니다. 이어지는 toUpperCase() 호출에서 NullPointerException 이 발생합니다. 생성 직후 new Member() 는 정상 참조를 반환합니다. 필드는 email = null , age = 0 입니다. 나이 대입 후 member.age = 40 이후에도 email = null 이며, age = 40 만 달라집니다. 이메일 사용 member.email 읽기는 성공하지만, 그 값으로 toUpperCase() 를 호출하면 NullPointerException 이 발생합니다. 필드 값은 그대로입니다. 이 예제의 기본 생성자는 이메일 필수 조건을 검사하지 않습니다. 정상 생성과 업무상 유효한 초기 상태는 같은 뜻이 아닙니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
기본 필드

new Member 뒤 email null, age 0입니다.

  • email: null
  • age: 0
  • 정상 객체 생성이 필수 업무 필드 설정을 대신하지 않습니다.
일부 대입과 읽기

age 40만 대입하고 email 필드를 읽습니다.

  • 필드: null/40
  • email 읽기: 성공, 결과 null
  • 필드 값 null을 읽는 것은 그 값의 메서드 호출과 다릅니다.
메서드 실패

null.toUpperCase를 호출해 NPE입니다.

  • 예외 뒤 필드: null/40 유지
  • 실패 대상: null 참조
  • 메서드 예외가 누락된 필드를 자동 초기화하지 않습니다.

초기화 누락 예제에서 필드 값과 실패 시점

new의 email=null/age 0에서 age만 40으로 바꾸고 email 값 읽기 성공 뒤 toUpperCase 호출에서 실패합니다. 초기화 누락과 역참조 실패의 다른 시점을 보여 줄 수 있습니다.

초기화 누락 예제에서 필드 값과 실패 시점

MissingInitialization에서 email은 null로 남고 age만 40으로 바뀐다. null 값 읽기는 가능하지만 그 값으로 toUpperCase를 호출하면 예외가 발생한다.

정상 생성 뒤에도 누락된 email은 null로 남는다

age만 대입한 객체에서 이메일을 읽는 것과 메서드를 호출하는 것을 구분합니다.

MissingInitialization의 같은 객체를 실행 순서대로 추적합니다.

실행 문장별 email과 age 상태 및 결과
실행 시점객체 필드관찰
생성 직후email = null

age = 0

new Member()가 정상 참조를 반환합니다. 필드는 언어 기본값입니다.
나이 대입 후email = null

age = 40

member.age = 40은 이메일을 초기화하지 않습니다.
이메일 사용email = null

age = 40

member.email 읽기는 성공합니다. 이어지는 toUpperCase() 호출에서 NullPointerException이 발생합니다.
정상 생성 뒤에도 누락된 email은 null로 남는다 newMember기본null0→age40,emailnull유지→email필드읽기결과null·age40유지→null.toUpperCase에서NPE. 필드읽기와메서드호출구분,예외뒤null40유지. new Member() email=null · age=0 필드는 Java 기본값 age = 40 email=null 유지 email 초기화는 없음 email 필드 읽기 결과 null · 읽기는 성공 필드: null / 40 toUpperCase() 호출 null 대상 메서드 호출 NullPointerException null 필드 읽기는 성공 · null 대상 메서드 호출에서 예외 · email은 계속 null
정상 생성 뒤에도 누락된 email은 null로 남는다 newMember기본null0→age40,emailnull유지→email필드읽기결과null·age40유지→null.toUpperCase에서NPE. 필드읽기와메서드호출구분,예외뒤null40유지. new Member() email=null · age=0 필드는 Java 기본값 age = 40 email=null 유지 email 초기화는 없음 email 필드 읽기 결과 null · 읽기는 성공 필드: null / 40 toUpperCase() 호출 null 대상 메서드 호출 NullPointerException 예외 뒤 필드값은 null / 40 기본값은 업무상 유효값과 다름
  1. 생성 직후

    new Member()는 정상 참조를 반환합니다. 필드는 email = null, age = 0입니다.

  2. 나이 대입 후

    member.age = 40 이후에도 email = null이며, age = 40만 달라집니다.

  3. 이메일 사용

    member.email 읽기는 성공하지만, 그 값으로 toUpperCase()를 호출하면 NullPointerException이 발생합니다. 필드 값은 그대로입니다.

이 예제의 기본 생성자는 이메일 필수 조건을 검사하지 않습니다. 정상 생성과 업무상 유효한 초기 상태는 같은 뜻이 아닙니다.

이 예의 조건

  • email 초기화가 누락된 원 순서입니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2new Member의 기본 null 0→age 40, email null 유지→email 필드 읽기 결과 null·age 40 유지→null.toUpperCase에서 NPE가 발생한다. 필드 읽기와 메서드 호출을 구분하며 예외 뒤에도 null 40을 유지한다.
11필드: null
1240
원 도표의 설명과 주석
구분내용
설명new Member의 기본 null 0→age 40, email null 유지→email 필드 읽기 결과 null·age 40 유지→null.toUpperCase에서 NPE가 발생한다. 필드 읽기와 메서드 호출을 구분하며 예외 뒤에도 null 40을 유지한다.
전체 읽기 정보초기화 누락 예제에서 필드 값과 실패 시점 초기화 누락 예제에서 필드 값과 실패 시점 MissingInitialization에서 email은 null로 남고 age만 40으로 바뀐다. null 값 읽기는 가능하지만 그 값으로 toUpperCase를 호출하면 예외가 발생한다. 정상 생성 뒤에도 누락된 email은 null로 남는다 age만 대입한 객체에서 이메일을 읽는 것과 메서드를 호출하는 것을 구분합니다. MissingInitialization 의 같은 객체를 실행 순서대로 추적합니다. 실행 문장별 email과 age 상태 및 결과 실행 시점 객체 필드 관찰 생성 직후 email = null age = 0 new Member() 가 정상 참조를 반환합니다. 필드는 언어 기본값입니다. 나이 대입 후 email = null age = 40 member.age = 40 은 이메일을 초기화하지 않습니다. 이메일 사용 email = null age = 40 member.email 읽기는 성공합니다. 이어지는 toUpperCase() 호출에서 NullPointerException 이 발생합니다. 생성 직후 new Member() 는 정상 참조를 반환합니다. 필드는 email = null , age = 0 입니다. 나이 대입 후 member.age = 40 이후에도 email = null 이며, age = 40 만 달라집니다. 이메일 사용 member.email 읽기는 성공하지만, 그 값으로 toUpperCase() 를 호출하면 NullPointerException 이 발생합니다. 필드 값은 그대로입니다. 이 예제의 기본 생성자는 이메일 필수 조건을 검사하지 않습니다. 정상 생성과 업무상 유효한 초기 상태는 같은 뜻이 아닙니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
기본 필드

new Member 뒤 email null, age 0입니다.

  • email: null
  • age: 0
  • 정상 객체 생성이 필수 업무 필드 설정을 대신하지 않습니다.
일부 대입과 읽기

age 40만 대입하고 email 필드를 읽습니다.

  • 필드: null/40
  • email 읽기: 성공, 결과 null
  • 필드 값 null을 읽는 것은 그 값의 메서드 호출과 다릅니다.
메서드 실패

null.toUpperCase를 호출해 NPE입니다.

  • 예외 뒤 필드: null/40 유지
  • 실패 대상: null 참조
  • 메서드 예외가 누락된 필드를 자동 초기화하지 않습니다.

클래스를 보는 것만으로는 어떤 필드를 반드시 채워야 하는지 강제되지 않습니다.

생성자 없이 다음 절차를 반복하면 순서와 누락을 모든 호출자가 책임집니다.

Member member = new Member();
member.email = email;
member.age = age;

필수 값이 늘어날수록 기존 생성 위치를 모두 찾아 수정해야 합니다.

생성자 매개변수로 필수 값을 요구하면 컴파일러가 누락된 호출을 찾습니다.


생성자 선언

src/ConstructorMember.java
public final class ConstructorMember {
    public static void main(String[] args) {
        Member member = new Member("[email protected]", 40);
        System.out.println(member.describe());
    }

    private static final class Member {
        private String email;
        private int age;

        Member(String email, int age) {
            this.email = email;
            this.age = age;
        }

        String describe() {
            return email + "=" + age;
        }
    }
}

Member(String email, int age)는 메서드처럼 보이지만 반환 타입이 없고 클래스 이름과 같습니다.

new Member("[email protected]", 40)는 메모리 할당, 필드 기본값 설정, 생성자 본문 실행을 거쳐 참조를 반환합니다.

개발자가 생성자를 직접 일반 메서드처럼 호출할 수는 없습니다.

이미 생성한 객체의 생성자를 일반 메서드처럼 다시 호출할 수는 없습니다. 뒤 절의 this(...) 위임은 같은 객체를 만드는 과정에서 다른 생성자 본문을 이어 실행하는 것이며, 기존 객체를 재초기화하는 호출과 구분합니다.

이후 같은 검증을 거쳐 값을 바꾸려면 별도의 행동 메서드가 필요합니다.

생성자를 다시 호출해 기존 객체를 재초기화하는 방식은 없습니다.


this 필드 참조

lab/MissingThisAssignment.java
public final class MissingThisAssignment {
    public static void main(String[] args) {
        Member member = new Member("[email protected]", 50);
        System.out.println(member.email + "=" + member.age);
    }

    private static final class Member {
        String email;
        int age;

        Member(String email, int age) {
            email = email;
            age = age;
        }
    }
}
잘못된 결과
null=0

이름을 찾을 때 가장 가까운 지역 범위의 매개변수가 우선합니다.

email = email은 매개변수 값을 같은 매개변수에 다시 대입하고, 필드는 건드리지 않습니다.

this.email은 현재 생성 중인 객체의 필드를 명시합니다.

Member(String email, int age) {
    this.email = email;
    this.age = age;
}

왼쪽은 객체 필드, 오른쪽은 생성자 매개변수입니다.

매개변수 이름을 inputEmail으로 바꾸면 this 없이도 구분되지만, 필드와 매개변수가 같은 개념이라면 같은 이름과 this가 대응을 분명하게 보여 줍니다.

this는 “현재 인스턴스의 참조”입니다.

인스턴스 메서드에서 this.describe()처럼 메서드를 호출하거나 다른 객체에 현재 인스턴스를 전달할 수도 있습니다.

다만 명확하지 않은 곳마다 this를 붙이는 것이 목표가 아니라 지역 변수와 필드의 경계를 드러내는 데 사용합니다.

매개변수 자기 대입과 this 필드 대입의 결과

매개변수 자기 대입은 필드 기본값을 유지하고 this 필드 대입은 전달값을 저장합니다. 서로 다른 생성자 구현의 대입 전후를 같은 역할로 비교할 수 있습니다.

매개변수 자기 대입과 this 필드 대입의 결과

MissingThisAssignment의 [email protected]과 50을 기준으로 매개변수만 다시 대입하는 코드와 본문의 this 교정 코드를 비교한다.

this로 매개변수와 현재 객체의 필드를 구분한다

자기 대입은 필드를 바꾸지 않아 null과 0이 그대로 남습니다.

MissingThisAssignment에 전달한 "[email protected]", 50을 기준으로 원본과 본문의 교정 코드를 비교합니다.

대입문이 바꾸는 저장 공간과 생성 후 필드 값
구현대입 대상생성 후 객체 필드
원본email = email;

age = age;

오른쪽도 왼쪽도 같은 매개변수입니다.

email = null

age = 0

출력: null=0

교정this.email = email;

this.age = age;

매개변수 값을 현재 객체의 필드에 저장합니다.

email = "[email protected]"

age = 50

같은 입력의 결과: [email protected]=50

this로 매개변수와 현재 객체의 필드를 구분한다 동일Lee50입력의서로다른구현. email=email/age=age는parameterselfloop여서필드null0. this.email/this.age는받은값을현재인스턴스필드로대입하여Lee50. this자체유효성검사아님. 매개변수 자신 받은 매개변수 [email protected] age = 50 email=email; age=age; this의 필드는 미대입 email=null · age=0 출력 null=0 this 필드는 바뀌지 않음 this로 필드 지정 받은 매개변수 [email protected] age = 50 this.email / this.age 필드에 매개변수 대입 [email protected] age=50 현재 인스턴스 상태 서로 다른 생성자 구현을 비교 실선 대입 · this는 검사 아님
this로 매개변수와 현재 객체의 필드를 구분한다 동일Lee50입력의서로다른구현. email=email/age=age는parameterselfloop여서필드null0. this.email/this.age는받은값을현재인스턴스필드로대입하여Lee50. this자체유효성검사아님. 매개변수 자신 받은 매개변수 [email protected] age = 50 email=email; age=age; this의 필드는 미대입 email=null · age=0 출력 null=0 this 필드는 바뀌지 않음 this로 필드 지정 받은 매개변수 [email protected] age = 50 this.email / this.age 필드에 매개변수 대입 [email protected] age=50 현재 인스턴스 상태 서로 다른 생성자 구현을 비교 실선 대입 · this는 검사 아님
원본: 매개변수에 자기 대입

email = email;과 age = age;는 매개변수만 다시 대입합니다.

객체 필드는 email = null, age = 0으로 남아 null=0을 출력합니다.

교정: 현재 객체 필드에 대입

this.email = email;과 this.age = age;는 매개변수 값을 필드에 저장합니다.

같은 입력이면 필드는 email = "[email protected]", age = 50이고 결과는 [email protected]=50입니다.

this는 현재 인스턴스의 참조입니다. 필드 대입을 구분할 뿐, 입력값 검증을 자동으로 추가하지는 않습니다.

이 예의 조건

  • 서로 다른 생성자 구현에 동일 Lee 50을 전달합니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2동일한 Lee 50 입력에 대한 서로 다른 구현이다. email=email/age=age는 매개변수의 자기 대입이어서 필드는 null 0이다. this.email/this.age는 받은 값을 현재 인스턴스 필드에 대입하여 Lee 50이 된다. this 자체가 유효성 검사는 아니다.
16this.email
17this.age
원 도표의 설명과 주석
구분내용
설명동일한 Lee 50 입력에 대한 서로 다른 구현이다. email=email/age=age는 매개변수의 자기 대입이어서 필드는 null 0이다. this.email/this.age는 받은 값을 현재 인스턴스 필드에 대입하여 Lee 50이 된다. this 자체가 유효성 검사는 아니다.
전체 읽기 정보매개변수 자기 대입과 this 필드 대입의 결과 매개변수 자기 대입과 this 필드 대입의 결과 MissingThisAssignment의 [email protected]과 50을 기준으로 매개변수만 다시 대입하는 코드와 본문의 this 교정 코드를 비교한다. this로 매개변수와 현재 객체의 필드를 구분한다 자기 대입은 필드를 바꾸지 않아 null과 0이 그대로 남습니다. MissingThisAssignment 에 전달한 "[email protected]" , 50 을 기준으로 원본과 본문의 교정 코드를 비교합니다. 대입문이 바꾸는 저장 공간과 생성 후 필드 값 구현 대입 대상 생성 후 객체 필드 원본 email = email; age = age; 오른쪽도 왼쪽도 같은 매개변수입니다. email = null age = 0 출력: null=0 교정 this.email = email; this.age = age; 매개변수 값을 현재 객체의 필드에 저장합니다. email = "[email protected]" age = 50 같은 입력의 결과: [email protected]=50 원본: 매개변수에 자기 대입 email = email; 과 age = age; 는 매개변수만 다시 대입합니다. 객체 필드는 email = null , age = 0 으로 남아 null=0 을 출력합니다. 교정: 현재 객체 필드에 대입 this.email = email; 과 this.age = age; 는 매개변수 값을 필드에 저장합니다. 같은 입력이면 필드는 email = "[email protected]" , age = 50 이고 결과는 [email protected]=50 입니다. this 는 현재 인스턴스의 참조입니다. 필드 대입을 구분할 뿐, 입력값 검증을 자동으로 추가하지는 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
매개변수 수신

email과 age 매개변수가 Lee 50을 받습니다.

  • 같은 이름의 필드는 별도 저장 위치입니다.
대입 대상 비교

자기 대입은 매개변수, this 대입은 현재 필드를 대상으로 합니다.

  • email=email: 필드 미대입
  • this.email=email: 필드에 기록
  • 이름 가리기에서 대상을 명시해야 필드가 변합니다.
생성 결과

잘못된 구현은 null 0, 필드 지정은 Lee 50입니다.

  • this 지정 자체는 입력 유효성 검사가 아닙니다.

매개변수 자기 대입과 this 필드 대입의 결과

매개변수 자기 대입은 필드 기본값을 유지하고 this 필드 대입은 전달값을 저장합니다. 서로 다른 생성자 구현의 대입 전후를 같은 역할로 비교할 수 있습니다.

매개변수 자기 대입과 this 필드 대입의 결과

MissingThisAssignment의 [email protected]과 50을 기준으로 매개변수만 다시 대입하는 코드와 본문의 this 교정 코드를 비교한다.

this로 매개변수와 현재 객체의 필드를 구분한다

자기 대입은 필드를 바꾸지 않아 null과 0이 그대로 남습니다.

MissingThisAssignment에 전달한 "[email protected]", 50을 기준으로 원본과 본문의 교정 코드를 비교합니다.

대입문이 바꾸는 저장 공간과 생성 후 필드 값
구현대입 대상생성 후 객체 필드
원본email = email;

age = age;

오른쪽도 왼쪽도 같은 매개변수입니다.

email = null

age = 0

출력: null=0

교정this.email = email;

this.age = age;

매개변수 값을 현재 객체의 필드에 저장합니다.

email = "[email protected]"

age = 50

같은 입력의 결과: [email protected]=50

this로 매개변수와 현재 객체의 필드를 구분한다 동일Lee50입력의서로다른구현. email=email/age=age는parameterselfloop여서필드null0. this.email/this.age는받은값을현재인스턴스필드로대입하여Lee50. this자체유효성검사아님. 매개변수 자신 받은 매개변수 [email protected] age = 50 email=email; age=age; this의 필드는 미대입 email=null · age=0 출력 null=0 this 필드는 바뀌지 않음 this로 필드 지정 받은 매개변수 [email protected] age = 50 this.email / this.age 필드에 매개변수 대입 [email protected] age=50 현재 인스턴스 상태 서로 다른 생성자 구현을 비교 실선 대입 · this는 검사 아님
this로 매개변수와 현재 객체의 필드를 구분한다 동일Lee50입력의서로다른구현. email=email/age=age는parameterselfloop여서필드null0. this.email/this.age는받은값을현재인스턴스필드로대입하여Lee50. this자체유효성검사아님. 매개변수 자신 받은 매개변수 [email protected] age = 50 email=email; age=age; this의 필드는 미대입 email=null · age=0 출력 null=0 this 필드는 바뀌지 않음 this로 필드 지정 받은 매개변수 [email protected] age = 50 this.email / this.age 필드에 매개변수 대입 [email protected] age=50 현재 인스턴스 상태 서로 다른 생성자 구현을 비교 실선 대입 · this는 검사 아님
원본: 매개변수에 자기 대입

email = email;과 age = age;는 매개변수만 다시 대입합니다.

객체 필드는 email = null, age = 0으로 남아 null=0을 출력합니다.

교정: 현재 객체 필드에 대입

this.email = email;과 this.age = age;는 매개변수 값을 필드에 저장합니다.

같은 입력이면 필드는 email = "[email protected]", age = 50이고 결과는 [email protected]=50입니다.

this는 현재 인스턴스의 참조입니다. 필드 대입을 구분할 뿐, 입력값 검증을 자동으로 추가하지는 않습니다.

이 예의 조건

  • 서로 다른 생성자 구현에 동일 Lee 50을 전달합니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2동일한 Lee 50 입력에 대한 서로 다른 구현이다. email=email/age=age는 매개변수의 자기 대입이어서 필드는 null 0이다. this.email/this.age는 받은 값을 현재 인스턴스 필드에 대입하여 Lee 50이 된다. this 자체가 유효성 검사는 아니다.
16this.email
17this.age
원 도표의 설명과 주석
구분내용
설명동일한 Lee 50 입력에 대한 서로 다른 구현이다. email=email/age=age는 매개변수의 자기 대입이어서 필드는 null 0이다. this.email/this.age는 받은 값을 현재 인스턴스 필드에 대입하여 Lee 50이 된다. this 자체가 유효성 검사는 아니다.
전체 읽기 정보매개변수 자기 대입과 this 필드 대입의 결과 매개변수 자기 대입과 this 필드 대입의 결과 MissingThisAssignment의 [email protected]과 50을 기준으로 매개변수만 다시 대입하는 코드와 본문의 this 교정 코드를 비교한다. this로 매개변수와 현재 객체의 필드를 구분한다 자기 대입은 필드를 바꾸지 않아 null과 0이 그대로 남습니다. MissingThisAssignment 에 전달한 "[email protected]" , 50 을 기준으로 원본과 본문의 교정 코드를 비교합니다. 대입문이 바꾸는 저장 공간과 생성 후 필드 값 구현 대입 대상 생성 후 객체 필드 원본 email = email; age = age; 오른쪽도 왼쪽도 같은 매개변수입니다. email = null age = 0 출력: null=0 교정 this.email = email; this.age = age; 매개변수 값을 현재 객체의 필드에 저장합니다. email = "[email protected]" age = 50 같은 입력의 결과: [email protected]=50 원본: 매개변수에 자기 대입 email = email; 과 age = age; 는 매개변수만 다시 대입합니다. 객체 필드는 email = null , age = 0 으로 남아 null=0 을 출력합니다. 교정: 현재 객체 필드에 대입 this.email = email; 과 this.age = age; 는 매개변수 값을 필드에 저장합니다. 같은 입력이면 필드는 email = "[email protected]" , age = 50 이고 결과는 [email protected]=50 입니다. this 는 현재 인스턴스의 참조입니다. 필드 대입을 구분할 뿐, 입력값 검증을 자동으로 추가하지는 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
매개변수 수신

email과 age 매개변수가 Lee 50을 받습니다.

  • 같은 이름의 필드는 별도 저장 위치입니다.
대입 대상 비교

자기 대입은 매개변수, this 대입은 현재 필드를 대상으로 합니다.

  • email=email: 필드 미대입
  • this.email=email: 필드에 기록
  • 이름 가리기에서 대상을 명시해야 필드가 변합니다.
생성 결과

잘못된 구현은 null 0, 필드 지정은 Lee 50입니다.

  • this 지정 자체는 입력 유효성 검사가 아닙니다.

생성자 불변식

src/ValidatedConstructor.java
public final class ValidatedConstructor {
    public static void main(String[] args) {
        printCreation("[email protected]", 40);
        printCreation("", 30);
        printCreation("[email protected]", 130);
    }

    private static void printCreation(String email, int age) {
        try {
            Member member = new Member(email, age);
            System.out.println("created=" + member.describe());
        } catch (IllegalArgumentException exception) {
            System.out.println("rejected=" + exception.getMessage());
        }
    }

    private static final class Member {
        private final String email;
        private final int age;

        Member(String email, int age) {
            if (email == null || email.isBlank()) {
                throw new IllegalArgumentException("email");
            }
            if (age < 14 || age > 120) {
                throw new IllegalArgumentException("age");
            }
            this.email = email;
            this.age = age;
        }

        String describe() {
            return email + "=" + age;
        }
    }
}
[email protected]=40
rejected=email
rejected=age

검증을 필드 대입보다 먼저 수행해 실패한 객체가 외부로 전달되지 않게 합니다.

생성자가 예외를 던지면 new 표현식이 정상 참조를 반환하지 않습니다.

생성된 모든 Member가 이메일과 나이 범위를 만족한다는 가정을 다른 메서드가 사용할 수 있습니다.

CLI에서 사용자 입력 오류를 예외로 끝내면 안 되므로 입력 경계에서 예외를 잡아 rejected로 바꿉니다.

생성자는 클래스 규칙을 지키고, CLI는 사용자와 대화를 계속하는 책임을 맡습니다.

검증 생성자의 첫 실패와 정상 참조 반환 경계

이메일과 나이 검사를 통과한 뒤 필드를 대입하고 new가 정상 참조를 반환합니다. 실패한 생성에서 외부 정상 참조가 나오지 않는 실행 경계를 나눌 수 있습니다.

검증 생성자의 첫 실패와 정상 참조 반환 경계

ValidatedConstructor의 Member는 이메일 검사 뒤 나이 검사를 수행하고 둘 다 통과해야 필드에 대입한다. 예외가 나면 new는 정상 참조를 반환하지 않으며 printCreation이 rejected 메시지로 바꾼다.

검사를 모두 통과해야 필드 대입과 정상 반환에 도달한다

이메일 검사에서 실패하면 나이 검사도 건너뛰고 호출자가 rejected로 알립니다.

ValidatedConstructor의 Member 생성자입니다. 앞의 단순 대입 예제와 달리 검증 코드가 있습니다.

이메일, 나이 순서로 검사하고 필드를 대입하는 생성자 이메일이 null 또는 공백이면 email 예외로 끝나고 나이는 검사하지 않는다. 이메일이 통과한 뒤 나이가 14 미만 또는 120 초과이면 age 예외로 끝난다. 둘 다 통과해야 필드 대입 뒤 new가 정상 참조를 반환한다. 예 아니요 예 아니요 Member(email, age) email이 null 또는공백인가? 예외로 종료"email" age < 14 또는age > 120인가? 예외로 종료"age" 현재 객체 필드에 대입 this.email / this.age new가 정상 참조를 반환
검사를 모두 통과해야 필드 대입과 정상 반환에 도달한다 이메일null공백먼저검사실패email예외끝/age미검사. 이메일유효뒤14..120검사실패age예외끝. 둘다통과필드대입/정상newref. Kim40정상생성,빈이메일/Lee130거절. new Member email / age 입력 아직 정상 참조 없음 email이 null·공백? 이메일 검사 먼저 예 · 즉시 throw email 예외 rejected=email 나이는 검사하지 않음 아니요 · 다음 검사 age < 14 또는 age > 120 ? 예 · 즉시 throw age 예외 rejected=age 필드 대입은 미실행 아니요 · 두 검사 통과 this 필드 대입 email / age 저장 생성자 정상 종료 new의 정상 참조 Kim40 생성 성공 created 출력 가능 예외: 정상 참조 반환 없음
  1. 이메일 검사

    email == null || email.isBlank()이면 IllegalArgumentException("email")로 종료합니다. 나이 검사는 실행하지 않습니다.

  2. 나이 검사

    이메일이 통과한 뒤 age < 14 || age > 120이면 IllegalArgumentException("age")로 종료합니다.

  3. 필드 대입과 정상 반환

    두 검사를 통과해야 this.email과 this.age에 대입하고, new가 정상 참조를 반환합니다.

어느 검사에서든 예외가 나면 필드 대입에 도달하지 않고, new도 정상 참조를 반환하지 않습니다. 호출자 printCreation이 예외를 잡아 rejected=email 또는 rejected=age를 출력합니다.

이 불변식은 검증을 직접 구현한 이 예제의 보장입니다. 생성자라는 문법만으로 입력값이 검증되지는 않습니다.

이 예의 조건

  • 원 main의 세 독립 호출 중 [email protected],40의 정상 생성 하나만 추적합니다.
  • 별도 빈 이메일 30과 [email protected],130의 실패 요청은 원 전체 표에 보존하며 정상 호출의 후속이 아닙니다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보검증 생성자의 첫 실패와 정상 참조 반환 경계 검증 생성자의 첫 실패와 정상 참조 반환 경계 ValidatedConstructor의 Member는 이메일 검사 뒤 나이 검사를 수행하고 둘 다 통과해야 필드에 대입한다. 예외가 나면 new는 정상 참조를 반환하지 않으며 printCreation이 rejected 메시지로 바꾼다. 검사를 모두 통과해야 필드 대입과 정상 반환에 도달한다 이메일 검사에서 실패하면 나이 검사도 건너뛰고 호출자가 rejected로 알립니다. ValidatedConstructor 의 Member 생성자입니다. 앞의 단순 대입 예제와 달리 검증 코드가 있습니다. 이메일 검사 email == null || email.isBlank() 이면 IllegalArgumentException("email") 로 종료합니다. 나이 검사는 실행하지 않습니다. 나이 검사 이메일이 통과한 뒤 age < 14 || age > 120 이면 IllegalArgumentException("age") 로 종료합니다. 필드 대입과 정상 반환 두 검사를 통과해야 this.email 과 this.age 에 대입하고, new 가 정상 참조를 반환합니다. 어느 검사에서든 예외가 나면 필드 대입에 도달하지 않고, new 도 정상 참조를 반환하지 않습니다. 호출자 printCreation 이 예외를 잡아 rejected=email 또는 rejected=age 를 출력합니다. 이 불변식은 검증을 직접 구현한 이 예제의 보장입니다. 생성자라는 문법만으로 입력값이 검증되지는 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
Kim의 입력 검증

[email protected]은 null·공백이 아니고 나이 40은 14–120 범위입니다.

  • email 검사: [email protected] → 통과
  • age 검사: 40 → 통과
  • 필드 대입: 두 검사 완료 전에는 없음
  • 이메일 검사를 통과해야 나이를 검사하고 두 조건이 모두 정상이어야 대입으로 갑니다.
현재 객체 필드 대입

두 검사 통과 후 this.email과 this.age에 입력값을 기록합니다.

  • this.email: [email protected]
  • this.age: 40
  • 호출자: new 정상 완료를 아직 기다림
  • 검증된 값이 생성 중인 현재 객체의 필드 상태가 됩니다.
new 정상 참조 반환

생성자가 정상 완료한 뒤 new가 Kim 40 객체의 참조를 반환합니다.

  • 반환: 정상 Member 참조
  • 객체 상태: email [email protected] · age 40
  • 별도 실패 입력: email/age 예외는 정상 참조 없이 종료, 원 표 보존
  • 호출자는 생성자 정상 완료 뒤에만 검증된 객체의 참조를 받습니다.

검증 생성자의 첫 실패와 정상 참조 반환 경계

이메일과 나이 검사를 통과한 뒤 필드를 대입하고 new가 정상 참조를 반환합니다. 실패한 생성에서 외부 정상 참조가 나오지 않는 실행 경계를 나눌 수 있습니다.

검증 생성자의 첫 실패와 정상 참조 반환 경계

ValidatedConstructor의 Member는 이메일 검사 뒤 나이 검사를 수행하고 둘 다 통과해야 필드에 대입한다. 예외가 나면 new는 정상 참조를 반환하지 않으며 printCreation이 rejected 메시지로 바꾼다.

검사를 모두 통과해야 필드 대입과 정상 반환에 도달한다

이메일 검사에서 실패하면 나이 검사도 건너뛰고 호출자가 rejected로 알립니다.

ValidatedConstructor의 Member 생성자입니다. 앞의 단순 대입 예제와 달리 검증 코드가 있습니다.

이메일, 나이 순서로 검사하고 필드를 대입하는 생성자 이메일이 null 또는 공백이면 email 예외로 끝나고 나이는 검사하지 않는다. 이메일이 통과한 뒤 나이가 14 미만 또는 120 초과이면 age 예외로 끝난다. 둘 다 통과해야 필드 대입 뒤 new가 정상 참조를 반환한다. 예 아니요 예 아니요 Member(email, age) email이 null 또는공백인가? 예외로 종료"email" age < 14 또는age > 120인가? 예외로 종료"age" 현재 객체 필드에 대입 this.email / this.age new가 정상 참조를 반환
검사를 모두 통과해야 필드 대입과 정상 반환에 도달한다 이메일null공백먼저검사실패email예외끝/age미검사. 이메일유효뒤14..120검사실패age예외끝. 둘다통과필드대입/정상newref. Kim40정상생성,빈이메일/Lee130거절. new Member email / age 입력 아직 정상 참조 없음 email이 null·공백? 이메일 검사 먼저 예 · 즉시 throw email 예외 rejected=email 나이는 검사하지 않음 아니요 · 다음 검사 age < 14 또는 age > 120 ? 예 · 즉시 throw age 예외 rejected=age 필드 대입은 미실행 아니요 · 두 검사 통과 this 필드 대입 email / age 저장 생성자 정상 종료 new의 정상 참조 Kim40 생성 성공 created 출력 가능 예외: 정상 참조 반환 없음
  1. 이메일 검사

    email == null || email.isBlank()이면 IllegalArgumentException("email")로 종료합니다. 나이 검사는 실행하지 않습니다.

  2. 나이 검사

    이메일이 통과한 뒤 age < 14 || age > 120이면 IllegalArgumentException("age")로 종료합니다.

  3. 필드 대입과 정상 반환

    두 검사를 통과해야 this.email과 this.age에 대입하고, new가 정상 참조를 반환합니다.

어느 검사에서든 예외가 나면 필드 대입에 도달하지 않고, new도 정상 참조를 반환하지 않습니다. 호출자 printCreation이 예외를 잡아 rejected=email 또는 rejected=age를 출력합니다.

이 불변식은 검증을 직접 구현한 이 예제의 보장입니다. 생성자라는 문법만으로 입력값이 검증되지는 않습니다.

이 예의 조건

  • 원 main의 세 독립 호출 중 [email protected],40의 정상 생성 하나만 추적합니다.
  • 별도 빈 이메일 30과 [email protected],130의 실패 요청은 원 전체 표에 보존하며 정상 호출의 후속이 아닙니다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보검증 생성자의 첫 실패와 정상 참조 반환 경계 검증 생성자의 첫 실패와 정상 참조 반환 경계 ValidatedConstructor의 Member는 이메일 검사 뒤 나이 검사를 수행하고 둘 다 통과해야 필드에 대입한다. 예외가 나면 new는 정상 참조를 반환하지 않으며 printCreation이 rejected 메시지로 바꾼다. 검사를 모두 통과해야 필드 대입과 정상 반환에 도달한다 이메일 검사에서 실패하면 나이 검사도 건너뛰고 호출자가 rejected로 알립니다. ValidatedConstructor 의 Member 생성자입니다. 앞의 단순 대입 예제와 달리 검증 코드가 있습니다. 이메일 검사 email == null || email.isBlank() 이면 IllegalArgumentException("email") 로 종료합니다. 나이 검사는 실행하지 않습니다. 나이 검사 이메일이 통과한 뒤 age < 14 || age > 120 이면 IllegalArgumentException("age") 로 종료합니다. 필드 대입과 정상 반환 두 검사를 통과해야 this.email 과 this.age 에 대입하고, new 가 정상 참조를 반환합니다. 어느 검사에서든 예외가 나면 필드 대입에 도달하지 않고, new 도 정상 참조를 반환하지 않습니다. 호출자 printCreation 이 예외를 잡아 rejected=email 또는 rejected=age 를 출력합니다. 이 불변식은 검증을 직접 구현한 이 예제의 보장입니다. 생성자라는 문법만으로 입력값이 검증되지는 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
Kim의 입력 검증

[email protected]은 null·공백이 아니고 나이 40은 14–120 범위입니다.

  • email 검사: [email protected] → 통과
  • age 검사: 40 → 통과
  • 필드 대입: 두 검사 완료 전에는 없음
  • 이메일 검사를 통과해야 나이를 검사하고 두 조건이 모두 정상이어야 대입으로 갑니다.
현재 객체 필드 대입

두 검사 통과 후 this.email과 this.age에 입력값을 기록합니다.

  • this.email: [email protected]
  • this.age: 40
  • 호출자: new 정상 완료를 아직 기다림
  • 검증된 값이 생성 중인 현재 객체의 필드 상태가 됩니다.
new 정상 참조 반환

생성자가 정상 완료한 뒤 new가 Kim 40 객체의 참조를 반환합니다.

  • 반환: 정상 Member 참조
  • 객체 상태: email [email protected] · age 40
  • 별도 실패 입력: email/age 예외는 정상 참조 없이 종료, 원 표 보존
  • 호출자는 생성자 정상 완료 뒤에만 검증된 객체의 참조를 받습니다.

생성 책임 분리

MemberRegistry의 add가 원시 값으로 Member를 생성할 수 있습니다.

src/ConstructingMemberRegistry.java
public final class ConstructingMemberRegistry {
    public static void main(String[] args) {
        MemberRegistry registry = new MemberRegistry(2);

        System.out.println(registry.add("[email protected]", 40));
        System.out.println(registry.add("[email protected]", 50));
        System.out.println(registry.add("[email protected]", 30));
        System.out.println(registry.summary());
    }

    private static final class MemberRegistry {
        private final Member[] members;
        private int size;

        MemberRegistry(int capacity) {
            if (capacity <= 0) {
                throw new IllegalArgumentException("capacity");
            }
            this.members = new Member[capacity];
        }

        boolean add(String email, int age) {
            if (size == members.length) {
                return false;
            }
            try {
                members[size] = new Member(email, age);
                size++;
                return true;
            } catch (IllegalArgumentException exception) {
                return false;
            }
        }

        String summary() {
            int total = 0;
            for (int index = 0; index < size; index++) {
                total += members[index].age();
            }
            return "count=" + size + ", total=" + total;
        }
    }

    private static final class Member {
        private final String email;
        private final int age;

        Member(String email, int age) {
            if (email == null || email.isBlank() || age < 14 || age > 120) {
                throw new IllegalArgumentException("invalid member");
            }
            this.email = email;
            this.age = age;
        }

        int age() {
            return age;
        }
    }
}
true
true
false
count=2, total=90

세 번째 요청은 용량 검사에서 거절돼 생성자도 호출하지 않습니다.

성공한 생성 뒤에만 size를 증가시킵니다.

Member 생성 규칙과 MemberRegistry 용량 규칙이 각각 자기 상태 가까이에 있습니다.

회원 생성 성공 뒤 배열 저장과 size 증가

Kim과 Lee는 생성 성공 후 저장·size 증가하고 Park은 용량 검사에서 생성 전에 거절됩니다. 생성 예외도 저장 확정 전에 잡아 상태를 보존하는 실제 add 게이트입니다.

회원 생성 성공 뒤 배열 저장과 size 증가

ConstructingMemberRegistry의 용량 2에서 첫 두 add는 생성 후 저장하고 size를 늘린다. 세 번째 add는 가득 찬 검사를 먼저 만나 Member 생성도 하지 않는다.

생성이 성공한 뒤 저장하고 회원 수를 늘린다

용량 2의 세 번째 요청은 생성자에 도달하기 전에 거절됩니다.

ConstructingMemberRegistry의 main: 용량 2, 처음 size = 0인 같은 저장소입니다.

세 add 요청의 생성 여부, 저장 위치, 회원 수 변화
요청생성·저장결과와 size
첫 번째"[email protected]", 40

검증 생성 성공 후 members[0]에 저장합니다.

true

0 → 1

두 번째"[email protected]", 50

검증 생성 성공 후 members[1]에 저장합니다.

true

1 → 2

세 번째"[email protected]", 30

size == members.length이므로 Member 생성자를 호출하지 않습니다.

false

2 → 2

생성이 성공한 뒤 저장하고 회원 수를 늘린다 같은capacity2저장소 Kim40 0→1true/Lee50 1→2true/Park30 capacityfull2유지false생성없음. 빈공간이어도constructorIAA catchfalse배열/size유지. 성공생성저장후별도size++. add(email, age) 배열·size 그대로 size == length ? 용량 2 검사 예: Park false 반환 생성·저장 없음 size 2 유지 아니요 Member 생성·검증 성공 참조를 얻으면 members[size]에 저장 예외 catch false · 상태 유지 size++ → true Kim 0→1 · Lee 1→2 count=2, total=90 가득 차면 생성 전 거절 · 생성 예외는 저장·size++ 전에 catch
생성이 성공한 뒤 저장하고 회원 수를 늘린다 같은capacity2저장소 Kim40 0→1true/Lee50 1→2true/Park30 capacityfull2유지false생성없음. 빈공간이어도constructorIAA catchfalse배열/size유지. 성공생성저장후별도size++. add(email, age) 같은 용량 2 저장소 배열·size는 아직 그대로 size == length ? 이미 가득 찼는가? 예 · 세 번째 Park 요청 false · 생성하지 않음 size: 2 → 2 배열도 그대로 아니요 · 생성 단계로 Member 생성·검증 이메일·14~120세 검사 정상 참조를 받은 뒤 members[size]에 저장 생성자가 예외를 던지면 catch → false 배열·size 그대로 빈 공간이어도 거절 생성·저장 성공 뒤에만 size++ → true 첫 Kim: 0 → 1 둘째 Lee: 1 → 2 count=2, total=90 생성 실패: size++ 미실행 Kim40 · Lee50 · Park30 순서
  1. 첫 번째: kim, 40

    "[email protected]", 40의 생성 성공 후 members[0]에 저장합니다. size: 0 → 1, 반환값은 true입니다.

  2. 두 번째: lee, 50

    "[email protected]", 50의 생성 성공 후 members[1]에 저장합니다. size: 1 → 2, 반환값은 true입니다.

  3. 세 번째: park, 30

    "[email protected]", 30은 용량 검사에서 거절됩니다. Member 생성자를 호출하지 않으며 size: 2 → 2, 반환값은 false입니다.

최종 요약은 count=2, total=90입니다. 저장 공간이 있어도 생성자가 IllegalArgumentException을 던지면 add가 false를 반환하며 배열 칸과 size를 바꾸지 않습니다.

이 예제는 생성·저장을 끝낸 다음 별도 문장으로 size++를 실행합니다. members[size++] = new Member(...)와 실행 순서가 다릅니다.

이 예의 조건

  • 원 main의 용량 2 같은 저장소에 Kim 40, Lee 50, Park 30을 순차 요청하는 경로만 추적합니다.
  • 빈 공간이 있는 별도 저장소의 생성자 예외 조건은 독립 분기이며 원 전체 표에 보존합니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2같은 capacity 2 저장소에서 Kim 40은 0→1 true, Lee 50은 1→2 true를 반환하고 Park 30은 capacity full로 2를 유지하며 false를 반환하고 생성하지 않는다. 빈 공간이 있어도 생성자의 IAA를 catch하면 false를 반환하며 배열/size를 유지한다. 생성에 성공해 저장한 후 별도로 size++를 실행한다.
원 도표의 설명과 주석
구분내용
설명같은 capacity 2 저장소에서 Kim 40은 0→1 true, Lee 50은 1→2 true를 반환하고 Park 30은 capacity full로 2를 유지하며 false를 반환하고 생성하지 않는다. 빈 공간이 있어도 생성자의 IAA를 catch하면 false를 반환하며 배열/size를 유지한다. 생성에 성공해 저장한 후 별도로 size++를 실행한다.
전체 읽기 정보회원 생성 성공 뒤 배열 저장과 size 증가 회원 생성 성공 뒤 배열 저장과 size 증가 ConstructingMemberRegistry의 용량 2에서 첫 두 add는 생성 후 저장하고 size를 늘린다. 세 번째 add는 가득 찬 검사를 먼저 만나 Member 생성도 하지 않는다. 생성이 성공한 뒤 저장하고 회원 수를 늘린다 용량 2의 세 번째 요청은 생성자에 도달하기 전에 거절됩니다. ConstructingMemberRegistry 의 main : 용량 2 , 처음 size = 0 인 같은 저장소입니다. 세 add 요청의 생성 여부, 저장 위치, 회원 수 변화 요청 생성·저장 결과와 size 첫 번째 "[email protected]", 40 검증 생성 성공 후 members[0] 에 저장합니다. true 0 → 1 두 번째 "[email protected]", 50 검증 생성 성공 후 members[1] 에 저장합니다. true 1 → 2 세 번째 "[email protected]", 30 size == members.length 이므로 Member 생성자를 호출하지 않습니다. false 2 → 2 첫 번째: kim, 40 "[email protected]", 40 의 생성 성공 후 members[0] 에 저장합니다. size: 0 → 1 , 반환값은 true 입니다. 두 번째: lee, 50 "[email protected]", 50 의 생성 성공 후 members[1] 에 저장합니다. size: 1 → 2 , 반환값은 true 입니다. 세 번째: park, 30 "[email protected]", 30 은 용량 검사에서 거절됩니다. Member 생성자를 호출하지 않으며 size: 2 → 2 , 반환값은 false 입니다. 최종 요약은 count=2, total=90 입니다. 저장 공간이 있어도 생성자가 IllegalArgumentException 을 던지면 add 가 false 를 반환하며 배열 칸과 size 를 바꾸지 않습니다. 이 예제는 생성·저장을 끝낸 다음 별도 문장으로 size++ 를 실행합니다. members[size++] = new Member(...) 와 실행 순서가 다릅니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 두 요청 성공

Kim 40과 Lee 50의 검증 생성 참조를 각각 저장한 뒤 size를 늘립니다.

  • Kim 요청: size 0→1, true
  • Lee 요청: size 1→2, true
  • 저장소: capacity=2, 두 회원 보유
  • 각 요청은 정상 생성과 배열 저장 이후에만 size를 증가시킵니다.
Park의 용량 거절

같은 저장소에 Park 30을 요청하면 가득 찬 용량 검사에서 생성 전에 끝납니다.

  • size: 2 유지
  • Park 반환: false
  • Member 생성: 실행하지 않음
  • 가득 찬 저장소의 거절이 생성자 검증보다 먼저 발생합니다.
원 main의 집계

두 성공 회원을 가진 같은 저장소의 count와 total을 읽습니다.

  • count: 2
  • total: 40+50=90
  • 별도 생성자 실패: 빈 공간이 있는 다른 조건, 원 표 보존
  • Park 거절로 배열과 size가 바뀌지 않아 집계도 두 정상 회원의 값을 유지합니다.

회원 생성 성공 뒤 배열 저장과 size 증가

Kim과 Lee는 생성 성공 후 저장·size 증가하고 Park은 용량 검사에서 생성 전에 거절됩니다. 생성 예외도 저장 확정 전에 잡아 상태를 보존하는 실제 add 게이트입니다.

회원 생성 성공 뒤 배열 저장과 size 증가

ConstructingMemberRegistry의 용량 2에서 첫 두 add는 생성 후 저장하고 size를 늘린다. 세 번째 add는 가득 찬 검사를 먼저 만나 Member 생성도 하지 않는다.

생성이 성공한 뒤 저장하고 회원 수를 늘린다

용량 2의 세 번째 요청은 생성자에 도달하기 전에 거절됩니다.

ConstructingMemberRegistry의 main: 용량 2, 처음 size = 0인 같은 저장소입니다.

세 add 요청의 생성 여부, 저장 위치, 회원 수 변화
요청생성·저장결과와 size
첫 번째"[email protected]", 40

검증 생성 성공 후 members[0]에 저장합니다.

true

0 → 1

두 번째"[email protected]", 50

검증 생성 성공 후 members[1]에 저장합니다.

true

1 → 2

세 번째"[email protected]", 30

size == members.length이므로 Member 생성자를 호출하지 않습니다.

false

2 → 2

생성이 성공한 뒤 저장하고 회원 수를 늘린다 같은capacity2저장소 Kim40 0→1true/Lee50 1→2true/Park30 capacityfull2유지false생성없음. 빈공간이어도constructorIAA catchfalse배열/size유지. 성공생성저장후별도size++. add(email, age) 배열·size 그대로 size == length ? 용량 2 검사 예: Park false 반환 생성·저장 없음 size 2 유지 아니요 Member 생성·검증 성공 참조를 얻으면 members[size]에 저장 예외 catch false · 상태 유지 size++ → true Kim 0→1 · Lee 1→2 count=2, total=90 가득 차면 생성 전 거절 · 생성 예외는 저장·size++ 전에 catch
생성이 성공한 뒤 저장하고 회원 수를 늘린다 같은capacity2저장소 Kim40 0→1true/Lee50 1→2true/Park30 capacityfull2유지false생성없음. 빈공간이어도constructorIAA catchfalse배열/size유지. 성공생성저장후별도size++. add(email, age) 같은 용량 2 저장소 배열·size는 아직 그대로 size == length ? 이미 가득 찼는가? 예 · 세 번째 Park 요청 false · 생성하지 않음 size: 2 → 2 배열도 그대로 아니요 · 생성 단계로 Member 생성·검증 이메일·14~120세 검사 정상 참조를 받은 뒤 members[size]에 저장 생성자가 예외를 던지면 catch → false 배열·size 그대로 빈 공간이어도 거절 생성·저장 성공 뒤에만 size++ → true 첫 Kim: 0 → 1 둘째 Lee: 1 → 2 count=2, total=90 생성 실패: size++ 미실행 Kim40 · Lee50 · Park30 순서
  1. 첫 번째: kim, 40

    "[email protected]", 40의 생성 성공 후 members[0]에 저장합니다. size: 0 → 1, 반환값은 true입니다.

  2. 두 번째: lee, 50

    "[email protected]", 50의 생성 성공 후 members[1]에 저장합니다. size: 1 → 2, 반환값은 true입니다.

  3. 세 번째: park, 30

    "[email protected]", 30은 용량 검사에서 거절됩니다. Member 생성자를 호출하지 않으며 size: 2 → 2, 반환값은 false입니다.

최종 요약은 count=2, total=90입니다. 저장 공간이 있어도 생성자가 IllegalArgumentException을 던지면 add가 false를 반환하며 배열 칸과 size를 바꾸지 않습니다.

이 예제는 생성·저장을 끝낸 다음 별도 문장으로 size++를 실행합니다. members[size++] = new Member(...)와 실행 순서가 다릅니다.

이 예의 조건

  • 원 main의 용량 2 같은 저장소에 Kim 40, Lee 50, Park 30을 순차 요청하는 경로만 추적합니다.
  • 빈 공간이 있는 별도 저장소의 생성자 예외 조건은 독립 분기이며 원 전체 표에 보존합니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2같은 capacity 2 저장소에서 Kim 40은 0→1 true, Lee 50은 1→2 true를 반환하고 Park 30은 capacity full로 2를 유지하며 false를 반환하고 생성하지 않는다. 빈 공간이 있어도 생성자의 IAA를 catch하면 false를 반환하며 배열/size를 유지한다. 생성에 성공해 저장한 후 별도로 size++를 실행한다.
원 도표의 설명과 주석
구분내용
설명같은 capacity 2 저장소에서 Kim 40은 0→1 true, Lee 50은 1→2 true를 반환하고 Park 30은 capacity full로 2를 유지하며 false를 반환하고 생성하지 않는다. 빈 공간이 있어도 생성자의 IAA를 catch하면 false를 반환하며 배열/size를 유지한다. 생성에 성공해 저장한 후 별도로 size++를 실행한다.
전체 읽기 정보회원 생성 성공 뒤 배열 저장과 size 증가 회원 생성 성공 뒤 배열 저장과 size 증가 ConstructingMemberRegistry의 용량 2에서 첫 두 add는 생성 후 저장하고 size를 늘린다. 세 번째 add는 가득 찬 검사를 먼저 만나 Member 생성도 하지 않는다. 생성이 성공한 뒤 저장하고 회원 수를 늘린다 용량 2의 세 번째 요청은 생성자에 도달하기 전에 거절됩니다. ConstructingMemberRegistry 의 main : 용량 2 , 처음 size = 0 인 같은 저장소입니다. 세 add 요청의 생성 여부, 저장 위치, 회원 수 변화 요청 생성·저장 결과와 size 첫 번째 "[email protected]", 40 검증 생성 성공 후 members[0] 에 저장합니다. true 0 → 1 두 번째 "[email protected]", 50 검증 생성 성공 후 members[1] 에 저장합니다. true 1 → 2 세 번째 "[email protected]", 30 size == members.length 이므로 Member 생성자를 호출하지 않습니다. false 2 → 2 첫 번째: kim, 40 "[email protected]", 40 의 생성 성공 후 members[0] 에 저장합니다. size: 0 → 1 , 반환값은 true 입니다. 두 번째: lee, 50 "[email protected]", 50 의 생성 성공 후 members[1] 에 저장합니다. size: 1 → 2 , 반환값은 true 입니다. 세 번째: park, 30 "[email protected]", 30 은 용량 검사에서 거절됩니다. Member 생성자를 호출하지 않으며 size: 2 → 2 , 반환값은 false 입니다. 최종 요약은 count=2, total=90 입니다. 저장 공간이 있어도 생성자가 IllegalArgumentException 을 던지면 add 가 false 를 반환하며 배열 칸과 size 를 바꾸지 않습니다. 이 예제는 생성·저장을 끝낸 다음 별도 문장으로 size++ 를 실행합니다. members[size++] = new Member(...) 와 실행 순서가 다릅니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
첫 두 요청 성공

Kim 40과 Lee 50의 검증 생성 참조를 각각 저장한 뒤 size를 늘립니다.

  • Kim 요청: size 0→1, true
  • Lee 요청: size 1→2, true
  • 저장소: capacity=2, 두 회원 보유
  • 각 요청은 정상 생성과 배열 저장 이후에만 size를 증가시킵니다.
Park의 용량 거절

같은 저장소에 Park 30을 요청하면 가득 찬 용량 검사에서 생성 전에 끝납니다.

  • size: 2 유지
  • Park 반환: false
  • Member 생성: 실행하지 않음
  • 가득 찬 저장소의 거절이 생성자 검증보다 먼저 발생합니다.
원 main의 집계

두 성공 회원을 가진 같은 저장소의 count와 total을 읽습니다.

  • count: 2
  • total: 40+50=90
  • 별도 생성자 실패: 빈 공간이 있는 다른 조건, 원 표 보존
  • Park 거절로 배열과 size가 바뀌지 않아 집계도 두 정상 회원의 값을 유지합니다.

연습 문제

SignupQuota에 캠페인 이름과 목표 가입자 수를 필수로 받는 생성자를 추가하세요.

이름은 공백일 수 없고 목표 가입자 수는 1~1,000명이어야 합니다. 독립 경계로 정상 이름과 목표 1·1,000, 목표 0·1,001, 그리고 null 이름을 각각 확인하세요. 이름과 목표가 모두 잘못됐을 때 어느 검사가 먼저 실패해야 할지도 판단합니다.

this로 필드와 매개변수를 구분하고 describe()가 daily=120을 반환하게 하세요.

해설 보기
src/SignupQuotaConstructor.java
public final class SignupQuotaConstructor {
    public static void main(String[] args) {
        SignupQuota goal = new SignupQuota("daily", 120);
        System.out.println(goal.describe());
    }

    private static final class SignupQuota {
        private final String name;
        private final int targetCount;

        SignupQuota(String name, int targetCount) {
            if (name == null || name.isBlank()) {
                throw new IllegalArgumentException("name");
            }
            if (targetCount < 1 || targetCount > 1_000) {
                throw new IllegalArgumentException("targetCount");
            }
            this.name = name;
            this.targetCount = targetCount;
        }

        String describe() {
            return name + "=" + targetCount;
        }
    }
}
daily=120

필수 값 두 개가 생성자 서명에 나타나므로 인수를 빼먹은 호출은 컴파일되지 않습니다.

생성 완료 뒤에는 두 필드가 항상 유효합니다. 1과 1,000은 범위에 포함되고 0과 1,001은 거절됩니다. 이름이 null이면 name == null이 참이어서 isBlank()를 호출하지 않습니다. 이름과 목표가 모두 잘못됐다면 먼저 있는 이름 검사에서 name 예외가 나며 필드 대입에 도달하지 않습니다.