Java에서 상속 관계를 다루다 보면, 이런 궁금증이 생긴다.
왜 같은 객체를 가리키는데, 변수랑 메서드 호출 결과가 다르지?
그래서 정리한다! (정확히는 내가 헷갈려서 정리한다..🫠)
그 이유는 간단하다.
자바는 변수와 메서드를 호출할 때 기준이 다르기 떄문이다.
아래의 코드를 보고 이해해보자.
class B {
int x = 1;
int getX() {
return x * 2;
}
}
class D extends B {
int x = 2;
int getX() {
return x * 3;
}
}
public class Main {
public static void main(String[] args) {
B a = new D(); // 부모 타입 참조, 자식 객체
D b = new D(); // 자식 타입 참조, 자식 객체
System.out.print(a.getX() + a.x + b.getX() + b.x);
}
}
이 때 결과가 어떻게 될까?
15가 나와야 한다.
a.getX() + a.x + b.getX() + b.x
2 * 3 + 1 + 2 * 3 + 2 = 15
즉, a.x는 1이 나와야 한다.
a는 B a = new D();로 업캐스팅된 상태다.
참조 변수의 타입이 B이고,
클래스의 변수를 호출할 때는 참조 변수의 타입이 무엇을 호출할지 결정하기 때문에
B Class의 인스턴스 변수 1이 나오는 것이다.
이게 바로 변수는 타입 기준이라는 의미다.
반면에 메서드를 호출할 때는,
객체를 기준으로 호출하게 된다.
a가 가리키는 객체는 실제로 new D()로 생성된 D 객체다.
그래서 오버라이딩 된 D Class의 getX() 메서드를 호출하는 것이다.
이게 바로 메서드는 객체 기준이라는 의미다.
여기서 정적 바인딩과 동적 바인딩 개념이 등장한다.
어떤 변수나 메서드가 메모리의 무엇을 가리킬지 결정하는 것을 말한다.
변수는 단순히 "값"을 담고 있는 데이터다.
복잡한 실행 로직이 없고, 빠른 처리가 중요하기 때문에
컴파일할 때 "이 타입의 변수를 가져오자"고 미리 결정해버린다.
그래서 변수는 타입 기준으로 동작하는 것이다.
즉, 컴파일러가 B 타입의 x를 미리(컴파일 시점에) 찾아서 고정시켜놓는다.
메서드는 실행할 때 진짜 객체의 동작을 실행해야 한다.
(다형성(polymorphism)을 위해서)
그래서 메서드는 실행 시점에 "진짜 객체 타입이 뭐지?"를 확인하고,
그 객체에 맞는 메서드를 찾아 호출한다.
그래서 메서드는 객체 기준 동작한다.