본문 바로가기
언어/자바의 정석

[자바의 정석] Chapter 7 객체 지향 프로그래밍II(2) - 오버라이딩(overriding)

by chan10 2021. 2. 16.

오버라이딩이란?

o  조상 클래스로부터 상속받은 내용을 변경하는 것오버라이딩이라고 한다. 상속받은 메소드를 자손 클래스 자신에 맞게 변경해야 하는 경우 오버라이딩을 한다.

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 클래스는 2차원 좌표였으나 Point3D3차원 좌표이기에 z축 멤버 변수가 추가되었다. 따라서 getLocation()메소드 호출 시 z축에 대한 값도 출력을 위해 오버라이딩 하였다.

 

오버라이딩 조건

o  오버라이딩은 메서드의 내용만 새로 작성하는 것이기에 메서드의 선언부는 조상의 메서드와 완전히 일치해야한다.

o  오버라이딩 성립하기 위한 조건

- 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와

1. 이름이 같아야 한다.

2. 매개변수가 같아야 한다.

3. 반환타입이 같아야 한다.

o  요약하면 선언부가 완전히 일치해야 한다. 다만, 접근 제어자와 예외는 제한된 조건에서 다르게 변경할 수 있다.

 

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

조상클래스에 정의된 메서드의 접근 제어자가 protected라면, 이를 오버라이딩하는 자손 클래스의 메서드는 접근 제어자가 protected public이어야 하며 대부분의 경우 같은 접근 제어자를 사용한다.

 

접근 제어자의 접근범위는 넓은 것에서 좁은 것 순으로 public, protected, (default), private이다.

 

2.     조상 클래스의 메서드보다 많은 수의 예외를 선언할 수 없다.

조상클래스의 메서드에 예외 개수가 2개이면 자손 클래스의 오버라이딩 메소드의 예외 개수는 2개보다 많게 선언할 수 없다.

class Parent{
    void parentMethod() throws IOException, SQLException{
        
    }
}
 
class Child extends Parent{
    void parentMethod() throws Exception{    //에러
        
    }
}

위처럼 오버라이딩을 했을 경우 예외의 개수로는 조상클래스보다 적다. 그러나 Exception은 모든 예외의 최고 조상이므로 더 많은 예외 개수를 던질 수 있도록 선언한 것이기에 잘못된 오버라이딩이다.

 

3.     인스턴스 메서드를 static메서드 또는 그 반대로 변경할 수 없다.

 

오버로딩 vs 오버라이딩

o  오버로딩(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) { } // 오버 라이딩
}

 

super

o  super자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데 사용되는 참조변수이다. 멤버 변수와 지역 변수의 이름이 같은 경우 this를 붙여 구별하듯이 상속받은 멤버와 자신의 멤버가 이름이 같은 때는 super를 붙여서 구별할 수 있다.

o  조상 클래스로부터 상속받은 멤버도 자손 클래스 자신의 멤버이기에 super대신 this로 사용할 수도 있기에 조상 클래스 멤버와 자손 클래스 멤버가 중복 정의되어 서로 구별이 필요한 경우에만 super를 사용하는 것이 좋다.

o  모든 인스턴스는 자신이 속한 인스턴스 주소가 지역 변수로 저장되는데 이것이 참조변수인 this super이다.

o  static 메서드는 인스턴스와 관련이 없기에 this와 마찬가지로 super또한 인스턴스 메서드에서만 사용할 수 있다.

public class Test {    
    public static void main(String[] args) {
        Child c = new Child();
        c.method();
    }
}
 
class Parent {
    int x=10;
}
class Child extends Parent {
    int x=20;
    
    void method() {
        System.out.println("x=" + x);
        System.out.println("this.x=" + this.x);    //현재 클래스
        System.out.println("super.x=" + super.x); // 조상 클래스
    }
    
}

o  변수뿐만 아니라 메서드에서도 super를 사용해서 호출할 수 있으며 특히 자손 클래스에서 조상 클래스의 메서드를 오버라이딩 할 때 사용한다.

class Point{
    int x;
    int y;
    
    String getLocation() {
        return "x : " + x + "y : " + y;
    }
}
 
