본문 바로가기

메서드의 값 전달

인자에서 매개변수로 복사되는 값과 필드 변경·매개변수 재대입의 차이를 실행으로 확인합니다.

메서드에 객체를 전달하면 객체 자체가 이동할까요, 참조가 전달될까요?

Java를 설명할 때 흔히 “기본형은 값 전달, 객체는 참조 전달”이라고 줄여 말하지만 이 표현은 호출자가 가진 변수를 메서드가 직접 바꿀 수 있다는 오해를 만듭니다.

자바는 메서드를 호출할 때도 변수의 값을 복사합니다.

기본형 인자는 숫자나 불리언 값을 복사하고, 참조형 인자는 참조값을 복사합니다.

매개변수는 호출된 메서드 안에서 새로 생기는 지역 변수입니다.

이 원칙 하나로 필드 변경은 호출자에게 보이지만 매개변수 재대입은 보이지 않는 이유를 설명할 수 있습니다.


기본형 인수 전달

다음 프로그램의 결과를 먼저 예측해 보세요.

src/members/ch04/v7/PrimitiveParameter.java
public class PrimitiveParameter {
    public static void main(String[] args) {
        int age = 30;
        increase(age);
        System.out.println("main=" + age);
    }

    static void increase(int value) {
        value += 10;
        System.out.println("method=" + value);
    }
}
method=40
main=30

increase(age)를 호출하는 순간 age의 값 30이 매개변수 value에 복사됩니다.

메서드 안의 value += 10은 지역 변수 value만 40으로 바꿉니다.

호출자의 age와 연결되어 있지 않습니다.

호출자의 값을 바꾸려면 결과를 반환하고 다시 대입해야 합니다.

age = increased(age);

static int increased(int value) {
    return value + 10;
}

반환값도 메서드에서 호출자 쪽으로 복사되는 값입니다.

increase의 지역 값 변경과 increased의 반환값 대입

void 호출은 복사한 value만 30→40으로 바꾸고 반환값 재대입 버전은 호출자 age를 40으로 만듭니다. 두 독립 호출의 지역값·반환·호출자 상태를 대응 단계로 보여 줄 수 있습니다.

increase의 지역 값 변경과 increased의 반환값 대입

두 경우 모두 호출자의 age가 30인 상태에서 독립적으로 시작합니다. void increase는 지역 변수 value만 40으로 바꾸고 호출자의 age는 30을 유지합니다. int increased는 value + 10의 계산 결과 40을 반환하고, age = increased(age)의 대입이 호출자의 age를 40으로 바꿉니다.

호출자의 값은 반환 뒤 재대입해야 바뀝니다

같은 숫자 30을 매개변수에 복사합니다. void 메서드의 지역 변경과 int 메서드의 반환값 대입을 비교합니다.

두 경우 모두 age = 30에서 독립적으로 시작합니다.

왼쪽은 PrimitiveParameter 원문, 오른쪽은 본문의 반환값 대안 조각
비교 지점void increase(int value)int increased(int value)
호출문increase(age);age = increased(age);
호출 시 복사age = 30에서 value = 30age = 30에서 value = 30
메서드 안value += 10;으로 지역 값이 40return value + 10;의 계산 결과가 40
반환값없음 · void40 · int
호출문 실행 후age = 30 유지반환값을 다시 대입해 age = 40
호출자의 값은 반환 뒤 재대입해야 바뀝니다 두사례독립age30시작;increase매개변수만40/호출자30유지;increased의value30그대로,value+10결과40반환후호출자대입으로age40.점선값복사·실선호출복귀. A · void increase 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increase value: 30 → 40 지역 값만 변경 복귀 · 반환값 없음 호출자 age = 30 호출자 값은 그대로 B · int increased 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increased value = 30 value + 10 = 40 40 반환 후 age에 대입 호출자 age = 40 반환값 대입으로 변경 B의 value는 30 그대로 실선 복귀 · 점선 인자 값 복사
호출자의 값은 반환 뒤 재대입해야 바뀝니다 두사례독립age30시작;increase매개변수만40/호출자30유지;increased의value30그대로,value+10결과40반환후호출자대입으로age40.점선값복사·실선호출복귀. A · void increase 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increase value: 30 → 40 지역 값만 변경 복귀 · 반환값 없음 호출자 age = 30 호출자 값은 그대로 B · int increased 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increased value = 30 value + 10 = 40 40 반환 후 age에 대입 호출자 age = 40 반환값 대입으로 변경 B의 value는 30 그대로 실선 복귀 · 점선 인자 값 복사
PrimitiveParameter · void increase
increase(age);
호출 시 숫자 30을 지역 변수 value에 복사합니다.
value += 10;은 value만 40으로 바꿉니다.
반환값은 없고 호출자의 age는 30입니다.
본문 대안 조각 · int increased
age = increased(age);
호출 시 숫자 30을 지역 변수 value에 복사합니다.
return value + 10;이 계산한 40을 반환합니다.
호출문이 그 반환값을 다시 대입하므로 age는 40입니다.

increased는 value 자체를 재대입하지 않습니다. 호출자의 age가 바뀌는 지점은 반환값을 받는 age = … 대입입니다.

이 예의 조건

  • 두 사례는 age 30에서 독립적으로 시작합니다.
