이것이 자바다 ch07 상속 (7.1~7.11)

dev·2022년 10월 20일
1

이것이 자바다

목록 보기
4/7

7.1 상속 개념

상속(inheritance)은 부모가 자식에게 물려주는 행위
객체지향 프로그램에서는 부모 클래스의 필드/메소드를 자식 클래스에게 물려줄 수 있음

잘 개발된 클래스를 재사용해서 새로운 클래스 만들어서 중복 코드를 줄여줌

상속 장점
: 중복 코드 줄여줌
: 클래스 수정을 최소화(부모 클래스만 수정하면 자식 클래스 모두 수정된다)
: 재사용성, 다형성(객체지향의 꽃)을 지원하기 위해

7.2 클래스 상속

자식 클래스에서 상속받을 부모클래스를 extends 키워드 뒤에 쓰면 상속 받을 수 있음

자바는 다중 상속 불가하기때문에 extends 뒤에 하나의 부모 클래스만 적을 수 있음

package ch07.sec02;

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("전화를 끊습니다.");
    }
}

7.3 부모 생성자 호출

자식 객체를 생성하면 부모 객체가 먼저 생성된 다음 자식 객체가 생성된다.
SmartPhone 객체만 생성되는 것처럼 보이지만, 사실은 부모인 Phone객체가 먼저 생성되고 그다음에 자식인 SmartPhone객체가 생성된것이다.

자식클래스 변수 = new 자식클래스();

스택 영역의 변수에 자식 객체의 주소가 저장되고,
힙 영역에 부모 객체가 생성된 후 자식객체가 상속 받아 생성

부모 객체는 어디서 생성자로 호출된걸까?
사실 자식 생성자의 맨 첫줄에는 super()라는 코드가 숨겨져있다.

public 자식 클래스() {
	super();
    ....
}

super()는 컴파일시 자동추가된다.
이것이 부모의 기본 생성자를 호출한다.
만약 부모의 기본 생성자가 없다면 자식 생성자 선언에서 컴파일 에러가 발생한다.
부모 클래스에 매개변수를 갖는 생성자만 있다면 개발자는 super(매개값, ....) 코드를 직접 넣어야한다.

부모클래스가 기본생성자를 갖고 있는 경우

package ch07.sec03.exam01;

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

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

public class SmartPhone extends Phone {
    //자식 생성자 선언
    public SmartPhone(String model, String color) {
        super();
        this.model = model;
        this.color = color;
        System.out.println("SmartPhone(String model, String color) 생성자 실행됨");
    }
}
package ch07.sec03.exam01;

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);

//        Phone() 기본 생성자 실행
//        SmartPhone(String model, String color) 생성자 실행됨
//        모델: 갤럭시
//        색상: 검정
    }
}

부모 클래스가 매개변수 있는 생성자를 가진경우

package ch07.sec03.exam02;

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

    //매개변수 있는 생성자 선언
    public Phone(String model, String color) {
        this.model = model;
        this.color = color;
        System.out.println("Phone(String model, String color) 생성자 실행");
    }
}
package ch07.sec03.exam02;

public class SmartPhone extends Phone {
    //자식 생성자 선언
    public SmartPhone(String model, String color) {
        super(model, color);
        System.out.println("SmartPhone(String model, String color) 생성자 실행됨");
    }
}
package ch07.sec03.exam02;

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);

//        Phone(String model, String color) 생성자 실행
//        SmartPhone(String model, String color) 생성자 실행됨
//        모델: 아이폰
//        색상: 노랑
    }
}

7.4 메소드 재정의 Overriding

부모 클래스의 모든 메소드가 자식 클래스에게 맞게 설계되지 않았을 수 있다. 그럴때 자식 클래스에서 재정의해서 사용하는 오버라이딩이다.

메소드 오버라이딩

: 상속된 메소드를 자식 클래스에서 재정의하는것
메소드가 오버라이딩 되었다면 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선 사용된다.

