[JAVA] 7. 상속

redcarrot01·2021년 5월 13일
0

JAVA

목록 보기
8/9
post-thumbnail

7. 상속

7.1 상속

클래스 상속

  1. 자바에서 상속은 여러 개의 부모 클래스를 상속할 수 없다.
  2. 부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.
  3. 부모와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메소드도 상속 대상에서 제외된다.
public class Cellphone {
    // 필드
    String model;
    String color;
    
    // 생성자 (생략)
    
    // 메소드
    void powerOn() { ... }
}
public class DmbCellPhone extends Cellphone {
    int channel;
    
    DmbCellPhone(String model, String color, int channel) {
        // cellphone클래스로부터 상속받은 필드
        this.model = model;
        this.color = color;
        
        this.channel = channel;
    }
}
public class DmbCellPhoneExample {
	public static void main(String[] args) {
		// DmbCellPhone 객체 생성
		DmbCellPhone dmbCellPhone = new DmbCellPhone("자바폰", "검정", 10);
		
		// CellPhone으로부터 상속 받은 필드
		System.out.println("모델: " + dmbCellPhone.model);
		System.out.println("색상: " + dmbCellPhone.color);
		
		// DmbCellPhone의 필드
		System.out.println("채널: " + dmbCellPhone.channel);
    }
}

부모 생성자 호출

아래처럼 DmbCellPhone 객체를 생성하면 내부적으로 부모인 CellPhone 객체 먼저 생성 후 자식인 DmbCellPhone 객체가 생성된다.

DmbCellPhone dmbCellPhone = new DmbCellPhone();
부모 객체를 생성하기 위해 부모 생성자 어디서 호출되는가?
// 자식 클래스의 생성자가 명시적으로 선언되지 않았다면
super();

// 자식 생성자 선언 and 명시적으로 부모 생성자 호출하고 싶다면
super(매개값...); // 매개타입 일치하는 부모 생성자 호출

super()는 부모 클래스의 기본 생성자를 호출한다.

만약 부모 클래스에 매개값이 있는 생성자만 있다면, 자식 클래스에서 super()를 사용할 때 적절한 매개값을 넣어 호출해주어야 한다.

부모클래스

기본 생성자 없고, name과 ssn을 매개값으로 받아 객체를 생성시키는 생성자만 있음

따라서 student(people상속하는) 클래스는 super(name, ssn)으로 people 클래스의 생성자 호출

public class People {
	public String name;
	public String ssn;
	
	public People(String name, String ssn) {
		this.name = name;
		this.ssn = ssn;
	}
}
public class Student extends People{
	public int studentNo;
	
	public Student(String name, String ssn, int studentNo) {
		super(name, ssn); // 부모 생성자 호출
		this.studentNo = studentNo;
	}
}
// super(name, ssn);를 주석처리하게 되면 컴파일 에러(부모 생성자 없으니 다른 생성자 명시 호출해라!!)

메서드 재정의

  • 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 말한다.
  • 부모의 메소드와 동일한 리턴 타입, 메소드 이름, 매개 변수 목록을 가져야 한다.
  • 접근 제한을 더 강하게 재정의할 수 없다. (public인데 private, default로 불가능) 더 약하게는 가능!
  • 새로운 예외(Exception)을 throws할 수 없다.
public class Parent {
    void method1() {
        System.out.println("부모1");
    }
    void method2() {
        System.out.println("부모2");
    }
}
pblic class Child extends Parent {
    @Override
    void method2() {
        System.out.println("자식2");
        super.method2(); // 자식클래스 내부에서 재정의된 부모 클래스의 메소드 호출하기
    }
}
public class ChildExample {
    public static void main(String[] args) {
        Child child = new Child();
        child.method1(); // 부모1
        child.method2(); // 자식2 \n 부모2
    }
}
//출력
부모1
자식2
부모2

자식 클래스에서 부모 클래스의 메소드를 재정의할면, 부모 클래스의 메소드는 숨겨지고 재정의된 자식 클래스만 사용됨

하지만, 자식 클래스 내부에서 재정의된 부모 클래스의 메소드를 호출할 수 있음

-> (자식클래스내부) super.부모메소드()

final 클래스와 final 메소드

final 키워드를 붙여 클래스를 선언하면 최종 클래스이므로 상속할 수 없게 된다.

즉, 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.

public final class 클래스 { ... }

final 키워드를 붙여 메소드를 선언하면 최종 메소드이므로 재정의할 수 없는 메소드가 된다.

즉, 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다.

public final 리턴타입 메소드( [매개변수, ... ] ) { ... }

