변수와 이름
선언·초기화·대입·읽기를 실행 순서로 구분하고 가입할 회원 한 명의 정보를 변수로 표현합니다.
앞 장에서는 문자열을 출력하고 명령의 실행 인자를 읽었습니다.
이제 회원가입에 필요한 이메일과 나이를 프로그램 안에 보관합니다.
같은 값이 여러 계산과 출력에 쓰일 때 리터럴을 반복하면 어느 값이 같은 의미인지 코드에 남지 않습니다.
변수는 메모리 칸이라는 설명보다 한 실행 동안 같은 의미를 다시 가리키는 이름으로 먼저 이해하는 편이 유용합니다.
중복 리터럴의 변경 누락
다음 프로그램은 회원 이메일을 바꾸는 과정에서 환영 문구 한 곳을 놓친 상태입니다.
이번 장의 lab 폴더에는 성공·실패 원인을 따로 관찰할 짧은 실험을, src에는 완성된 프로그램을 저장합니다. 먼저 java-board에서 lab을 만드세요. PowerShell은 New-Item -ItemType Directory -Force lab | Out-Null, macOS·Linux는 mkdir -p lab을 사용합니다.
아래 코드를 lab/RepeatedLiteral.java로 저장한 뒤 다음 두 명령을 순서대로 실행합니다.
javac -encoding UTF-8 -d out lab/RepeatedLiteral.java
java -cp out RepeatedLiteral다음 파일들도 같은 방식으로 경로와 클래스 이름을 바꾸어 실행합니다. 컴파일 실패를 관찰하는 실험은 오류를 확인한 뒤 멈추며, 이전에 남은 .class를 실행해 성공으로 판단하지 않습니다.
public final class RepeatedLiteral {
public static void main(String[] args) {
System.out.println("[email protected]");
System.out.println("[email protected]");
System.out.println("[email protected]");
}
}출력 세 줄은 모두 정상 문장이라 컴파일러가 모순을 찾지 못합니다.
프로필과 감사 로그에는 새 이메일이 보이지만 환영 문구는 이전 이메일을 사용합니다.
세 문자열이 같은 회원 이메일을 뜻한다는 관계가 소스에 없기 때문입니다.
public final class NamedValue {
public static void main(String[] args) {
String email = "[email protected]";
System.out.println("profile=" + email);
System.out.println("welcome=" + email);
System.out.println("audit=" + email);
}
}이제 변경 지점은 초기화 식 하나입니다.
변수 이름은 세 사용처가 같은 값을 읽는다는 사실을 드러냅니다.
단순히 글자 수를 줄인 것이 아니라 수정 누락이 생길 구조를 제거했습니다.
선언과 초기화
String email;은 문자열 참조를 담을 지역 변수를 선언합니다.
아직 프로그램이 읽을 수 있는 값은 없습니다.
첫 대입인 email = "[email protected]";이 초기화입니다.
두 문장을 합친 String email = "[email protected]";도 같은 순서로 이해할 수 있습니다.
초기화하지 않은 지역 변수를 읽으면 임의의 숫자가 나오는 것이 아니라 컴파일이 실패합니다.
public final class UninitializedLocal {
public static void main(String[] args) {
String email;
System.out.println(email);
}
}lab\UninitializedLocal.java:4: error: variable email might not have been initialized
System.out.println(email);
^이 오류는 JVM이 프로그램을 실행하기 전에 javac가 모든 경로를 검사해 발견합니다.
선언만 한 뒤 조건에 따라 값을 넣는 코드에서는 어떤 경로로 와도 초기화가 끝났다는 사실을 컴파일러가 확인할 수 있어야 합니다.
지역 변수와 달리 객체 필드는 뒤에서 기본값을 받지만, 그 기본값이 업무적으로 유효하다는 뜻은 아닙니다.
재대입의 실행 과정
수학의 등호와 Java의 대입 연산자 =는 역할이 다릅니다.
email = "[email protected]";은 양쪽이 같다는 명제가 아니라 오른쪽 값을 왼쪽 변수의 현재 값으로 저장하라는 문장입니다.
public final class VariableTrace {
public static void main(String[] args) {
String email = "[email protected]";
String copiedEmail = email;
System.out.println("before.current=" + email);
System.out.println("before.copied=" + copiedEmail);
email = "[email protected]";
System.out.println("after.current=" + email);
System.out.println("after.copied=" + copiedEmail);
}
}String copiedEmail = email;이 실행될 때 오른쪽 변수의 참조값을 새 변수에 복사합니다.
이후 email에 새 문자열을 대입해도 copiedEmail은 이전 문자열을 계속 가리킵니다.
변수를 읽는다고 값이 사라지지 않으며, 대입하지 않은 다른 변수까지 함께 바뀌지도 않습니다.
기존 이메일은 email이라는 이름으로 더는 읽을 수 없지만 copiedEmail로는 읽을 수 있습니다.
값을 변경할 때는 “변수에 회원이 쌓인다”가 아니라 “현재 값이 새 값으로 대체된다”고 표현해야 합니다.
여러 회원을 보존하는 구조는 배열과 객체에서 만듭니다.
이번 장 뒤쪽 예제의 saved=... 출력도 검사를 통과했다는 표시입니다. 아직 회원 목록이나 파일·데이터베이스에 데이터를 저장하는 기능은 없습니다. 변수의 현재 값과 실제 저장 기능을 구분하며 읽으세요.
재대입은 현재 값만 바꾸고 복사한 값은 남긴다
선언→초기화→참조 복사→email 재대입에서 copiedEmail은 옛 참조를 유지합니다. 고정 두 변수의 값이 바뀌는 대표 상태 추적입니다.
JAVA · VALUE TIMELINE
재대입은 현재 값만 바꾸고 복사한 값은 남긴다
선언·초기화·복사·재대입을 같은 실행 축에 놓으면 두 변수의 현재 값이 언제 갈라지는지 보입니다.
| 단계 | copiedEmail | |
|---|---|---|
| T0 · 선언 | 초기화 전 · 읽기 불가 | 아직 선언되지 않음 |
| T1 · 초기화 | [email protected] | 아직 선언되지 않음 |
| T2 · 참조값 복사 | [email protected] | [email protected] |
| T3 · email 재대입 | [email protected] | [email protected] 유지 |
- T0 · 선언
email: 초기화 전이라 읽기 불가copiedEmail: 아직 선언되지 않음 - T1 · 초기화
email:[email protected]copiedEmail: 아직 선언되지 않음 - T2 · 참조값 복사
email과copiedEmail모두[email protected]을 가리킵니다. - T3 · email만 재대입
email:[email protected]copiedEmail:[email protected]유지
- 각 문장이 끝난 뒤의 상태
- T3에서 email만 새 참조값을 가짐
복사는 그 시점의 참조값을 새 변수에 대입합니다. 이후 한 변수에 새 문자열을 대입해도 다른 변수의 현재 값은 바뀌지 않습니다.
이 예의 조건
- original/updated는 본문 이메일 문자열의 약칭입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 23 | updated |
| 24 | original은 본문 이메일의 약칭 |
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | 재대입은 현재 값만 바꾸고 복사한 값은 남긴다 email은 선언 뒤 [email protected]으로 초기화되고 copiedEmail이 그 참조값을 복사합니다. 이후 email만 [email protected]으로 재대입되어 두 변수의 현재 값이 달라지는 실행 타임라인입니다. JAVA · VALUE TIMELINE 재대입은 현재 값만 바꾸고 복사한 값은 남긴다 선언·초기화·복사·재대입을 같은 실행 축에 놓으면 두 변수의 현재 값이 언제 갈라지는지 보입니다. 실행 순서에 따른 두 변수의 현재 참조값 단계 email copiedEmail T0 · 선언 초기화 전 · 읽기 불가 아직 선언되지 않음 T1 · 초기화 [email protected] 아직 선언되지 않음 T2 · 참조값 복사 [email protected] [email protected] T3 · email 재대입 [email protected] [email protected] 유지 T0 · 선언 email : 초기화 전이라 읽기 불가 copiedEmail : 아직 선언되지 않음 T1 · 초기화 email : [email protected] copiedEmail : 아직 선언되지 않음 T2 · 참조값 복사 email 과 copiedEmail 모두 [email protected] 을 가리킵니다. T3 · email만 재대입 email : [email protected] copiedEmail : [email protected] 유지 각 문장이 끝난 뒤의 상태 T3에서 email만 새 참조값을 가짐 복사는 그 시점의 참조값을 새 변수에 대입합니다. 이후 한 변수에 새 문자열을 대입해도 다른 변수의 현재 값은 바뀌지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 선언과 초기화 email 선언 뒤 original 문자열 참조로 초기화합니다. |
|
|
| 참조값 복사 copiedEmail에 email의 현재 참조값을 복사합니다. |
|
|
| email 재대입 email만 updated 문자열 참조로 바꿉니다. |
|
재대입은 현재 값만 바꾸고 복사한 값은 남긴다
선언→초기화→참조 복사→email 재대입에서 copiedEmail은 옛 참조를 유지합니다. 고정 두 변수의 값이 바뀌는 대표 상태 추적입니다.
JAVA · VALUE TIMELINE
재대입은 현재 값만 바꾸고 복사한 값은 남긴다
선언·초기화·복사·재대입을 같은 실행 축에 놓으면 두 변수의 현재 값이 언제 갈라지는지 보입니다.
| 단계 | copiedEmail | |
|---|---|---|
| T0 · 선언 | 초기화 전 · 읽기 불가 | 아직 선언되지 않음 |
| T1 · 초기화 | [email protected] | 아직 선언되지 않음 |
| T2 · 참조값 복사 | [email protected] | [email protected] |
| T3 · email 재대입 | [email protected] | [email protected] 유지 |
- T0 · 선언
email: 초기화 전이라 읽기 불가copiedEmail: 아직 선언되지 않음 - T1 · 초기화
email:[email protected]copiedEmail: 아직 선언되지 않음 - T2 · 참조값 복사
email과copiedEmail모두[email protected]을 가리킵니다. - T3 · email만 재대입
email:[email protected]copiedEmail:[email protected]유지
- 각 문장이 끝난 뒤의 상태
- T3에서 email만 새 참조값을 가짐
복사는 그 시점의 참조값을 새 변수에 대입합니다. 이후 한 변수에 새 문자열을 대입해도 다른 변수의 현재 값은 바뀌지 않습니다.
이 예의 조건
- original/updated는 본문 이메일 문자열의 약칭입니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 23 | updated |
| 24 | original은 본문 이메일의 약칭 |
| 구분 | 내용 |
|---|---|
| 전체 읽기 정보 | 재대입은 현재 값만 바꾸고 복사한 값은 남긴다 email은 선언 뒤 [email protected]으로 초기화되고 copiedEmail이 그 참조값을 복사합니다. 이후 email만 [email protected]으로 재대입되어 두 변수의 현재 값이 달라지는 실행 타임라인입니다. JAVA · VALUE TIMELINE 재대입은 현재 값만 바꾸고 복사한 값은 남긴다 선언·초기화·복사·재대입을 같은 실행 축에 놓으면 두 변수의 현재 값이 언제 갈라지는지 보입니다. 실행 순서에 따른 두 변수의 현재 참조값 단계 email copiedEmail T0 · 선언 초기화 전 · 읽기 불가 아직 선언되지 않음 T1 · 초기화 [email protected] 아직 선언되지 않음 T2 · 참조값 복사 [email protected] [email protected] T3 · email 재대입 [email protected] [email protected] 유지 T0 · 선언 email : 초기화 전이라 읽기 불가 copiedEmail : 아직 선언되지 않음 T1 · 초기화 email : [email protected] copiedEmail : 아직 선언되지 않음 T2 · 참조값 복사 email 과 copiedEmail 모두 [email protected] 을 가리킵니다. T3 · email만 재대입 email : [email protected] copiedEmail : [email protected] 유지 각 문장이 끝난 뒤의 상태 T3에서 email만 새 참조값을 가짐 복사는 그 시점의 참조값을 새 변수에 대입합니다. 이후 한 변수에 새 문자열을 대입해도 다른 변수의 현재 값은 바뀌지 않습니다. |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 선언과 초기화 email 선언 뒤 original 문자열 참조로 초기화합니다. |
|
|
| 참조값 복사 copiedEmail에 email의 현재 참조값을 복사합니다. |
|
|
| email 재대입 email만 updated 문자열 참조로 바꿉니다. |
|
회원 정보의 변수 보관
처음에는 입력 처리나 메서드를 섞지 않고 회원 정보 한 건을 지역 변수로만 표현합니다.
public class MemberValues {
public static void main(String[] args) {
String email = "[email protected]";
int age = 45;
boolean active = true;
System.out.println("email=" + email);
System.out.println("age=" + age);
System.out.println("active=" + active);
}
}[email protected]
age=45
active=trueemail, age, active는 한 회원의 서로 다른 속성을 보관합니다.
아직 객체를 배우기 전이므로 세 변수의 관계를 이름과 가까운 배치로 드러냅니다.
실행 인자를 숫자로 바꾸는 방법, 값이 없을 때의 분기, 여러 기능을 메서드로 나누는 방법은 각각 뒤 문서에서 배웁니다.
변수를 배우는 첫 수업에서는 이름·타입·현재 값의 관계에만 집중합니다.
지역 변수의 수명
email, age, active는 main의 중괄호 안에서 선언했으므로 그 블록 안에서만 사용할 수 있습니다.
너무 넓은 범위에 변수를 두면 같은 이름이 언제 바뀌는지 추적할 코드가 늘어납니다.
블록과 변수 수명은 ch2-10에서 더 자세히 다룹니다.
한 줄에 여러 변수를 선언할 수도 있지만 서로 다른 의미와 초기화 식을 가진 값은 한 줄씩 두는 편이 수정하기 쉽습니다.
반대로 좌표처럼 항상 함께 읽는 짧은 값이라도 이름이 모호해지면 분리합니다.
문법상 가능한 선언 방식과 사람이 검토하기 좋은 방식은 구분해야 합니다.
변수 이름을 읽고 뜻을 알 수 있어야 합니다.
value보다 age, text보다 email이 안전합니다.
아직 명명 규칙 전체를 다루지 않았지만 의미를 이름에 포함하면 서로 다른 회원 값을 섞는 오류를 줄일 수 있습니다.
연습 문제
EmailTrace를 작성하세요.
email을[email protected]으로 초기화합니다.copiedEmail에 현재 이메일을 복사합니다.email만[email protected]으로 변경합니다.- 두 변수를 출력해 복사한 값이 자동으로 바뀌지 않았음을 확인합니다.
- 현재 이메일로 환영 문구를 만들어 출력합니다.
해설 보기
public final class EmailTrace {
public static void main(String[] args) {
String email = "[email protected]";
String copiedEmail = email;
email = "[email protected]";
String welcome = "welcome, " + email;
System.out.println("current=" + email);
System.out.println("copied=" + copiedEmail);
System.out.println(welcome);
}
}copiedEmail에는 복사 시점의 참조값이 남고 welcome은 재대입 뒤의 현재 이메일을 읽어 만듭니다.
실행 순서를 바꾸어 재대입 전에 환영 문구를 만들면 이전 이메일이 들어가므로 파생 값을 계산하는 시점도 결과의 일부입니다.