[이것이 자바다 - 7] 상속

Uicheon·2022년 6월 7일
0

이것이 자바다

목록 보기
5/8
post-thumbnail

7. 상속

7.1 상속 개념

  • 현실에서의 상속: 부모가 자식에게 물려주는 행위
  • 객체지향에서의 상속: 부모 클래스의 멤버를 자식 클래에스에 물려주기.

상속은 이미 잘 개발된 클래스를 활용하기에, 코드의 중복을 줄여준다.
단, 상속을 해도 부모 클래스의 모든 필드와 메소드를 물려받는 것은 아니다.
부모 클래스에서 private 접근 제한을 갖는 필드/메소드는 상속 대상에서 제외 된다.

상속을 이용하면 클래스의 수정을 최소화 시킬 수 있다.
부모 클래스의 수정으로 모든 자식 클래스들의 수정 효과를 가져오기 때문에 유지 보수 시간을 줄인다.
(이 말은 잘못 설계한 부모 클래스의 수정은 모든 클래스의 수정을 의미한다.)

7.2 클래스 상속

프로그램에서는 자식이 부모를 선택한다.
자식 클래스를 선언할 때 어떤 부모 클래스를 상속 받을 것인지 결정한다.

class 자식클래스 extends 부모클래스{
	//필드, 생성자, 메소드
}

다른 언어와 달리 자바는 다중 상속을 허용하지 않는다.

7.3 부모 생성자 호출

자식 객체를 생성하면, 부모 객체가 먼저 생성되고 자식 객체가 그 다음에 생성된다.
아래 코드는 DmbCellPhone 객체만 생성하는 것 처럼 보이지만, 내부적으론 부모인 CellPhone객체가 먼저 생성되고, DmbCellPhone 객체가 생성된다

DmbCellPhone dmbCellPhone = new DmbCellPhone();

모든 객체는 클래스의 생성자를 호출해야만 생성된다.
부모 객체도 예외는 아니다. 그렇다면 부모 생성자를 어디서 호출한 것일까?
DmbCellPhone 생성자가 명시적으로 선언되지 않았다면, 컴파일러는 다음과 같은 기본 생성자를 생성해 낸다.

public DmbCellPhone(){
	super(); //부모 생성자 호출
    //super(매갯값 ...) -> 일치하는 부모 생성자 호출, 없을 시 컴파일 오류
}

super()는 부모의 기본 생성자를 호출한다.
부모클래스에 기본 생성자가 없고, 매개 변수가 있는 생성자만 있다면 반드시 자식 생성자에서 부모 생성자 호출을 위해 super(매갯값, ...)를 명시적으로 호출해야 한다.
super는 자식 생성자 첫 줄에 위치해야 한다.

7.4 메소드 재정의

부모 클래스의 모든 메소드가 자식 클래스에 맞게 설계되어 있다면, 가장 이상적인 상속이지만, 어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수도 있다.
이 경우 상속된 일부 메소드는 자식 클래스에서 다시 수정해서 사용해야 한다.
이런 경우를 위해 메소드 오버라이딩 기능을 제공한다.

7.4.1 메소드 재정의(@Override)

메소드 오버라이딩은 자식 클래스에서 동일한 메소드를 재정의하는 것을 말한다.
메소드가 오버라이딩 되었다면, 부모 객체의 메소드는 숨겨지기 때문에, 자식 객체에서 메소드를 호출하면, 오버라이딩된 자식 메소드가 호출된다.

메소드 오버라이딩 규칙

  • 부모 메소드와 동일한 시그너처(리턴 타입, 메소드 이름, 매개 변수 리스트)
  • 접근 제한을 더 강하게 오버라이딩할 수 없다.
  • 새로운 예외(Exception)을 throws할 수 없다.

7.4.2 부모 메소드 호출(super)

자식 클래스에서 부모 클래스의 메소드를 오버라이딩하게 되면, 부모 클래스의 메소드는 숨겨지고 오버라이딩 된 자식 메소드만 사용된다.
그러나 다음과 같이 부모 클래스의 메소드를 호출 할 수 있다.

super.부모메소드(); //super는 부모 객체를 참조

7.5 final 클래스와 final 메소드

final 키워드는 해당 선언이 최종 상태이고, 결코 수정될 수 없음을 뜻한다.
클래스/메소드 선언 시에 final 키워드를 붙이면 상속과 관련이 있다.

7.5.1 상속할 수 없는 final 클래스

클래스 선언시 final 키워드를 붙이면 최종적인 클래스 이므로 상속할 수 없다.

public final class 클래스{
	...
}

대표적인 예로는 자바 표준 API String이 있다.

7.5.2 오버라이딩 할 수 없는 final 메소드

메소드 선언 시 final 키워드를 붙이면, 이 메소드는 최종적인 메소드이므로, 오버라이딩할 수 없는 메소드가 된다.

public class car{
	//필드
    ...
    //메소드
    ...
    
    //final 메소드
    public final void stop(){...} //상속 할 수 없음.
}

7.6 protected 접근 제한자

protectedpublicdefault의 중간 쯤이다.
같은 패키지에서는 default와 같이 접근 제한이 없지만, 다른 패키지에서는 자식 클래스만 접근을 허용한다.

