본문 바로가기

개발(합니다)/Java&Spring

[java-기초-07] 상속

반응형

상속이란

  • 현실에서 상속은 부모가 자식에게 물려주는 행위이고 객체 지향에서도 부모 클래스의 멤버를 자식 클래스에게 물려주는 필드와 메소드를 의미한다.
  • 상속을 하면서 중복이 줄어들고 다형성을 가질 수 있다.
  • 부모 클래스에서 private 접근 제한을 갖는 필드와 메서드는 상속 대상에서 제외된다.
  • 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면 default 접근 제한을 갖는 필드와 메서드도 상속 대상에서 제외 된다.
  • 자손의 변경은 조상에 영향을 미치지 않는다.

포함(컴포지션, composite)이란

  • 클래스의 멤버로 참조변수를 선언하는 방식을 포함이라 한다.
  • 상속 대신에 포함을 사용할 수 있는지 확인하자.
  • 작은 단위의 클래스를 만들고, 이 들을 조합하는 클래스
  • 포함과 상속 중 어느것으로 구현해야 할지 모르겠다면 아래를 확인해라.
    • 상속 관계 '~은 ~이다.' (is-a) : 원은 점이다.
    • 포함 관계 '~은 ~을 가지고 있다.' (has-a) : 원은 점을 가지고 있다.
  • 거의 포함관계가 성립된다.
class Circle extends Point { // 상속
    int r;
}

class Circle {
    Point c = new Point(); // 포함
    int r;
}

클래스 상속

  • 현실에서 상속은 부모가 자식을 선택해서 물려주지만, 프로그램에서는 자식이 부모를 선택한다.
  • 클래스는 단일 상속으로 한개의 부모만 가질 수 있다.
    • 부모가 없는 클래스는 Object를 상속 받는다.
    class 자식클래스 extends 부모클래스 {
        // 필드
        // 생성자
        // 메서드
    }

부모 클래스 호출

  • 부모 생성자는 자식 생성자의 맨 첫줄에서 호출 된다.
자식클래스(매개변수, ..) { // 생성자
    super(매개값, ...)
    ...
}
  • 예시
public class People {
    public String name;
    public String ssn;

    public People(String name, String ssn) {
        this.name = name;
        this.ssn = ssn;
    }

    class Student extends People {
        public int studentNo;

        public Student(String name, String ssn, int studentNo) {
            super(name, ssn); // 부모 생성자 호출
            this.studentNo = studentNo;
        }
    }
}
  • 실습

메서드 재정의(@Override)

  • 부모 클래스의 모든 메서드가 자식 클래스에게 맞게 설계되어 있다면 가장 이상적인 상속이지만, 어떤 메서드는 자식 클래스가 사용하기에 적합하지 않을 수도 있기에 상속 된 일부 메서드는 자식 클래스에서 다시 수정해서 사용해야 하며 이를 위해 메서드 오버라이딩 기능을 제공한다.
  • 접근 제한을 더 강하게 오버라이딩할 수 없다는 것은 부모 메서드가 public 접근 제한을 가지고 있을 경우 오버라이딩하는 자식 메서드는 default나 private 젭근 제한으로 수정할 수 없다는 뜻이다.
  • 규칙
    • 부모의 메서드와 동일한 시그너처(리턴 타입, 메서드 이름, 매개 변수 리스트)를 가져야 한다.
    • 접근 제한을 더 강하게 오버라이딩할 수 없다.
    • 새로운 예외(Exception)를 throws할 수 없다.(예외는 10장에서 학습한다).
public class Computer extends Calculator {
    @Override
    double areaCircle(double r) {
        System.out.println("Computer 객체의");
    }

}

메서드 호출 호출(super)

  • 자식 클래스에서 부모 클래스의 메서드를 오버라이딩하게 되면, 부모 클래스의 메서드는 숨겨지고 오버라이딩된 자식 메서드만 사용된다.
  • 자식 클래스 내부에서 부모 클래스의 메서드를 호출해야 하는 상황이 발생하면 super 키워드를 붙여 부모 메서드를 호출할 수 있다.
class Child extends Parent {
    void method2() { }
    void method3() {
        super.method2(); // 부모의 method2() 호출
        }
}

final로 선언 된 클래스와 메서드

  • class의 경우 최종적인 클래스이므로 상속할 수 없는 클래스가 된다.
  • method의 경우 최종적인 메소드이므로 오버라이딩할 수 없는 메서드가 된다.

타입 변환과 다형성

  • 다형성은 같은 타입이지만 실행 결과가 다양한 객체를 이용할 수 있는 성질을 말한다.
  • 부모 타입에 모든 자식 객체가 대입될 수 있다.

