nathan_H

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

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

결과

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

마무리


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

참고
이것이 자바다

Comments