메소드 오버라이딩 규칙

  • 부모 메소드의 선언부(리턴타입, 메소드 이름, 매개변수)와 동일해야함
  • 접근 제한을 더 강하게 오버라이딩 할 수 없다(public -> private로 변경 불가)
  • 새로운 예외를 throws 할 수 없다.

다음은 Calculator의 원넓이 구하는 메소드 areaCircle()메소드에서 원주율 파이가 부정확하므로
자식클래스 Computer에서 오버라이딩해서 좀 더 정확한 Math.PI(원주율 파이 상수)를 사용해 원의 넓이를 구하도록 했음

package ch07.sec04.exam01;

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

public class Computer extends Calculator {
    //메소드 오버라이딩

    @Override
    public double areaCircle(double r) {
        System.out.println("Computer 객체의 areaCircle() 실행");
        return Math.PI * r * r;
    }
}
package ch07.sec04.exam01;

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

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

        Computer computer = new Computer();
        System.out.println("원 면적: " + computer.areaCircle(r));

//        Calculator 객체의 areaCircle() 실행
//        원 면적: 314.159
//
//        Computer 객체의 areaCircle() 실행
//        원 면적: 314.1592653589793
    }
}

자바는 오버라이딩이 된 메소드 위에 @Override 어노테이션을 붙여준다. 어노테이션은 컴파일단계에서 정확히 오버라이딩 되었는지 체크하고, 문제가 있으면 컴파일 에러를 출력해준다.

부모 메소드 호출

메소드 재정의시 부모메소드는 숨겨지고, 자식메소드만 사용되므로 일부만 변경해도 중복된 내용을 자식은 갖고있게된다.
부모메소드 100줄이면 자식 메소드가 1줄만 더 추가하고싶어도 100줄을 다시 작성해야함
이럴때 자식메소드와 부모메소드의 공동 작업 처리 기법을 이용하면 된다.

"자식 메소드 내에서 부모 메소드를 호출" 하면 된다.
super.메소드명();
그러면 숨겨진 부모 메소드를 호출 할 수 있어서 부모메소드를 재사용함으로써 중복작업내용을 최소화한다.

다음의 예제에서는 Airplane의 fly()메소드를 자식클래스인 SupersonicAirplane에서 오버라이딩 했다.

일반비행모드일때는 Airplane의 fly()를 사용하고
초음속모드일때는 SupersonicAirplane의 fly()를 사용한다.

package ch07.sec04.exam02;

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

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

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

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();
        }
    }
}
package ch07.sec04.exam02;

public class SupersonicAirplaneExample {
    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을 붙이면 어떻게 되는지 알아보자.
(final 클래스와 final 메소드는 상속과 관련이 있다)

final 클래스

클래스 선언할때 final 키워드를 class앞에 붙이면 최종 클래스되므로 상속 불가 클래스가 된다. 즉, final 클래스는 부모클래스가 될수 없고, 자식클래스를 만들수 없다.

public final class 클래스명 {...}

대표적인 예가 String 클래스이다.
String 클래스는 final로 되어있어서 extends 할수없음

다음 예제는 Member 클래스 선언시 final로 지정함으로써 Member를 상속해 VeryImportantPerson을 선언할수 없음을 보여준다.

package ch07.sec05.exam01;

public class VeryImportantPerson extends Member {
}
package ch07.sec05.exam01;

public class VeryImportantPerson extends Member {
}
//extends Member 때문에 에러남 
//final 클래스라서 상속 불가

final 메소드

메소드 선언시 final 키워드 붙이면 최종 메소드이므로 오버라이딩 불가 메소드가 된다.
즉 부모 클래스상속받아 자식클래스 선언할때 부모클래스에 final로 선언된 메소드는 재정의할수 없다.

public final 리턴타입 메소드(매개변수, ...) {...}

다음 예제는 Car 클래스의 stop() 메소드를 final로 선언했기때문에 자식클래스인 SupportCar에서 stop()을 오버라이딩 할 수 없음을 보여줌

package ch07.sec05.exam02;

public class Car {
    //필드 선언
    public int speed;

