Programming Language/자바(JAVA)

Do it! 자바 프로그래밍 입문 / 8장 상속

토자맨 2023. 1. 3. 03:31

8.1 상속이란?

클래스 B가 클래스 A를 상속 받는 것은 A의 내용들을 B가 상속 받는 다는 것을 의미한다.
아래처럼 클래스를 선언할 때 extends를 이용해서 상속받는다.

class B extends A
{
}

protected 예약어 : 외부에선 접근이 불가능하면서 상속받은 클래스에서는 접근이 가능하게 하는 예약어
|public|외부에서 접근 가능|
|protected|자기 자신, 상속 클래스에서만 접근 가능|
|private|자기 자신만 접근 가능|

8.2 상속에서 클래스 생성과 형 변환

하위 클래스가 생성되는 과정

하위 클래스 생성 : 상위 클래스 디폴트 생성자 호출 -> 하위 클래스 생성자 호출
super() : 상위 클래스에 접근할 때 사용하는 예약어(매개변수가 있는 생성자를 호출할 수도 있음)
super 예약어를 작성하지 않는다면 자동으로 super()이 실행돼서 디폴트 생성자가 호출되는 것

class VIP()
{
    public VIP()
    {
        super(int ID, String Name) // 상위 클래스의 디폴트 생성자가 아닌 매개변수가 있는 생성자를 호출
    }
}

아래처럼 super 예약어를 이용해서 상위 클래스의 멤버에도 접근할 수 있음

class VIP()
{
    public String showVIPInfo()
    {
        return super.showCustomerInfo() // 상위 클래스인 Customer 클래스의 멤버 함수 showCustomerInfo() 함수를 호출
    }
}

상위 클래스로 묵시적 클래스 형 변환

"하위 클래스인 VIP 클래스를 상위 클래스인 Customer 클래스"로 형 변환할 수 있음
형 변환 하게 되면 상위 클래스의 멤버 변수와 메소드만 사용 가능하다.
(힙 메모리에 상위, 하위 클래스의 변수 모두 생성은 되지만 사용은 상위 클래스 멤버 변수만 가능함)
But, 그 반대인 상위 "

클래스 -> 하위 클래스

"는 불가능하다

Customer vc = new VIP() // VIP 클래스를 생성했지만 Customer 클래스로 형변환 했기 때문에 Customer 멤버 변수와 메소드에 접근 가능 

8.3 메서드 오버라이딩

상위 클래스 메소드(함수) 재정의

메소드 오버라이딩 : 상위 클래스에 정의된 메소드를 하위 클래스에 재정의하는 것
하위 클래스에 재정의 할 때 반환형, 메소드 이름, 매개변수 개수, 매개변수 자료형이 상위 클래스의 메소드와 같아야 한다. 만약 넷 중 하나라도 다르면 오버라이딩이 아닌 아예 다른 함수로 본다.

묵시적 클래스 형 변환과 메소드(함수) 재정의(가상 메소드)

만약 형 변환을 한 인스턴스의 함수를 호출할 때 호출하는 함수가 재정의된 함수인 경우 어떤 함수가 호출될까?
-> 재정의한 함수를 호출할 경우 생성된 인스턴스의 메소드를 호출하게 된다.(new 클래스 <- 얘의 메소드 호출한다는 의미)

Customer vc = new VIP("10030", "나몰라", 2000)
vc.calcPrice(10000); // VIP의 calcPrice() 메소드를 호출

형 변환한 객체의 경우
멤버 변수는 힙 메모리에 특정 객체만의 멤버 변수가 생성되지만
멤버 메소드는 특정 객체의 멤버 메소드가 생성되는 것이 아닌 메소드가 호출될 때 메소드 테이블의 메소드 주소를 참조해서 해당 메소드를 호출한다.
즉, 인스턴스를 생성할 때 변수는 특정 인스턴스 전용 변수 생성, 메소드는 따로 생성하지 X

8.4 다형성

다형성 : 가상 메소드를 이용하여 하나의 코드에서 여러 실행 결과나 나오는 것

class Animal{
    public void move()
    {
        System.out.println("동물이 움직입니다.");
    }
}

class Human extends Animal{
    public void move()
    {
        System.out.println("사람이 두 발로 걷습니다. ");
    }

    public void readBook()
    {
        System.out.println("사람이 책을 읽습니다. ");
    }
}

class Tiger extends Animal{
    public void move()
    {
        System.out.println("호랑이가 네 발로 뜁니다. ");
    }

    public void hunting() 
    {
        System.out.println("호랑이가 사냥을 합니다. ");
    }
}

class Eagle extends Animal{
    public void move()
    {
        System.out.println("독수리가 하늘을 납니다 ");
    }

    public void flying() 
    {
        System.out.print("독수리가 날개를 쭉 펴고 멀리 날아갑니다");
    }
}

public class AnimalTest1 {

     public static void main(String[] args) {
          AnimalTest1 aTest = new AnimalTest1();
          aTest.moveAnimal(new Human());
          aTest.moveAnimal(new Tiger());
          aTest.moveAnimal(new Eagle());
     }

     public void moveAnimal(Animal animal) {   // 매개 변수의 자료형이 상위 클래스
          animal.move(); }                     // 재정의 된 메서드 호출
}

위의 animal.move()에서 각 클래스의 재정의된 move 메소드가 호출된다.
결국 여러 클래스 자료형을 사용하지 않고 Animal 자료형 하나로 여러 클래스의 재정의된 메소드를 간편하게 사용할 수 있다는 것이 장점이다.

8.5 다형성 활용하기

배열에 상위, 하위 클래스 인스턴스를 넣어서 관리

