7. 상속

7.1 상속 개념

상속은 부모가 자식에게 물려주는 행위를 말한다.
객체 지향프로그램엣도 부모 클래스의 필드와 메소드를 자식클래스에게 물려줄 수 있다.
상속은 이미 잘 개발됨 클래스를 재사용해서 새로운 클래스를 만들기때문에 중복되는 코드를 줄여 개발시간을 단축시킨다.
상속의 또 다른 이점은 크래스의 수정을 최소화할 수 있다.
부모 클래스를 수정하면 자식 클래스에 수정효과를 가져온다.

7.2 클래스 상속

현실에서 상속은 부모가 자식을 선택해서 물려주지만 프로그램에서는 자식이 부모를 선택한다.
자식클래스를 선언할때 어떤 부모로부터 상속받을것인지를 결정하고 부모클래스를 extends뒤에 서술한다.
다중상속을 허용하지 않는다.

public class Phone {
    // 필드선언
    public String model;
    public String color;

    // 메소드 선언
    public void bell() {
        System.out.println("벨이 울립니다.");
    }

    public void sendVoice(String message) {
        System.out.println("자기: " + message);
    }

    public void receiveVoice(String message) {
        System.out.println("상대방: " + message);
    }

    public void hangUp() {
        System.out.println("전화를 끊습니다.");
    }
}

public class SmartPhone extends Phone{
    //필드선언
    public boolean wifi;

    //생성자 선언
    public SmartPhone(String model, String color) {
        this.model = model; //Phone로부터 상속받은필드
        this.color = color; //Phone로부터 상속받은필드
    }

    //메소드 선언
    public void setWifi(boolean wifi) {
        this.wifi = wifi;
        System.out.println("와이파이 상태를 변경했습니다.");
    }

    public void internet() {
        System.out.println("인터넷에 연결합니다.");
    }
}

public class SmartPhoneExample {
    public static void main(String[] args) {
        //smartphone객체 생성
        SmartPhone myPhone = new SmartPhone("갤럭시" , "은색");

        //Phone으로부터 상속받은 필드읽기
        System.out.println("모델: " + myPhone.model);
        System.out.println("색상: " + myPhone.color);

        //SmartPhone의 필드 읽기
        System.out.println("와아피이 상태: " + myPhone.wifi);

        //Phone으로부터 상속받은 메소드 호출
        myPhone.bell();
        myPhone.sendVoice("여보세요");
        myPhone.receiveVoice("안녕하세요 저는 홍길동이에요");
        myPhone.sendVoice("네 반갑습니다.");
        myPhone.hangUp();

        //smartphone의 메소드 호출
        myPhone.setWifi(true);
        myPhone.internet();
    }
}

7.3 부모 생성자 호출

현실에서 부모 없는 자식이 있을 수 없듯이 자바에서도 자식객체를 생성하면 부모 객체가 먼저 생성된다음에 자식객체가 생성된다.
자식클래스 변수 = new 자식클래스();하면 자식객체만 생성되는 것처럼 보이지만 부모가 먼저 생성되고 자식객체가 생성이 된다.
모든 객체는 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아니다.
그렇다면 부모 객체의 생성자는 어디서 호출된 것일까? 부모생성자는 자식생성자의 맨 첫줄에 숨겨져있는 super()에 의해 호출된다.

컴파일과정에서 자동우로 추가 되는데 부모의 기본 생성자를 호출한다. 만약 부모 클래스에 기본 생성자가 없다면 자식생성자 선언에서 컴파일 에러가 발생한다.
부모 클래스에 기본생성자가 없고 매개변수를 갖는 생성자만 잇다면 매개값코드를 직접 넣어야한다.

public class Phone {
    // 필드선언
    public String model;
    public String color;

    //기본생성자 선언
    public Phone() {
        System.out.println("Phone() 생성자 실행");
    }
}

