본문 바로가기
언어/Java

OOP의 특성 - 1. 상속

by 넬준 2022. 5. 12.

Java는 OOP의 특성 중 하나인 상속을 지원한다.

상속받는 하위 클래스의 이름 옆에 'extends 상위 클래스'를 적어주면 된다.

 

public class Lower extends Upper {
	...
}

 

상속은 말 그대로 부모의 재산을 자식이 물려받는 것이다.

이를 프로그래밍에 적용해보면, 상위 클래스(부모)가 가지는 멤버(필드, 메서드)를 하위 클래스(자식)가 그대로 사용할 수 있다는 의미다.

더 나아가 'extends' 키워드에서 알 수 있듯이 하위 클래스는 상위 클래스를 확장한다는 의미까지 될 수 있다.

 

하지만, 상위 클래스의 모든 멤버를 하위 클래스가 사용할 수 있는 것은 아니다.

상위 클래스 멤버의 접근제어자에 따라 사용 가능 여부가 결정된다.

 

Lower 클래스(하위 클래스)와 Upper 클래스(상위 클래스)가 상속관계에 있다.

 

하지만 private 접근 제어자를 가진 name 멤버필드와 printAddress() 멤버메서드는 Lower 클래스에서 접근할 수 없어 컴파일 오류가 뜬 것을 볼 수 있다.

만일 Lower 클래스와 Upper 클래스가 다른 패키지에 있다면, default 접근 제어자를 가진 age 필드에도 접근할 수가 없다.

 

super(필요 시 매개변수)

 

Upper 클래스가 상위 클래스, Lower 클래스가 하위 클래스라고 해보자.

 

Lower lower = new Lower();

 

위 코드가 실행되는 과정을 자세히 보자.

 

일단, 하위 클래스의 객체가 생성되려면 반드시 상위 클래스의 객체가 먼저 생성이 되어야 한다.

위 코드만 보면, 언뜻 Lower 클래스의 객체만 생성될 것 같지만, 그 전에 상위 클래스의 객체 생성이 먼저 이뤄져야 한다.

 

즉, Upper 클래스의 객체가 먼저 생성되고, 이어서 Lower 클래스의 객체가 생성이 된다. 

 

위 코드에서는 Lower 클래스의 생성자만 호출했는데 어떻게 상위 클래스의 객체가 생성될 수 있을까?

 

이는 Lower 클래스의 생성자 내부를 보면 알 수 있다.

 

public class Upper {
    public Upper() {}
}

class Lower extends Upper{
    public Lower() {
        super();
    }
}


Lower 클래스의 생성자에 쓰여있는 super()가 바로 상위 클래스의 생성자를 호출하는 부분이다.

즉, Lower 클래스의 생성자가 호출되면, 맨 처음 super()가 실행되면서 상위 클래스의 생성자가 호출되는 것이다.

 

만일 어떤 클래스의 생성자 내부에 super() 같이 상위 클래스의 생성자를 호출하는 구문이 없다면 기본 생성자처럼 컴파일러가 자동으로 상위 클래스의 기본 생성자를 호출하는 super() 구문을 추가해준다.

따라서 상위 클래스에는 기본 생성자가 있어야 하고, 아니라면 상위 클래스의 생성자에 맞는 유형의 super(매개변수)가 하위 클래스의 생성자 첫 줄에 작성되어 있어야 한다.

 

 

메서드 오버라이딩

 

상위 클래스에 정의된 메서드를 모든 하위 클래스에서 그대로 사용할 수 있다면 이상적이겠지만, 현실적으로 하위클래스에 따라 해당 메서드를 새로 재정의해서 사용해야 하는 경우도 많다.

이를 '메서드 오버라이딩'이라고 한다. 

 

만일, 상위 클래스의 메서드를 하위 클래스에서 오버라이딩 했다면, 하위 클래스에서 해당 메서드를 호출하면 상위 클래스의 메서드가 아닌 오버라이딩된 메서드가 호출된다. 즉, 상위 클래스의 해당 메서드가 숨겨진다고 볼 수 있다. 기껏 하위 클래스에 맞게 상위 클래스의 메서드를 오버라이딩 했는데 상위 클래스의 기존 메서드가 그대로 호출되면 의미가 없게 된다.

 