protected 접근 제한자

  • protected는 public과 default 의 중간쯤에 해당
  • 같은 패키지에서는 default와 같이 접근 제한 X
  • 다른 패키지에서는 자식 클래스만 접근 허용
  • 필드, 생성자, 메소드 선언에 사용 가능
package sec01.exam07.pack1;
public class A {
    protected String field; // 필드
    
    protected A() { ... } // 생성자
    
    protected void method() { ... } // 메소드
}
package sec01.exam07.pack2;

import sec01.exam07.pack1.A;

public class C {
	public void method() {
        // 접근 불가능 - 다른 패키지에 있으므로
		/*
		A a = new A();
		a.field = "value";
		a.method();
		*/
	}
}
package sec01.exam07.pack2;

import sec01.exam07.pack1.A;

public class D extends A {
	public D() { 
        // 접근가능 , D는 자식클래스이므로, 
		super(); // 단 new 연산자 사용해서 생성자를 호출할 수는 없다.
		this.field = "value";
		this.method();
	}
}

7.2 타입 변환과 다양성

다형성 : 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질

자동 타입 변환

  • 자식은 부모 타입으로 자동 타입 변환이 가능하다.
  • 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급 가능
  • 고양이가 동물을 상속받았다면 고양이는 동물이다 성립
//Cat 클래스로부터 Cat 객체를 생성하고, 
// 이것을 Animal 변수에 대입하면 자동 타입 변환 일어남
// cat과 animal은 타입만 다를 뿐 동일한 Cat 객체를 참조한다

// cat == animal => true : 타입은 달라도 동일한 객체 참조
Cat cat = new Cat();
Animal animal = cat;
Animal animal2 = new Cat();
  • 부모가 아니더라도 상위 타입이라면 자동 타입 변환 가능

(자식클래스 객체가)부모 타입으로 자동 타입 변환된 경우

  • 부모 클래스에 선언된 필드와 메소드만 접근 가능

  • 메소드가 자식 클래스에서 재정의되었다면 자식 클래스의 메소드가 호출됨 - 다형성과 관련

자동 타입 변환 후의 멤버 접근

public class Parent {
	public void method1() {
		System.out.println("Parent-method1()");
	}
	
	public void method2() {
		System.out.println("Parent-method2()");
	}
}
public class Child extends Parent {
	@Override // 재정의
	public void method2() {
		System.out.println("Child-method2()");
	}
	
	public void method3() {
		System.out.println("Child-method3()");
	}
}
public class ChildExample {
	public static void main(String[] args) {
		  Child child = new Child();

		  Parent parent = child;

		  parent.method1();

		  parent.method2();

		  //parent.method3();  (호출 불가능)
        // Child 객체는 Parent 부모 타입 변환 이후에는 method3() 호출 불가
	}
}
Parent-method1()
Child-method2()

필드의 다형성

자동 타입 변환은 다형성을 구현하기 위해 필요하다.

필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가 달라질 수 있다. -> 다형성

자동차로 예를 들면, 더 좋은 부품이 나왔을 때 부품을 언제든지 교체할 수 있다 -> 다형성

다형성 구현 조건 3가지

  1. 자식 클래스는 부모 클래스의 필드와 메소드 가지고 있음
  2. 자식 클래스는 부모의 메소드 재정의 가능
  3. 자식 타입을 부모 타입을 변환
class Car {
    Tire frontLeftTire = new Tire();
    Tire backLeftTire = new Tire();
    Tire frontRightTire = new Tire();
    Tire backRightTire = new Tire();
    void run() { ... }
}

frontRightTirebackLeftTireHankookTireKumhoTire로 교체하고 싶다면

Car myCar = new Car();
myCar.frontRightTire = new HankookTire();
myCar.backLeftTire = new KumhoTire();
myCar.run();

이렇게 해주면 교체할 수 있다.

Tire 클래스 타입인 frontRightTire, backLeftTire는 Tire 객체가 저장되어야 하지만,

Tire의 자식 객체가 저장되어도 됨. -> 자식타입은 부모타입으로 자동 타입 변환

Car 객체는 Tire클래스에 선언된 필드와 메소드만 사용하고, HankoorTireKumhoTire 객체에는 부모인 Tire의 필드와 메소드를 가지고 있기 때문에 전혀 문제가 없다.

void run() {
	frontRightTire.roll()
	backLeftTire.roll()
}

HankoorTireKumhoTire 로 교체되면, roll()메소드 재정의하여 HankoorTireKumhoTire 의 메소드가 호출됨

자동 타입 변환을 이용하여 Tire 필드값을 교체함으로써 Car의 run()메소드를 수정하지 않아도 다양한 roll() 메소드의 실행결과를 얻게 됨 ->필드의 다형성

