오버라이딩

양성빈·2022년 6월 18일

참고
자바의 정석

오버라이딩

오버라이딩이란?

상위 클래스에서 상속받은 메서드를 재정의 하는 것을 오버라이딩이라고 한다.
좀 더 풀어서 말하면 상위클래스에서 정의된 메서드의 구현 내용이 하위 클래스에서 구현할 내용과 맞지 않는 경우 하위 클래스에서 동일한 이름의 메서드로 재정의 하는 것이다.

class Point {
	int x;
    int y;
    
    String getLocation() {
    	return "x: " + x + ", y: " + y;
    }
}

class Point3D extends Point {
	int z;
    
    @Overriding
    String getLocation() {
    	return "x: " + x + ", y: " + y + ", z: " + z;
    }
}

위의 코드를 보면, Point 클래스에서 getLocation 메서드로 x, y좌표를 출력한다. 하지만 Point3D 클래스에서는 z좌표까지 출력을 해야한다. 이럴때 보통 우리는 새로운 메서드를 정의하지만 정말 좋지 못한 방법이다. 그러면 어떻게 하면 좋을까?
바로 Point 상위 클래스의 getLocation을 재정의하면 될것이다. 상위클래스와 같은 메서드 명으로 정의하고 위에다가 @Overriding이라는 애노테이션을 붙여주면 된다.

IDE를 사용하면 @Overriding이라는 애노테이션은 저절로 붙여준다.

오버라이딩의 조건

오버라이딩은 메서드의 내용만을 새로 작성하는 것으로 메서드의 선언부는 상위클 래스와 완전히 일치해야한다. 이것말고 지켜야할 규칙이 있는데 아래의 내용을 살펴보자.

오버라이딩 조건
1. 상위클래스의 메서드 이름과 같아야 한다.
2. 매개변수가 같아야 한다.
3. 반환타입이 같아야 한다.

💡 참고
JDK1.5부터 공변 반환타입이 추가되어, 반환타입을 하위클래스의 타입으로 변경하는 것은 가능하도록 조건이 완화되었다.

설명이 길었는데 요약하자면, 선언부가 서로 일치해야 한다는 것이다.
단, 접근제어자와 예외는 제한된 조건하에서만 다르게 변경할 수 있다.

  1. 접근 제어자는 상위 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.

    • 예를 들어 상위 클래스에 접근제어자가 protected로 설정이 되어 있다면 하위클래스의 접근제어자는 protected나 public으로 설정해줘야한다.
    • 보통은 같은 범위의 접근제어자를 사용하지만, 가끔 특이한 경우에 다른 접근제어자를 사용해야 하는 경우가 있는데, 이럴때 이 원칙을 생각하면 될것이다.
    • 접근 제어자의 접근 범위를 넓은 범위에서 좁은 범위 순으로 표현하면 다음과 같다. (public > protected > (default) > private)
  2. 상위 클래스의 메서드보다 많은 수의 예외를 선언 할 수 없다.
    아래의 코드를 살펴보자.

class Parent {
	void method() throws IOException, SQLExcetion {
    	// ...
    }
}

class Child extends Parent {
    @Overriding
    void method() throws IOExcetion {
    	// ...
    }
}
  • 위의 코드를 살펴보면 올바르게 오버라이딩이 구현된 코드이다. 상위 클래스에서 method()라는 메서드에는 예외 클래스 개수가 2개이고 하위 클래스는 예외 클래스 개수가 1개임으로 적절히 구현된 코드이다.
  • 하지만 주의해야 할 점은 단순히 선언된 예외의 개수의 문제가 아니다.
    아래의 코드를 살펴보자.
class Parent {
	void method() throws IOException, SQLExcetion {
    	// ...
    }
}

class Child extends Parent {
    @Overriding
    void method() throws Excetion {
    	// ...
    }
}
  • 위의 코드는 분명 상위 클래스의 예외 클래스 개수보다 하위 클래스의 예외 클래스의 개수가 적다. 하지만 위 코드는 오버라이딩의 잘못 구현된 예이다. 이유가 무엇일까?
  • 하위 클래스의 예외 클래스는 모든 예외의 최상위 클래스인 Exception 클래스이므로 가장 많은 개수의 예외를 던질수 있어서 잘못된 예이다.
  • 이처럼 예외의 상속구조도 오버라이딩을 구현할 때 잘 생각해야하는 부분이다.