관계도의 보충 설명
원 도표의 전체 관계·라벨·조건
구분내용
2두 사례는 독립적으로 age 30에서 시작한다. increase는 매개변수만 40으로 바꾸고 호출자는 30을 유지한다. increased의 value는 30 그대로이며 value+10의 결과 40을 반환한 후 호출자 대입으로 age 40이 된다. 점선은 값 복사이고 실선은 호출 복귀이다.
원 도표의 설명과 주석
구분내용
설명두 사례는 독립적으로 age 30에서 시작한다. increase는 매개변수만 40으로 바꾸고 호출자는 30을 유지한다. increased의 value는 30 그대로이며 value+10의 결과 40을 반환한 후 호출자 대입으로 age 40이 된다. 점선은 값 복사이고 실선은 호출 복귀이다.
전체 읽기 정보increase의 지역 값 변경과 increased의 반환값 대입 increase의 지역 값 변경과 increased의 반환값 대입 두 경우 모두 호출자의 age가 30인 상태에서 독립적으로 시작합니다. void increase는 지역 변수 value만 40으로 바꾸고 호출자의 age는 30을 유지합니다. int increased는 value + 10의 계산 결과 40을 반환하고, age = increased(age)의 대입이 호출자의 age를 40으로 바꿉니다. 호출자의 값은 반환 뒤 재대입해야 바뀝니다 같은 숫자 30을 매개변수에 복사합니다. void 메서드의 지역 변경과 int 메서드의 반환값 대입을 비교합니다. 두 경우 모두 age = 30 에서 독립적으로 시작합니다. 왼쪽은 PrimitiveParameter 원문, 오른쪽은 본문의 반환값 대안 조각 비교 지점 void increase(int value) int increased(int value) 호출문 increase(age); age = increased(age); 호출 시 복사 age = 30 에서 value = 30 age = 30 에서 value = 30 메서드 안 value += 10; 으로 지역 값이 40 return value + 10; 의 계산 결과가 40 반환값 없음 · void 40 · int 호출문 실행 후 age = 30 유지 반환값을 다시 대입해 age = 40 PrimitiveParameter · void increase increase(age); 호출 시 숫자 30 을 지역 변수 value 에 복사합니다. value += 10; 은 value 만 40 으로 바꿉니다. 반환값은 없고 호출자의 age 는 30 입니다. 본문 대안 조각 · int increased age = increased(age); 호출 시 숫자 30 을 지역 변수 value 에 복사합니다. return value + 10; 이 계산한 40 을 반환합니다. 호출문이 그 반환값을 다시 대입하므로 age 는 40 입니다. increased 는 value 자체를 재대입하지 않습니다. 호출자의 age 가 바뀌는 지점은 반환값을 받는 age = … 대입입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
인자 값 복사

두 메서드의 value에 숫자 30을 복사합니다.

  • caller age: 30
  • parameter value: 30
  • 매개변수는 호출자 변수와 다른 저장 위치입니다.
메서드 내부

increase는 value를 40으로 바꾸고, increased는 value 30에서 40을 계산합니다.

  • void value: 40
  • int 반환식: 30+10=40
  • 지역 재대입과 반환값 계산이 다른 동작입니다.
복귀와 채택

void 호출자의 age는 30, 반환값 대입 호출자는 40입니다.

  • A caller: 30
  • B caller: 40
  • 호출자 재대입이 있어야 호출자 기본형 값이 변합니다.

increase의 지역 값 변경과 increased의 반환값 대입

void 호출은 복사한 value만 30→40으로 바꾸고 반환값 재대입 버전은 호출자 age를 40으로 만듭니다. 두 독립 호출의 지역값·반환·호출자 상태를 대응 단계로 보여 줄 수 있습니다.

increase의 지역 값 변경과 increased의 반환값 대입

두 경우 모두 호출자의 age가 30인 상태에서 독립적으로 시작합니다. void increase는 지역 변수 value만 40으로 바꾸고 호출자의 age는 30을 유지합니다. int increased는 value + 10의 계산 결과 40을 반환하고, age = increased(age)의 대입이 호출자의 age를 40으로 바꿉니다.

호출자의 값은 반환 뒤 재대입해야 바뀝니다

같은 숫자 30을 매개변수에 복사합니다. void 메서드의 지역 변경과 int 메서드의 반환값 대입을 비교합니다.

두 경우 모두 age = 30에서 독립적으로 시작합니다.

왼쪽은 PrimitiveParameter 원문, 오른쪽은 본문의 반환값 대안 조각
비교 지점void increase(int value)int increased(int value)
호출문increase(age);age = increased(age);
호출 시 복사age = 30에서 value = 30age = 30에서 value = 30
메서드 안value += 10;으로 지역 값이 40return value + 10;의 계산 결과가 40
반환값없음 · void40 · int
호출문 실행 후age = 30 유지반환값을 다시 대입해 age = 40
호출자의 값은 반환 뒤 재대입해야 바뀝니다 두사례독립age30시작;increase매개변수만40/호출자30유지;increased의value30그대로,value+10결과40반환후호출자대입으로age40.점선값복사·실선호출복귀. A · void increase 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increase value: 30 → 40 지역 값만 변경 복귀 · 반환값 없음 호출자 age = 30 호출자 값은 그대로 B · int increased 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increased value = 30 value + 10 = 40 40 반환 후 age에 대입 호출자 age = 40 반환값 대입으로 변경 B의 value는 30 그대로 실선 복귀 · 점선 인자 값 복사
호출자의 값은 반환 뒤 재대입해야 바뀝니다 두사례독립age30시작;increase매개변수만40/호출자30유지;increased의value30그대로,value+10결과40반환후호출자대입으로age40.점선값복사·실선호출복귀. A · void increase 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increase value: 30 → 40 지역 값만 변경 복귀 · 반환값 없음 호출자 age = 30 호출자 값은 그대로 B · int increased 호출자 age = 30 각 사례는 독립 시작 숫자 30을 매개변수에 복사 increased value = 30 value + 10 = 40 40 반환 후 age에 대입 호출자 age = 40 반환값 대입으로 변경 B의 value는 30 그대로 실선 복귀 · 점선 인자 값 복사
PrimitiveParameter · void increase
increase(age);
호출 시 숫자 30을 지역 변수 value에 복사합니다.
value += 10;은 value만 40으로 바꿉니다.
반환값은 없고 호출자의 age는 30입니다.
본문 대안 조각 · int increased
age = increased(age);
호출 시 숫자 30을 지역 변수 value에 복사합니다.
return value + 10;이 계산한 40을 반환합니다.
호출문이 그 반환값을 다시 대입하므로 age는 40입니다.

