상속

star_pooh·2024년 11월 13일
0

TIL

목록 보기
18/39
post-thumbnail

클래스 간의 관계와 상속

  • 부모 클래스의 필드와 메소드를 자식 클래스에게 물려주는 것
  • 상속을 사용하면 적은 양의 코드로 새로운 클래스를 작성할 수도 있고, 공통적인 코드를 사용하기 때문에 추가 / 변경이 쉬워짐
  • 정리하면, 상속을 사용하면 코드의 중복이 제거되고 재사용성이 크게 증가하여 생산성과 유지 보수에 매우 유리해짐

✨상속✨

  • extends라는 키워드를 사용
public class 자식클래스 extends 부모클래스 {
}
  • 확장의 개념이기 때문에 부모, 자식이라는 용어에 헷갈리지 말 것(부모가 자식보다 더 큰 범위일 것이다 등...)

// 부모 클래스
public class Car {
    String company; // 자동차 회사
    private String model; // 자동차 모델
    private String color; // 자동차 색상
    private double price; // 자동차 가격

    char gear = 'P'; // 기어의 상태, P,R,N,D

    public String getModel() {
        return model;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public char changeGear(char type) {
        gear = type;
        return gear;
    }

    public void horn() {
        System.out.println("빵빵");
    }

}

// 자식 클래스
public class SportsCar extends Car{
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙~\n");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car();
        // 부모 클래스 객체에서 자식 클래스 멤버 사용
        // car.engine = "Orion"; // 에러
        // car.booster(); // 에러

        // 자식 클래스 객체 생성
        SportsCar sportsCar = new SportsCar();
        sportsCar.engine = "Orion";
        sportsCar.booster(); // 엔진 Orion 부앙~

        // 자식 클래스 객체에서 부모 클래스 멤버 사용
        sportsCar.company = "GENESIS";
        sportsCar.setModel("GV80");
        System.out.println("sportsCar.company = " + sportsCar.company); // GENESIS
        System.out.println("sportsCar.getModel() = " + sportsCar.getModel()); // GV80
        
        System.out.println();
		sportsCar.horn(); // 빵빵
        System.out.println(sportsCar.changeGear('D')); // D
    }
}

클래스 간의 관계

  • 상속관계 : is ~ a (~은 ~다)
    • ex) 고래는 포유류다
  • 포함관계 : has ~ a (~은 ~을 가지고 있다)
    • 자동차는 타이어를 가지고 있다
// 타이어 클래스
public class Tire {
    String company; // 타이어 회사
    double price; // 타이어 가격

    public Tire(String company, double price) {
        this.company = company;
        this.price = price;
    }
}
// 자동차 클래스
public class Car {
    static final String company = "GENESIS"; // 자동차 회사
    String model; // 자동차 모델
    String color; // 자동차 색상
    double price; // 자동차 가격

    char gear = 'P'; // 기어의 상태, P,R,N,D
    
    Tire[] tire;

    public Car(String model, String color, double price) {
        this.model = model;
        this.color = color;
        this.price = price;
    }

    public void setTire(Tire ... tire) {
        this.tire = tire;
    }

    public char changeGear(char type) {
        gear = type;
        return gear;
    }

    public void horn() {
        System.out.println("빵빵");
    }
}

public class Main {
    public static void main(String[] args) {
        // 자동차 객체 생성
        Car car = new Car("GV80", "Black", 50000000);

        // 타이어 선언
        Tire[] tires = new Tire[]{
                new Tire("KIA", 150000), new Tire("금호", 150000),
                new Tire("Samsung", 150000), new Tire("LG", 150000)
        };

        // 자동차 객체에 타이어 등록
        car.setTire(tires);

        // 등록된 부품 확인하기
        for (Tire tire : car.tire) {
            System.out.println("tire.company = " + tire.company);
            // KIA 금호 Samsung LG
        }
    }
}

다중 상속

⚠️ Java는 다중 상속을 허용하지 않음

  • 클래스 간의 관계가 복잡해지는 문제 발생
  • 부모 클래스들이 같은 이름의 멤버를 가지고 있다면, 자식 클래스에서 같은 이름의 멤버를 구별할 수 있는 방법이 없음(다이아몬드 문제)

final 클래스와 final 메소드

클래스, 메소드에 final 키워드를 지정하면 더 이상 상속할 수 없는 클래스, 메소드가 됨

public final class Car {}

public class SportsCar extends Car {} // 에러 발생
public class Car {
    public final void horn() {
        System.out.println("빵빵");
    }
}

public class SportsCar extends Car {
    public void horn() { // 에러 발생
        super.horn();
    }
}

