[6] Java - Extends

harold·2021년 3월 2일
0

java

목록 보기
7/13
post-thumbnail

상속


부모 클래스의 모든 필드메소드
자식 클래스가 재정의 하는 것


[ 클래스의 부모-자식 상속 관계 표현 ]


상속에 대한 매우 치명적인 오해

상속의 이유와 목적을 물어보면 다음과 같이 답을 하는 경우를 매우 흔히 본다.

" 상속은 코드의 재활용을 위한 문법이다 "

그러나 객체지향 기반의 개발 경험이 풍부한 개발자나 대학원 전공자에게 물어보면
다음과 같이 대답을 한다.

" 상속은 코드의 재활용을 목적으로 사용하는 문법이 아니며
연관된 일련의 클래스들에 대해 공통적인 규약을 정의할 수 있습니다. "

만약 재활용 목적으로 상속을 사용할 경우 무의미하게 코드가 복잡해지고,
기대와 달리 코드를 재활용하지 못하는 상황을 쉽게 경험하게 될 것

코드의 재활용은 프로그래머라면 누구나 바라는 일이다.


// 부모 클래스: Animal
// 모두 공통적인 특성을 정의
class Animal {
	// name필드와 makeSound 메서드를 중복 작성하지 않고 여러 종류의
    // 동물을 모델링할 수 있다.
    // 각 하위 클래스에서는 자체적인 동작을 추가로 정의하거나
    // 부모 클래스에서 상속받은 동작을 재정의할 수 있다.
    // 이것이 객체 지향 프로그래밍의 상속을 통한 코드 재사용과 일반화의
    // 예시이다.
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public void makeSound() {
        System.out.println(name + " makes a sound");
    }

    public void eat() {
        System.out.println(name + " is eating");
    }
}

// 하위 클래스: Dog
class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " barks");
    }

    public void fetch() {
        System.out.println(name + " fetches a ball");
    }
}

// 하위 클래스: Cat
class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " meows");
    }

    public void scratch() {
        System.out.println(name + " is scratching");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("Buddy");
        Cat cat = new Cat("Whiskers");

        // Dog 클래스의 메서드 사용
        dog.makeSound();  // 오버라이드된 메서드 호출
        dog.fetch();      // Dog 클래스에만 있는 메서드 호출

        // Cat 클래스의 메서드 사용
        cat.makeSound();  // 오버라이드된 메서드 호출
        cat.scratch();    // Cat 클래스에만 있는 메서드 호출
    }
}

상속과 생성자의 호출 코딩해보기

위 예제를 활용하여 상속에 대한 이해를 하기 위해 다음 클래스 각각에 대한 생정자를 삽입해보자
물론 상속 관계를 고려하여 각 클래스 별로 필요한 생성자를 삽입해야 한다.


class Car{
    int gasolineGauge;

    public Car(int gasolineGauge) {
        this.gasolineGauge = gasolineGauge;
    }
}

class HybridCar extends Car {
    int electricGauge;

    public HybridCar(int gasolineGauge, int electricGauge) {
        super(gasolineGauge);
        this.electricGauge = electricGauge;
    }
}

class HybridWaterCar extends HybridCar {
    int waterGauge;

    public HybridWaterCar(int gasolineGauge, int electricGauge, int waterGauge) {
        super( gasolineGauge , electricGauge );
        this.waterGauge = waterGauge;
    }
    public void showCurrentGauge(){
        System.out.println("잔여 가솔린 : " + gasolineGauge);
        System.out.println("잔여 전기량 : " + electricGauge);
        System.out.println("잔여 워터랑 : " + waterGauge);
    }
}

public class 상속과생성자의호출 {
    public static void main(String[] args) {
        HybridWaterCar hwc = new HybridWaterCar(10, 20 , 30 );
        hwc.showCurrentGauge();
    }
}


[ 출력 값 ]

잔여 가솔린 : 10
잔여 전기량 : 20
잔여 워터랑 : 30

클래스 변수, 클래스 메소드와 상속 그리고 접근 지시자

다음 예제 코드를 보자

class SuperCLS{
	// 문제 풀이 후 접근 지시자 설명 예정
    static int count = 0;

    public SuperCLS() {
        count++;
    }
}

class SubCLS extends SuperCLS {
    public void showCount(){
        System.out.println(count);
    }
}

public class Main {
    public static void main(String[] args) {
		SuperCls sc = new SuperCls();
        SubCls sbc = new SubCls();
        SuperCls dsc = new SubCls();
        sbc.showCount();
    }
}


[ 출력 값 ]

3

상속 관계를 맺은 참조변수 간 대입과 형 변환

다음과 같이 상속 관계를 맺은 두 클래스가 있다고 가정했을때
몇가지 문제를 보며 대입 가능 여부를 가늠해보자.

class A{
	...
}

  class B extends A{
	...
}