increased는 value 자체를 재대입하지 않습니다. 호출자의 age가 바뀌는 지점은 반환값을 받는 age = … 대입입니다.

이 예의 조건

  • 두 사례는 age 30에서 독립적으로 시작합니다.

관계도의 보충 설명

원 도표의 전체 관계·라벨·조건
구분내용
2두 사례는 독립적으로 age 30에서 시작한다. increase는 매개변수만 40으로 바꾸고 호출자는 30을 유지한다. increased의 value는 30 그대로이며 value+10의 결과 40을 반환한 후 호출자 대입으로 age 40이 된다. 점선은 값 복사이고 실선은 호출 복귀이다.
원 도표의 설명과 주석
구분내용
설명두 사례는 독립적으로 age 30에서 시작한다. increase는 매개변수만 40으로 바꾸고 호출자는 30을 유지한다. increased의 value는 30 그대로이며 value+10의 결과 40을 반환한 후 호출자 대입으로 age 40이 된다. 점선은 값 복사이고 실선은 호출 복귀이다.
전체 읽기 정보increase의 지역 값 변경과 increased의 반환값 대입 increase의 지역 값 변경과 increased의 반환값 대입 두 경우 모두 호출자의 age가 30인 상태에서 독립적으로 시작합니다. void increase는 지역 변수 value만 40으로 바꾸고 호출자의 age는 30을 유지합니다. int increased는 value + 10의 계산 결과 40을 반환하고, age = increased(age)의 대입이 호출자의 age를 40으로 바꿉니다. 호출자의 값은 반환 뒤 재대입해야 바뀝니다 같은 숫자 30을 매개변수에 복사합니다. void 메서드의 지역 변경과 int 메서드의 반환값 대입을 비교합니다. 두 경우 모두 age = 30 에서 독립적으로 시작합니다. 왼쪽은 PrimitiveParameter 원문, 오른쪽은 본문의 반환값 대안 조각 비교 지점 void increase(int value) int increased(int value) 호출문 increase(age); age = increased(age); 호출 시 복사 age = 30 에서 value = 30 age = 30 에서 value = 30 메서드 안 value += 10; 으로 지역 값이 40 return value + 10; 의 계산 결과가 40 반환값 없음 · void 40 · int 호출문 실행 후 age = 30 유지 반환값을 다시 대입해 age = 40 PrimitiveParameter · void increase increase(age); 호출 시 숫자 30 을 지역 변수 value 에 복사합니다. value += 10; 은 value 만 40 으로 바꿉니다. 반환값은 없고 호출자의 age 는 30 입니다. 본문 대안 조각 · int increased age = increased(age); 호출 시 숫자 30 을 지역 변수 value 에 복사합니다. return value + 10; 이 계산한 40 을 반환합니다. 호출문이 그 반환값을 다시 대입하므로 age 는 40 입니다. increased 는 value 자체를 재대입하지 않습니다. 호출자의 age 가 바뀌는 지점은 반환값을 받는 age = … 대입입니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
인자 값 복사

두 메서드의 value에 숫자 30을 복사합니다.

  • caller age: 30
  • parameter value: 30
  • 매개변수는 호출자 변수와 다른 저장 위치입니다.
메서드 내부

increase는 value를 40으로 바꾸고, increased는 value 30에서 40을 계산합니다.

  • void value: 40
  • int 반환식: 30+10=40
  • 지역 재대입과 반환값 계산이 다른 동작입니다.
복귀와 채택

void 호출자의 age는 30, 반환값 대입 호출자는 40입니다.

  • A caller: 30
  • B caller: 40
  • 호출자 재대입이 있어야 호출자 기본형 값이 변합니다.

참조형 인수 전달

이번에는 Member를 전달합니다.

