지난 포스팅에서는 메소드 오버로딩에 대해 정리하였다면, 이번 포스팅에선 오버라이딩에 대해 정리한다.
1. 오버라이딩
먼저 오버라이딩의 뜻을 사전에서 찾아보면 다음과 같다.

프로그래밍에서 오버라이딩은 "덮어 씌운다" 라는 의미로 풀이될 수 있다.
즉, 메소드 오버라이딩은 기존의 메소드에 덮어 씌운다고 생각하면 된다.
오버라이딩을 사용하면 상속을 보다 유연하게 사용할 수 있다.
아래의 예제를 보자.
package com.inheritance;
public class InhMain {
public static void main(String[] args) {
//TODO Auto-generated method stub
Point p1 = new Point();
Point3D p3d1 = new Point3D();
p1.printNum();
p3d1.printNum();
}
}
class Point {
protected int x = 100;
protected int y = 200;
static int number;
public void printNum() {
System.out.println("x = " + x);
System.out.println("y = " + y);
}
}
class Point3D extends Point // Point 클래스를 상속받은 Point3D 클래스 선언
{
int z = 300;
}
위 소스에서보면 부모인 Point 클래스에 printNum() 메소드에서 멤버변수의 값을 출력하는 것을 볼 수 있다.
main() 에서는 똑같이 printNum() 메소드를 호출하고 있다.
만약 자식클래스의 값을 출력해 보면 어떻게 될까?

당연히 부모클래스의 메소드를 상속받아도 정의된 내용에 z 를 출력하는 부분이 없기에 z는 출력되지 않는다.
이런 상황에서 z를 출력하는 방법은 2가지가 있다.
1. 당연히 자식클래스에서 메소드를 새로 만든다.
2. 부모클래스의 메소드를 오버라이딩 한다.
1번의 경우는 자식 클래스를 다음과 같이 메소드를 추가한다.
class Point3D extends Point // Point 클래스를 상속받은 Point3D 클래스 선언
{
int z = 300;
public void printNum() {
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("z = " + z);
}
}

물론 정상적으로 실행결과를 볼 수 있다. 하지만 메모리에서는 추가된 메소드 만큼 손해가 발생할 것이다.
그럼 2번, 오버라이딩을 사용하여 보자.
class Point3D extends Point // Point 클래스를 상속받은 Point3D 클래스 선언
{
int z = 300;
@Override
public void printNum() {
// TODO Auto-generated method stub
super.printNum();
System.out.println("z = " + z);
}
}
1번 소스와 비교해보면 두 가지가 달라진 것을 볼 수 있다.
첫 째는 메소드 위에 @Override 라는 문구가 생긴 것.
둘 째는 메소드 내부에 x,y 를 출력하는 문장 대신에 super.printNum(); 이 생겼다는 것이다.
코드를 보면 부모클래스의 printNum() 메소드를 호출하고 추가적으로 z 를 출력하는 것을 볼 수 있다.
당연히 부모클래스의 메소드에서 x, y 를 출력하기 때문에 실행 결과는 1번과 같이 출력된다.
※ @ 에 대해서는 추후 자세하게 다룰 예정이다.

이처럼 오버라이딩은 자식클래스가 부모클래스의 메소드에 추가적인 기능을 만들고 싶어 부모의 메소드에 추가된 기능으로 덮어 씌운 것이다.
다시 말해 부모 클래스에서 선언된 메소드를 자식 클래스에서 똑같이 선언하여 부모의 메소드를 덮어 씌운 것이다.
이 경우를 다시 생각해보면 자식 클래스에는 상속받은 printNum 메소드와 자신의 printNum 메소드, 총 2개의
같은 이름을 가진 메소드가 있다는 것을 알 수 있다.
하지만 자신의 메소드로 상속받은 메소드를 덮어 씌웠기에 main()에서 호출한 p3d1.printNum; 부분은 자식의
메소드만 호출이 되는 것이다.
이렇게 오버라이딩을 사용하여 더 유연하게 상속을 사용할 수 있지만 몇 가지 지켜야하는 규칙이 있다.
1. 반환형이 동일해야 한다.
2. 메소드 이름이 같아야 한다.
3. 매개변수의 자료형이 같아야 한다.
4. 매개변수의 갯수가 같아야 한다.
※ 3번과 4번처럼 매개변수의 갯수와 자료형은 메소드 시그니쳐 라고 한다.
부모 :: public void printNum(int a)
오버라이딩 가능!
자식 :: public void printNum(int a)
2. super
super는 부모클래스의 멤버를 지정할 때 사용하는 예약어이다.
위의 소스에서 자식의 메소드를 다시 한 번 보자.
@Override
public void printNum() {
// TODO Auto-generated method stub
super.printNum();
System.out.println("z = " + z);
}
메소드 내부를 보면 super 가 있는 것을 확인할 수 있다.
즉, 해당 문장은 부모 클래스에 있는 printNum() 메소드를 나타내는 것이란 걸 super 예약어를 통해 알 수 있다.
이전에 비슷한 개념으로 사용한 this 와 비교해 본다면
this :: 자신의 멤버를 지정하는 지시자
super :: 자식 클래스에서 부모 클래스의 멤버를 지정하는 지시자.
라고 정리할 수 있다.
또한 this에서 사용한 this.(dot) 와 this( a, b, c) 의 의미로 사용할 수 있듯이 super 또한 가능하다.
package com.inheritance;
public class InhMain {
public static void main(String[] args) {
//TODO Auto-generated method stub
Point p1 = new Point();
Point3D p3d1 = new Point3D();
p1.printNum();
p3d1.printNum();
}
}
class Point {
protected int x = 100;
protected int y = 200;
public int k = 0;
public int a;
public int b;
public int c;
static int number;
public Point() {
}
public Point(int a, int b, int c) {
this.a = a;
this.b = b;
this.c = c;
}
public void printNum() {
System.out.println("x = " + x);
System.out.println("y = " + y);
System.out.println("a = " + a);
System.out.println("b = " + b);
System.out.println("c = " + c);
}
}
class Point3D extends Point // Point 클래스를 상속받은 Point3D 클래스 선언
{
int z = 300;
public Point3D() {
super(770,880,990);
}
@Override
public void printNum() {
// TODO Auto-generated method stub
super.printNum();
super.k = 50000;
System.out.println("z = " + z);
System.out.println("k = " + k);
}
}
위 소스에서 자식 클래스의 생성자와 printNum() 메소드에서 super 가 쓰인 것을 볼 수 있다.
this 와 마찬가지로 super( ) 를 붙이게 되면 부모 클래스의 생성자를 나타내며,
super. 를 사용하면 부모 클래스의 메소드 또는 멤버 변수에 접근할 수 있다.
당연한 얘기지만 super(770,880,990); 부분은 자식의 생성자 에서만 사용이 가능하다.

실행 결과를 보면 a,b,c 의 값이 처음 0,0,0 에서 자식 클래스의 생성자가 실행되며 770, 880, 990 으로
출력된 것을 확인할 수 있다.