<예제코드가 길어서 따로 정리는 안하지만, 이해가 부족하면 코드보기>

매개변수의 다형성

자동 타입 변환은 메소드를 호출할 때도 많이 발생한다.

매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수 있다.

매개변수의 타입이 클래스일 경우, 해당 클래스의 객체뿐 아니라 자식 객체까지도 매개값으로

사용할 수 있다는 것.

class Driver {
    void drive(Vehicle vehicle) {
        vehicle.run();
    }
}
class Vehicle {
    void run() {
        System.out.println("차량이 달립니다.");
    }
}
class Bus extends Vehicle {
    @Override
    void run() {
        System.out.println("버스가 달립니다.");
    } 
}
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
Bus bus = new Bus();

driver.drive(vehicle); //차량이 달립니다.
driver.drive(bus); //버스가 달립니다.

강제 타입 변환

  • 부모 타입 -> 자식 타입 변환

  • 자식 -> 부모 자동타입 변환 후 다시 자식 타입으로 변환할 때 사용

  • 자식에 선언된 필드와 메소드를 꼭 사용해야 할 때 강제 타입 변환한다.

    • 자식타입 -> 부모타입 자동 타입 변환하면, 부모에 선언된 필드와 메소드만 사용가능
    • 만약, 자식에 선언된 필드와 메소드 사용하고 싶다면, 강제 타입 변환해서 다시 자식 타입으로 변환해야 함
Parent parent = new Child();
Child child = (Child) parent;
Parent parent = new Parent();
Child child = (Child) parent; (X) 처음부터 부모 타입인 객체는 자식 타입으로 변환 불가능

객체 타입 확인

instanceof 연산자 활용 : 주로 매개값의 타입을 조사

타입을 확인하지 않고 강제 타입 변환 시도하면 ClassCastException 발생

// '타입'으로 '객체'가 생성되었나?
boolean result = 객체 instanceof 타입
public void method(Parent parent) {
    if(parent instanceof Child) {
        Child child = (Child) parent;
    }
}

7.3 추상 클래스

추상 클래스

객체를 직접 생성할 수 있는 실체 클래스들의 공통적인 특성을 추출해서 선언한 클래스

추상 클래스와 실체 클래스는 상속의 관계를 각지고 있고,

실체 클래스는 추상 클래스의 모든 특성(필드, 메소드)을 물려받고 추가적인 특성을 가질 수 있다.

추상 클래스의 용도

  1. 공통된 필드와 메소드의 이름을 통일할 목적

    실체 클래스를 설계하는 사람이 여러 사람일 경우, 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있다.

    추상 클래스를 이용해 이를 방지하고 필드와 메소드 이름을 통일할 수 있다.

  2. 실체 클래스를 작성할 때 시간 절약

    공통 필드와 메소드를 추상 클래스에 모두 선언해두고, 다른 점만 실체 클래스에 선언하면 시간이 절약된다.

    설계자가 공통된 필드와 메소드를 추려내어 추상 클래스로 설계 규격을 만들어 전달하는 것이 좋다.

추상 클래스 선언

  • 클래스에 abstract를 붙이면, new 연산자 못씀
  • 추상 클래스도 필드, 생성자, 메소드 선언을 할 수 있지만, new 연산자로 직접 생성자를 호출할 수 없다.
    • 자식 클래스에서 super()를 써서 생성자 호출한다
  • 상속을 통해 자식 클래스 객체만 만들 수 있다.
    • 자식 클래스를 통해 추상 클래스의 필드나 메소드를 사용할 수 있다.
public abstract class Phone {
    ...
}
Phone phone = new Phone(); //(X) 불가능
SmartPhone smartPhone = new SmartPhone(); //(O) Phone 추상 클래스를 상속한 자식 클래스 가능

추상 메소드와 재정의

  • 메소드의 선언만 통일하고, 실행 내용은 실체 클래스마다 달라야 하는 경우
  • 하지만 반드시 해당 메소드를 구현하도록 강제하고 싶을 경우

추상메소드는 abstract 키워드와 함께 메소드의 선언부만 있고, 실행내용인 {}는 없다.

자식 클래스가 추상 메소드를 재정의하지 않으면 컴파일 에러가 발생한다.

public abstract class Animal { // 추상클래스
    public abstract void sound(); // 추상메소드, {} 없음 !!!
}
public class Dog extends Animal {
    @Override
    public void sound() { // 추상메소드 재정의
        System.out.println("멍멍");
    }
}
public class Cat extends Animal {
    @Override
    public void sound() { // 추상메소드 재정의
        System.out.println("야옹");
    }
}

관심 있을 만한 포스트

0개의 댓글