src/members/ch04/v7/ReferenceParameter.java
public class ReferenceParameter {
    public static void main(String[] args) {
        Member member = new Member();
        member.email = "[email protected]";
        member.age = 30;
        member.active = false;

        activate(member);

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

    static void activate(Member target) {
        target.active = true;
    }
}

호출 전 member에 x101이 들어 있다면 activate의 target에도 x101이 복사됩니다.

두 변수는 서로 다른 지역 변수지만 같은 인스턴스에 도달합니다.

target.active = true는 target 변수를 수정한 것이 아니라 x101 객체의 필드를 수정했으므로 호출 뒤 member.active로 읽어도 true입니다.


매개변수 재대입

참조값이 복사된다는 사실을 확인하기 가장 좋은 반례는 메서드 안에서 매개변수를 새 객체로 바꾸는 것입니다.

src/members/ch04/v7/ReassignParameter.java
public class ReassignParameter {
    public static void main(String[] args) {
        Member member = new Member();
        member.email = "[email protected]";

        replace(member);

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

    static void replace(Member target) {
        target = new Member();
        target.email = "[email protected]";
    }
}

replace를 시작할 때는 member와 target이 모두 x101을 가리킵니다.

target = new Member()가 새 객체 x202를 만들고 지역 변수 target에 새 참조를 넣습니다.

호출자의 member 변수에는 대입하지 않았으므로 여전히 x101입니다.

메서드가 끝나면 지역 변수 target은 사라지고, 다른 참조가 없다면 x202는 더 이상 도달할 수 없습니다.

“객체를 메서드에 넘기면 메서드가 호출자의 변수를 바꿀 수 있다”는 설명은 정확하지 않습니다.

메서드는 공유 객체의 필드를 바꿀 수 있지만 호출자의 지역 변수 자체를 다른 참조로 교체할 수는 없습니다.

공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계

activate는 같은 객체의 필드를 바꾸고 replace는 매개변수 target만 새 객체로 바꿉니다. 호출자 참조가 유지되는 두 변경 대상의 전후를 비교합니다.

공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계

ReferenceParameter와 ReassignParameter는 별도 프로그램입니다. activate의 target은 호출자의 member와 같은 객체를 참조해 active를 true로 바꿉니다. replace에서는 target만 새 객체를 가리키며, 호출자의 member는 기존 객체를 계속 가리킵니다.

공유 객체 변경과 매개변수 재대입을 구별합니다

둘 다 참조값을 지역 매개변수로 복사합니다. 필드 변경은 공유 객체에 보이고, 매개변수 재대입은 호출자의 변수에 대입하지 않습니다.

서로 다른 두 프로그램입니다. 둘 다 호출 시 member의 참조값을 지역 매개변수 target에 복사합니다.

ReferenceParameter · target.active = true; 직후, 반환 전

activate 안의 두 참조는 같은 객체를 가리킨다member와 target이 같은 [email protected], age 30인 객체를 가리킨다. target.active = true가 객체 필드를 바꿔 호출자도 true를 읽는다. membermain의 지역 변수targetactivate 매개변수같은 Member 객체email = "[email protected]"age = 30active = true
공유 객체 변경과 매개변수 재대입을 구별합니다 ReferenceParameter:member/target→같은method/30/true객체;ReassignParameter:member→original객체유지,target만temporary새객체참조. 두프로그램독립이며현재참조4방향. A · ReferenceParameter member main의 참조값 target activate의 지역 참조값 같은 Member [email protected] age = 30 active = true B · ReassignParameter member main의 참조는 유지 기존 Member [email protected] target replace 안에서만 재대입 새 Member [email protected] 호출자 변수에는 미대입 target의 별도 도착지 실선: 호출 안의 현재 참조 A와 B는 별도 프로그램
member와 target의 도착지는 같은 객체
email = "[email protected]", age = 30
target.active = true;가 그 객체의 필드를 바꿉니다.

반환 뒤 member.active로 읽어도 true입니다. target의 참조값은 바꾸지 않았습니다.

ReassignParameter · target.email = "[email protected]"; 직후, 반환 전

replace의 재대입은 target만 다른 객체로 옮긴다member는 [email protected]인 기존 객체를 계속 참조한다. 새 객체를 대입한 target만 [email protected] 객체를 가리킨다. 두 객체의 이메일 필드만 표시한다. membermain의 지역 변수targetreplace 매개변수호출자가 만든 객체email = "[email protected]"replace가 만든 새 객체email = "[email protected]"
member · 호출자가 만든 객체
email = "[email protected]"
호출자의 변수에는 새 참조를 대입하지 않았습니다.
target · replace가 만든 새 객체
target = new Member();로 매개변수만 재대입합니다.
그 새 객체의 email은 "[email protected]"입니다.

두 객체의 이메일 필드만 표시했습니다. 반환 뒤 호출자 출력은 [email protected]입니다. target의 수명이 끝나고 다른 참조가 없다면 새 객체에는 도달할 수 없으며, 이것이 즉시 회수를 뜻하지는 않습니다.

이 예의 조건

  • activate 매개변수는 객체 참조값 복사입니다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계 공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계 ReferenceParameter와 ReassignParameter는 별도 프로그램입니다. activate의 target은 호출자의 member와 같은 객체를 참조해 active를 true로 바꿉니다. replace에서는 target만 새 객체를 가리키며, 호출자의 member는 기존 객체를 계속 가리킵니다. 공유 객체 변경과 매개변수 재대입을 구별합니다 둘 다 참조값을 지역 매개변수로 복사합니다. 필드 변경은 공유 객체에 보이고, 매개변수 재대입은 호출자의 변수에 대입하지 않습니다. 서로 다른 두 프로그램입니다. 둘 다 호출 시 member 의 참조값을 지역 매개변수 target 에 복사합니다. ReferenceParameter · target.active = true; 직후, 반환 전 member와 target의 도착지는 같은 객체 email = "[email protected]" , age = 30 target.active = true; 가 그 객체의 필드를 바꿉니다. 반환 뒤 member.active 로 읽어도 true 입니다. target 의 참조값은 바꾸지 않았습니다. ReassignParameter · target.email = "[email protected]"; 직후, 반환 전 member · 호출자가 만든 객체 email = "[email protected]" 호출자의 변수에는 새 참조를 대입하지 않았습니다. target · replace가 만든 새 객체 target = new Member(); 로 매개변수만 재대입합니다. 그 새 객체의 email 은 "[email protected]" 입니다. 두 객체의 이메일 필드만 표시했습니다. 반환 뒤 호출자 출력은 [email protected] 입니다. target 의 수명이 끝나고 다른 참조가 없다면 새 객체에는 도달할 수 없으며, 이것이 즉시 회수를 뜻하지는 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
호출자 객체

member가 [email protected], age 30 객체를 참조합니다.

