기록하자..
상속 | 백기선님 LIVE-STUDY 본문
상속
학습할 것
- 자바 상속의 특징
- super 키워드
- 메소드 오버라이딩
- 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
- 더블 디스패치 (Double Dispatch)
- 추상 클래스
- final 키워드
- Object 클래스
자바 상속의 특징
상속은 OOP의 중요한 특징 중 하나이다. 상속은 기존의 클래스를 재사용해서 새로운 클래스를 작성하는 것을 말해준다. 이미 잘 개발된 클래스를 이용해 새로운 클래스를 만들기 때문에 코드의 중복을 줄여준다.
자바에서 상속을 구현하기 위해서는 extends 키워드를 사용한다.
class Parent {
}
class Child extends Parent {
}
위에서 얘기했듯이 여기서 새로운 클래스는 Chlid이고 기존의 클래스는 Parent이다. 프로그램에서 기존 클래스를 부모 클래스, 상위 클래스라고 하고, 새로운 클래스를 자식 클래스, 하위 클래스 라고 한다.
상속은 부모 클래스의 멤버들을 자식 클래스에게 물려줄 수 있다.
class Parent {
int field1;
void method1() {}
}
class Child extends Parent {
String field2;
void method2() {}
}

Child child = new Child();
child.field1 = 10;
child.method1();
child.field2 = "hello";
child.method2();
자식 클래스가 실제로 Child 객체를 만들어 사용할 때는 마치 Child가 Parent의 field1과 method1()을 가지고 있는 것 처럼 보이게 된다.
주의할 점
상속을 하게 되더라도 주의할 점이 있는데 자식 클래스는 모든 메서드와 필드를 부모 클래스로부터 물려받지 않는다.
- 부모 클래스에서
private키워드를 달고 있는 멤버들은 상속이 불가능하다. - 부모 클래스와 자식 클래스가 다른 패키지에 존재한다면,
default접근 제한자를 갖는 멤버들도 상속이 불가능하다. - 생성자와 초기화 블럭({})은 상속되지 않는다.
부모 생성자 호출
자식 클래스의 객체를 생성하게 되면 부모 객체가 먼저 생성되고, 자식 객체가 생성된다.
자식 클래스의 객체를 생성자에서 생성하게 되면 부모 생성자는 자식 클래스의 맨 첫 줄에서 호출된다.
class Child extends Parent {
public Chlid() {
super();
}
}
자식 클래스의 생성자에서 부모 클래스의 생성자를 명시하지 않았다면 위와 같이 컴파일러가 기본 생성자를 호출하게 된다.
주의할 점은 명시적으로 호출하려면 super()은 반드시 자식 생성자 첫 줄에 위치해야 한다.
여기서 super 키워드는 자식 클래스가 부모 클래스를 참조하기 위한 참조변수이다.
super 키워드
위에서 봤듯이 super는 자식 클래스가 부모 클래스를 참조하기 위한 참조변수이다. 부모와 자식의 멤버를 구분하기 위해서 사용하는데, 이 점을 제외하고는 this와 비슷하다고 생각하면 된다.
그래서 this와 마찬가지로 super역시 부모의 인스턴스를 가리키기 떄문에 static 메서드에서 사용할 수 없고, 인스턴스 메서드에서만 사용할 수 있다.
부모 클래스의 멤버에 접근
public class Parent {
public int field1;
public void method1() {
System.out.println("Called Parent class");
}
}
public class Child extends Parent {
public String field2;
public void method2() {
super.method1();
System.out.println("Called Child class");
}
}
Child child = new Child();
child.method2();
Called Parent class
Called Child class부모 클래스의 생성자 호출
public class Parent {
public int field1;
public Parent() {
System.out.println("Called Parent Constructor");
}
}
public class Child extends Parent {
public int field2;
public Child() {
super();
System.out.println("Called Child Constructor");
}
}
Called Parent Constructor
Called Child Constructor메서드 오버라이딩
자식 클래스에서 부모 클래스의 메서드를 그대로 사용하게 되면 좋겠지만, 어떤 메서드는 자식 클래스에 맞게 변형될 필요가 있다. 자바는 이런 경우를 위해서 메서드 오버라이딩(Overriding) 기능을 제공한다.
상속에서 메서드 오버라이딩은 자식 클래스에서 부모 클래스에 있는 메서드를 재정의하는 것을 말한다. 메서드가 오버라이딩 되었다면 부모 클래스에 있는 메서드는 자식에서 재정의한 메서드에 의해 가려지게 되고 자식 객체에서 메서드를 호출하게 되면 오버라이딩한 메서드가 호출되게 된다.
public class Parent {
public void method1() {}
public void method2() {
System.out.println("Parent method");
}
}
public class Child extends Parent {
@Override
public void method2() {
System.out.println("Child method");
}
public void method3() {}
}
Chlid child = new Child();
child.method2();
Child method다음과 같이 메서드를 오버라이딩 할 때에는 부모의 메서드와 동일한 시그니처(리턴 타입, 메서드 이름, 매개변수 목록)를 가져야 하는 것을 기억하자.
다형성 (Polymorphism)
자바에서 다형성이란 동일한 이름을 가지고 여러 형태의 동작을 취하는 테크닉을 말한다.
method overloading을 통해 컴파일 시간 다형성을 실현할 수 있고, method overriding을 통해 런타임 다형성을 실현할 수 있다.
일반적으로 우리가 말하는 다형성이란 런타임 다형성을 말한다. 런타임에 upcasting된 자식 클래스의 오버라이딩된 메서드를 호출한다.
다이나믹 메서드 디스패치
위의 런타임 다형성의 다른 말은 다이나믹 메서드 디스패치이다.
다이나믹 메서드 디스패치는 오버라이드된 메서드가 런타임시간에 실행되는 것을 말한다.
class Animal {
public void move() {
System.out.println("Animals can move");
}
}
class Dog extends Animal {
public void move() {
System.out.println("Dogs can walk and run");
}
}
public class TestDog {
public static void main(String args[]) {
Animal a = new Animal(); // Animal 참조, Animal 객체
Animal b = new Dog(); // Animal 참조, Dog 객체
a.move(); // Animal 클래스의 메서드 호출
b.move(); // Dog 클래스의 메서드 호출
}
}
Animals can move
Dogs can walk and run
Animal b = new Dog(); 에서 Animal 타입의 객체에 자식 타입인 Dog을 upcasting하고, b는 자식 객체의 주소를 가리키게 된다. 따라서 Dog의 메서드가 호출되는 것이다.
컴파일 타임에는 참조타입만 확인하지만, 런타임에는 JVM이 객체의 타입을 확인하고 그 객체의 메서드를 호출하게 되는 것이다.
더블 디스패치 (Double Dispatch)
더블 디스패치는 말 그대로 디스패치를 두 번 한다는 것이다.
토비님의 더블 디스패치의 예를 보면 텍스트와 사진 두 가지 포스트를 올릴 수 있고, SNS에 모든 포스트를 한꺼번에 올리겠다고 해보자.
public class Dispatch {
interface Post { void postOn(SNS sns); }
static class Text implements Post {
@Override
public void postOn(SNS sns) {
if(sns instanceof Facebook) {
System.out.println("text -> " + sns.getClass().getSimpleName());
}
else if (sns instanceof Twitter) {
System.out.println("text -> " + sns.getClass().getSimpleName());
}
}
}
static class Picture implements Post {
@Override
public void postOn(SNS sns) {
if(sns instanceof Facebook) {
System.out.println("picture -> " + sns.getClass().getSimpleName());
}
else if (sns instanceof Twitter) {
System.out.println("picture -> " + sns.getClass().getSimpleName());
}
}
}
interface SNS { }
static class Facebook implements SNS{ }
static class Twitter implements SNS{ }
}
위 코드는 SNS의 종류가 추가될 때마다 Post의 구현체들에 if절이 추가된다는 문제점이 있다. 또 다른 SNS인 Instagram을 Text와 Picture에 구현해두고 if를 통해 처리하지 않았다면 Instagram 업로드를 처리하지 못한다. 즉, 코드 관리에 비용이 드는 문제가 있다. 이를 double dispatch를 통해서 해결할 수 있다.
먼저 postOn의 비즈니스 로직을 SNS로 옮기고, Post의 구현체들에서 SNS의 post 메서드를 호출하며 자기 자신을 인자로 넘겨준다.
public class Dispatch {
interface Post {
void postOn(SNS sns);
}
static class Text implements Post {
@Override
public void postOn(SNS sns) {
sns.postOn(this);
}
}
static class Picture implements Post {
@Override
public void postOn(SNS sns) {
sns.postOn(this);
}
}
interface SNS {
void postOn(Text text);
void postOn(Picture picture);
}
static class Facebook implements SNS {
@Override
public void postOn(Text post) {
System.out.println("text-facebook");
}
@Override
public void postOn(Picture picture) {
System.out.println("picture-facebook");
}
}
static class Twitter implements SNS {
@Override
public void postOn(Text post) {
System.out.println("text-twitter");
}
@Override
public void postOn(Picture picture) {
System.out.println("picture-twitter");
}
}
}
새로운 SNS를 구현해서 업로드한다고 해도 Post의 코드를 전혀 건드릴 필요가 없어졌다. 단순히 새로운 SNS의 코드만 잘 구현하면 된다.
방문자 패턴
방문자 패턴은 실제 로직을 가지고 있는 객체(Visitor)가 로직을 적용할 객체(Element)를 방문하면서 실행되는 패턴이다.
이런 방문자 패턴은 더블 디스패치를 이용했다고 할 수 있다.
Text와 Picture가 객체(Element)이고, postOn()메서드는 Visitor인 SNS에 의해서 호출된다. 객체(Element)가 Visitor인 SNS에 데이터 처리를 모두 맡기는 것을 방문자 패턴이라고 한다.
추상 클래스
객체를 직접 생성할 수 있는 클래스를 실체 클래스라고 한다면, 이들의 공통적인 특징을 추출해 선언한 클래스를 추상 클래스라고 한다. 추상 클래스와 실체 클래스는 상속의 관계를 가지고 있는데, 추상 클래스가 부모이고 실체 클래스는 자식으로 구현되어 실체 클래스는 추상 클래스의 모든 특성을 물려받고 추가적인 특성을 가질 수 있다.
추상 클래스는 실체 클래스의 공통적인 특성을 추출해 만들었기 때문에 실제 객체를 생성할 수 없다.
추상 클래스 선언
추상 클래스를 선언하기 위해서는 abstract 키워드를 사용한다.
public abstract class Animal {
// field
// constructor
// method
}
추상 클래스도 필드, 생성자, 메서드를 가질 수 있다.
추상 메서드와 오버라이딩
실체 클래스들의 메서드 시그니처만 동일하고 몸체는 각기 다른 경우 추상 클래스에서 통일할 수 없기 때문에, 추상 메서드를 작성할 수 있다.
public abstract void sound();
메서드의 선언부만 존재하고 몸체가 없는 것을 확인할 수 있다. 추상 클래스를 설계할 때 하위 클래스가 동작을 결정하고 싶게 한다면, 추상 메서드를 사용하면 된다. 주의할 점은 자식 클래스는 반드시 추상 메서드를 재정의해야 한다는 것이다. 그렇지 않으면 컴파일 에러가 발생한다.
Animal 추상 클래스를 선언하고 sound() 메서드를 추상 메서드로 선언했다고 하고, 자식 클래스에서 고유한 소리를 내도록 sound() 메서드를 재정의 해야 한다.
public abstract class Animal {
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("야옹");
}
}
final 키워드
자바에서 final 키워드는 변경 불가능하다는 의미를 가진다. final키워드는 클래스, 필드, 메서드 선언시에 사용할 수 있다. 각 사용처마다 해석이 달라지는데, 필드의 경우에는 읽기 전용 상수라고 생각하면 된다.
클래스와 메서드 선언 시에 final은 상속과 관련이 있다.
클래스를 선언할 때 앞에 final키워드를 붙이면 상속 불가능한 클래스가 된다.
public final class Car {}
자바에서 제공하는 String 클래스도 다음과 같이 final 클래스로 선언되어 있다.
public final class String {...}
메서드의 경우 선언할 때 final 키워드를 붙이게 되면 오버라이딩 불가능한 메서드를 의미한다.
Object 클래스
Object 클래스는 자바에서 최상위 부모 클래스를 의미한다. 모든 클래스의 직접, 간접적인 부모 클래스이다.
다른 클래스로부터 상속받지 않는 클래스들은 자동으로 Object 클래스를 상속받게 된다.
public class Something {}
다음과 같은 클래스가 있을 때 컴파일러가 자동으로 extends Object를 붙여주게 된다.
public class Something extends Object {}
우리가 그동안 toString() 같은 메서드를 사용할 수 있었던 이유도 Object클래스에서 정의된 것들이기 떄문이다.Object 클래스에서 상속받는 메서드들은 다음과 같다.
protected Object clone() throws CloneNotSupportedException- 해당 객체의 복사본을 만들어주고 반환하는 메서드이다. 예외가 발생하는 경우는 복사 불가능한 경우이다.
public boolean equals(Object obj)- 다른 객체와 비교해 자신과 동일한지 판단하는 메서드이다. 참고로 오버라이딩 하지 않으면 자동으로 동일성 비교를 해준다. 이 메서드를 오버라이딩 해 동등성 비교의 기준을 마련할 수 있고, hashCode() 메서드 또한 오버라이딩 해야 한다.
public int hashCode()- 객체의 해시코드 값을 반환해준다. 두 객체가 같은 경우 hashCode가 같아야 하기 때문에
equals()메서드도 오버라이딩 해야한다.
- 객체의 해시코드 값을 반환해준다. 두 객체가 같은 경우 hashCode가 같아야 하기 때문에
protected void finalize() throws Throwable- 가비지 컬렉터가 더 이상 해당 객체의 참조를 하지 않는다고 판단할 때 호출하는 메서드이다.
public final Class getClass()- 객체의 런타임 클래스를 호출한다.
public String toString()- 객체의 문자열 형태를 반환한다. 오버라이딩 하지 않으면 의미없는 정보를 보여주기 때문에 오버라이딩 하는 것을 권장하고 있다.
참고자료
'자바' 카테고리의 다른 글
| 인터페이스 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |
|---|---|
| 패키지 | 백기선님 LIVE-STUDY (0) | 2022.05.31 |
| 클래스 | 백기선님 LIVE-STUDY (0) | 2022.05.02 |
| 제어문 | 백기선님 LIVE-STUDY (0) | 2022.05.02 |
| 자바가 제공하는 다양한 연산자 | 백기선님 LIVE-STUDY (0) | 2022.03.24 |