class Main{
	public static void main( String args[] ){
	    A ab = new B();
    	B bb = new B();	
        
        A a1 = ab;
	}
}

질문 1 ) A a = ab; 코드는 대입이 가능할까 ?

가능하다 !

그 이유는 다형성은 상위 클래스 타입의 참조 변수가 -> 하위 클래스의 객체를
가리킬 수 있는 성질을 나타낸다.

ab는 B 클래스의 객체이며 a는 B 클래스의 객체를 가리킬 수 있지만
a의 타입은 A 클래스로 선언되었기 때문에 A 클래스에 정의된 멤버 변수와 메서드에만 접근 가능하다.

이러한 다형성은 코드의 유연성을 제공하며, 동일한 인터페이스를 사용하면서도 여러 클래스들의 객체를 다룰 수 있게 해준다.


질문 2 ) A a = bb; 코드는 대입이 가능할까 ?

가능하다 !

객체 지향 프로그래밍에서 상위 클래스 타입으로 여러 하위 클래스를 다룰 수 있는 중요한 개념이다.

위 처럼 선언하면 하위 클래스의 특정 동작을 활용하거나
상위 클래스하위 클래스에 공통적으로 정의된 메서드와 변수를 사용할 수 있다.

상위 클래스 A의 참조변수 a는 하위 클래스 B의 객체 bb를 가리킬 수 있으며
를 통해 A 클래스의 멤버에 접근할 수 있다.

다만, 이떄는 A 클래스에 정의된 멤버만 접근 가능하며
B 클래스에 추가로 정의된 멤버에는 접근할 수 없다.


질문 3 ) B b = ab 코드는 선언이 가능할까?

불 가능하다..

ab가 A 클래스 타입으로 선언되었지만
B 클래스의 객체를 B 클래스의 타입으로 참조할 수 없기 떄문이다.

java에서는 하위 클래스의 객체를 -> 상위 클래스의 참조로 변환하는 것은 자동으로 허용되지만
상위 클래스 객체를 하위 클래스의 참조로 변환하는 것은 명시적인 형 변환이 필요하며,
이러한 casting을 할 수 없는 경우 compile error가 발생하기 떄문이다.

따라서 위 compiler error를 해결하기 위해선
B b = (B)ab 로 선언해야 한다.

이는 ab가 참조하는 인스턴스가 -> B 인스턴스임을 프로그래머가 보장한다는 의미이다.

따라서 compiler는 이를 허용한다. 그러니 프로그래머는 이러한 형 변환을 진행하는 경우
대입의 가능성을 정확히 판단하여 치명적인 실수가 발생하지 않도록 주의해야 한다.


메소드 오버라이딩


하위 클래스에서 상위 클래스의 메서드를 다시 정의하는 과정이며
다형성의 핵심 원리 중 하나로, 상속 관계에 있는 클래스 간에 메서드의 동작을
변경할 수 있게 한다.


오버라이딩 시 주요 특징과 주의점

1. 시그니처의 일치

오버라이딩하는 메서드의 시그니처( 이름, 매개변수 타입, 반환 타입 )는
상위 클래스의 메서드와 정확하게 일치해야 한다. 그렇지 않으면 새로운 메서드를
정의하는 것이 된다.

2. @Override 어노테이션 사용

@Override 어노테이션을 사용하여 컴파일러에게 해당 메서드가 super클래스에서 오버라이딩 된 메서드임을 명시적으로 알려준다.
이렇게하면 오나타 잘못된 시그니처를 방지할 수 있다.

3. 접근 제어자 관리

오버라이딩된 메서드의 접근 제어자는
슈퍼 클래스의 메서드와 같거나 더 넓은 범위로 변경할 수 있다.
예를 들어, super 클래스의 protected로 선언된 메서드를 -> public으로
오버라이딩 할 수 있다.

4. final , static, private 메서드는 오버라이딩 불가!

final은 오버라이딩을 금지하고

staticprivate 메서드는 오버라이딩 할 수 없다.
ㄴ 인스턴스에 종속되지 않으므로 오버라이딩 대상이 아님 !

5. 예외 처리 주의

오버라이딩 된 메서드는 super 클래스의 메서드가 던지는 예외와 동일하거나
해당 예외의 하위 클래스를 던질 수 있다.
그러나 더 많은 예외를 던지거나 던지지 않는 것은 허용되지 않는다.

class SuperClass {
    void somethingMethod() throws IOException {}
}

class SubClass extends SuperClass {
    @Override
    // 가능
    void somethingMethod() throws FileNotFoundException {} 
    
   	// 불가능
	void somethingMethod() throws Exception {}
}

void somethingMethod() throws Exception 선언이 불가능한 이유는
Super Class에 상속받은 메서드의 IOException보다 Exception이 우선순위가 더 높기 때문
따라서 상속받은 메서드의 excepion의 우선 순위도 생각해야 한다.

0개의 댓글