  • member: Member 참조
  • active: 변경 전 상태
  • 호출 인자로 같은 참조값을 넘깁니다.
매개변수 경로

target이 호출자와 같은 객체를 가리킵니다.

  • member/target: 같은 객체
  • 객체 수: 1
  • 매개변수 자체 재대입 없이 필드 접근을 수행합니다.
필드 쓰기

target.active=true를 저장합니다.

  • 객체 active: true
  • caller 읽기: true
  • 객체 필드 변경은 같은 참조의 호출자에게도 보입니다.

공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계

activate는 같은 객체의 필드를 바꾸고 replace는 매개변수 target만 새 객체로 바꿉니다. 호출자 참조가 유지되는 두 변경 대상의 전후를 비교합니다.

공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계

ReferenceParameter와 ReassignParameter는 별도 프로그램입니다. activate의 target은 호출자의 member와 같은 객체를 참조해 active를 true로 바꿉니다. replace에서는 target만 새 객체를 가리키며, 호출자의 member는 기존 객체를 계속 가리킵니다.

공유 객체 변경과 매개변수 재대입을 구별합니다

둘 다 참조값을 지역 매개변수로 복사합니다. 필드 변경은 공유 객체에 보이고, 매개변수 재대입은 호출자의 변수에 대입하지 않습니다.

서로 다른 두 프로그램입니다. 둘 다 호출 시 member의 참조값을 지역 매개변수 target에 복사합니다.

ReferenceParameter · target.active = true; 직후, 반환 전

activate 안의 두 참조는 같은 객체를 가리킨다member와 target이 같은 [email protected], age 30인 객체를 가리킨다. target.active = true가 객체 필드를 바꿔 호출자도 true를 읽는다. membermain의 지역 변수targetactivate 매개변수같은 Member 객체email = "[email protected]"age = 30active = true
공유 객체 변경과 매개변수 재대입을 구별합니다 ReferenceParameter:member/target→같은method/30/true객체;ReassignParameter:member→original객체유지,target만temporary새객체참조. 두프로그램독립이며현재참조4방향. A · ReferenceParameter member main의 참조값 target activate의 지역 참조값 같은 Member [email protected] age = 30 active = true B · ReassignParameter member main의 참조는 유지 기존 Member [email protected] target replace 안에서만 재대입 새 Member [email protected] 호출자 변수에는 미대입 target의 별도 도착지 실선: 호출 안의 현재 참조 A와 B는 별도 프로그램
member와 target의 도착지는 같은 객체
email = "[email protected]", age = 30
target.active = true;가 그 객체의 필드를 바꿉니다.

반환 뒤 member.active로 읽어도 true입니다. target의 참조값은 바꾸지 않았습니다.

ReassignParameter · target.email = "[email protected]"; 직후, 반환 전

replace의 재대입은 target만 다른 객체로 옮긴다member는 [email protected]인 기존 객체를 계속 참조한다. 새 객체를 대입한 target만 [email protected] 객체를 가리킨다. 두 객체의 이메일 필드만 표시한다. membermain의 지역 변수targetreplace 매개변수호출자가 만든 객체email = "[email protected]"replace가 만든 새 객체email = "[email protected]"
member · 호출자가 만든 객체
email = "[email protected]"
호출자의 변수에는 새 참조를 대입하지 않았습니다.
target · replace가 만든 새 객체
target = new Member();로 매개변수만 재대입합니다.
그 새 객체의 email은 "[email protected]"입니다.

두 객체의 이메일 필드만 표시했습니다. 반환 뒤 호출자 출력은 [email protected]입니다. target의 수명이 끝나고 다른 참조가 없다면 새 객체에는 도달할 수 없으며, 이것이 즉시 회수를 뜻하지는 않습니다.

이 예의 조건

  • activate 매개변수는 객체 참조값 복사입니다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계 공유 필드 수정과 지역 매개변수 재대입의 다른 참조 관계 ReferenceParameter와 ReassignParameter는 별도 프로그램입니다. activate의 target은 호출자의 member와 같은 객체를 참조해 active를 true로 바꿉니다. replace에서는 target만 새 객체를 가리키며, 호출자의 member는 기존 객체를 계속 가리킵니다. 공유 객체 변경과 매개변수 재대입을 구별합니다 둘 다 참조값을 지역 매개변수로 복사합니다. 필드 변경은 공유 객체에 보이고, 매개변수 재대입은 호출자의 변수에 대입하지 않습니다. 서로 다른 두 프로그램입니다. 둘 다 호출 시 member 의 참조값을 지역 매개변수 target 에 복사합니다. ReferenceParameter · target.active = true; 직후, 반환 전 member와 target의 도착지는 같은 객체 email = "[email protected]" , age = 30 target.active = true; 가 그 객체의 필드를 바꿉니다. 반환 뒤 member.active 로 읽어도 true 입니다. target 의 참조값은 바꾸지 않았습니다. ReassignParameter · target.email = "[email protected]"; 직후, 반환 전 member · 호출자가 만든 객체 email = "[email protected]" 호출자의 변수에는 새 참조를 대입하지 않았습니다. target · replace가 만든 새 객체 target = new Member(); 로 매개변수만 재대입합니다. 그 새 객체의 email 은 "[email protected]" 입니다. 두 객체의 이메일 필드만 표시했습니다. 반환 뒤 호출자 출력은 [email protected] 입니다. target 의 수명이 끝나고 다른 참조가 없다면 새 객체에는 도달할 수 없으며, 이것이 즉시 회수를 뜻하지는 않습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
호출자 객체

member가 [email protected], age 30 객체를 참조합니다.