Object 클래스

  • Object 클래스는 Java 내 모든 클래스의 최상위 부모 클래스
  • 따라서, 모든 클래스는 Object 클래스의 메소드 사용 가능
  • 부모 클래스가 없는 자식 클래스는 컴파일러에 의해 자동으로 Object 클래스를 상속받게 됨

오버라이딩

오버라이딩

부모 클래스로부터 상속받은 메소드의 내용을 재정의 하는 것

  • 부모 클래스의 메소드를 그대로 사용하는 것도 가능하지만, 자식 클래스의 상황에 맞게 변경이 필요한 경우에 오버라이딩을 사용
  • @Override를 사용해 오버라이딩임을 명시 (@은 어노테이션이라고 읽음)
  • 오버라이딩을 사용하기 위해 필요한 조건
    • 선언부가 부모 클래스의 메소드와 일치해야 함
    • 부모 클래스의 메소드보다 좁은 범위의 접근 제어자로 변경 불가
    • 부모 클래스의 메소드보다 많은 수의 예외 처리 불가
// 부모 클래스
public class Car {
    String company; // 자동차 회사
    private String model; // 자동차 모델
    private String color; // 자동차 색상
    private double price; // 자동차 가격

    double speed;  // 자동차 속도 , km/h
    char gear = 'P'; // 기어의 상태, P,R,N,D

    public double brakePedal() {
        speed = 0;
        return speed;
    }

    public void horn() {
        System.out.println("빵빵");
    }
}

public class SportsCar extends Car{
    String engine;
    public void booster() {
        System.out.println("엔진 " + engine + " 부앙~\n");
    }

    public SportsCar(String engine) {
        this.engine = engine;
    }

    @Override
    public double brakePedal() {
        speed = 100;
        System.out.println("스포츠카에 브레이크란 없다");
        return speed;
    }

    @Override
    public void horn() {
        booster();
    }
}

public class Main {
    public static void main(String[] args) {
        // 부모 클래스 자동차 객체 생성
        Car car = new Car();
        car.horn(); // 빵빵

        // 자식 클래스 스포츠카 객체 생성
        SportsCar sportsCar = new SportsCar("Orion");

        // 오버라이딩한 메소드 호출
        sportsCar.brakePedal(); // 스포츠카에 브레이크란 없다
        sportsCar.horn(); // 엔진 Orion 부앙~

    }
}

super와 super()

  • super는 부모 클래스의 멤버를 참조할 수 있는 키워드
  • 객체 내부 생성자 및 메소드에서 부모 클래스의 멤버에 접근하기 위해 사용
  • 자식 클래스 내부에서 선언한 멤버와 부모 클래스에서 상속받은 멤버와 이름이 같을 경우 이를 구분하기 위해 사용
// 부모 클래스 Car
String model;
String color;
double price;

// 자식 클래스 SportsCar
String model = "Ferrari";
String color = "Red";
double price = 300000000;

// 자식 클래스의 메소드
public void setCarInfo(String model, String color, double price) {
    super.model = model; // model은 부모 필드에 set
    super.color = color; // color는 부모 필드에 set
    this.price = price; // price는 자식 필드에 set
}
  • super()는 부모 클래스의 생성자를 호출할 수 있는 키워드
  • 객체 내부 생성자 및 메소드에서 해당 객체의 부모 클래스의 생성자를 호출하기 위해 사용
  • 자식 클래스의 객체가 생성될 때, 부모 클래스들이 모두 합쳐져서 하나의 인스턴스가 생성
  • 이 때, 부모 클래스의 멤버들의 초기화 작업이 먼저 수행이 되어야 함
    • 따라서 자식 클래스의 생성자에서는 부모 클래스의 생성자가 호출됨
    • ⚠️ 단, 부모 클래스의 생성자는 반드시 첫 줄에서 호출이 되어야 함
  • 오버로딩된 부모 클래스의 생성자가 없다고 하더라도 부모 클래스의 기본 생성자를 호출해야 함
    • 따라서 컴파일러가 super();를 자식 클래스 생성자 첫 줄에 자동으로 추가
// 부모 클래스 Car 생성자
public Car(String model, String color, double price) {
    this.model = model;
    this.color = color;
    this.price = price;
}

// 자식 클래스 SportsCar 생성자
public SportsCar(String model, String color, double price, String engine) {
     // this.engine = engine; // super()가 첫 줄이 아니기 때문에 에러 발생
    super(model, color, price); // 부모 클래스의 멤버들의 초기화 작업
    this.engine = engine;
}

다형성

참조 변수의 타입 변환

  • 자동 타입 변환
    • 부모 타입 변수 = 자식 타입 객체는 자동으로 부모 타입으로 변환
    • 자식 객체는 부모 객체의 멤버를 상속받기 때문에 부모와 동일하게 취급될 수 있음