public class Upper {
    public void print() {
        System.out.println("Upper 클래스의 print() 호출");
    }
}

class Lower extends Upper{
    @Override
    public void print() {
        System.out.println("Lower 클래스의 overriding한 print() 호출");
    }
}

 

위와 같은 상황일 때, 아래 코드를 실행하면

 

public class Test {
    public static void main(String[] args) {
        Lower lower = new Lower();
        lower.print();
    }
}

 

콘솔창에 다음과 같은 결과가 출력된다.

 

Lower 클래스의 overriding한 print() 호출

 

Upper 클래스의 print()가 아닌 Lower 클래스에서 새롭게 오버라이딩한 print()가 호출되는 것을 알 수 있다.

 

만일, 오버라이딩된 메서드가 아닌 상위 클래스의 기존 메서드를 호출하고 싶다면 super 키워드를 이용하면 된다.

super 키워드는 this 키워드와 마찬가지로 상위 클래스의 인스턴스를 가리킨다.

 

public class Test {
    public static void main(String[] args) {
        Lower lower = new Lower();
        super.print();
    }
}

위 코드를 실행하면, 콘솔창에 다음과 같이 출력된다.

 

Upper 클래스의 print() 호출

 

메서드 오버라이딩 시 주의점

하위 클래스에서 메서드를 오버라이딩할 때 주의할 점이 있다.

 

1. 상위 클래스의 메서드와 메서드 시그니처(리턴 타입, 메서드 이름, 매개변수)가 같아야 한다.

- 즉, 컴파일러가 같은 메서드라고 인식할 수 있어야 한다.

- 단, 리턴 타입은 자동 형변환이 가능하다면 달라도 된다.

 

2. 접근 제어자는 기존과 같거나 더 넓게만 설정할 수 있다.

- private -> default -> protected -> public 순으로 접근 범위가 넓어진다.

 

public에서 default로의 변경은 접근 범위가 좁아지기 때문에 컴파일 에러가 발생한다.

@Override 어노테이션을 붙여주면 해당 메서드가 올바르게 오버라이딩 됐는지 문법적으로 컴파일러가 체크해준다. 따라서 생략이 가능하지만 되도록이면 붙여주는 것이 좋다.

 

3. 새로운 예외를 발생할 수 없다. (추후 내용 추가 및 수정 예정)

- 더 넓은 범위의 checked exception을 throw할 수 없다. unchecked exception(Runtime Exception)은 상관없다.

-> 부모클래스에서 예외를 명시하지 않았으면, 자식클래스에서는 unchecked exception만 새로 명시할 수 있다.

-> 부모클래스에서 checked exception을 명시했으면, 자식클래스에서는 같은 exception이나, 그 자식 exception, 혹은        새로운 unchecked exception만 명시할 수 있다.

 

상속의 비밀

 

1. Object 클래스는 모든 클래스의 최상위 클래스다.

 

Lower lower = new Lower();

 

위에서 Lower 클래스가 하위 클래스이고, Upper 클래스가 상위 클래스일 때 위 코드가 실행된 경우에

Lower 클래스의 객체가 생성되기 전에 Upper 클래스의 객체가 생성된다고 했다. 여기서 한 단계 더 나아가보면, Upper 클래스의 객체가 생성되기 전에 Object 클래스의 객체가 제일 먼저 생성된다.

Object 클래스는 모든 클래스에 대해 상위 클래스이기 때문에 Upper 클래스는 Object 클래스의 하위 클래스이고, Object 클래스는 Upper 클래스의 상위 클래스다.

 

2. 자바에서는 클래스 당 단 하나의 클래스만 상속할 수 있다.

 

따라서, 만일 클래스 명 뒤에 extends 구문이 없다면 'extends Object' 구문이 생략되었다고 볼 수 있다. 이 역시 컴파일러가 자동으로 추가해준다.

 

 

 

 

 

 


참고

 

https://www.benchresources.net/java-overriding-widening-and-narrowing-for-access-modifier-return-type-and-exception-handling/

'언어 > Java' 카테고리의 다른 글

배열과 맵 toString()  (0) 2022.05.25
OOP의 특성 - 2. 다형성  (0) 2022.05.13
생성자  (1) 2022.05.11
static  (0) 2022.05.10
Wrapper Class  (0) 2022.05.10

댓글