public class SmartPhone extends Phone{
    //필드선언
    public boolean wifi;

    //생성자 선언
    public SmartPhone(String model, String color) {
        super(); //생략가능 컴파일시 자동추가됨
        this.model = model; 
        this.color = color; 
    }
}

public class SmartPhoneExample {
    public static void main(String[] args) {
        //smartphone객체 생성
        SmartPhone myPhone = new SmartPhone("갤럭시" , "은색"); //Phone() 생성자 실행

        //상속받은 필드 읽기
        System.out.println("모델: " + myPhone.model);
        System.out.println("색상: " + myPhone.color);
    }
}

7.4 메소드 재정의

부모클래스의 모든 메소드가 자식 클래스에 맞게 설계되어 잇다면 가장 이상적인 상속이지만
어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수있다.
이러한 메소드는 자식 크래스에서 재정의해서 사용해야한다. 이것을 메소드 오버라이딩이라고 한다.

7.4.1 메소드 오버라이딩

메소드 오버라이딩은 상속된 메소드를 자식 클래스에서 재정의하는 것을 말한다.
메소드가 오버라이딩 되었다면 부모 메소드는 숨겨지고 자식메소드가 우선적으로 사용된다.
메소드 오버라이딩할 때는 규칙을 주의해야한다.
1.부모메소드의 선언부(리턴타이브, 메소드이름, 매개변수)와 동일해야한다.
2.접근제한을 더 강하게 오버라이딩할 수 없다.(public -> private으로 변경불가)
3.새로운 예외를 throws할 수없다.

public class Calculator {
    //메소드 선언
    public double areaCircle(double r) {
        System.out.println("Calculator 객체의 areaCircle() 실행");
        return 3.14159 * r * r;
    }
}

public class Computer extends Calculator {
    //메소드 오버라이딩
    @Override
    public double areaCircle(double r) {
        System.out.println("Computer 객체의 areaCircle() 실행");
        return Math.PI * r * r;
    }
}

public class ComputerExample {
    public static void main(String[] args) {
        int r = 10;

        Calculator calc = new Calculator();
        System.out.println("원 면적: " + calc.areaCircle(r)); //314.159
        System.out.println(); 

        Computer com = new Computer();
        System.out.println("원 면적: " + com.areaCircle(r)); //314.1592653589793
    }
}

7.4.2 부모 메소드 호출

메소드를 재정의하면 부모메소드는 숨겨지고 자식메소드만 사용되기 때문에
비록 부모메소드의 일부만 변경된다하더라도 중복된 내용을 자식메소드도 가지고 있어야한다.
예를들어 부모 메소드가 100줄의 코드를 가지고 있을 경우 자식메소드에서 1줄만 추가하고 싶더라도 100줄의 코드를 다시작성해야한다.
이문제는 super키워드와 도트(.) 연산자를 통해 부모메소드를 호출해서 해결 할 수 있다.

super.method()위치는 작업 처리 전후 어디든지 올 수 있다. 우선 처리가 되어야할 내용을 먼저 작성하면된다.
부모메소드를 재사용함으로써 자식 메소드의 중복작업내용을 없애는 효과를 가져온다.

public class Airplane {
    //메소드 선언
    public void land() {
        System.out.println("착륙합니다.");
    }

    public void fly() {
        System.out.println("일반 비행합니다.");
    }

    public void takeOff() {
        System.out.println("이륙합니다.");
    }
}

public class SupersonicAirplane extends Airplane {
    //상수선언
    public static final int NORMAL = 1;
    public static final int SUPERSONIC = 2;

    //상태 필드 선언
    public int flyMode = NORMAL;

    //메소드 재정의
    @Override
    public void fly() {
        if(flyMode == SUPERSONIC) {
            System.out.println("초음속 비행합니다.");
        } else {
            //Airplane 객체의 fly()메소드 호출
            super.fly();
        }
    }
}