// 부모 클래스
class Mammal {
    // 포유류는 새끼를 낳고 모유수유를 한다.
    public void feeding() {
        System.out.println("모유수유를 합니다.");
    }
}

// 자식 클래스
class Whale extends Mammal {
    // 고래는 포유류이면서 바다에 살며 수영이 가능하다.
    public void swimming() {
        System.out.println("수영하다.");
    }

    @Override
    public void feeding() {
        System.out.println("고래는 모유수유를 합니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 고래는 포유류이기 때문에 포유류 타입으로 변환가능
        Mammal mammal = new Whale();

        // 모든 포유류가 바다에 살고 수영을 할 수 있는 것은 아니기 때문에 swimming 메소드는 실행 불가
        // 즉, 부모 클래스에 swimming이 선언되어있지 않기 때문에 사용 불가
        // mammal.swimming(); // 에러 발생

        // 모든 포유류가 고래처럼 수영이 가능한 것이 아니기 때문에 타입변환 불가능
        // 즉, 부모타입의 객체는 자식타입의 변수로 변환 불가능
        // Whale whale = new Mammal(); // 에러 발생

        mammal.feeding();
    }
}
  • 강제 타입 변환
    • 자식 타입 변수 = (자식 타입) 부모 타입 객체
    • 부모 타입 객체는 자식 타입 변수로 자동 타입 변환되지 않기 때문에 타입 변환 연산자를 사용하여 강제로 변환
  • ⚠️ 주의할 점
    • 자식 타입 객체가 부모 타입으로 자동 타입 변환된 후, 다시 자식 타입으로 변환될 때만 강제 타입 변환이 가능
    • 부모 타입 변수로는 자식 타입 객체의 고유한 멤버를 사용할 수 없기 때문에 사용이 필요한 경우가 생겼을 때 강제 타입 변환을 사용
// 부모 클래스
class Mammal {
    // 포유류는 새끼를 낳고 모유수유를 한다.
    public void feeding() {
        System.out.println("모유수유를 합니다.");
    }
}

// 자식 클래스
class Whale extends Mammal {
    // 고래는 포유류이면서 바다에 살며 수영이 가능하다.
    public void swimming() {
        System.out.println("수영하다.");
    }

    @Override
    public void feeding() {
        System.out.println("고래는 모유수유를 합니다.");
    }
}

public class Main {
    public static void main(String[] args) {
        // 자동 타입변환된 부모 타입의 변수의 자식 객체
        Mammal mammal = new Whale();
        mammal.feeding();

        // 자식 객체의 swimming 메소드를 사용하고 싶은 경우,
        // 다시 자식 타입으로의 강제 타입 변환 필요
        Whale whale = (Whale) mammal;
        whale.swimming();

        Mammal newMammal = new Mammal();
        // 자동 타입 변환된 부모 타입 변수가 아닌 부모 객체를 
        // 자식 타입의 변수로 강제 타입 변환하려고 하면 ClassCastException 에러 발생
        // Whale newWhale = (Whale) newMammal;
    }
}

✨다형성✨

여러 가지 형태를 가질 수 있는 능력

  • 소프트웨어를 구성하고 있는 객체를 바꿨을 때, 실행 성능 및 결과물이 다르게 나올 수 있음
  • 위에서 설명한 참조 타입 변환을 활용하여 다형성을 구현할 수 있음
Tire tire = new HankookTire("HANKOOK");
Tire tire = new KiaTire("KIA");
  • 부모 타입 변수 = 자식 타입 객체로 자동 타입 변환된 변수를 사용하여 다양한 객체를 생성할 수 있음
// 매개변수의 타입이 부모 타이어
public Car(Tire tire) {
    this.tire = tire;
}

// 매개 값으로 자식 타이어 객체 사용 가능
Car car1 = new Car(new KiaTire("KIA"));
Car car2 = new Car(new HankookTire("HANKOOK"));
  • 매개변수에도 다형성을 적용할 수 있음
  • Car 생성자의 매개변수 타입이 부모 타이어이기 때문에 자식 타이어 객체들을 매개 값으로 전달 가능
// 리턴 타입이 부모 타이어
Tire getHankookTire() {
	// 리턴 값으로 자식 타이어 객체 설정 가능
    return new HankookTire("HANKOOK");
}

Tire getKiaTire() {
    return new KiaTire("KIA");
}

Tire hankookTire = car1.getHankookTire();
// 자동 타입 변환된 리턴 값은 강제 타입 변환도 가능
KiaTire kiaTire = (KiaTire) car2.getKiaTire();
  • 리턴 타입에도 다형성을 적용할 수 있음
  • 반환 타입이 부모 타이어이기 때문에 자식 타이어 객체들을 리턴 값으로 지정할 수 있음
  • 자동 타입 변환이 된 리턴 값인 자식 타이어 객체를 부모 타이어로 강제 타입 변환할 수도 있음
// 부모 클래스
public class Tire {
    String company; // 타이어 회사

