[JAVA] 상속성

서정범·2023년 4월 3일
0

JAVA

목록 보기
5/6

🔥 학습 목표

  • 상속 관계
  • 포함과 상속 관계
  • 메서드 오버라이딩
  • super와 super()

📌 상속성이란?

❗ 자바 언어에서 상속이란 기존 클래스를 재활용하여 새로운 클래스를 작성하는 자바의 문법적 요소이다.

간단하게 말하자면, 상위 클래스와 하위 클래스로 나누어 상위 클래스의 멤버를 하위 클래스에서 그대로 사용하여 상,하위 클래스 간의 멤버를 공유하는 것을 의미한다. 이때 이 두 클래스가 상속관계에 있다 말하고, 하위 클래스는 상위클래스의 모든 멤버를 상속받아 사용할 수 있다.

여기서 말하는 멤버는 필드, 메서드, 이너 클래스가 있습니다.

따라서, 하위 클래스의 멤버는 언제나 상위 클래스의 멤버 수와 같거나 많습니다.

위의 그림에서 총 4개의 클래스가 정의되어 있습니다.

아래의 3개의 클래스를 먼저 살펴보면 Programmer, Dancer, Singer의 속성과 기능이 정의되어 있습니다. 그 중 name, age, walk등과 같이 3개의 클래스가 중복으로 가지고 있는 속성과 기능들이 보일 것이다.

Programmer, Dancer, Singer은 사람입니다. 그리고 사람이라면 가져야할 공통점이 존재합니다. 이 그림은 그 공통점을 하나로 모아 Person이라는 클래스에 정의한 것이고, 하위의 3개 클래스는 이 Person을 상속받아 공통의 멤버들을 사용할 수 있습니다.

자바는 C++등 다른 객체지향언어와는 달리 상속할 때 단일 상속만 허용합니다. 다만 자바에서도 인터페이스(interface)라는 문법 요소를 통해 다중 상속과 비슷한 효과를 낼 수 있는 방법이 존재합니다.

인터페이스(Interface)

사전적 의미로, 이행하다라는 뜻을 가지고 있습니다.

확장의 의미보단 인터페이스에서 정의된 멤버들을 클래스에서 구현을 하겠다는 의미로 보면 적당합니다.

여러개의 인터페이스를 받아 메서드를 구현 할 수 있습니다. => 이것이 '다중 상속'과 비슷한 역할을 해줌

사용하는 이유(특징)

특징이라고도 볼 수 있는데, 사용하는 이유는 다음과 같습니다.

'코드를 재사용'하기 때문에 보다 적은 양의 코드로 새로운 클래스를 작성 가능 => '중복 제거'

중복을 제거함으로서 '유지보수성' 이 좋아집니다.

✅ 기능의 '추가/확장/개선/재정의 등'

✅ 클래스 간의 '계층적인 관계짓기'

포함관계(HAS-A) vs 상속관계(IS-A)

포함(Composite) 은 상속처럼 클래스를 재사용할 수 있는 방법으로, 클래스의 멤버로 다른 클래스 타입의 참조 변수를 선언하는 것을 의미합니다.

public class Employee {
    int id;
    String name;
    Address address;

    public Employee(int id, String name, Address address) {
        this.id = id;
        this.name = name;
        this.address = address;
    }

    void showInfo() {
        System.out.println(id + " " + name);
        System.out.println(address.city+ " " + address.country);
    }

    public static void main(String[] args) {
        Address address1 = new Address("서울", "한국");
        Address address2 = new Address("도쿄", "일본");

        Employee e = new Employee(1, "김코딩", address1);
        Employee e2 = new Employee(2, "박해커", address2);

        e.showInfo();
        e2.showInfo();
    }
}

class Address {
    String city, country;

    public Address(String city, String country) {
        this.city = city;
        this.country = country;
    }
}

// 출력값
1 김코딩
서울 한국
2 박해커
도쿄 일본

해당 코드는 한 회사의 근로자(Employee)를 표현하기 위한 Employee 클래스의 멤버 변수로 근로자가 사는 개략적인 주소를 나타내는 Address 클래스가 정의되어 있습니다.

원래라면 Address 클래스에 포함되어 있는 필드(인스턴스 변수 city, country)를 각각 Employee 클래스의 변수로 정의해주어야 하지만, Address 클래스로 해당 변수들을 묶어준 다음 Employee 클래스 안에 참조 변수를 선언하는 방법으로 코드의 중복을 없애고 포함관계로 재사용하고 있습니다.

객체지향 프로그래밍에서 상속보다는 포함 관계를 사용하는 경우가 더 많은데, 이는 상속관계보다 포함관계가 결합도 측면에서 더 좋은 코드라고 볼 수 있기 때문입니다.

관계 설정 기준

상속관계와 포함관계 중 어느 것을 선택해야 하는지는 클래스 간의 관계를 살펴보면 됩니다.

클래스 간의 관계가 '~은 ~이다(IS-A)' 관계인지 '~은 ~을 가지고 있다(HAS-A)' 관계인지 문장을 만들어 생각해 보는 것이다.