public class AirplaneExample{
    public static void main(String[] args) {
        SupersonicAirplane sa = new SupersonicAirplane();
        sa.takeOff();
        sa.fly(); //일반 비행합니다.
        sa.flyMode = SupersonicAirplane.SUPERSONIC; 
        sa.fly(); //초음속 비행합니다.
        sa.flyMode = SupersonicAirplane.NORMAL; 
        sa.fly(); //일반 비행합니다.
        sa.land();
    }
}

7.5 final클래스와 final메소드

필드선언시에 final을 붙이면 초기값 설정 후 값을 변경할 수없다.
크래스와 메소드에 final을 붙이면 상속과 관련이 있게 된다.

7.5.1 final 클래스

클래스를 선언할때 final키워드를 class아펭 붙이면 최종적인 클래스이므로 더이상 상속할 수없는 클래스가 된다.
즉 final클래스는 부모 클래스가 될 수 없어 자식클래스를 만들 수 없다.
대표적인 예가 String클래스이다.

###7.5.2 final 메소드
메소드를 선언할때 final키워드를 붙이면 이 메소드는 최종 메소드이므로 오버라이딩 할 수 없게 된다.
즉 부모 클래스를 상속해서 자식클래스로 선언할때 부모 클래스의 final메소드는 자식클래스에서 재정의 할수없다.

7.6 protected 접근 제한자

protected는 상속과 관련이 있고 public와 default중간쯤에 해당하는 접근제한을 한다.
protected | 제한대상 필드 생성자 메소드 | 제한범위 같은 패키지거나 자식객체만 사용가능
protected는 같은 패키지에서는 default처럼 접근 가능하나 다른 패키지에서는 자식클래스만 접근을 허용한다.

7.7 타입변환

타입변환이란 다른 타입으로 변환하는 것을 말한다.
기본타입 변환에 대해서 학스한 것과 마찬가지로 클래스도 타입변환이 있다.
클래스의 타입변환는 상속관계에 있는 클래스 사이에서 발생한다.

7.7.1 자동 타입 변환

자동 타입 변환은 의미 그대로 자동적으로 타입 변환이 일어나는 것을 말한다.
부모타입변수 = 자식객체;인경우에 자동타입변환이 일어난다.
자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다.
예를들어 고양이가 동물의 특징과 기능을 상속받앗다면 고양이는 동물이다가 성립된다.
Cat객체를 생성하고 이것을 Animal 변수에 대입하면 자동타입변환이 일어난다.

자식객체를 생성하면 부모객체가 먼저 생성되고 자식객체가 생성되는 것으로 인해 되는 것 같다.

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

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(); 호출불가
    }
}

7.7.2 강제 타입 변환

자식 타입은 부모 타입은 자동변환되지만 반대로 부모 타입은 자식타입으로 자동변환 되지 않는다.
대산 캐스팅 연산자로 강제 타입 변환 할 수 있다.
자식타입 변수 = (자식 타입) 부모타입 객체;
-> 컬렉션 공부할때 int x = (Integer) object객체 했던 것 처럼

무조건 부모 타입 객체를 자식 타입으로 강제 변환할 수 있는 것은 아니다.
자식 객체가 부모 타입으로 자동변환 된 후 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.

자식객체가 부모 타입으로 자동변환하면 부모 타입에 선언된 필드와 메소드만 사용가능하다는 제약사양이 따른다.
만약 자식 타입에 선언된 필드와 메소드를 꼭 사용해야한다면 강제타입변환을 해서 다시 자식 타입으로 변환해야한다.

public class Parent {
    //필드선언
    public String field1;

    //메소드선언
    public void method1() {
        System.out.println("Parent-method1()");
    }

    //메소드선언
    public void method2() {
        System.out.println("Parent-method2()");
    }
}

public class Child extends Parent{
    //필드선언
        public String field2;

    //메소드 선언
    public void method3() {
        System.out.println("Child-method3()");
    }
}