    public Tire(String company) {
        this.company = company;
    }

    public void rideComfort() {
        System.out.println(company + " 타이어 승차감은?");
    }
}
// 자식 클래스
public class KiaTire extends Tire{
    public KiaTire(String company) {
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 60);
    }
}
// 자식 클래스
public class HankookTire extends Tire {
    public HankookTire(String company) {
        super(company);
    }

    @Override
    public void rideComfort() {
        System.out.println(super.company + " 타이어 승차감은 " + 100);
    }
}

public class Car {
    Tire tire;
    
	// 매개변수에 다형성 적용
    public Car(Tire tire) {
        this.tire = tire;
    }

	// 리턴 값에 다형성 적용
    Tire getHankookTire() {
        return new HankookTire("HANKOOK");
    }

    Tire getKiaTire() {
        return new KiaTire("KIA");
    }
}

public class Main {
    public static void main(String[] args) {
    	// 자동 타입 변환 (부모 = 자식)
        Car car1 = new Car(new KiaTire("KIA"));
        Car car2 = new Car(new HankookTire("HANKOOK"));

        Tire hankookTire = car1.getHankookTire();
        // 강제 타입 변환 (자식 = (자식) 부모)
        KiaTire kiaTire = (KiaTire) car2.getKiaTire();

        // 오버라이딩된 메서드 호출
        car1.tire.rideComfort(); // KIA 타이어 승차감은 60
        car2.tire.rideComfort(); // HANKOOK 타이어 승차감은 100
    }
}

instance of

다형성으로 인해 해당 클래스 객체의 원래 클래스명을 확인하고 싶을 때 사용할 수 있는 명령어

  • 해당 객체가 내가 의도하는 클래스의 객체인지 확인 가능
  • 리턴 값은 boolean
class Parent { }
class Child extends Parent { }
class Brother extends Parent { }

public class Main {
    public static void main(String[] args) {
    	Parent pc = new Child();  // 다형성 허용 (자식 -> 부모)
		Parent p = new Parent();
		System.out.println(p instanceof Object); // true
        System.out.println(p instanceof Parent); // true
        System.out.println(p instanceof Child);  // false

        Parent c = new Child();
		System.out.println(c instanceof Object); // true
        System.out.println(c instanceof Parent); // true
        System.out.println(c instanceof Child);  // true
    }
}

추상 클래스

✨추상 클래스✨

클래스가 설계도라면 추상 클래스는 미완성된 설계도

  • abstract 키워드를 사용하여 추상 클래스 선언
  • 추상 클래스는 추상 메소드를 포함 할 수 있음
    • 추상 메소드가 없어도 추상 클래스 선언 가능
  • 추상 클래스는 자식 클래스에 상속되어 자식 클래스에 의해서만 완성될 수 있음
  • 추상 클래스는 여러 개의 자식 클래스들에서 공통적인 필드나 메소드를 추출해서 만들 수 있음
public abstract class 추상클래스명 {
	추상메소드(); // 생략 가능
}

추상 메소드

아직 구현되지 않은 미완성된 메소드

  • abstract 키워드를 사용하여 추상 메소드 선언
  • 추상 메소드를 사용할 경우, 클래스도 반드시 추상 클래스여야함
  • 일반적인 메소드와 달리 {}블록이 없음
    • 정의만 할 뿐, 실행 내용은 가지고 있지 않음
public abstract class 추상클래스명 {
	abstract 리턴타입 추상메소드이름(매개변수, ...);
}

추상 클래스 상속 및 사용방법

  • extends 키워드를 사용하여 클래스에서 상속
  • 상속받은 클래스에서 추상 클래스의 추상 메소드는 반드시 오버라이딩 필요
public class 클래스명 extends 추상클래스명 {
	@Override
    public 리턴타입 추상메소드이름(매개변수, ...) {
    	// 실행문
    }
}
// Car 추상 클래스
public abstract class Car {
    double speed;

    public double brakePedal() {
        speed = 0;
        return speed;
    }

    public abstract void horn();
}
// BenzCar 클래스
public class BenzCar extends Car {
    @Override
    public void horn() {
        System.out.println("Benz 빵빵");
    }
}
// AudiCar 클래스
public class AudiCar extends Car {
    @Override
    public void horn() {
        System.out.println("Audi 빵빵");
    }
}

public class Main {
    public static void main(String[] args) {
        Car car1 = new BenzCar();
        car1.horn(); // Benz 빵빵
        System.out.println();

        Car car2 = new AudiCar();
        car2.horn(); // Audi 빵빵
    }
}

0개의 댓글

관련 채용 정보