상위 클래스 자료형으로 배열을 선언한다.
-> 배열에 인스턴스를 추가하면 자동으로 상위 클래스 자료형으로 묵시적 형 변환이 된다.

public class CustomerTest {

    public static void main(String[] args) {

        // 상위 클래스인 Customer 자료형으로 배열을 선언한다.
        ArrayList<Customer> customerList = new ArrayList<Customer>();

        // 상위, 하위 클래스의 인스턴스를 배열에 추가하면 상위 클래스 자료형으로 형 변환이 된다.
        Customer customerLee = new Customer(10010, "이순신");
        Customer customerShin = new Customer(10020, "신사임당");
        Customer customerHong = new GoldCustomer(10030, "홍길동");
        Customer customerYul = new GoldCustomer(10040, "이율곡");
        Customer customerKim = new VIPCustomer(10050, "김유신", 12345);

        // 여기선 미리 형 변환을 하고 add() 했지만 형 변환을 안하고 하위 클래스 인스턴스를 넣어도 자동으로 묵시적 형 변환이 된다.
        customerList.add(customerLee);
        customerList.add(customerShin);
        customerList.add(customerHong);
        customerList.add(customerYul);
        customerList.add(customerKim);

        System.out.println("====== 고객 정보 출력 =======");

        for( Customer customer : customerList){
            System.out.println(customer.showCustomerInfo());
        }

        System.out.println("====== 할인율과 보너스 포인트 계산 =======");

        int price = 10000;
        for( Customer customer : customerList){
            int cost = customer.calcPrice(price);
            System.out.println(customer.getCustomerName() +" 님이 " +  + cost + "원 지불하셨습니다.");
            System.out.println(customer.getCustomerName() +" 님의 현재 보너스 포인트는 " + customer.bonusPoint + "점입니다.");
        }
    }
}

상속은 왜 사용할까?

상속을 사용하지 않고 상위 클래스(Customer 클래스)에 Gold, VIP 내용을 추가해서 코드를 작성할 수도 있다.

if(customerGrade == "VIP") {}
else if(customerGrade == "GOLD") {}
else if(customerGrade == "SILVER") {}

이런 조건문을 필요한 함수마다 집어 넣으면 가능하다.
하지만 이런식으로 하면 코드가 너무 복잡하고 가독성이 안좋아져서 상속을 이용해 가독성 좋은 코드를 만드는 것이다.

상속은 언제 사용할까?

상속은 'IS-A 관계'일 때 사용한다.
IS-A 관계 : '사람은 포유류다'와 같이 상위 클래스가 하위 클래스를 포괄하는 경우(특정 개념(포유류)을 구체화(사람) 하는 경우)

단순히 코드 재활용을 목적으로 사용하지 않는다.('HAS-A 관계'일 때는 사용하지 않는다)
HAS-A 관계 : '학생이 과목을 듣는다.'와 같이 (특정 클래스(학생)이 다른 클래스(과목)을 소유하고 있는 경우)

8.6 다운 캐스팅과 instanceof

다운 캐스팅(하위 클래스로 형 변환)

다운 캐스팅 : 상위 클래스로 형 변환을 한 인스턴스를 다시 하위 클래스 자료형으로 형 변환하는 경우
(상위 클래스 자료형 -> 하위 클래스 자료형)
.

instanceof

다운 캐스팅을 하기 전에 상위 클래스를 상속받은 여러 하위 클래스 중 어떤 하위 클래스인지 확인하는 예약어

'상위 클래스 -> 하위 클래스' 형 변환은 자료형을 명시적으로 써줘야 형 변환이 된다.

class Shape{
    void draw()
    {
        System.out.println("draw Shape");
    }
}

class Circle extends Shape{
    void draw()
    {
        System.out.println("draw Circle");
    }
    void circle()
    {
        System.out.println("원 입니다.");
    }
}

class Rectangle extends Shape{
    void draw()
    {
        System.out.println("draw Rectangle");
    }
    void rectangle()
    {
        System.out.println("사각형 입니다.");
    }
}

class Triangle extends Shape{
    void draw()
    {
        System.out.println("draw Triangle");
    }
    void triangle()
    {
        System.out.println("삼각형 입니다.");
    }
}


public class ShapeTest {

    ArrayList<Shape> sList = new ArrayList<Shape>();

    public static void main(String[] args) {

        ShapeTest sTest = new ShapeTest();
        sTest.addShape();
        System.out.println("원래 타입으로 다운 캐스팅 ");
        sTest.testCasting();

    }

    public void addShape()
    {
        sList.add(new Circle());    //ArrayList 에 추가되면서 Shape 타입으로 형 변환
        sList.add(new Rectangle());
        sList.add(new Triangle());

        for( Shape s : sList){     // 모두 Shape 타입으로 꺼내서 draw 호출하면
            s.draw();              // 오버라이딩된 함수가 호출 됨
        }
    }

    public void testCasting()
    {
        for(int i=0; i<sList.size(); i++){  //모든 배열 항목들을 하나씩 돌면서

            Shape s = sList.get(i);        // 일단 Shape 타입으로 가져옴
            if(s instanceof Circle){       //Circle 이면
                Circle c = (Circle)s;      //Circle 로 형변환
                c.circle();
            }
            else if(s instanceof Rectangle){  
                Rectangle r = (Rectangle)s;
                r.rectangle();
            }
            else if(s instanceof Triangle){
                Triangle t = (Triangle)s;
                t.triangle();
            }
            else{
                System.out.println("지원되지 않는 타입입니다.");
            }
        }
    }
}

public void testCasting() 메소드처럼 if-else 조건문을 이용하여 명시적으로 형 변환을 하고 하위 클래스에만 있는 메소드를 실행한다.