    //메소드 선언
    public void speedUp() {
        speed += 1;
    }

    //final 메소드
    public final void stop() {
        System.out.println("차를 멈춤");
        speed = 0;
    }
}
package ch07.sec05.exam02;

public class SportsCar extends Car {
    @Override
    public void speedUp() {
        speed += 10;
    }

//    @Override
//    public void stop() {
//        System.out.println("스포츠카를 멈춤");
//        speed = 0;
//    }

    //stop()을 오버라이딩 할 수 없음
}

7.6 protected 접근 제한자

public, private 접근제한자를 사용해 객체 외부에서 필드, 생성자, 메소드의 접근 여부를 결정했다.
protected 접근제한자는 상속과 관련이 있고, public과 default의 중간쯤에 해당하는 접근제한을 한다.

public > protected > (default) > private

  • protected :
    • 제한대상 : 필드, 생성자, 메소드
    • 제한범위 : 같은 패키지이거나 자식객체만 사용가능

protected는 같은 패키지에서는 접근가능, 다른 패키지에서는 자식 클래스만 접근가능
protected는 필드, 생성자, 메소드 선언에 사용될수있다.

다른 패키지의 자식클래스에서도 new 연산자를 사용해 생성자를 직접 호출할 수는 없고, 자식생성자에서 super() 로 부모생성자를 호출할 수 있다.

package ch07.sec06.package1;

public class A {

    //필드 선언
    protected String field;

    //생성자 선언
    protected A() {}

    //메소드 선언
    protected void method() {}
}
package ch07.sec06.package2;

import ch07.sec06.package1.A;

public class D extends A {

    //생성자 선언
    public D() {
        //A() 생성자 호출
        super();
    }

    //메소드 선언
    public void method1() { //상속을 통해서만 사용 가능
        //A 필드값 변경
        this.field = "value"; //OK
        //A 메소드 호출
        this.method(); //OK
    }

    //메소드 선언
    public void method2() { //직접 객체 생성해서 사용하는것은 불가
//        A a = new A();
//        a.field = "value";
//        a.method();
    }
}

7.7 타입 변환

타입을 다른타입으로 변환하는것
기본타입 변환처럼 클래스도 타입변환이 있다.
클래스의 타입 변환은 상속관계에 있는 클래스 사이에서 발생한다.

자동 타입 변환 promotion

자동적으로 타입변환이 일어나는것
부모타입 변수 = 자식타입객체; // 자식 -> 부모타입 자동타입변환

자식은 부모의 특징과 기능을 상속받으므로 부모와 동일취급 될수있다.

Human 클래스를 Woman 클래스가 상속받았다면 '여성은 인간이다'가 성립한다.

그래서 Woman객체를 생성하고 Human 변수에 대입하면 자동 타입변환이 일어난다.

Woman wm = new Woman();
Human hm = wm;

//Human hm = new Woman(); 도 가능하다. 

wm와 hm 변수 둘다 Woman객체를 참조한다(Woman객체의 주소를 가진다)
Woman객체는 Human객체를 상속받고있다.

hm과 wm 두 참조변수의 ==연산 결과는 true이다.
hm == wm => true

바로 위의 부모가 아니라도 상속계층에서 상위타입이라면 자동타입변환이 일어날수있다.

package ch07.sec07.exam01;

class A {}

class B extends A {}

class C extends A {}

class D extends B {}

class E extends C {}

public class PromotionExample {
    public static void main(String[] args) {
        B b = new B();
        C c = new C();
        D d = new D();
        E e = new E();

        A a1 = b;
        A a2 = c;
        A a3 = d;
        A a4 = e;

        B b1 = d;
        C c1 = e;

        //컴파일 에러(상속관계 아님)
//        B b3= e;
//        C c2 = d;
    }
}

부모타입으로 자동 타입변환 된 이후에는 부모 클래스에 선언된 필드/메소드만 접근 가능
변수는 자식 객체를 참조하지만 변수로 접근가능한 멤버는 부모클래스 멤버로 한정된다.

만약 자식클래스에서 오버라이딩된 메소드가 있으면 부모메소드 대신 오버라이딩된 메소드가 호출된다.

package ch07.sec07.exam02;

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

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

public class Child extends Parent {
    //메소드 오버라이딩
    @Override
    public void method2() {
        System.out.println("Child-method2()");
    }

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

public class ChildExample {
    public static void main(String[] args) {
        //자식 객체 생성
        Child child = new Child();

        //자동 타입 변환
        Parent parent = child;

        //메소드 호출
        parent.method1();// Parent-method1()
        parent.method2();// Child-method2()
//        parent.method3(); //(호출 불가능);
    }
}

강제 타입 변환

자식타입은 부모타입으로 자동형변환 된다.
반대로 부모타입은 자식타입으로 자동형변환 되지 않음.
대신 캐스팅 연산자로 강제타입변환(Casting) 가능

자식타입 변수 = (자식타입) 부모타입객체;

자식 객체가 부모타입으로 자동변환된 후 다시 자식타입으로 변환할때에만 강제타입변환 사용가능

Parent parent = new Child; // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환

자식 객체가 부모타입으로 자동변환하면 부모타입에 선언된 필드/메소드만 사용가능하다는 제약이 생긴다.

만약 자식타입에 선언된 필드/메소드를 꼭 사용해야하면 강제타입변환으로 다시 자식타입으로 변환해야한다.

package ch07.sec07.exam03;

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

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

