
오늘 면접장에서 코드 한 장을 받았습니다. 4년 동안 자바로 밥 벌어 먹고살았기에, 생성자 정도는 눈 감고도 짠다는 자신감이 있었죠. 하지만 결과는 처참했습니다. 여러분도 아래 코드를 보고 10초 안에 결과를 맞춰보세요.
public class Test {
public static void main(String[] args) {
int age = 10;
User user = new User(10, "hello");
agePlus(age);
agePlusUser(user);
System.out.println(age); // 출력값 A
System.out.println(user.getAge()); // 출력값 B
}
private static void agePlus(int age) {
age += 1;
}
private static void agePlusUser(User user) {
user.setAge(user.getAge() + 1);
}
static class User {
private int age;
private String name;
public User(int age, String name) {
this.age += 1; // <--- 문제의 그 줄
this.name = name;
}
// Getter, Setter 생략
}
}
당연히 10과 12라고 생각하셨나요?
축하합니다. 저와 함께 수치심을 나눌 준비가 되셨군요.
정답은 10과 2입니다.
new User(10, ...)를 보는 순간, 우리 뇌는 생성자 안의 코드를 당연히 this.age = age + 1 (전달받은 값에 1을 더함)로 필터링해서 해석해버립니다.
하지만 실제 코드를 뜯어보면 자바의 아주 기초적인 Variable Scope(변수 범위)와 초기화 문제가 숨어있습니다.
+= 연산자의 정체this.age += 1은 풀어쓰면 this.age = this.age + 1입니다.
여기서 우변의 this.age는 파라미터로 받은 age(10)가 아니라, 인스턴스 멤버 변수 this.age를 가리킵니다.
자바에서 int 타입의 멤버 변수는 명시적으로 초기화하지 않으면 기본값 0을 가집니다.
결국 생성자 로직은 다음과 같이 실행됩니다:
this.age는 0으로 초기화됨.0 + 1을 수행하여 this.age는 1이 됨.그 후 agePlusUser 메서드에서 1 + 1이 수행되어 최종 결과는 2가 나옵니다.
만약 int age가 아니라 Wrapper 클래스인 Integer age였다면 결과가 달라졌을까요?
아니요, 여전히 결과는 10입니다.
Integer는 불변 객체(Immutable Object)이기 때문입니다. 메서드 내부에서 age += 1을 하는 순간 새로운 Integer 객체가 생성되어 로컬 변수에 할당될 뿐, 메서드 밖의 원본 객체에는 아무런 영향을 주지 못합니다.
4년 차 개발자로서 기본 중의 기본인 생성자 로직에서 털리고 나니, 쥐구멍에라도 숨고싶은 하루였습니다. 자바는 알면 알수록 깐깐하고, 내 뇌는 생각보다 믿을 게 못 된다는 걸 다시 한번 느꼈습니다.
부끄러운걸 부끄럽게 여기지 않는 모습이 멋집니다.