김영한 java 강의를 들으면서 생긴 질문들
객체 참조 비교와 오버라이딩 #
이전에 궁금했던 사항인데 오버라이딩과 연결되는 부분이라 정리함
Java에서 객체는 참조로 저장되고 전달된다. ==
을 통한 비교도 참조를 통해 이루어짐. 이는 JavaScript에서도 마찬가지인데(===
이긴 하지만...) React에서는 ===
을 통해(정확히는 Object.is
지만 여기선 크게 중요하지 않음) 객체를 정확하게 비교할 수 있도록 하기 위해서 객체를 불변처럼 다룰 것을 굉장히 강조한다.
그래서 java에도 비슷한 거, 적어도 객체의 참조 비교로 인한 이슈가 있을까? 있다.
가장 간단한 예시로 BigDecimal의 비교를 들 수 있다.
BigDecimal hundred = new BigDecimal("100.00");
BigDecimal ten = new BigDecimal("10");
BigDecimal hundred2=ten.multiply(ten);
System.out.println(hundred2==hundred);
System.out.println(hundred.equals(hundred2));
여기서 둘 다 false가 나온다. 100과, 10에 10을 곱해서 나온 BigDecimal이 다른 것이다. 이는 ==
, equals
모두 객체 참조를 비교하기 때문이다. 그리고 또 하나 문제가 있는데 BigDecimal의 equals는 값뿐 아니라 scale(정밀도)까지 비교한다. 따라서 돈 계산 등에서 10000원과 (1000.0 * 10)이 다를 수도 있다.
따라서 compareTo
를 사용하거나 equals를 오버라이딩해야 한다. 다른 경우로는 .create(...)
를 통해 찾은 객체와 findById(id)
로 찾은 객체가 다를 경우 등이 있다.
동적 바인딩 #
클래스 객체 생성 시 슈퍼클래스의 생성자를 무조건 호출하고, 또한 슈퍼클래스 인스턴스까지 포함해서 인스턴스를 생성한다.
그럼 메서드를 호출 시 메서드는 어떻게 호출될까? 타입 검사는 정적 타입으로 이루어지지만, 메서드 호출은 동적 바인딩으로 이루어진다. 즉, 메서드 호출 시점에 어떤 메서드가 호출될지는 런타임에 결정된다.
가령 Car를 상속한 ElectricCar가 있다고 하자. 그리고 다음과 같이 코드를 작성했다고 하자.
Car car = new ElectricCar();
car.run();
// car.electricRun(); // 컴파일 에러
car
는 Car 타입이기 때문에 ElectricCar
에만 있는 메서드를 사용할 수 없다. Java는 정적 타입 언어니까.
그런데 만약 ElectricCar
에서 run
메서드를 오버라이딩했다면 어떨까? 그러면 car.run()
을 호출했을 때 ElectricCar
의 run
메서드가 호출된다. car
는 실제로는 ElectricCar
의 인스턴스이기 때문이다. 이런 걸 동적 바인딩이라고 한다. 어떤 메서드가 호출될지 런타임에 결정되는 것이다.
이는 virtual 메서드 테이블을 통해 이루어진다. https://hyunsb.tistory.com/58
그리고 이를 통해 다형성을 구현할 수도 있다.
Car cars[] = new Car[3];
cars[0] = new Car();
cars[1] = new ElectricCar();
cars[2] = new HybridCar();
// Car 클래스에 run() 메서드가 있고, ElectricCar와 HybridCar에서 오버라이딩했다고 가정
// 그러면 전부 Car 타입이지만 오버라이딩된 메서드들이 호출된다.
for (Car car : cars) {
car.run();
}