    //메소드 선언
    public void method2() {
        System.out.println("부모 - method2()");
    }
}
package ch07.sec07.exam03;

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

    //메소드 선언
    public void method3() {
        System.out.println("자식 - method3()");
    }
}
package ch07.sec07.exam03;

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.field1 = "data2"; //가능
        child.method3(); //가능
        
//        부모 - method1()
//        부모 - method2()
//        자식 - method3()
    }
}

7.8 다형성 🥕🥕🥕

사용법은 동일하지만 실행결과가 다양하게 나오는 성질
프로그램을 구성하는 객체를 바꾸면 프로그램 실행 성능이 다르게 나옴

사용방법이 동일하다 == 동일한 메소드를 가지고있다

다형성 구현을 위해 필요한것

  • 자동 타입 변환
  • 메소드 재정의(오버라이딩)

필드 다형성

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

package ch07.sec08.exam01;

public class Tire {
    //메소드 선언
    public void roll() {
        System.out.println("회전합니다.");
    }
}
package ch07.sec08.exam01;

public class HankookTire extends Tire {
    //메소드 재정의(오버라이딩)
    @Override
    public void roll() {
        System.out.println("한국타이어가 회전합니다");
    }
}
package ch07.sec08.exam01;

public class KumhoTire extends Tire {
    //메소드 재정의(오버라이딩)
    @Override
    public void roll() {
        System.out.println("금호 타이어가 회전합니다");
    }
}
package ch07.sec08.exam01;

public class Car {
    //필드 선언
    public Tire tire;

    //메소드 선언
    public void run() {
        //tire 필드에 대입된 객체의 roll() 메소드 호출
        tire.roll();
    }
}
package ch07.sec08.exam01;

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();//금호 타이어가 회전합니다
    }
}

매개변수 다형성

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

어떤 자식객체가 제공되느냐에 따라 메소드 실행결과가 달라진다.

package ch07.sec08.exam02;

public class Vehicle {
    //메소드 선언
    public void run() {
        System.out.println("차량이 달립니다.");
    }
}
package ch07.sec08.exam02;

public class Bus extends Vehicle {
    //메소드 재정의(오버라이딩)
    @Override
    public void run() {
        System.out.println("버스가 달립니다.");
    }
}
package ch07.sec08.exam02;

public class Taxi extends Vehicle {
    //메소드 재정의(오버라이딩)

    @Override
    public void run() {
        System.out.println("택시가 달립니다");
    }
}
package ch07.sec08.exam02;