  • member: Member 참조
  • active: 변경 전 상태
  • 호출 인자로 같은 참조값을 넘깁니다.
매개변수 경로

target이 호출자와 같은 객체를 가리킵니다.

  • member/target: 같은 객체
  • 객체 수: 1
  • 매개변수 자체 재대입 없이 필드 접근을 수행합니다.
필드 쓰기

target.active=true를 저장합니다.

  • 객체 active: true
  • caller 읽기: true
  • 객체 필드 변경은 같은 참조의 호출자에게도 보입니다.

초기화 메서드

객체를 여러 개 만들다 보면 필드 대입 코드가 반복됩니다.

Member first = new Member();
first.email = "[email protected]";
first.age = 40;
first.active = false;

Member second = new Member();
second.email = "[email protected]";
second.age = 35;
second.active = false;

참조형 매개변수가 공유 객체를 바꾼다는 점을 이용해 초기화 메서드를 만들 수 있습니다.

src/members/ch04/v7/MemberInitializer.java
public class MemberInitializer {
    public static void main(String[] args) {
        Member first = new Member();
        init(first, "[email protected]", 40, false);

        Member second = new Member();
        init(second, "[email protected]", 35, false);

        print(first);
        print(second);
    }

    static void init(
        Member target,
        String email,
        int age,
        boolean active
    ) {
        target.email = email;
        target.age = age;
        target.active = active;
    }

    static void print(Member member) {
        System.out.printf(
            "%s / %d세 / %s%n",
            member.email, member.age, member.active
        );
    }
}

init은 새 객체를 만들지 않습니다.

호출자가 만든 객체의 참조를 받아 필드를 채웁니다.

이 방식은 중복을 줄이지만 호출 전에 빈 객체를 만들어야 하고, 초기화를 빼먹은 객체도 여전히 존재할 수 있습니다.

생성자가 필요한 이유가 여기서 나타나지만, 그 개선은 다음 장의 객체 생명주기에서 다룹니다.


팩터리 메서드

현재 문법만으로도 객체 생성과 필드 초기화를 한 메서드에 모을 수 있습니다.

src/members/ch04/v7/MemberFactory.java
public class MemberFactory {
    public static void main(String[] args) {
        Member member = create("[email protected]", 45, false);
        print(member);
    }

    static Member create(
        String email,
        int age,
        boolean active
    ) {
        Member created = new Member();
        created.email = email;
        created.age = age;
        created.active = active;
        return created;
    }

    static void print(Member member) {
        System.out.printf(
            "%s / %d세 / %s%n",
            member.email, member.age, member.active
        );
    }
}

create 안의 지역 변수 created는 메서드가 끝나면 사라집니다.

하지만 반환문이 그 변수의 참조값을 호출자에게 복사합니다.

호출자의 member가 같은 객체를 가리키므로 객체는 계속 도달 가능한 상태입니다.

create의 지역 변수 종료와 반환된 객체의 도달 가능성

create가 지역 변수 created의 참조값을 반환하면 main의 member가 이를 받아 같은 객체를 계속 가리킵니다. 프레임 종료 전후에서 지역 변수 수명과 객체 도달 가능성을 구분할 수 있습니다.

create의 지역 변수 종료와 반환된 객체의 도달 가능성

MemberFactory의 create가 만든 객체 하나를 두 시점에서 표시합니다. 반환 전에는 created가, 반환값 대입 후에는 main의 member가 같은 객체를 참조합니다. 두 시점 모두 email은 [email protected], age는 45, active는 false입니다. 지역 변수 created의 수명이 끝나도 그 객체의 소멸을 뜻하지는 않습니다.

반환된 참조로 같은 객체에 계속 도달합니다

create의 지역 변수는 호출이 끝나면 종료됩니다. 반환값을 받은 main의 member가 같은 객체를 계속 참조합니다.

MemberFactory · create("[email protected]", 45, false)

아래 두 시점은 new 한 번으로 만든 같은 객체를 다시 표시한 것입니다.

return 전후에 같은 객체를 가리키는 지역 변수return created 직전에는 create의 created가 객체를 참조한다. create 반환값 대입 직후에는 main의 member가 같은 객체를 참조한다. 객체 필드는 두 시점 모두 [email protected], 45, false이다. ① return created; 직전② main의 반환값 대입 직후 · print 호출 전createdcreate의 지역 변수membermain의 지역 변수create에서 만든 객체email = "[email protected]"age = 45active = false반환 전과 같은 객체email = "[email protected]"age = 45active = false
반환된 참조로 같은 객체에 계속 도달합니다 return전create의created→객체,반환값대입후main의member→같은객체. emailfactory/45/false보존,new1번이며프레임·지역변수종료≠객체소멸. 반환 전 · create 프레임 created 현재 프레임의 지역 참조 같은 Member 객체 [email protected] age = 45 active = false 반환 후 · main 프레임 member 현재 프레임의 지역 참조 같은 Member 객체 [email protected] age = 45 active = false 같은 객체 · new 1번 created 지역변수 종료 member 참조는 유지
① return created; 직전
create의 지역 변수 created가 새로 만든 객체를 참조합니다.
email = "[email protected]", age = 45, active = false
② main의 반환값 대입 직후 · print 호출 전
created의 참조값을 반환받아 member에 복사했습니다.
member는 새로 복제한 객체가 아니라 위와 같은 객체를 가리킵니다.
필드는 그대로 "[email protected]", 45, false입니다.

create가 끝나면 지역 변수 created의 수명은 끝납니다. 호출자의 member가 같은 객체를 참조하므로 그 객체에는 계속 도달할 수 있습니다.

이 예의 조건

