오버 라이딩이란
조상 클래스로부터 상속받은 메서드의 내용을 변경하는 것을 오버 라이딩이라고 한다.
(override의 의미는 ~위에 덮어쓰다(overwrite)이다.)
class Point {
int x;
int y;
String getLocation() {
return "x :" + x + ", y : "+y;
}
}
class Point3D extends Point {
int z;
String getLocation() { // 오버라이딩
return "x :" + x + ", y : "+ y + ", z :" +z;
}
}
Point클래스의 getLocation은 한 점의 x, y좌표를 문자열로 반환하도록 작성되었다.
Point클래스는 3차원 좌표계의 한 점을 표현하기 위한 것이므로 조상인 Point클래스로부터 상속받은 getLocation()은 Point 3D클래스에 맞지 않는다.
그래서 이 메서드를 Point3D클래스 자신에 맞게 z축의 좌표값도 포함하여 반환하도록 오버 라이딩하였다.
오버 라이딩의 조건
오버 라이딩은 메서드의 내용만을 새로 작성하는 것이므로 메서드는 조상의 것과 완전히 일치해야 한다.
- 이름이 같아야 한다.
- 매개변수가 같아야 한다.
- 반환타입이 같아야 한다.
한마디로 선언부가 서로 일치해야 한다는 것이다. 다만 접근 제어자(access modifier)와 예외(exception)는 제한된 조건 하에서만 다르게 변경할 수 있다.
1. 접근 제어자는 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
만일 조상 클래스에 정의된 메서드의 접근 제어자가 protected라면, 이를 오버 라이딩하는 자손 클래스의 메서드는 접근 제어자가 protected나 public이어야 한다. 대부분의 경우 같은 범위의 접근 제어자를 사용한다.
Public > protected > (default), privvate이다.
2. 조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.
아래의 코드를 보면 child클래스의 parentMethod()에 선언된 예외의 개수가 조장인 Parent클래스의 parentMethod()에 선언된 예외의 개수보다 적으므로 바르게 오버 라이딩되었다.
class Parent {
void parentMethod() throws IOException, SQLException {
...
}
}
class Child extends Parent {
void parentMethod() throws IOException {
...
}
...
}
여기서 한 가지 주의할 것이 있는데 예외 문의 개수만 문제가 아니라 조상 클래스 여부도 따져야 한다.
조상 클래스의 메서드를 자손 클래스에서 오버라이딩할 때
1. 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
2. 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
3. 인스턴스메서드를 static메서드로 또는 그 반대로 변경할 수 없다.
오버 로딩 vs 오버 라이딩
오버 로딩과 오버 라이딩은 혼동하기 쉽지만 차이가 명백하다.
오버 로딩은 기존에 없는 새로운 메서드를 추가하는 것이고, 오버 라이딩은 조상으로부터 상속받은 메서드의 내용을 변경하는 것이다.
오버로딩(overloading) 기존에 없는 새로운 메서드를 정의하는 것(new)
오버라이딩(overriding) 상속받은 메서드의 내용을 변경하는 것(change, modify)
아래의 코드를 보고 오버 로딩과 오버 라이딩을 구별할 수 있어야 한다.
class Parent {
void parentMethod() {}
}
class Child extends Parent {
void parentMethod() {}
void parentMethod(int i) {}
void childMethod() {}
void childMethod(int i) {}
void childMethod() {}
}
프로그래밍은 어떤 메서드가 어떤 인스턴스로 호출되는가를 실행할 때 알게 되고 컴파일 때는 모른다.
super
super는 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는 데 사용되는 참조 변수이다.
멤버 변수와 지역변수의 이름이 같을 때 this를 사용할 수 있다. 그래도 조상 클래스의 멤버와 자손 클래스의 멤버가 중복 정의되어 서로 구별해야 하는 경우에만 super를 사용하는 것이 좋다.
조상의 멤버와 자신의 멤버를 구별하는 데 사용된다는 점을 제외하고는 super와 this는 근본적으로 같다.
모든 인스턴스 메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장되는데, 이것이 참조 변수인 this와 super의 값이 같다.
static메서드(클래스 메서드)는 인스턴스와 관련이 없다. 그래서 this와 마찬가지로 super 역시 static메서드에서는 사용할 수 없고 인스턴스 메서드에서만 사용할 수 있다.
super() - 조상 클래스의 생성자()
this()와 마찬가지로 super() 역시 생성자이다. this()는 같은 클래스의 다른 생성자를 호출하는 데 사용되지만, super()는 조상 클래스의 생성자를 호출하는 데 사용된다.
자손 클래스의 인스턴스를 생성하면, 자손의 멤버와 조상의 멤버가 모두 합쳐진 하나의 인스턴스가 생성된다.
이때 조상 클래스 멤버의 초기화 작업이 수행되어야 하기 때문에 자손 클래스의 생성자에서 조상 클래스의 생성자가 호출되어야 한다.
생성자의 첫 줄에서 조상 클래스의 생성자를 호출해야 하는 이유는 자손 클래스의 멤버가 조상 클래스의 멤버를 사용할 수도 있으므로 조상의 멤버들이 먼저 초기화되어 있어야 하기 때문이다.
이와 같은 조상 클래스 생성자의 호출은 클래스의 상속관계를 거슬러 올라가면서 계속 반복된다. 마지막으로 모든 클래스의 최고 조상인 Object클래스의 생성자인 Object()까지 가서야 끝이 난다.
그래서 Object클래스를 제외한 모든 클래스의 생성자는 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다. 그렇지 않으면 컴파일러는 생성자의 첫 줄에 super();를 자동적으로 추가할 것이다.
Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자.this() 또는 super(),를 호출해야 한다.
그렇지 않으면 컴파일러가 자동적으로 super();를 생성자의 첫 줄에 삽입한다.
인스턴스를 생성할 때는 클래스를 선택하는 것만큼 생성자를 선택하는 것도 중요하다.
1. 클래스 - 어떤 클래스의 인스턴스를 생성할 것인가?
2. 생성자 - 선택한 클래스의 어떤 생성자를 이용해서 인스턴스를 생성할 것인가?