public class Driver {
    //메소드 선언(클래스 타입의 매개변수를 가지고 있음)
    public void drive(Vehicle vehicle) {
        vehicle.run();
    }
}
package ch07.sec08.exam02;

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 객체를 제공하고 driver() 메소드 호출
        Taxi taxi = new Taxi();
        driver.drive(taxi);//택시가 달립니다
        //한줄로 줄이면 driver.drive(new Taxi());
    }
}
package ch07.sec08.exam02;

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 객체를 제공하고 driver() 메소드 호출
        Taxi taxi = new Taxi();
        driver.drive(taxi);//택시가 달립니다
        //한줄로 줄이면 driver.drive(new Taxi());
    }
}

7.9 객체 타입 확인

매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지 확인하는 방법이 있다.
매개변수가 아니라도 변수가 참조하는 객체의 타입 확인을 하려면
instanceOf 연산자를 사용할수 있다.

instanceOf
instanceOf 연산자의 좌항에는 객체, 우항에는 타입.
좌항의 객체가 우항의 타입이면 true, 아니면 false 산출

boolean result = 객체 instanceOf 타입;

Parent타입, Child타입이 있을때,
instanceOf로 Child타입인지 확인
왜냐면 Child타입이 아니면 강제타입변환을 못하기때문
강제타입변환을 하는 이유는 Child 객체의 모든 멤버(필드/메소드)에 접근하기 위해서

if(parent instanceOf Child child) {
	//child 변수 사용
}

Java 12부터는 instanceOf 연산의 결과가 true일 경우, 우측 타입 변수를 사용할수 있어서 강제타입변환이 필요없음

=> Child타입 맞는데 왜 Child타입으로 형변환 하는건지...?

package ch07.sec09;

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

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

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

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("공부를 합니다");
    }
}
package ch07.sec09;

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

        //person이 참조하는 객체가 Student 타입인지 확인
        if(person instanceof Student) {
            //Student 객체일 경우 강제타입변환
            Student student = (Student) person;
            //Student 객체만 갖고있는 필드/메소드 사용
            System.out.println("studentNo : " + student.studentNo);
            student.study();
        }

        //person이 참조하는 객체가 Student 타입일 경우 student 변수에 대입(타입 변환 발생), java 12부터 사용가능
//        if(person instanceof Student student) {
//            System.out.println("studentNo : " + student.studentNo);
//            student.study();
//        }
    }

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

        System.out.println();

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

7.10 추상 클래스

사전적 의미로 추상(abstract)은 실체 간에 공통되는 특성을 추출한것
(실체) : 새,곤충,물고기
(실체들의 공통점) : '동물' 이라는것
(동물): 실체들의 공통 특성을 가지고있는 추상적인것

추상 클래스란?

실체 클래스 : 객체를 생성할 수 있는 클래스
추상 클래스 : 실체 클래스들의 공통 필드/메소드를 추출해서 선언한 클래스

실체 클래스객체를 생성할 수 있는 클래스
추상 클래스실체 클래스들의 공통 필드/메소드를 추출해서 선언한 클래스

추상 클래스
실체 클래스의 부모역할
실체 클래스는 추상클래스를 상속해서 공통 필드/메소드를 물려받을수 있다.
추상클래스는 실체클래스에서 공통 필드/메소드를 추출해서 만들었으므로 new연산자를 사용해서 객체 생성 불가함
실체클래스를 만들기위한 부모클래스로만 사용된다.
즉, expands 키워드 뒤에만 올수있음

추상 클래스 선언

클래스 선언에 abstract 키워드를 붙이면 추상 클래스 선언이 된다.
추상클래스는 new로 직접 객체 생성 못하고, 상속으로 자식클래스만 만들수 있음

추상클래스도 필드/메소드 선언 가능
자식 객체 생성시 super()로 추상클래스의 생성자가 호출되므로 생성자도 반드시 필요함

package ch07.sec10.exam01;

public abstract class Phone {
    //필드 선언
    String owner;

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

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

    void turnOff() {
        System.out.println("폰 전원을 끕니다.");
    }
}
package ch07.sec10.exam01;

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

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