//pakage1
public class A{
	protected member
}

public class B{
	// member 접근 가능
}

//pakage2
public class C{
	// member 접근 불가능
}

class D extends A{
	// member 접근 가능
}

protected는 필드, 생성자, 메소드 선언에 사용될 수 있다.

7.7 타입 변환과 다형성

다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용 할 수 있는 성질을 말한다.
하나의 타입에 여러 객체를 대입함으로써 다양한 기능을 이용할 수 있도록 해준다.
다형성을 위해 자바는 부모 클래스로 타입 변환을 허용한다.
즉, 부모 타입에 모든 자식 객체가 대입 될 수 있다.

public class Car{
	Tire t1 = new HankookTire();
    Tire t2 = new KumhoTire();
}

7.7.1 자동 타입 변환(Promotion)

자동 타입 변환(Promotion)은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.

부모클래스 변수 = 자식클래스 타입; //자동 타입 변환
Cat cat = new Cat();
Animal animal = cat; //new Cat();

cat과 animal 변수는 타입만 다를 뿐, 동일한 cat 객체를 참조한다.
또한, 바로 위의 부모가 아니라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.
형 변환후 일어나는 일들은 다음과 같다.

  • 부모 클래스에 선언된 필드/메소드만 접근 가능하다.
  • 변수로 접근한 멤버는 부모 클래스 멤버로만 한정된다.
  • 그러나 메소드가 자식 클래스에서 오버라이딩 되었다면,
    자식 클래스의 메소드가 대신 호출된다.(다형성)

7.7.2 필드의 다형성

그렇다면 왜 자동 타입 변환이 필요할까?
그냥 자식 타입으로 사용하면 될 것 아닌가? 아니다. 다형성 때문이다.
다형성은 동일한 타입을 사용하지만 다양한 결과가 나오는 성질이다.

public class character{
Attackable weapon;

    public Character(Attackable weapon) {
        this.weapon = weapon;
    }

    public Character() {

    }

    public void attack() {
        weapon.attack()
    }

    public Attackable getWeapon() {
        return weapon;
    }

    public void setWeapon(Attackable weapon) {
        this.weapon = weapon;
    }
}

public class Attackable{
	String name;
    int damage;
    
    Attackable(String name, int damage){
    	this.name = name;
        this.damage = damage;
    }
    
    public void attack() {
        System.out.println("attacks with " + name);
        System.out.println("damages " + damage);
    }
}


public class Gun extends Attackable{
	Gun(String name, int damage){
    	super(name, damage);
    }
    
    @Override
    public void attack() {
        super.attack();
        System.out.println("BANG!");
    }
}

pulic class OneHandSword extends Attackable{
	OneHandSword(String name, int damage){
    	super(name, damage);
    }
    
    @Override
    public void attack() {
        super.attack();
        System.out.println("Swoosh!");
    }
}
public static void main(String[] args) {

    Character character = new Character();
    Attackable gun = new Gun("revolver", 10);
    Attackable oneHandSword = new OneHandSword("light saver", 5);

    //총 장착
    character.setWeapon(gun);
    character.attack();

    //칼 장착
    character.setWeapon(oneHandSword);
    character.attack();
    
    //저장된 무기 장착 ?

}

위를 보면 똑같은 Attackable 을 상속 받은 gunOneHandSword가 Attackable을 받아 다른 결과를 출력한다.

예시가 다형성은 만족한다.
그러나 setWeapon으로 의존성을 일일히 주입해주기에 객체지향적이라고 보긴 어렵다.