  • 반환 전후 객체는 같고 main의 print 호출 전 상태입니다.
관계도의 보충 설명
원 도표의 설명과 주석
구분내용
전체 읽기 정보create의 지역 변수 종료와 반환된 객체의 도달 가능성 create의 지역 변수 종료와 반환된 객체의 도달 가능성 MemberFactory의 create가 만든 객체 하나를 두 시점에서 표시합니다. 반환 전에는 created가, 반환값 대입 후에는 main의 member가 같은 객체를 참조합니다. 두 시점 모두 email은 [email protected], age는 45, active는 false입니다. 지역 변수 created의 수명이 끝나도 그 객체의 소멸을 뜻하지는 않습니다. 반환된 참조로 같은 객체에 계속 도달합니다 create의 지역 변수는 호출이 끝나면 종료됩니다. 반환값을 받은 main의 member가 같은 객체를 계속 참조합니다. MemberFactory · create("[email protected]", 45, false) 아래 두 시점은 new 한 번으로 만든 같은 객체를 다시 표시한 것입니다. ① return created; 직전 create 의 지역 변수 created 가 새로 만든 객체를 참조합니다. email = "[email protected]" , age = 45 , active = false ② main의 반환값 대입 직후 · print 호출 전 created 의 참조값을 반환받아 member 에 복사했습니다. member 는 새로 복제한 객체가 아니라 위와 같은 객체를 가리킵니다. 필드는 그대로 "[email protected]" , 45 , false 입니다. create 가 끝나면 지역 변수 created 의 수명은 끝납니다. 호출자의 member 가 같은 객체를 참조하므로 그 객체에는 계속 도달할 수 있습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
팩토리 지역

create의 created가 새 객체를 참조합니다.

  • 팩토리 지역 변수 수명과 객체 수명은 같지 않습니다.
참조 반환

return created가 현재 참조값을 반환합니다.

  • 반환: 같은 Member 참조
  • 새 객체 복사: 없음
  • return이 객체 필드를 복제하지 않습니다.
호출자 보관

main의 member가 반환 참조를 받습니다.

  • 지역 created가 끝나도 호출자의 객체 참조는 남습니다.

create의 지역 변수 종료와 반환된 객체의 도달 가능성

create가 지역 변수 created의 참조값을 반환하면 main의 member가 이를 받아 같은 객체를 계속 가리킵니다. 프레임 종료 전후에서 지역 변수 수명과 객체 도달 가능성을 구분할 수 있습니다.

create의 지역 변수 종료와 반환된 객체의 도달 가능성

MemberFactory의 create가 만든 객체 하나를 두 시점에서 표시합니다. 반환 전에는 created가, 반환값 대입 후에는 main의 member가 같은 객체를 참조합니다. 두 시점 모두 email은 [email protected], age는 45, active는 false입니다. 지역 변수 created의 수명이 끝나도 그 객체의 소멸을 뜻하지는 않습니다.

반환된 참조로 같은 객체에 계속 도달합니다

create의 지역 변수는 호출이 끝나면 종료됩니다. 반환값을 받은 main의 member가 같은 객체를 계속 참조합니다.

MemberFactory · create("[email protected]", 45, false)

아래 두 시점은 new 한 번으로 만든 같은 객체를 다시 표시한 것입니다.

return 전후에 같은 객체를 가리키는 지역 변수return created 직전에는 create의 created가 객체를 참조한다. create 반환값 대입 직후에는 main의 member가 같은 객체를 참조한다. 객체 필드는 두 시점 모두 [email protected], 45, false이다. ① return created; 직전② main의 반환값 대입 직후 · print 호출 전createdcreate의 지역 변수membermain의 지역 변수create에서 만든 객체email = "[email protected]"age = 45active = false반환 전과 같은 객체email = "[email protected]"age = 45active = false
반환된 참조로 같은 객체에 계속 도달합니다 return전create의created→객체,반환값대입후main의member→같은객체. emailfactory/45/false보존,new1번이며프레임·지역변수종료≠객체소멸. 반환 전 · create 프레임 created 현재 프레임의 지역 참조 같은 Member 객체 [email protected] age = 45 active = false 반환 후 · main 프레임 member 현재 프레임의 지역 참조 같은 Member 객체 [email protected] age = 45 active = false 같은 객체 · new 1번 created 지역변수 종료 member 참조는 유지
① return created; 직전
create의 지역 변수 created가 새로 만든 객체를 참조합니다.
email = "[email protected]", age = 45, active = false
② main의 반환값 대입 직후 · print 호출 전
created의 참조값을 반환받아 member에 복사했습니다.
member는 새로 복제한 객체가 아니라 위와 같은 객체를 가리킵니다.
필드는 그대로 "[email protected]", 45, false입니다.

create가 끝나면 지역 변수 created의 수명은 끝납니다. 호출자의 member가 같은 객체를 참조하므로 그 객체에는 계속 도달할 수 있습니다.

이 예의 조건

