Programming Laguage/Java

[Java] 상속 핵심 개념과 추상 클래스

nathan_H 2020. 5. 1. 17:49

상속


1

https://blog.itthis.me/57

상속이란?

  • 현실 세계 관점
    • 부모가 자식을 선택해서 물려주는 행위
  • 객체 지향 프로그래밍 관점
    • 자식(하위, 파생) 클래스가 부모(상위) 클래스의 멤버를 물려받는 것
    • 자식이 "부모를 선택"해 물려받음
    • 상속 대상 : 부모의 필드와 메소드

활용


  • 상속의 효과
    • 부모 클래스 "재사용"해서 자식 클래스에서 빠르게 개발 가능
    • 중복 코드 줄임
    • 유지 보수 편리성
    • 객체 다형성 구현
  • 상속 대상 제한
    • 부모 클래스의 private 접근 제한을 갖는 필드와 메소드는 제외
    • 부모 클래스가 다른 패키지에 존재하는 경우, default 접근 제한을 갖는 필드와 메소드도 제외

클래스 상속


  • 자식 클래스가 상속할 부모 클래스를 "지정"하는 키워드
    • 자식이 부모를 지정!
  • 자바의 상속은 다중 상속을 허용하지 않음
public class CellPhone {

    String model;
    String color;

    void powerOn() {
        System.out.println("Power On.");
    }

    void powerOff() {
        System.out.println("Power Off");
    }
}

class DmbCellPhone extends CellPhone {
    int channel;

    public DmbCellPhone(String model, String color, int channel) {
        this.model = model;
        this.color = color;
        this.channel = channel;
    }

    void turnOnDmb() {
        System.out.println("채널" + channel + "번 DMB 방송 수신");
    }

    public static void main(String[] args) {
        DmbCellPhone dmbCellPhone = new DmbCellPhone("nathanPhone", "black", 10);

        System.out.println("Mode : " + dmbCellPhone.model);
        System.out.println("Color : " + dmbCellPhone.color);
        System.out.println("Channel : " + dmbCellPhone.channel);

        dmbCellPhone.powerOn();
        dmbCellPhone.turnOnDmb();
        dmbCellPhone.powerOff();
    }
}

출력

Mode : nathanPhone
Color : black
Channel : 10
Power On.
채널10번 DMB 방송 수신
Power Off

부모 생성자 호출 (super())


https://stellan.tistory.com/entry/Java-상속

  • 자식 객체 생성되면 부모 객체도 생성되는가?
    • 부모 없는 자식이 없기에 당연히 부모 객체도 자동으로 생성
    • 부모 생성자 호출 완료 후 자식 생성자 호출 완료
  • 명시적인 부모 생성자 호출
    • 부모 객체 생성할 때, 부모 생성자 선택해 호출
ChildClass(parameters, ..) {
    super(parameter, ..);
    ...
}
  • 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 classNo;

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

메소드 재정의 (Override)


  • 부모 클래스에서 정의된 메소드가 자식 클래스에 적합하지 않은 경우, 자식 클래스에서 상속 메소도를 재정의 하는 것
  • 메소드 재정의 조건
    • 부모 클래스의 메소드와 동일한 시그니처를 가져야 함
      • 시그니처 : 리턴 타입, 메소드 이름, 변수 리스트!
    • 접근 제한을 더 강하게 오버라이딩 불가
      • public → default, private로 불가
      • 반대로 default, private → public은 가능
    • 새로운 예외 불가능
  • 메소드 재정의 효과
    • 부모 메소드 숨김 효과
    • 자식 메소드는 재정의된 메소드로 실행이 됨
  • Override 어노테이션
    • 생략 가능하나 명시적으로 작성하는게 좋음
    • 컴파일러에게 부모 클래스의 메소드 선언부와 동일한지 검사 지시
public class Calculator {

    double areaCircle(double r) {
        System.out.println("install Calculator Object areaCircle()");
        return 3.14 * r * r;
    }
}
// 상속
public class Computer extends Calculator{
    @Override
    double areaCircle(double r) {
        System.out.println("Install Computer areaCircle()");
        return Math.PI * r * r;
    }