public class ChildExample {
    public static void main(String[] args) {
        //객체 생성 및 자동타입변환
        Parent parent = new Child();

        //Parent 타입으로 필드와 메소드 사용
        parent.field1 = "data1";
        parent.method1();
        parent.method2();

        /*
        parent.field2 = "data2";
        parent.method3(); 불가능
        */

        //강제타입 변환
        Child child = (Child) parent;

        //Child 타입으로 필드와 메소드 사용
        child.field2 = "data2";
        child.method3();
    }
}

7.8 다형성

다형성이란 사용방법은 동일하지만 실행결과가 다양하게 나오는 성질을 말한다.
자동차 부품을 교환하면 성능이 다르게 나오듯이 객체는 부품과 같아서 프로그램을 구성하는 객체를 바꾸면 프로그램의 실행성능이 다르게 나올 수 있다.
객체 사용방법이 동일하다는 것은 동일한 메소드를 가지고 있다는 뜻이다.
만약 오버라이딩 되어 있으면 자식객체의 오버라이딩된 메소드가 호출된다.
두 자식객체가 있다면 각각 오버라이딩된 내용이 달라서 실행결과가 다르게 나온다.
이것이 바로 다형성이다.

다형성을 구현하기 위해서는 자동타입변환과 메소드 재정의가 필요하다.

7.8.1 필드 다형성

필드 다형성은 필드 타입은 동일하지만 대입되는 객체가 달라져서 실행결과가 다양하게 나오는 것이다.

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() {
        //타이어 필드에 대입된 객체의 roll()메소드 호출
        tire.roll();
    }
}

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

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

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

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

myCar.tire = new Tire();이부분
Car객체안에서 보면 Tire tire = new Tire();이 된것임.
run()메소드 안에서는 이 변수 tire를이용해서 Tire객체의 roll메소드를 실행 tire.roll();
자동타입 변환이 되기 때문에 Tire tire = new HankookTire();이 되고 같은 과정을 거치면 오버라이딩 된 메소드가 실행됨

7.8.2 매개변수 다형성

다형성은 필드보다는 메소드를 호출할 때 많이 발생한다. 메소드가 클래스 타입의 매개변수를 가지고 있을 경우
호출할 때 동일한 타입의 객체를 제공하는 것이 정석이지만 자식객체를 제공할 수도 잇다.

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); //버스가 달립니다.

        //매개변수로 Taxi객체를 제공하고 driver 메소드 호출
        Taxi taxi = new Taxi();
        driver.drive(taxi); //택시가 달립니다.
    }
}

driver.drive(매개변수)를 하면
public void drive(Vehicle vehicle) {
vehicle.run();
}
메소드가 호출이된다. 자동타입변환이 되기때문에
Vehicle vehicle = new Bus(); vehicle변수에 bus객체를 넣을 수있다.
public void drive(Bus bus) {
bus.run();
}
으로 사용할 수 있게 되서 bus의 run메소드가 실행이 되는 것이다.

7.9 객체 타입 확인

매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지를 확인하는 방법이 있다.
꼭 매개변수가 아니더라도 변수가 참조하는 객체의 타입을 확인하고 자할때 instanceof 연산자를 사용할 수 있다.
instanceof 연산자의 좌항에는 객체가 오고 우항에는 타입이 오는데 좌항의 객체가 우항의 타입이면 true 그렇지 않으면 false를 산출한다.
자바12부터는 instanceof연산결과가 true일경우 우측 타입 변수를 사용할 수 잇어서 강제타입변환이 필요없다.

public class Person {
    //필드선언
    public String name;

    //생성자선언
    public Person(String name) {
        this.name = name;
    }

    //메소드 선언
    public void walk() {
        System.out.println("걷습니다.");
    }
}

public class Student extends Person{
    //필드선언
    public int studentNo;

    public Student(String name, int studentNo) {
        super(name);
        this.studentNo = studentNo;
    }