class Point3D extends Point{
    int z;
    
    String getLocation() {    // 오버라이딩
        return super.getLocation() + "z : " + z;    //조상 메서드 호출
    }
}

-       조상 클래스에 메서드의 내용에 추가적으로 내용을 덧붙이는 것이라면 super를 사용해서 조상클래스의 메서드를 포함시키는 것이 좋다. 이렇게 하면 나중에 조상 클래스의 메서드 내용이 변경되어도 자동으로 반영되기 때문이다.

 

super( ) – 조상 클래스의 생성자

o  this( )가 같은 클래스의 생성자를 호출하는 데 사용했다면 super( )는 조상클래스의 생성자를 호출하는데 사용된다.

o  자손 클래스의 인스턴스를 생성 시 조상 클래스의 멤버도 포함되어 있기에 조상 클래스 멤버에 대한 초기화 작업이 필요하다. 그래서 자손 클래스 생성자에서 조상 클래스 생성자가 호출되어야 한다.

o  조상클래스의 생성자 호출은 자손 클래스 생성자 맨 첫 줄에 호출되어야 하는데 자손 클래스 멤버가 조상 클래스 멤버를 사용할 수도 있기에 먼저 초기화가 되어있어야 하기 때문이다.

o  Object클래스를 제외한 모든 클래스는 생성자의 첫 줄에 반드시 자신의 다른 생성자 또는 조상의 생성자를 호출해야 한다. 그렇지 않으면 컴파일러가 자동으로 super( );를 생성자 첫 줄에 추가한다.

class Point{
    int x;
    int y;
    // 조상 클래스 생성자
    Point(int x, int y){
//        super();         // 컴파일 시 자동으로 추가
        this.x = x;
        this.y = y;
    }
    
    String getLocation() {
        return "x : " + x + "y : " + y;
    }
}
 
class Point3D extends Point{
    int z;
 
    // 자손 클래스 생성자
    
    Point3D(){    // 기본 생성자
        this(100200300);    // Point3D(int x, int y, int z) 호출
                    // 에러 발생X, 첫 줄에 super가 아닌 생성자 this사용
    }
    
    Point3D(int x, int y, int z) {
//        super();    //에러, 조상 클래스에서 Point()생성자를 찾을 수 없음
        super(x,y);    //조상 클래스 생성자 형식에 맞게 호출
        this.z = z;
    }
 
/*
    Point3D(int x, int y, int z) {
                    // 에러, 첫줄에 super();가 없다
        this.x = x;
        this.y = y;
        this.z = z;
//        super();    // 에러, 첫줄이 아니기에 에러 발생 
    }
*/
    
    String getLocation() {    // 오버라이딩
        return super.getLocation() + "z : " + z;
    }
}

-       Point3D 생성자 첫 줄에 다른 생성자를 작성하지 않으면 컴파일 시 super()[=Point()]를 자동으로 첫 줄에 넣어준다. 그러나 조상 클래스인 Point 클래스에 기본 생성자인 Point()가 없어 컴파일 에러가 발생한다.

(생성자가 정의되어 있는 클래스에는 컴파일러가 자동으로 기본 생성자를 추가하지 않는다.)

-       따라서 조상 클래스(Point)에서 기본 생성자Point()를 수동으로 추가하거나 자손 클래스의 Point3D 생성자에서 조상 클래스 생성자 형식에 맞게 추가하면 된다.

-       Point 클래스의 생성자는 어떤 생성자도 호출하고 있지 않기에 컴파일 시 첫 줄에 super()가 추가되어 조상 클래스인 Object클래스를 호출하게 된다.

o  인스턴스 생성 시 생성자 호출 순서는 아래와 같다.

Point3D p3 = new Point3D(); ->Point3D( ) -> Point3D(int x, int y, int z) -> Point(int x, int y) -> Object()

1.     인스턴스 생성

2.     기본 생성자 호출

3.     (오버 로딩)생성자 호출

4.     조상 클래스 생성자 호출

5.     조상 클래스 생성자 호출

Point3D p3 = new Point3D();

Point3D( )

Point3D(int x, int y, int z)

Point(int x, int y)

Object( )