    public static void main(String[] args) {
        int r = 10;
        Calculator calculator = new Calculator();
        System.out.println(calculator.areaCircle(r));
        Computer computer = new Computer();
        System.out.println(computer.areaCircle(r));
    }
}

결과

install Calculator Object areaCircle()
314.0
Install Computer areaCircle()
314.1592653589793

부모 메소드 사용(super)

  • 메소드 재정의는 부모 메소드 숨기는 효과가 있음
    • 자식 클래스에서는 재정의된 메소드만 호출!
  • 자식 클래스에서 수정되기 전 부모 메소드 호출 - super 사용

부모

public class AirPlane {

    public void fly() {
        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 {
            super.fly();
        }
    }
    public static void main(String[] args) {
        SupersonicAirPlane sa = new SupersonicAirPlane();

        sa.fly();
        sa.flyMode = SupersonicAirPlane.SUPERSONIC;
        sa.fly();
        sa.flyMode = SupersonicAirPlane.NORMAL;
        sa.fly();
    }
}

결과

일반비행
초음속 비행
일반비행

물려받을 수 없는 클래스와 메소드


final


  • final 키워드의 용도
    1. final 필드 : 수정 불가 필드
    2. final 클래스 : 부모로 사용 불가한 클래스 (상속 불가)
    3. final 메소드 : 자식이 재정의할 수 없는 메소드

상속할 수 없는 final 클래스

public final Java { .. }

public class Python extends Java {...} // 불가능

오버라이딩 불가한 final 메소드

public class Java {
     public final void stream() {
      ...
     }
}

class Python extends Java {
      @Override
            public void stream() {..} // 불가능
}

protected


3

https://worri.tistory.com/98

  • 상속과 관련된 접근 제한자
    • 같은 패키지 : default와 동일 (접근 제한 없음)
    • 다른 패키지 : 자식 클래스만 접근 허용

같은 패키지 A와 B 클래스

package package1;

public class A {
    protected String field;

    protected A() {
    }

    protected void method() {

    }
}
package package1;

public class B{

    public void method() {
        A a = new A();
        a.field = "nathan";
        a.method();
    }
}
  • 같은 패키지에서는 자유롭게 접근 가능!

A와 다른 패키지인 C와 D

package package2;

import package1.A;

public class C {
    public void method() {
        /* 불가능
        A a = new A();
        a.field = "nathan";
        a.method();
         */
    }
}
package package2;

import package1.A;

public class D extends A {
    public D() {
        super();
        this.field = "nathan";
        this.method();
    }
}
  • 다른 패키지의 경우 new 로 부모 클래스를 생성자로 직접 호출이 불가능하고 상속을 통해 접근이 가능함.

다형성


  • 앞서 초반에 상속 효과 중 하나인 객체의 다형성에 대해 이야기 했는데, 그렇다면 객체 다형성이 무엇이고 어떻게 활용을 할 수 있기에 효과적이라고 하는 것 일까?

다형성이란?

4

https://medium.com/@wnsgud167/review-diary-java-2-자바-복습-일기-7a01ea690e21

  • 같은 타입이지만 실행 결과가 다양한 객체 대입 가능한 성질
    • 부모 타입에는 모든 자식 객체가 대입 가능
      • 자식 타입은 부모 타입으로 자동 타입 변환
    • 효과 : 객체의 부품화 가능

타입변환과 다형성


  • 자동 타입 변환
    • 프로그램 실행 도중에 자동 타입 변환이 일어나는 것
    • 바로 위 부모가 아니더라도 "상속 계층의 상위"면 자동 타입 변환 가능
public class A {

}

class B extends A{

}

class C extends A {

}

class D extends B {

}

class E extends C {

}

class Main {
    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 b2 = e;

//        B b3 = e; 변환 불가능, 상속 관계가 아님
//        E e1 = d; 변환 불가능, 상위 관계가 아님  
    }
}

강제 타입 변환

  • 부모 타입을 자식 타입으로 변환하는 것
  • 조건
    • 자식 타입을 부모 타입으로 자동 변환 후, 다시 자식 타입으로 변환할 때
    • 자식 타입이 부모 타입으로 자동 변환하면, 부모 타입에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따름.
  • 강제 타입이 필요한 경우
    • 자식 타입이 부모 타입으로 자동 변환
      • 부모 타입에 선언된 필드와 메소드만 사용 가능
    • 자식 타입에 선언된 필드와 메소드를 다시 사용해야 할 경우