public class PhoneExample {
    public static void main(String[] args) {
        //Phone phone = new Phone(); //추상클래스는 객체 생성 불가

        SmartPhone smartPhone = new SmartPhone("홍길동");

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

Phone객체는 new연산자로 직접 생성 불가하지만, 자식객체인 SmartPhone은 new연산자로 객체 생성이 가능하고, Phone으로부터 물려받은 turnOn()과 turnOff()메소드 호출이 가능하다.

추상 메소드와 재정의

자식 클래스들이 가진 공통 메소드로 추상클래스 작성시, 메소드 선언부(리턴타입, 메소드명, 매개변수)만 동일하고 실행 내용은 자식클래스마다 달라야하는 경우가 많다.

그런 경우를 위해 추상클래스는 자식 클래스의 공통 메소드라는것만 정의하고, 실행내용을 가지지 않을 수 있다.

abstract 키워드가 붙고, 메소드 실행내용인 {}중괄호가 없다.

abstract 리턴타입 메소드명(매개변수, ...);

public abstract class Animal {
	abstract void sound();
}

추상클래스는 자식 클래스에서 반드시 재정의(오버라이딩)를 해서 실행내용을 채워야한다.

package ch07.sec10.exam02;

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

    //추상 메소드
    public abstract void sound();
}
package ch07.sec10.exam02;

public class Dog extends Animal {
    //추상 메소드 재정의
    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}
package ch07.sec10.exam02;

public class Cat extends Animal {
    //추상 메소드 재정의
    @Override
    public void sound() {
        System.out.println("야옹");
    }
}
package ch07.sec10.exam02;

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로 들어간다.
        animal.sound(); //재정의된 메소드 호출
    }
}

7.11 봉인된 클래스

기본적으로 final클래스를 제외한 모든 클래스는 부모 클래스가 될수 있음

그런데 Java 15부터는 무분별한 자식클래스 생성 방지를 위해 봉인된(sealed) 클래스가 도입되었다.

Person 클래스의 자식은 Employee, Manager 둘만 허용, 그 이외는 자식클래스가 될수 없도록 Person클래스를 봉인된 클래스로 선언
public sealed class Person permits Employee, Manager {....}

봉인된 Person클래스를 상속하는 Employee, Manager는 final 또는 non-sealed 키워드로 선언하거나, sealed 키워드를 사용해서 또다른 봉인 클래스로 선언해야한다.

public sealed class Person permits Employee, Manager {....}

public final class Employee extends Person {...} //자식클래스 못 만듦

public non-sealed class Manager extends Person {...} //자식 클래스 만들수 있음

final은 더이상 상속불가하다는 뜻
non-sealed는 봉인을 해제한다는 뜻

package ch07.sec11;

// Java version 15부터 사용가능한 sealed 클래스
public sealed class Person permits Employee, Manager {
    //필드
    public String name;

    //메소드
    public void work() {
        System.out.println("하는 일이 결정되지 않았습니다.");
    }
}
package ch07.sec11;

public final class Employee extends Person {
    @Override
    public void work() {
        System.out.println("제품을 생산합니다.");
    }
}
package ch07.sec11;

public class Manager extends Person {
    @Override
    public void work() {
        System.out.println("생산 관리를 합니다.");
    }
}
package ch07.sec11;

public class Director extends Manager {
    @Override
    public void work() {
        System.out.println("제품을 기획합니다.");
    }
}
package ch07.sec11;

public class SealedExample {
    public static void main(String[] args) {
        Person p = new Person();
        Employee e = new Employee();
        Manager m = new Manager();
        Director d = new Director();

        p.work(); //하는 일이 결정되지 않았습니다.
        e.work(); //제품을 생산합니다.
        m.work(); //생산 관리를 합니다.
        d.work(); //제품을 기획합니다.
    }
}

출처: 이것이 자바다 (개정판) : JAVA 프로그래밍의 기본서 - 신용권, 임경균 저

profile
hello world!

0개의 댓글