자손이 조상을 담을 수 없는 이유 

  • 자손은 조상을 담을 수 없지만 조상은 자손을 담을 수 있고 자손이 재정의한 메서드를 그대로 사용할 수 있다.
Cat cat = new Cat();
Animal animal = cat; // 자동 타입 변환이 된다. new Cat(); 도 가능하다.

필드의 다형성

public class Test {
    class Car {
        // 필드
        Tire frontLeftTire = new Tire();
        Tire frontRightTire = new Tire();
        // 메서드
        void run() {}
    }

    public static void main(String args[]) {
        Car myCar = new Car();
        myCar.fronRightTire = new HankookTire();
        myCar.backLeftTire = new KumhoTire();
     }   
}    

다형성 실습

public class Tire {
    // 필드
    public int maxRotation;
    public int accumulatedRotation;
    public String location;

    // 생성자
    public Tire(String location, int maxRotation) {
        this.location = location;
        this.maxRotation = maxRotation;
    }

    // 메서드
    public boolean roll() {
        ++accumulatedRotation;
        if (accumulatedRotation < maxRotation) {
            System.out.println(location + " Tire 수명 : " + (maxRotation - accumulatedRotation) + "회");
            return true;
        } else {
            System.out.println("*** " + location + "Tire 펑크 ***");
            return false;
        }
    }
}
  • Tire.java
public class Car {
    // Tire frontLeftTire = new Tire("앞왼쪽", 6);
    // Tire frontRightTire = new Tire("앞오른쪽", 2);
    // Tire backLeftTire = new Tire("뒤왼쪽", 3);
    // Tire backRightTire = new Tire("뒤오른쪽", 4);
    Tire[] tires = { 
    new Tire("앞왼쪽", 6), new Tire("앞오른쪽", 2), 
    new Tire("뒤왼쪽", 3), new Tire("뒤오른쪽", 4) };

    // 생성자
    int run() {
        System.out.println("[자동차가 달립니다.]");
        // if (frontLeftTire.roll() == false) {
        // stop();
        // return 1;
        // }
        // if (frontRightTire.roll() == false) {
        // stop();
        // return 2;
        // }
        // if (backLeftTire.roll() == false) {
        // stop();
        // return 3;
        // }
        // if (backRightTire.roll() == false) {
        // stop();
        // return 4;
        // }
        for (int i = 0; i < tires.length; i++) {
            if (tires[i].roll() == false) {
                stop();
                return (i + 1);
            }
        }

        return 0;
    }

    void stop() {
        System.out.println("[자동차가 멈춥니다.]");
    }
}
  • Car.java
public class HankookTire extends Tire {
    // 필드
    // 생성자
    public HankookTire(String location, int maxRotation) {
        super(location, maxRotation);
    }

    @Override
    public boolean roll() {
        ++accumulatedRotation;
        if (accumulatedRotation < maxRotation) {
            System.out.println(location + " Tire 수명 : " + (maxRotation - accumulatedRotation) + "회");
            return true;
        } else {
            System.out.println("*** " + location + "HankookTire 펑크 ***");
            return false;
        }
    }
}
  • HankookTire.java
public class KumhoTire extends Tire {
    // 필드
    // 생성자

    public KumhoTire(String location, int maxRotation) {
        super(location, maxRotation);
    }

    @Override
    public boolean roll() {
        ++accumulatedRotation;
        if (accumulatedRotation < maxRotation) {
            System.out.println(location + " Tire 수명 : " + (maxRotation - accumulatedRotation) + "회");
            return true;
        } else {
            System.out.println("*** " + location + "KumhoTire 펑크 ***");
            return false;
        }
    }
}
  • KumhoTire.java
public class CarExample {
    public static void main(String[] args) {
        Car car = new Car();

        for (int i = 1; i <= 5; i++) {
            int problemLocation = car.run();

            // switch (problemLocation) {
            // case 1:
            // System.out.println("앞왼쪽 HankookTire로 교체");
            // car.frontLeftTire = new HankookTire("앞왼쪽", 15);
            // break;
            // case 2:
            // System.out.println("앞오른쪽 KumhoTire 교체");
            // car.frontRightTire = new KumhoTire("앞오른쪽", 13);
            // break;
            // case 3:
            // System.out.println("뒤왼쪽 HankookTire로 교체");
            // car.backLeftTire = new HankookTire("뒤왼쪽", 14);
            // break;
            // case 4:
            // System.out.println("뒤오른쪽 KumhoTire 교체");
            // car.backRightTire = new KumhoTire("뒤오른쪽", 17);
            // break;
            // }
            if (problemLocation != 0) {
                System.out.println(car.tires[problemLocation - 1].location + 
                " HankookTire로 교체");
                car.tires[problemLocation - 1] = 
                new HankookTire(car.tires[problemLocation - 1].location, 15);
            }

            System.out.println("-----------");
        }
    }
}
  • CarExample.java

