변수와 이름
선언·초기화·대입·읽기를 실행 순서로 구분하고 가입할 회원 한 명의 정보를 변수로 표현합니다.
지금까지는 실행 환경 정보만 출력했습니다.
이제 회원가입에 필요한 이메일과 나이를 프로그램 안에 보관합니다.
같은 값이 여러 계산과 출력에 쓰일 때 리터럴을 반복하면 어느 값이 같은 의미인지 코드에 남지 않습니다.
변수는 메모리 칸이라는 설명보다 한 실행 동안 같은 의미를 다시 가리키는 이름으로 먼저 이해하는 편이 유용합니다.
중복 리터럴의 변경 누락
다음 프로그램은 회원 이메일을 바꾸는 과정에서 환영 문구 한 곳을 놓친 상태입니다.
public final class RepeatedLiteral {
public static void main(String[] args) {
System.out.println("profile=updated@example.com");
System.out.println("welcome=original@example.com");
System.out.println("audit=updated@example.com");
}
}profile=updated@example.com
welcome=original@example.com
audit=updated@example.com출력 세 줄은 모두 정상 문장이라 컴파일러가 모순을 찾지 못합니다.
프로필과 감사 로그에는 새 이메일이 보이지만 환영 문구는 이전 이메일을 사용합니다.
세 문자열이 같은 회원 이메일을 뜻한다는 관계가 소스에 없기 때문입니다.
public final class NamedValue {
public static void main(String[] args) {
String email = "updated@example.com";
System.out.println("profile=" + email);
System.out.println("welcome=" + email);
System.out.println("audit=" + email);
}
}profile=updated@example.com
welcome=updated@example.com
audit=updated@example.com이제 변경 지점은 초기화 식 하나입니다.
변수 이름은 세 사용처가 같은 값을 읽는다는 사실을 드러냅니다.
단순히 글자 수를 줄인 것이 아니라 수정 누락이 생길 구조를 제거했습니다.
선언과 초기화
String email;은 문자열 참조를 담을 지역 변수를 선언합니다.
아직 프로그램이 읽을 수 있는 값은 없습니다.
첫 대입인 email = "member@example.com";이 초기화입니다.
두 문장을 합친 String email = "member@example.com";도 같은 순서로 이해할 수 있습니다.
초기화하지 않은 지역 변수를 읽으면 임의의 숫자가 나오는 것이 아니라 컴파일이 실패합니다.
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 = "updated@example.com";은 양쪽이 같다는 명제가 아니라 오른쪽 값을 왼쪽 변수의 현재 값으로 저장하라는 문장입니다.
public final class VariableTrace {
public static void main(String[] args) {
String email = "original@example.com";
String copiedEmail = email;
System.out.println("before.current=" + email);
System.out.println("before.copied=" + copiedEmail);
email = "updated@example.com";
System.out.println("after.current=" + email);
System.out.println("after.copied=" + copiedEmail);
}
}before.current=original@example.com
before.copied=original@example.com
after.current=updated@example.com
after.copied=original@example.comString copiedEmail = email;이 실행될 때 오른쪽 변수의 참조값을 새 변수에 복사합니다.
이후 email에 새 문자열을 대입해도 copiedEmail은 이전 문자열을 계속 가리킵니다.
변수를 읽는다고 값이 사라지지 않으며, 대입하지 않은 다른 변수까지 함께 바뀌지도 않습니다.
기존 이메일은 email이라는 이름으로 더는 읽을 수 없지만 copiedEmail로는 읽을 수 있습니다.
값을 변경할 때는 “변수에 회원이 쌓인다”가 아니라 “현재 값이 새 값으로 대체된다”고 표현해야 합니다.
여러 회원을 보존하는 구조는 배열과 객체에서 만듭니다.
회원 정보의 변수 보관
처음에는 입력 처리나 메서드를 섞지 않고 회원 정보 한 건을 지역 변수로만 표현합니다.
public class MemberValues {
public static void main(String[] args) {
String email = "variables@example.com";
int age = 45;
boolean active = true;
System.out.println("email=" + email);
System.out.println("age=" + age);
System.out.println("active=" + active);
}
}email=variables@example.com
age=45
active=trueemail, age, active는 한 회원의 서로 다른 속성을 보관합니다.
아직 객체를 배우기 전이므로 세 변수의 관계를 이름과 가까운 배치로 드러냅니다.
실행 인자를 숫자로 바꾸는 방법, 값이 없을 때의 분기, 여러 기능을 메서드로 나누는 방법은 각각 뒤 문서에서 배웁니다.
변수 첫 수업에서는 이름·타입·현재 값의 관계에만 집중합니다.
지역 변수의 수명
email, age, active는 main의 중괄호 안에서 선언했으므로 그 블록 안에서만 사용할 수 있습니다.
너무 넓은 범위에 변수를 두면 같은 이름이 언제 바뀌는지 추적할 코드가 늘어납니다.
블록과 변수 수명은 ch2-10에서 더 자세히 다룹니다.
한 줄에 여러 변수를 선언할 수도 있지만 서로 다른 의미와 초기화 식을 가진 값은 한 줄씩 두는 편이 수정하기 쉽습니다.
반대로 좌표처럼 항상 함께 읽는 짧은 값이라도 이름이 모호해지면 분리합니다.
문법상 가능한 선언 방식과 사람이 검토하기 좋은 방식은 구분해야 합니다.
변수 이름을 읽고 뜻을 알 수 있어야 합니다.
value보다 age, text보다 email이 안전합니다.
아직 명명 규칙 전체를 다루지 않았지만 의미를 이름에 포함하면 서로 다른 회원 값을 섞는 오류를 줄일 수 있습니다.
연습 문제
EmailTrace를 작성하세요.
email을original@example.com으로 초기화합니다.copiedEmail에 현재 이메일을 복사합니다.email만updated@example.com으로 변경합니다.- 두 변수를 출력해 복사한 값이 자동으로 바뀌지 않았음을 확인합니다.
- 현재 이메일로 환영 문구를 만들어 출력합니다.
해설 보기
public final class EmailTrace {
public static void main(String[] args) {
String email = "original@example.com";
String copiedEmail = email;
email = "updated@example.com";
String welcome = "welcome, " + email;
System.out.println("current=" + email);
System.out.println("copied=" + copiedEmail);
System.out.println(welcome);
}
}current=updated@example.com
copied=original@example.com
welcome, updated@example.comcopiedEmail에는 복사 시점의 참조값이 남고 welcome은 재대입 뒤의 현재 이메일을 읽어 만듭니다.
실행 순서를 바꾸어 재대입 전에 환영 문구를 만들면 이전 이메일이 들어가므로 파생 값을 계산하는 시점도 결과의 일부입니다.