DI, DI 컨테이너, IoC 컨테이너 (ex: Context-*.xml, 혹은 annotaion(@bean, AppConfig.java)를 사용하면 된다.
이에 관해서는 따로 작성해야 할 듯하다.
하지만 무기가 런타임에 불변성을 가져야 할 필요가 있나?

you're technically correct, the best kind of correct

7.7.3 하나의 배열로 객체 관리

4개의 타이어를 각각 필드로 저장하는 것 보다, 배열로 저장하는 게 낫다.

class Car{
	// Tire FLTire = new Tire();
    // Tire FRTire = new Tire();
    // ...
    
    Tire[] Tires = {
    	new Tire("앞왼쪽", 4);
        ...
    }
}

int run(){
	for(int i=0; i<tires.length; i++){
    	if(tire.roll()==false){
        	stop();
            return (i+1);
		}
    }
}

7.7.4 매개 변수 다형성

자동 타입 변환은 필드 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
메소드 호출시 매개 변수 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값 다양화를 위해 자식 타입 객체를 지정할 수도 있다.

위와 같은 부모-자식 클래스가 있다고 해보자.

class Driver{
	void drive(Vehicle vehicle){
    	vehicle.run()
	}
}
Vehicle vehicle = new Vehicle();
Bus bus = new Bus();
//drvier.drive(vehicle); 
driver.drive(bus); // 자동 타입 변환

뭔가 여기까지 오면 눈치 채겠지만, Vehicle을 상속받는 Bus 객체가 매개값으로 사용되면 자동 타입 변환이 발생한다.
매개 변수 타입이 클래스일 경우, 해당 클래스의 객체 뿐만아니라 자식 객체까지도 매개값으로 사용할 수 있다.
또한 오버라이딩 된 메소드를 호출한다면 메소드의 실행 결과는 다양해진다.
(위의 attackable 과도 같다. character의 attack에 Attackable 인자를 넘기면 된다.)

7.7.5 강제 타입 변환(Casting)

강제 타입 변환(Casting)은 부모 타입을 자식 타입으로 변환하는 것을 말한다.
모든 부모 타입을 자식 클래스 타입으로 강제 변환은 할 수 없다.
자식 타입이 부모 타입으로 자동 변환한 후, 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.

  • (자식->부모, Promotion, 자동 타입 변환)
    부모에 선언된 필드와 메소드만 사용 가능하다.
  • (부모->자식, Casting, 강제 타입 변환)
    자식 타입에 선언된 필드/메소드를 사용해야 한다면, 강제 타입 변환을 해야한다.
// field1, method1,2는 부모
// filed2, method3은 자식

Parent parent = new Child();
parent.field1 = "xxx";   	//o
parent.method1();			//o
parent.method2();			//o

parent.field2();			//x
parent.method3();			//x

Child child = (Child) parent;
child.field2 = "yyy";		//o
child.method3(); 			//o

7.7.6 객체 타입 확인(instanceof)

강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 떄문에 다음과 같이 부모 타입의 변수가 부모 객체를 참조할 경우 자식 타입으로 변환 할 수 없다.

Parent parent = new Parent()
Child child = (Child) parent; //안된다.

그렇다면 부모 변수가 참조하는 객체가 부모 객체인지, 자식 객체인지 확인하는 방법은 없을 까?
instanceof 연산을 사용하면 된다.

좌항은 객체, 우항은 타입이 온다.
좌항의 객체가 우항이 객체라면 true, 아니면 false를 반환한다.

boolean result = 좌항(객체) instanceof 우항(타입)

public vod method(Parent parent){
	if(parent istnaceof Child){ //강제 타입 변환 가능?
    	Child child = (Child) parent;
    }
}

만약 타입을 확인하지 않고 강제 타입 변환을 시도한다면 ClassCastException예외가 발생할 수 있다.

7.8 추상 클래스

7.8.1 추상 클래스의 개념

추상은 실체 간에 공통되는 특성을 추출한 것을 말한다.
즉, 실체들의 공통되는 특성을 가지고 있는 추상적인것이라 볼 수 있다.
객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다.
추상 클래스실체 클래스는 상속의 관계를 가지고 있다.

추상 클래스는 실체 클래스의 공통되는 필드/메소드를 추출해서 만들었기 때문에 직접 생성해서 사용할 수 없다.
즉, 추상 클래스는 새로운 실체 클래스를 만들기 위해 부모 클래스로만 사용된다.

7.8.2 추상 클래스의 용도

  1. 실체 클래스들의 공통된 필드와 메소드의 이름을 통일할 목적
    핸드폰 클래스를 설계한다 하자.
  • 필드명 불일치
    Telephone 클래스에선 owner, SmartPhone에서는 user라고 저장
  • 메소드명 불일치
    Telephone 클래스에선 powerOn(), SmartPhone에서는 turnOn()

이렇게 공통된 필드와 메소드 이름을 통일 하기 위해 사용한다.

  1. 실체 클래스 작성시 시간 절약
    공통 필드/메소드는 추상 클래스인 Phone에 모두 선언하고, 실체 클래스마다 다른 점만 선언하게 되면, 실체 클래스 작성시에 시간을 절약 할 수 있다.
    즉 추상 클래스는 설계 규격의 역활을 하게 된다.

7.8.3 추상 클래스 선언

추상 클래스 선언은 선언에 abstact 키워드를 붙이면 된다.

public abstract class 클래스{
	//필드
    //생성자
    //메소드
}

추상 클래스는 new 연산자로 직접 생성자를 호출 할 수 없지만,
자식 객체가 생성될 때 super(...)를 호출해서 추상 클래스 객체를 생성하므로, 추상 클래스도 생성자가 있어야 한다.

7.8.4 추상 메소드와 오버라이딩

추상 클래스는 실체 클래스가 공통적으로 가져야 할 필드/메소드를 정의해놓은 것이다.
그러나 실행 내용은 실체 클래스마다 달라야 하는 경우가 있다.

ex) Animal class의 sound() 메소드. 동물마다 소리가 달라야 한다.

이런 경우를 위하여 추상 클래스는 추상 메소드를 선언할 수 있다.
자식 클래스는 반드시 추상 메소드를 재정의 해서 실행 내용을 작성해야 한다.

[public | protected] abstact 리턴타입 메소드명(매개변수, ...);

어떤 소리를 내는지 결정할 수 없지만, 동물은 소리를 낸다는 공통적인 특성이 있으므로, 추상 메소드로 선언해야 한다.
만약 자식 클래스에서 추상 메소드를 Override하지 않는다면 에러가 발생한다.

profile
컨셉입니다~

0개의 댓글