    //메소드 선언
    public void study() {
        System.out.println("공부를 합니다.");
    }
}

public class InstanceofExample {
    public static void personInfo(Person person) {
        System.out.println("name: " + person.name);

        /*
        //person이 참조하는 객체가 Student 타입인지 확인
        if (person instanceof Student) {
            Student student = (Student) person;
            //Student객체만 가지고 있는 필드 및 메소드 사용
            student.study();
        }
        */

        // 자바 12부터 가능한 문법
        if (person instanceof Student student) {
            System.out.println("studnetNo: " + student.studentNo);
            student.study();
        }
    }

    public static void main(String[] args) {
        //Person 객체를 매개값으로 제공하고 personInfo()메소드 호출
        Person p1 = new Person("홍길동");
        personInfo(p1); //name: 홍길동

        System.out.println();

        //Student 객체를 매개값으로 제공하고 메소드호출
        Person p2 = new Student("김길동", 10);
        personInfo(p2);
        //name: 김길동 / 공부를 합니다.
    }
}

person이 참조하는 객체가 Student 타입이 맞다면
객체를 Studnet타입으로 강제타입변환하고 study 메소드 사용하는 코드
public static void personInfo(Person person)
Person person = new Person / new Student다가능함 자동타입변환

7.10 추상클래스

사전적으로 추상은 실체간에 공통되는 특성을 추출한 것을 말한다.

7.10.1 추상클래스란?

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

7.10.2 추상클래스 선언

클래스 선언에 abstract 키워드를 붙이면 추상클래스 선언이 된다.
추상클래스는 new 연산자를 이용해서 객체를 직접 만들지 못하고 상속을 통해 자식클래스만 만들 수 있다.

자식객체가 생성될때 super()로 추사을래스의 생성자가 호출되기때문에 생성자도 반드시 있어야한다.

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{
    //생성자 선언
    public SmartPhone(String owner) {
        super(owner); 
    }

    //메소드선언
    void internetSearch() {
        System.out.println("인터넷 검색을 합니다.");
    }
}

public class SmartPhoneExample{
    public static void main(String[] args) {

        //Phone phone = new Phone(); 불가능

        //smartphone객체 생성
        SmartPhone smarthPhone = new SmartPhone("홍길동"); 

        smarthPhone.turnOn();
        smarthPhone.internetSearch();
        smarthPhone.turnOff();
    }
}

7.10.3 추상 메소드와 재정의

자식클래스들이 가지고 있는 공통메소드를 뽑아내어 추상클래스로 작성할 때 메소드 선언부 만 동일하고 실행내용이 자식마다 다른 경우가 잇다.
Animal 추상클래스에서 sound()라는 메소드를 선언할수잇지만 소리는 동물마다 다르니 추상클래스에서 통일하여 작성할 수없다.
이런 경우를 위해 추상메소드를 선언할 수 있다.
abstract키워드가 붙고 실행내용인 {}가 없다.
공통메소드라는 것을 정의할뿐 실행내용을 가지지 않는다.

public abstract class Animal {
    //메소드 선언
    public void breath() {
        System.out.println("숨을 쉽니다.");
    }

    //추상 메소드 선언
    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("야옹");
    }
}

public class AbstractMethodExample {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound(); //멍멍

        Cat cat = new Cat();
        cat.sound(); //야옹

        //매개변수의 다형성
        animalSound(new Dog()); //멍멍
        animalSound(new Cat()); //야옹


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

'기초단계 > JAVA' 카테고리의 다른 글

2023.02.18 Java 복습  (0) 2023.02.19
2023.02.17-2 Java 복습  (0) 2023.02.17
2023.02.16-2 Java 복습  (1) 2023.02.16
2023.02.16-1 Java 복습  (0) 2023.02.16
2023.02.15-2 Java 복습  (0) 2023.02.15

+ Recent posts