💻 정리
상위 클래스의 메서드를 하위 클래스에서 오버라이딩 할 때,
1. 접근 제어자를 상위 클래스의 메서드보다 좁은 범위로 변경 할 수 없다.
2. 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.
3. 인스턴스 메서드를 static메서드 또는 그 반대로 변경이 불가능하다.

🙋🏻 Q. 상위 클래스에 정의돤 static 메서드를 하위 클래스에서 똑같은 이름의 static메서드로 재정의 가능한가?
💁🏻 A. 가능하다. 하지만, 이것은 각 클래스에 별개의 static 메서드를 정의한 것일 뿐 오버라이딩이 아니다. 각 메서드는 클래스이름으로 구별하며, 호출할 때는 '참조변수.메서드()' 대신에, '클래스이름.메서드이름()'으로 하는 것이 정석이다. static 멤버들은 자신들이 정의된 클래스에 묶여 있으며, 상속이 안된다고 생각하면 좋을 것이다.

오버로딩 vs 오버라이딩

오버로딩과 오버라이딩이 용어가 헷갈려서 혼돈이 되지만, 명확히 다른 수행을 하는 것이다. 한번 살펴보자.

오버로딩: 기존에 없는 새로운 매서드를 정의하는 것
오버라이딩: 상속받은 메서드의 내용을 변경하는 것

class Parent {
	void method() {}
}

class Child extends Parent {
	void method() {} // 오버라이딩
    void method(int i) {} // 오버로딩
    
    void childMethod() {} // 자식 클래스의 메서드
    void childMethod(int i) {} // 오버로딩
}

super

super는 하위 클래스에 상위 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 멤버변수와 지역변수를 구분할때 this를 사용했듯이 상속받은 멤버와 자신의 멤버를 구분할때 super를 붙을때 구별한다.
상위 클래스로부터 상속받은 멤버도 하위클래스의 멤버임으로 this로 접근이 가능하지만, 중복정의가 되었을 경우 super와 this로 구분해서 사용하는 것이 좋다. 그리고 모든 인스턴스 메서드에는 자신이 속한 인스턴스의 주소가 지역변수로 저장하는데 이것이 참저변수인 this와 super다. 또한, this와 마찬가지로 super역시 static메서드에서는 사용할 수 없고 인스턴스 메서드에 사용이 가능하다.

변수만이 아니라 메서드 역시 super를 써서 부모 클래스의 메서드를 호출 할 수 있다. 특히, 상위 클래스의 메서드를 하위 클래스에서 오버라이딩한 경우에 super를 사용한다.

class Point {
    int x, y;

    String getLocation() {
        return "x : " + x + ", y: " + y;
    }
}

class Point3D extends Point {
    int z;

    @Override
    String getLocation() {
        return super.getLocation() + ", z: " + z;
    }
}

super() - 상위 클래스의 생성자

this()와 마찬가지로 super()도 생성자이다. this()는 같은 클래스의 다른 생성자를 호출하는데 사용되며, super()는 상위 클래스의 생성자를 호출하는데 사용된다.
하위 클래스의 인스턴슨 생성시, 상위 클래스의 멤버가 포함된 인스턴스가 생성이 된다. 그래서 하위클래스의 인스턴스가 상위클래스의 멤버들을 사용할 수 있는것이다. 이때, 상위 클래스의 멤버들이 초기화되어야 함으로 상위클래스의 생성자가 호출되어야 함으로, 하위 클래스에서 상위 클래스의 생성자가 호출되어야 한다. 그래서 this()와 비슷하게 하위 클래스의 생성자의 첫 줄에 상위 클래스의 생성자를 호출해야 한다. 왜냐하면, 하위 클래스의 멤버가 상위 클래스의 멤버를 사용할 수 있기 때문이다.
그러면 우리가 하나 생각해볼 것이 있다. Object는 모든 클래스의 최상위 클래스인데 우리가 일반적으로 클래스를 정의할 때 super()를 붙여주지 않았다. 그 이유는 extends Object와 비슷한 이유다. 컴파일러가 자동으로 생성자에 super()를 추가해준다.

Object클래스를 제외한 모든 클래스의 생성자 첫 줄에 생성자 this() 또는 super()를 호출해야 한다. 그렇지 않으면, 컴파일러가 자동으로 super()를 생성자의 첫줄에 삽입한다.

profile
모든 것을 즐길줄 아는 개발자입니다!

0개의 댓글