STUDY/JAVA

[Java] 이것이 자바다 Ch.07

Eunjng 2024. 1. 13. 21:48

'이것이 자바다' 교재 및 강의를 참고하여 정리한 내용입니다.

Ch.07 상속

상속 개념

  • 상속: 부모가 자식에게 물려주는 행위
    • 중복 코드를 줄여 개발 시간 단축
    • 클래스의 수정 최소화

클래스 상속

  • public class 자식클래스 extends 부모클래스 { }
  • 다중 상속 비허용

부모 생성자 호출

  • 자식 객체를 생성하면 부모 객체가 먼저 생성된 다음에 자식 객체가 생성된다.
  • 모든 객체는 생성자를 호출해야만 생성되고, 부모 생성자는 자식 생성자의 맨 첫 줄에 숨겨져 있는 super()에 의해 호출된다.
public 자식클래스(...) {
    super();
    ...
}

메소드 재정의

  • 부모 클래스의 어떤 메소드가 자식 클래스가 사용하기에 적합하지 않은 경우, 자식 클래스에서 해당 메소드를 재정의해서 사용해야 한다.
  • 메소드 오버라이딩: 상속된 메소드를 자식 클래스에서 재정의하는 것
    • 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용
      • 주의해야 할 규칙
        • 부모 메소드의 선언부(리턴 타입, 메소드 이름, 매개변수)와 동일해야 한다.
        • 접근 제한을 더 강하게 오버라이딩할 수 없다(public -> private 변경 불가)
        • 새로운 예외를 throws 할 수 없다.
  • @Override 어노테이션: 컴파일 단계에서 정확히 오버라이딩 되었는지 체크하고, 문제 시 컴파일 에러 출력

final 클래스와 final 메소드

  • final 클래스
    • 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.
  • final 메소드
    • 자식 클래스에서 재정의할 수 없다.

protected 접근 제한자

  • protected는 같은 패키지에서는 default처럼 접근 가능하나, 다른 패키지에서는 자식 클래스만 접근을 허용한다.
  • 필드와 생성자, 메소드 선언에 사용될 수 있다.
// package1
public class A {
    protected String field;        // 필드 선언
    protected A() {}            // 생성자 선언
    protected void method() {}    // 메소드 선언
}

// package2
import ch07.sec06.package1.A;

public class D extends A {
    public D() {
        super();    // A() 생성자 호출
    }

    public void method1() {
        // A 필드값 변경 및 메소드 호출 가능(상속)
        this.field = "value";
        this.method();
    }

    public void method2() {
    // 아래처럼 직접 객체 생성해서 사용하는 것은 불가
        // A a = new A();
        // a.field = "value";
        // a.method;
    }
}

타입 변환

  • 클래스의 타입 변환은 상속 관계에 있는 클래스 사이에서 발생
  • 부모타입 변수 = 자식타입객체;일 때 자동 타입 변환 발생
class Animal {...}
class Cat extends Animal {...}

// Cat 객체를 생성하고 Animal 변수에 대입하면 자동 타입 변환
Cat cat = new Cat();
Animal animal = cat;
// Animal animal = new Cat();도 가능

cat == animal    // true

  • 부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근 가능. 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정된다.
  • 그러나 자식 클래스에서 오버라이딩된 메소드가 있다면 부모 메소드 대신 오버라이딩된 메소드가 호출 - 다형성

강제 타입 변환

  • 부모 타입은 자식 타입으로 자동 변환되지 않고, 캐스팅 연산자로 강제 타입 변환을 할 수 있다.
  • 자식타입 변수 = (자식타입) 부모타입객체;
  • 자식 객체가 부모 타입으로 자동 변환된 후 다시 자식 타입으로 변환할 때 강제 타입 변환 사용 가능
Parent parent = new Child();    // 자동 타입 변환
Child child = (Child) parent;    // 강제 타입 변환
  • 자식 타입에 선언된 필드와 메소드를 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환해야 한다.

다형성

  • 다형성: 사용 방법은 동일하지만 실행 결과가 다양하게 나오는 성질
  • 다형성 구현을 위해서는 자동 타입 변환과 메소드 재정의가 필요

필드 다형성

  • 필드 타입은 동일하지만, 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것
// package1

public class Tire {
    public void roll() {
        System.out.println("회전합니다");
    }
}

public class HankookTire extends Tire {
    @Override
    public void roll() {
        System.out.println("한국 타이어가 회전합니다");
    }
}

public class KumhoTire extends Tire {
    @Override
    public void roll() {
        System.out.println("금호 타이어가 회전합니다");
    }
}

public class Car {
    public Tire tire;
    public void run() {
        // tire 필드에 대입된 객체의 roll() 메소드 호출
        tire.roll();
    }
}

public class CarExample {
    public static void main(String[] args) {
        // Car 객체 생성
        Car myCar = new Car();

        // Tire 객체 장착
        myCar.tire = new Tire();
        myCar.run();                    // 회전합니다

        // HankookTire 객체 장착
        myCar.tire = new HankookTire();    
        myCar.run();                    // 한국 타이어가 회전합니다

        // KumhoTire 객체 장착
        myCar.tire = new KumhoTire();
        myCar.run();                    // 금호 타이어가 회전합니다
    }
}