매개변수의 다형성

  • 자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메서드를 호출할 때 많이 발생한다.
  • 메서드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 타입 객체를 지정할 수도 있다.
  • 부모가 가진 메서드를 오버라이딩한 자식을 부모 객체가 타입 변환하여 메서드 호출 시 자식이 가진 메서드를 호출한다.
  • 부모가 3개의 메서드를 가지고 자식이 5개의 메서드를 가진다면 부모 객체로 타입 변환 시 부모가 가진 메서드 3개만 사용할 수 있다.
class Driver {
    void drive(Vehicle vecicle) {
        vehicle.run();
    }
}       

// 정석의 경우
Driver driver1 = new Driver();
Vehicle vehicle = new Vehicle();
driver1.drive(vehicle);

// 자식을 사용할 경우
Driver driver2 = new Driver();
Bus bus = new Bus();
driver.drive(bus); // 자동 타입 변환 발생 : Vehicle vehicle = bus;

강제 타입 변환(Casting)

  • 강제 타입 변환은 부모 타입을 자식 타입으로 변환하는 것을 의미하며 자식 타입이 부모 타입으로 자동 변환한 후 다시 자식 타입으로 변환할 때 강제 타입 변환이 가능하다.
자식클래스 변수 = (자식클래스) 부모클래스타입; // 자식타입이 부모 타입으로 변환된 상태

객체 타입 확인(instanceof)

  • 어떤 객체가 어떤 클래스의 인스턴스인지 확인하려면 instanceof 연산자를 사용한다.
// boolean result = 좌항(객체) instanceof 우항(타입)

public void method(Parent parent) {
    if(parent instancof Child) { // 자식 타입이 부모타입으로 변환되었다면 강제 형변환하라.
        Child child = (Child) parent; 
    }
}

추상 클래스란

  • 사전적 의미로 추상은 실체 간에 공통되는 특성을 추출한 것을 말하며 실체가 없는 클래스이다.
  • 추상 클래스는 미완성 설계도이고 클래스는 완성된 설계도이다.
  • 미완성 설계도로 완성할 설계도를 만들기 전에 최소한의 골격을 구성하기 위한 미완성 설계도이다.
  • 미완성 설계도이므로 변경이 용이하다.
  • 각 단계별로 필요한 추상클래스를 만들어 필요에 따라 해ㅅ당 추상클래스를 상속받아 재구현이 용이하다.
  • 용도
    • 실체 클래스들의 공통된 필드와 메서드의 이름을 통일할 목적 : 소유자를 owner라고 user라고 할 수 있는데 하나로 통일
    • 실체 클래스를 작성할 때 시간을 절약 : 공통적인 부분 외에 실체 클래스마다 다른 점만 실체 클래스에 선언
    public abstract class Test {
        // 필드
        // 생성자
        // 메서드
    }

변경이 용이하다.

추상 메서드와 오버라이딩

  • 모든 실체들이 가지고 있는 메서드의 실행 내용이 동일하다면 추상 클래스에 메서드를 작성하는 것이 좋다.
  • 단, 메서드 선언만 통일화하고 실행 내용은 실체 클래스마다 달라야 하는 경우에는 추상 메서드를 선언할 수 있으며 자식 클래스는 반드시 추상 메서드를 오버라이딩 해야한다.
  • [public | protected] abstract 리턴 타입 메서드명(매개변수, ...);
import javax.swing.AbstractCellEditor;

public class Test2 {

    public Dog dog = new Dog();
    public Cat cat = new Cat();

    abstract class Animal {
        public String kind;

        public void breathe() {
            System.out.println("숨을 쉽니다.");
        }

        public abstract void sound();
    }

    class Dog extends Animal {
        public Dog() {
            this.kind = "포유류";
        }

        @Override
        public void sound() {
            System.out.println("wall");
        }
    }

    class Cat extends Animal {
        public Cat() {
            this.kind = "포유류";
        }

        @Override
        public void sound() {
            System.out.println("miya");
        }
    }

    public static void main(String[] args) {
        Test2 t2 = new Test2();
        Dog dog = t2.dog; // 일반적인 호출
        Cat cat = t2.cat; // 일반적인 호출

        dog.sound();
        cat.sound();
        System.out.println("--------------");

        Animal a = null;
        a = t2.dog;
        a.sound(); // 부모 타입 변환
        a = t2.cat; 
        a.sound(); // 부모 타입 변환
        System.out.println("--------------");

        animalSound(t2.dog); // 부모 타입의 매개 변수
        animalSound(t2.cat); // 부모 타입의 매개 변수

    }

    public static void animalSound(Animal animal) {
        animal.sound();
    }
}
반응형