  • 반환 전후 객체는 같고 main의 print 호출 전 상태입니다.

관계도의 보충 설명

원 도표의 설명과 주석
구분내용
전체 읽기 정보create의 지역 변수 종료와 반환된 객체의 도달 가능성 create의 지역 변수 종료와 반환된 객체의 도달 가능성 MemberFactory의 create가 만든 객체 하나를 두 시점에서 표시합니다. 반환 전에는 created가, 반환값 대입 후에는 main의 member가 같은 객체를 참조합니다. 두 시점 모두 email은 [email protected], age는 45, active는 false입니다. 지역 변수 created의 수명이 끝나도 그 객체의 소멸을 뜻하지는 않습니다. 반환된 참조로 같은 객체에 계속 도달합니다 create의 지역 변수는 호출이 끝나면 종료됩니다. 반환값을 받은 main의 member가 같은 객체를 계속 참조합니다. MemberFactory · create("[email protected]", 45, false) 아래 두 시점은 new 한 번으로 만든 같은 객체를 다시 표시한 것입니다. ① return created; 직전 create 의 지역 변수 created 가 새로 만든 객체를 참조합니다. email = "[email protected]" , age = 45 , active = false ② main의 반환값 대입 직후 · print 호출 전 created 의 참조값을 반환받아 member 에 복사했습니다. member 는 새로 복제한 객체가 아니라 위와 같은 객체를 가리킵니다. 필드는 그대로 "[email protected]" , 45 , false 입니다. create 가 끝나면 지역 변수 created 의 수명은 끝납니다. 호출자의 member 가 같은 객체를 참조하므로 그 객체에는 계속 도달할 수 있습니다.
예의 값과 변화
관찰·상황값·보충 정보변화·이유
팩토리 지역

create의 created가 새 객체를 참조합니다.

  • 팩토리 지역 변수 수명과 객체 수명은 같지 않습니다.
참조 반환

return created가 현재 참조값을 반환합니다.

  • 반환: 같은 Member 참조
  • 새 객체 복사: 없음
  • return이 객체 필드를 복제하지 않습니다.
호출자 보관

main의 member가 반환 참조를 받습니다.

  • 지역 created가 끝나도 호출자의 객체 참조는 남습니다.

지역 변수의 수명과 객체의 수명은 같지 않습니다.

객체는 특정 지역 변수에 소속되는 것이 아니라 도달 가능한 참조가 있는 동안 사용될 수 있습니다.


변경 메서드의 이름

참조형 매개변수를 받는 메서드는 전달된 객체를 읽기만 할 수도 있고 필드를 바꿀 수도 있습니다.

static void print(Member member) { ... }    // 읽기
static void activate(Member member) { ... } // 변경

컴파일러가 이 차이를 메서드 시그니처만으로 강제하지는 않습니다.

따라서 이름, 문서, 객체 설계로 변경 여부를 드러내야 합니다.

print가 몰래 active = true로 바꾼다면 호출자는 출력만 요청했다고 생각했다가 예상하지 못한 상태 변경을 만납니다.

현재의 Member는 필드 접근 제한도 없고 객체가 스스로 행동하지도 않는 단순 데이터 묶음입니다.

이후 접근 제어와 캡슐화를 배우면 변경 책임을 객체 안으로 옮길 수 있습니다.

지금은 호출 경계에서 어떤 값이 복사되는지 확실히 보는 것이 우선입니다.


연습 문제

다음 두 메서드를 구현하고 차이를 설명하세요. 이번 문제는 현재 나이가 14–120 범위이고 celebrateBirthday와 replace에 실제 Member 객체를 전달한다는 전제입니다. 입력 문자열의 변환이나 null 복구는 이 두 메서드의 역할에 넣지 않습니다.

  1. int nextAge(int current)는 다음 나이를 반환합니다.
  2. void celebrateBirthday(Member member)는 전달된 회원의 나이를 1 증가시킵니다.
  3. 현재 나이가 120이면 두 메서드 모두 IllegalStateException을 던집니다.
  4. replace(Member member)에서 매개변수에 새 객체를 대입해도 호출자 변수가 바뀌지 않는 실행 사례를 추가합니다.
해설 보기
src/members/ch04/exercise/BirthdayParameter.java
public class BirthdayParameter {
    public static void main(String[] args) {
        int age = 30;
        age = nextAge(age);
        System.out.println("number=" + age);

        Member member = new Member();
        member.email = "[email protected]";
        member.age = 30;
        celebrateBirthday(member);
        System.out.println("member=" + member.age);

        replace(member);
        System.out.println("after replace=" + member.email);
    }

    static int nextAge(int current) {
        requireBirthdayAllowed(current);
        return current + 1;
    }

    static void celebrateBirthday(Member member) {
        requireBirthdayAllowed(member.age);
        member.age++;
    }

    static void replace(Member member) {
        member = new Member();
        member.email = "[email protected]";
    }

    static void requireBirthdayAllowed(int currentAge) {
        if (currentAge >= 120) {
            throw new IllegalStateException("maximum age");
        }
    }
}
number=31
member=31
after [email protected]

경계도 별도로 확인하세요. 119세이면 두 버전 모두 120세가 되지만, 120세이면 maximum age 예외가 먼저 발생합니다. 객체 버전에서는 member.age++에 도달하지 않으므로 원래 120이 유지됩니다. 예외를 잡지 않은 main은 그 자리에서 끝나므로 숫자 버전과 객체 버전의 실패는 호출 하나씩 바꾸어 따로 실행하세요.

숫자 버전은 계산 결과를 반환하므로 호출자가 다시 대입해야 합니다.

객체 버전은 복사된 참조로 공유 객체의 필드를 바꿉니다.

replace는 지역 매개변수만 새 참조로 바꾸므로 호출자의 member.email은 그대로입니다.


메서드 호출도 대입과 똑같이 값을 복사합니다.

이 규칙을 알면 필드 변경, 재대입, 반환 참조를 하나의 흐름으로 설명할 수 있습니다.

다음 절에서는 참조값이 아예 없음을 나타내는 null과 점 연산이 만날 때 왜 실행이 중단되는지 조사합니다.