① Method Overriding
Method overriding은 객체지향에서 매우 중요한 개념이기 때문에, 다음 세 가지 개념을 잘 기억해 두어야 한다.
1) Method Overriding은 Super 클래스에서 선언된 메서드의 이름, 리턴 타입, 파라미터 선언까지 완전히 동일한 메서드를 Sub 클래스에서 새롭게 재정의 하는 것이다. 즉, 완전히 동일한 메서드인데 body를 다르게 쓰는 것이다.
하지만 메서드를 이렇게 정의하면, 메서드를 호출했을 때 어떤 메서드를 쓰는지 구분이 어렵다. overloading에서는 파라미터로 구분했는데, overriding은 이름과 파라미터까지 모두 같기 때문이다.
Overriding에서는 Sub 클래스의 메서드로 Super 클래스의 메서드를 가린다.
Sub가 가장 최신 메서드이기 때문에 overriding이 걸려있으면 가장 마지막 Sub Class에서 정의된 메서드가 호출된다.
그런데, 어차피 가장 마지막 Sub class의 메서드를 사용할 것이라면, 호출이 안되는 Super Class의 코드는 왜 필요하며 super reference variable을 사용하는 이유는 무엇인가?
super를 한 단계 높은 Super class를 참조하는 reference 변수로만 알면 안 된다. 현업에서는 깊은 상속보다는 넓은 상속을 많이 쓰는데, 예를 들어 B와 C가 A를 상속받았다고 가정하자. B와 C가 A의 메서드에 오버라이딩을 걸면 메서드 이름, 리턴 타입, 파라미터가 같다. 메서드 이름이 똑같다면 하는 일이 비슷하다는 뜻이고 하는 일이 비슷하다는 개념을 코드로 살펴보면 겹치는 코드가 나온다는 의미이다.
즉, A를 상속받은 B와 C에서는 겹치는 코드가 나올 것이기 때문에, 겹치는 부분은 A에 적고, B와 C에 super를 적어주면 된다. 그렇다면 설계 과정에서, 어떤 메서드가 오버라이딩을 거는 대상이 될까? 바로 super class를 상속받는 모든 sub class에 공통적으로 필요한 기능이 오버라이딩을 걸어야 하는 메서드이다.
2) Super Class의 reference 변수는 Sub Class의 인스턴스를 참조할 수 있다.
지금까지는 A a = new A() 형태의 코드가 주로 나왔지만, 현업에서는 좌변과 우변에 똑같은 클래스 타입을 적는 일은 드물다. C → B → A 상속 관계일 때 (UML에서는 상속을 받는 쪽에서 주는 쪽으로 선이 그려진다.) A a = new C();의 형태가 자주 사용된다.
class X {...}
class Y extends X {...}
class Z extends Y {...}
X rv1 = new Y(); // O
X rv2 = new X(); // O
Y rv3 = new Z(); // O
Z rv4 = new Z(); // O
Y rv5 = rv4; // O
X rv6 = rv4; // O
X rv7 = new Z(); // O
Y rv8 = rv7; // X
Z rv9 = rv7; // X
3) Overriding이 걸린 메서드는 Type Casting이 불필요하다.
2번 예시에서 확인할 수 있듯이, Super class의 reference 변수는 Sub class의 인스턴스를 참조할 수 있다. 하지만 Sub class의 메서드를 직접 호출할 수는 없다. 만약 호출하고 싶다면 해당 메서드를 소유하고 있는 클래스 타입으로의 명시적인 형 변환이 필요하다.
A, B, C 클래스에 각각 x, y, z 메서드가 있고 A의 reference 변수 a1, a2, a3가 각각 A, B, C class의 인스턴스를 참조한다고 가정하면, a1.x()는 가능하지만 a2.y()나 a3.z()는 compile error가 발생한다. 따라서 ((B)a2).y()와 ((C)a3).z()라고 사용해야 한다.
class Car {
private int speed;
public void showCurrentSpeed() {...}
public void setSpeed(int speed) {...}
}
class Truck extend Car {
private int cargo;
public void showCurrentSpeed() {...}
public void setCargo(int cargo) {...}
}
public class Overriding {
public static void main(String[] args) {
Car car1 = new Truck();
((Truck)car1).setCargo(10);
Truck car2 = new Truck();
car2.setSpeed(100);
car2.setCargo(10);
car2.showCurrentSpeed();
}
}
위의 예시에서 볼 수 있듯이, Truck class의 reference 변수 car2는 Truck의 메서드와 Car의 메서드 모두 호출할 수 있지만, Car class의 reference 변수 car1이 Truck의 메서드 setCargo()를 호출하기 위해서는 Truck으로의 명시적인 형 변환이 필요하다.
그렇다면, super class의 reference 변수로 Overriding이 걸린 메서드를 호출할 수 있을까? Sub class의 메서드를 호출할 때 형 변환이 필요했던 이유는, 해당 메서드를 가지고 있는 클래스가 누구인지 알려주기 위함인데, 오버라이딩이 걸렸다면 호출된 메서드는 Sub Class의 메서드일 것이기 때문에 형 변환을 하지 않아도 된다. 따라서 Overriding이 걸린 메서드는 Type Casting이 불필요하다.
② Overloading vs Overriding
class GrandFather {
public void call() {}
public void hello() {}
}
class Father extends GrandFather {
public void call(int father) {}
public void hello() {}
}
class Son extends Father {
public void call(double son) {}
public void hello() {}
}
public class Overriding {
public static void main(String[] args) {
GrandFather gft = new Son();
Father ft = new Son();
Son s = new Son();
son.call();
son.call(1);
son.call(1.0);
gft.hello();
ft.hello();
s.hello();
}
위의 코드를 살펴보면 우선 Sub class를 메모리에 올리기 위해 전부 new son()으로 인스턴스를 생성했음을 알 수 있다. 또한 Super class의 reference 변수인 gft와 ft가 모두 Son Class의 인스턴스를 참조하고 있다.
call()은 하나의 클래스 Son안에서 이름이 같은 메서드를 호출하고 있기 때문에 Overloading의 예시이고, hello()는 완전히 동일한 메서드를 Sub 클래스에서 새롭게 정의하고 있으므로 Overriding의 예시이다. 만약, gft가 Son의 hello를 호출하고 싶다면 형 변환이 필요할 것이다.
'JAVA' 카테고리의 다른 글
[JAVA] Chapter 12. Abstract & Interface (1) (0) | 2023.07.16 |
---|---|
[JAVA] Chapter 11. Inheritance (3) (0) | 2023.07.15 |
[JAVA] Chapter 11. Inheritance (1) (0) | 2023.07.13 |
[JAVA] Chapter 10. Array (0) | 2023.07.12 |
[JAVA] Chapter 9. String (0) | 2023.07.11 |
댓글