위의 예시로 예를 들어보면, "Employee는 Address이다." 라는 문장은 어색하지만, "Employee는 Address를 가지오 있다."는 어색하지 않은 올바른 문장임을 알 수 있습니다. 따라서 이 경우에는 상속 관계보다는 포함관계가 적합합니다.

반면 Car 클래스와 SportCar라는 클래스가 있다고 할 때, "SportsCar는 Car를 가지고 있다." 라는 문장 보다는 "SportsCar는 Car이다."라는 문장이 훨씬 더 자연스럽다. 따라서 이 경우에는 Car를 상위클래스로 하는 상속관계를 맺어주는 것이 더 적합하다고 할 수 있습니다.

📌 메서드 오버라이딩

메서드 오버라이딩(Method Overriding) 은 상속관계를 전제로 상위 클래스의 메서드와 이 상위 클래스를 상속받은 하위 클래스의 메서드의 이름을 동일하게 작성해 메서드를 재정의 하는 것을 말합니다.

메서드 오버라이딩을 사용하려면 세 가지 조건을 만족시켜야 됩니다.

  1. 메서드 선언부(메서드 시그니쳐)가 상위 클래스의 그것과 완전히 일치해야한다.
  2. 접근 제어자의 범위가 상위 클래스의 메서드보다 같거나 넓어야 한다.
  3. 예외는 상위 클래스의 메서드보다 많이 선언할 수 없다.

메서드 시그니처(method signature)

메서드 오버로딩의 핵심이라고도 불리는 메서드 시그니처는 메서드의 선언부에 명시되는 매개변수의 리스트를 가르킵니다.

만약 두 메소드가 매개변수의 개수와 타입, 그 순서까지 모두 같다면, 이 두 메서드의 시그니처는 같다고 할 수 있습니다.

메서드 오버로딩(Method Overloadding)

메서드 오버로딩(overloadding)이란 같은 이름의 메소드를 중복하여 정의하는 것을 의미합니다.

사용 조건은 다음과 같습니다.

  1. 메서드의 이름이 같아야 합니다.
  2. 메소드의 시그니처, 즉 매개변수의 개수 또는 타입이 달라야 합니다.
class CodeRunner{
    public static void main(String[] args){
    	Os os = new Os();
        Mac mac = new Mac();
        Windows windows = new Windows();
        
        os.run();
        mac.run();
        windows.run();
	}
}
class Os{
    void run(){
        System.out.println("OS를 실행합니다.");
    }
}

class Mac extends Os{
    void run(){
        System.out.println("Mac을 실행합니다.");
    }
}
class Windows extends Os{
    void run(){
        System.out.println("Windows를 실행합니다.");
    }
}
//출력값
OS를 실행합니다.
Mac을 실행합니다.
Windows를 실행합니다.

이러한 방식으로 사용할 수 있습니다.

또한, 모든 객체를 상위 클래스 타입 하나로 선언하면 다음과 같이 간편하게 배열로 선언하여 관리할 수 있다는 편리성이 있습니다.

class CodeRunner{
    public static void main(String[] args){
        Os[] os = new Os[]{ new Os(),new Mac(), new Windows()};
        for(Os os1 : os){
            os1.run();
        }
    }
}

class Os{
    void run(){
        System.out.println("OS를 실행합니다.");
    }
}

class Mac extends Os{
    void run(){
        System.out.println("Mac을 실행합니다.");
    }
}
class Windows extends Os{
    void run(){
        System.out.println("Windows를 실행합니다.");
    }
}

//출력값
OS를 실행합니다.
Mac을 실행합니다.
Windows를 실행합니다.

super vs super()

super은 상위 클래스의 객체를 의미하고, super() 은 상위 클래스의 생성자를 호출하는 것을 의미합니다.

이 또한 상속관계를 전제로 사용될 수 있습니다.

public class Super {
    public static void main(String[] args) {
        Lower low = new Lower();
        low.callNum();
    }
}

class Upper {
    int count = 20; // super.count
}

class Lower extends Upper {
    int count = 15; // this.count

    void callNum() {
        System.out.println("count = " + count);
        System.out.println("this.count = " + this.count);
        System.out.println("super.count = " + super.count);
    }
}

// 출력값
count = 15
count = 15
count = 20

Lower 클래스는 Upper 클래스로부터 변수 count를 상속받는데, 공교롭게도 자신의 인스턴스 변수 count와 이름이 같아 둘을 구분할 방법이 필요합니다.

이런 경우, 두 개의 같은 이름의 변수를 구분하기 위한 방법이 바로 super 키워드입니다.

만약, super 키워드를 붙이지 않는다면, 자바 컴파일러는 해당 객체는 자신의 속한 인스턴스 객체의 멤버를 먼저 참조합니다.

반면 경우에 따라 상위 클래스의 변수를 참조해야 하는 경우가 종종 있는데, 그 경우 super 키워드를 사용하면 부모 객체의 멤버 값을 참고할 수 있습니다.


Reference

profile
개발정리블로그

0개의 댓글

관련 채용 정보