매개변수 다형성

  • 메소드가 클래스 타입의 매개변수를 가지고 있을 경우, 호출할 때 동일한 타입의 객체를 제공하는 것이 정석이지만 자식 객체를 제공할 수도 있다. 여기서 다형성이 발생
public class Vehicle {
    public void run() {
        System.out.println("차량이 달립니다");
    }
}

public class Bus extends Vehicle {
    @Override
    public void run() {
         System.out.println("버스가 달립니다");
    }
}

public class Taxi extends Vehicle {
    @Override
    public void run() {
         System.out.println("택시가 달립니다");
    }
}

public class Driver {
    public void drive(Vehicle vehicle) {
        vehicle.run();
    }
}

public class DriverExample {
    public static void main(String[] args) {
        // Driver 객체 생성
        Driver driver = new Driver();

        // 매개값으로 Bus 객체를 제공하고 driver() 메소드 호출
        Bus bus = new Bus();
        driver.drive(bus);                    // 버스가 달립니다
        // driver.drive(new Bus());와 동일

        Taxi taxi = new Taxi();
        driver.drive(taxi);                    // 택시가 달립니다
    }
}

객체 타입 확인

  • instanceof 연산자를 사용해 변수가 참조하는 객체의 타입 확인
public void method(Parent parent) {
    // parent 매개변수가 참조하는 객체가 Child인지 조사
    // Child 타입이 아니라면 강제 타입 변환을 할 수 없기 때문
    if(parent instanceof Child) {
        Child child = (Child) parent;
        // child 변수 사용
    }
}

// Java 12부터는 instanceof 연산의 결과가 true일 경우 우측 타입 변수를 사용할 수 있기 때문에 강제 타입 변환 불필요
if(parent instanceof Child child) {
    // child 변수 사용
}

추상 클래스

  • 객체를 생성할 수 있는 클래스를 실체 클래스라고 한다면, 이 클래스들의 공통적인 필드나 메소드를 추출해서 선언한 클래스를 추상 클래스라고 한다.
  • 추상 클래스는 실체 클래스의 부모 역할을 하기 때문에, 실체 클래스는 추상 클래스를 상속해서 공통적인 필드나 메소드를 물려받을 수 있다.

  • 추상 클래스는 new 연산자를 사용해서 객체를 직접 생성할 수 없다.
  • 추상 클래스는 새로운 실체 클래스를 만들기 위한 부모 클래스로만 사용된다. 즉, extends 뒤에만 올 수 있다.

추상 클래스 선언

  • public abstract class 클래스명 {...}
  • 추상 클래스도 필드, 메소드를 선언할 수 있고 생성자가 반드시 있어야 한다.
public abstract class Phone {
    // 필드 선언
    String owner;

    // 생성자 선언
    Phone(String owner) {
        this.owner = owner;
    }

    // 메소드 선언
    void turnOn() {
        System.out.println("폰 전원을 켭니다.");
    }
    void turnOff() {
        System.out.println("폰 전원을 끕니다.");
    }
}

public class SmartPhone extends Phone {
    SmartPhone(String owner) {
        // Phone 생성자 호출
        super(owner);
    }

    void internetSearch() {
        System.out.println("인터넷 검색을 합니다.");
    }
}

public class PhoneExample {
    public static void main(String[] args) {
        // Phone phone = new Phone();    -> 불가
        SmartPhone smartPhone = new SmartPhone("홍길동");

        smartPhone.turnOn();            // 폰 전원을 켭니다.
        smartPhone.internetSearch();    // 인터넷 검색을 합니다.
        smartPhone.turnOff();            // 폰 전원을 끕니다.
    }
}

추상 메소드와 재정의

  • 자식 클래스들이 가지고 있는 공통 메소드를 뽑아내어 추상 클래스로 작성할 때, 메소드 선언부만 동일하고 실행 내용은 자식 클래스마다 달라야 하는 경우가 있다.
  • 이 경우를 위해 추상 클래스는 다음과 같은 추상 메소드를 선언할 수 있다.
    • abstract 리턴타입 메소드명(매개변수, ...);
  • 추상 메소드는 자식 클래스에서 반드시 재정의해서 실행 내용을 채워야 한다.
public abstract class Animal {
    public void breathe() {
        System.out.println("숨을 쉽니다");
       }
    public abstract void sound();
}

public class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}

봉인된 클래스

  • final 클래스를 제외한 모든 클래스는 부모 클래스가 될 수 있지만, Java 15부터는 무분별한 자식 클래스 생성 방지를 위해 봉인된(sealed) 클래스가 도입되었다.
  • public sealed class Person permits Employee, Manager {...}
    • permits 키워드 뒤에 상속 가능한 자식 클래스 지정
  • 봉인된 Person 클래스를 상속하는 Employee와 Manager는 final 또는 non-sealed 키워드로 선언하거나, sealed 키워드를 사용해서 또 다른 봉인 클래스로 선언해야 한다.
    • public final class Employee extends Person {...}
    • public non-sealed class Manager extends Person {...}
      • final은 더 이상 상속 불가, non-sealed는 봉인 해제
      • Employee는 더 이상 자식 클래스를 만들 수 없지만 Manager는 자식 클래스를 만들 수 있다.