public static void main(String[] args) {
            Parent parent = new Child(); 
            Child child = (Child)parent; 
}

객체 타입 확인

  • 부모 타입이면 모두 자식 타입으로 강제 타입 변환할 수 있는 것은 아님 (자식에서 변환된 (자식의 인스턴스인) 부모 타입이어야 함)
public static void main(String[] args) {
            Parent parent = new Parent(); 
            Child child = (Child) parent; // 불가능
}
  • 위 코드는 ClassCastException 터짐
  • 먼저 자식 타입인지 확인 후 강제 타입 실행해야 함
public class InstanceOfExample {

    public static void method1(Parent parent) {
        if (parent instanceof Child) {
            Child child = (Child) parent;
            System.out.println("method1 - Child로 변환 성공");
        } else {
            System.out.println("method1 - Child로 변환되지 않음");
        }
    }

    public static void method2(Parent parent) {
        Child child = (Child) parent;
        System.out.println("method2 - Child로 변환 성공");
    }

    public static void main(String[] args) {
        Parent parentA = new Child();
        method1(parentA);
        method2(parentA);

        Parent parentB = new Parent();
        method1(parentB);
        method2(parentB); // 예외 발생
    }
}

결과

method1 - Child로 변환 성공
method2 - Child로 변환 성공
method1 - Child로 변환되지 않음
Exception in thread "main" java.lang.ClassCastException: class chapter7.casting.Parent cannot be cast to class

필드의 다형성


  • 다형성을 구현하는 기술적 방법
    • 객체 형태로 필드 생성 (부모 타입)
    • 메소드 재정의 (오버라이딩)
    • 다양하게 생성한 자식 클래스 타입을 부모 타입으로 자동 변환

객체 형태로 필드 생성

public class Tire {

    // Field
    public int maxRotation;

    public int accumulatedRotation;

    public String location;

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

    // 메소드
    public boolean roll() {
        ++accumulatedRotation;

        if (accumulatedRotation < maxRotation) {
            System.out.println(location + "Tire 수명 : " +
                    (maxRotation-accumulatedRotation) + "회");
            return true;
        } else {
            System.out.println("*** " + location + " Tire 펑크 ***");
            return false;
        }
    }
}
public class Car {

    // field
    Tire frontLeftTire = new Tire(6, "앞왼쪽");
    Tire frontRightTire = new Tire(2, "앞오른쪽");
    Tire backLeftTire = new Tire(3, "뒤왼쪽");
    Tire backRightTire = new Tire(4,"뒤오른쪽");

    int run() {
        System.out.println("===부릉!!!=====");

        if (!frontLeftTire.roll()) {
            stop();
            return 1;
        }

        if (!frontRightTire.roll()) {
            stop();
            return 2;
        }

        if (!backLeftTire.roll()) {
            stop();
            return 3;
        }

        if (!backRightTire.roll()) {
            stop();
            return 4;
        }
        return 0;
    }

    void stop() {
        System.out.println("=====Stop======");
    }

}

매소드 재정의

public class HankookTire extends Tire {

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

    @Override
    public boolean roll() {
        ++accumulatedRotation;

        if (accumulatedRotation < maxRotation) {
            System.out.println(location + " HankookTire 수명 : " + (maxRotation - accumulatedRotation)
            + "회");
            return true;
        } else {
            System.out.println("*** " + location + " HankookTire 펑크 ***");
            return false;
        }
    }
}

매개변수의 다형성


  • 매개변수가 클래스 타입일 경우
    • 해당 클래스의 객체 대입이 원칙이나 자식 객체 대입하는 것도 허용
      • 자동 타입 변환
      • 매개변수의 다형성

코드

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 = new Driver();

        Bus bus = new Bus();
        Taxi taxi = new Taxi();

        driver.drive(bus);
        driver.drive(taxi);
    }
}

결과

버스가 달립니다.
택시가 달립니다.

