Programming Laguage/Java
[Java] 상속 핵심 개념과 추상 클래스
nathan_H
2020. 5. 1. 17:49
상속
상속이란?
- 현실 세계 관점
- 부모가 자식을 선택해서 물려주는 행위
- 객체 지향 프로그래밍 관점
- 자식(하위, 파생) 클래스가 부모(상위) 클래스의 멤버를 물려받는 것
- 자식이 "부모를 선택"해 물려받음
- 상속 대상 : 부모의 필드와 메소드
활용
- 상속의 효과
- 부모 클래스 "재사용"해서 자식 클래스에서 빠르게 개발 가능
- 중복 코드 줄임
- 유지 보수 편리성
- 객체 다형성 구현
- 상속 대상 제한
- 부모 클래스의 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 키워드의 용도
- final 필드 : 수정 불가 필드
- final 클래스 : 부모로 사용 불가한 클래스 (상속 불가)
- 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
- 상속과 관련된 접근 제한자
- 같은 패키지 : 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 로 부모 클래스를 생성자로 직접 호출이 불가능하고 상속을 통해 접근이 가능함.
다형성
- 앞서 초반에 상속 효과 중 하나인 객체의 다형성에 대해 이야기 했는데, 그렇다면 객체 다형성이 무엇이고 어떻게 활용을 할 수 있기에 효과적이라고 하는 것 일까?
다형성이란?
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);
}
}
결과
버스가 달립니다.
택시가 달립니다.
추상 클래스
개념
추상
- 실체들 간에 공통되는 특성을 추출한 것
- 아이폰, 맥, 아이패드 → 전자제품(추상)
추상 클래스
- 실체 클래스들의 공통되는 필드와 메소드를 정의한 클래스
- 추상 클래스는 실체 클래스의 부모 클래스 역할(단독 객체가 아님)
- 실체 클래스 : 실제 객체를 만들어 사용할 수 있는 클래스
용도
- 실체 클래스의 공통된 필드와 메소드의 이름 통일
- 실체 클래스 설계자가 여러 사람일 경우
- 실체 클래스마다 필드와 메소드가 제각기 다른 이름을 가질 수 있음
- 실체 클래스 구현 시간 절약
- 실체 클래스는 추가적인 필드와 메소드만 선언하면 됨
- 실체 클래스 설계 규격을 만들고자 할때
- 실체 클래스가 가져아할 필드와 메소드를 추상 클래스에 미리 정의
- 실체 클래스는 추상 클래스를 상속 받아 작성
구현
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();
}
}
결과
멍멍
야옹
===========
멍멍
야옹
멍멍
야옹
마무리
- 자바는 상속을 통해 더욱 효율적으로 클래스를 구현하고 활용할 수 있는 환경을 제공해주고 있다. 상속을 통해 코드의 중복성을 줄이고 다형성을 구현해 실제 세계를 모방 더 나아가 새로운 세계를 창조해 프로그램을 구현할 수 있다.
- 또한 추상 메소드를 활용해 각 클래스 간의 공통되는 특징을 추출해 추상화시켜 큰 범위 설계 규격을 만들어 미리 필드와 메서드를 재정의함으로써 프로그램의 안정성을 높일 수 있다.
- 추상 클래스 이외에 자바는 인터페이스라는 것을 제공하는데, 인터페이스는 추상 클래스와 혼동이 되는 경우가 많다. 그래서 다음 포스팅에는 인터페이스의 개념과 어떤 점에서 추상 클래스와 다르고 어떻게 활용되는지 알아볼까 한다.
참고
이것이 자바다