추상 클래스


5

http://blog.daum.net/_blog/BlogTypeView.do?blogid=0Nu8o&articleno=71&categoryId=7&regdt=20091119145902

개념


추상

  • 실체들 간에 공통되는 특성을 추출한 것
    • 아이폰, 맥, 아이패드 → 전자제품(추상)

추상 클래스

  • 실체 클래스들의 공통되는 필드와 메소드를 정의한 클래스
  • 추상 클래스는 실체 클래스의 부모 클래스 역할(단독 객체가 아님)
    • 실체 클래스 : 실제 객체를 만들어 사용할 수 있는 클래스

용도


  • 실체 클래스의 공통된 필드와 메소드의 이름 통일
    • 실체 클래스 설계자가 여러 사람일 경우
    • 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있음
  • 실체 클래스 구현 시간 절약
    • 실체 클래스는 추가적인 필드와 메소드만 선언하면 됨
  • 실체 클래스 설계 규격을 만들고자 할때
    • 실체 클래스가 가져아할 필드와 메소드를 추상 클래스에 미리 정의
    • 실체 클래스는 추상 클래스를 상속 받아 작성

구현


public abstract class Phone {

    public String owner;

    public Phone(String owner) {
        this.owner = owner;
    }

    public void turnOn() {
        System.out.println("폰 전원을 켭니다.");
    }

    public void turnOff() {
        System.out.println("폰 전원을 끕니다.");
    }
}
public class SmartPhone extends Phone {

    public SmartPhone(String owner) {
        super(owner);
    }

    public void internetSearch() {
        System.out.println("인터넷을 검색합니다.");
    }
}
public class PhoneExample {

    public static void main(String[] args) {
//        Phone phone = new Phone(); 추상 클래스는 선언 못함. 실체 객체에서만 활용됨.

        SmartPhone smartPhone = new SmartPhone("홍나단");

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

결과

폰 전원을 켭니다.
폰 전원을 끕니다.
인터넷을 검색합니다.

추상 메소드와 오버라이딩


  • 메소드 이름은 동일, 실행 내용이 실체 클래스마다 다른 메소드
  • 구현 방법
    • 추상 클래스에는 메소드의 선언부만 작성(추상 메소드)
    • 실체 클래스에서 메소드의 실행 내용 작성(오버라이딩)

동물은 모두 숨을 쉬고 소리를 내지만, 소리는 각기 다름!

public abstract class Animal {

    public String kind;

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

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

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

    @Override
    public void sound() {
        System.out.println("야옹");
    }
}
public class Dog extends Animal {
    public Dog() {
        this.kind = "포유류";
    }

    @Override
    public void sound() {
        System.out.println("멍멍");
    }
}
public class AnimalExample {

    public static void main(String[] args) {
        Dog dog = new Dog();
        Cat cat = new Cat();

        dog.sound();
        cat.sound();

        System.out.println("===========");

        Animal animal = null;
        animal = new Dog();
        animal.sound();
        animal = new Cat();
        animal.sound();

        // method polymorphism
        animalSound(new Dog());
        animalSound(new Cat());
    }

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

결과

멍멍
야옹
===========
멍멍
야옹
멍멍
야옹

마무리


  • 자바는 상속을 통해 더욱 효율적으로 클래스를 구현하고 활용할 수 있는 환경을 제공해주고 있다. 상속을 통해 코드의 중복성을 줄이고 다형성을 구현해 실제 세계를 모방 더 나아가 새로운 세계를 창조해 프로그램을 구현할 수 있다.
  • 또한 추상 메소드를 활용해 각 클래스 간의 공통되는 특징을 추출해 추상화시켜 큰 범위 설계 규격을 만들어 미리 필드와 메서드를 재정의함으로써 프로그램의 안정성을 높일 수 있다.
  • 추상 클래스 이외에 자바는 인터페이스라는 것을 제공하는데, 인터페이스는 추상 클래스와 혼동이 되는 경우가 많다. 그래서 다음 포스팅에는 인터페이스의 개념과 어떤 점에서 추상 클래스와 다르고 어떻게 활용되는지 알아볼까 한다.

참고
이것이 자바다