개발/Java

Java 상속 (extends)

Debin 2022. 5. 20.
반응형

백기선님이 과거에 진행했던 Java 스터디 6주차 스터디 입니다.

자바 상속의 특징

상속이란 무엇인가?

  • 상속이란, 기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.
  • 기존 클래스를 상속 받아 새로운 클래스를 만들면, 코드를 공통적으로 관리하므로 코드의 추가 및 변경이 매우 용이하다.
  • 결국 코드의 재사용성을 높이고 코드의 중복을 제거해 프로그램의 생산성과 유지보수에 크게 기여한다.
  • 자바 상속은 extends 키워드를 사용해 상속을 진행한다.
  • 상속해주는 클래스를 흔히 '조상 클래스' 상속 받는 클래스를 '자손 클래스'라고 칭한다.
class Child extends Parent {

}

이제 자바 상속의 특징에 대해 간단히 정리해보자.

  • 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
  • 자손 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
  • 즉 자손 클래스의 인스턴스를 생성하면 조상 클래스의 멤버와 자손 클래스의 멤버가 합쳐진 하나의 인스턴스로 생성된다.
  • 자바는 다중상속을 허용하지 않고, 단일 상속만 받을 수 있다.
  • 조상 클래스의 메서드를 오바라이딩(재정의)하여 사용할 수 있다.

super 키워드

super 키워드도 this처럼 2가지가 있다. 바로 super과 super()이다. this와 비슷하게 감이 확 온다.

  • super : 자손 클래스에서 조상 클래스로부터 상속받은 멤버를 참조하는데, 사용되는 참조 변수다.
    멤버 변수와 지역 변수의 이름이 같을 때 this를 붙여서 구별했는데, 상속받은 멤버와 자신의 멤버와 이름이 같을 때는 super을 사용한다.
  • super() : this()와 마찬가지로 생성자다. 차이점은 바로 조상 클래스의 생성자라는 점이다. super()을 통해 상속받은 멤버 변수들을 초기화할 수 있다.

먼저 super을 사용하는 경우를 코드로 확인하자.

class Parent {
    int x = 10;
}

class Child extends {
    int x =20;
    void method(){
        System.out.println("x=" + x);
        System.out.println("this.x=" + this.x);
        System.out.println("super.x=" + super.x);  
    }
}

//Child의 method()를 호출한다면 출력 결과는 아래와 같다.
// x=20
// this.x=20
// super.x=10

super.x와 this.x가 서로 다른 값을 참조하는 것을 확인할 수 있다.

조상 클래스의 메서드를 사용하고 싶다면 똑같이 super.메서드명() 이렇게 사용할 수 있다.

 

이제 super() 예시를 살펴보자. 아래와 같은 코드를 사용하면 조상 클래스의 생성자에 조상 클래스의 멤버변수가 초기화된다.

class Point {
    int x;
    int y;
    Point(int x, int y){
        this.x = x;
        this.y = y;
    }
}

class Point3D extends Point{
    int z;  
    Point3D(int x, int y, int z){
        super(x,y);
    	this.z = z;
    }
}

메소드 오버라이딩

오버라이딩이란 조상 클래스로부터 상속받은 메서드의 내용을 재정의해 사용하는 것이다.

오버라이딩에도 몇 가지 조건이 있다. 아래 조건만 따르면 조상 클래스로부터 상속받은 메서드를 자유롭게 오버라이딩할 수 있다.

  • 자손 클래스에서 오버라이딩하는 메서드는 조상 클래스의 메서드와
    • 이름이 같아야 한다
    • 매개변수가 같아야 한다.
    • 반환 타입이 같아야 한다.

더 자세한 내용은 아래 포스팅에 정리해두었다.

https://devdebin.tistory.com/99?category=1004578 

 

오버로딩과 오버라이딩

오늘은 오버로딩과 오버라이딩에 대해 공부해보겠습니다. 단어가 유사하다 보니 처음에 공부하시는 분들이 많이 헷갈려합니다. 오버로딩 한 클래스 내에 같은 이름의 메서드를 여러 개 정의하

devdebin.tistory.com

다이나믹 메소드 디스패치

다이나믹 메소드 디스패치, 즉 동적 메소드 디스패치란 무엇일까?

동적 메소드 디스패치에 대해 알아보면서 정적 메소드 디스패치에 대해서도 알아보겠다. 

먼저 간단하게 정의를 살펴보자.

  • 동적 메서드 디스패치 : 컴파일 시간이 아닌 런타임에 오버라이딩된 메서드에 대한 호출을 결정하는 방식이다.
  • 정적 메서드 디스패치 : 컴파일 시간에 메서드에 대한 호출을 결정하는 방식이다.

먼저 상대적으로 쉬운 정적 메서드 디스패치에 대해 알아보자.

컴파일 시간에 메서드에 대한 호출을 결정하는 방식이다. 아래와 같은 코드를 확인해보자.

class Parent {
	public void print(){
        System.out.println("Parent print");
    }
}

class FirstChild extends Parent {
	public void print(){
        System.out.println("FirstChild print");
    }
}

class SecondChild extends Parent {
	public void print(){
        System.out.println("SecondChild print");
    }
}


FirstChild fc = new FirstChild();
fc.print();
SecondChild sc = new SecondChild();
sc.print();

 

위와 같은 경우에는 부모의 메소드가 아닌 모두 오버라이딩된 자식 클래스의 메소드가 호출된다.

이런 경우에는 컴파일 시점에 어떤 메소드가 실행될지 알 고 있다. 이것을 정적 메서드 디스패치라고 부른다.

 

동적 메서드 디스패치에 대해 알아보기 전, 동적 메서드 디스패치는 먼저 다형성 개념에 대해 알아야 한다.

다형성이란 쉽게 말해 조상 클래스 타입의 참조 변수로 자손 클래스의 인스턴스를 참조할 수 있다는 것이다.

바로 코드로 확인해보자.

interface Parent {
	public void print();
}

class FirstChild implements Parent {
	public void print(){
        System.out.println("FirstChild print");
    }
}

class SecondChild implements Parent {
	public void print(){
        System.out.println("SecondChild print");
    }
}


Parent t1 = new FirstChild();
t1.print();
Parent t2 = new SecondChild();
t2.print();

위와 같은 코드는 t1.print()와 t2.print() 시점에 어떤 인스턴스가 들어올 지, 어떤 메소드를 호출할 지 모른다.

Parent의 print는 구현부도 없다. 컴파일 시점에서는 뭐가 호출될 지 전혀 모른다.

이러면 런타임 때 어떤 메서드가 호출될 지 결정하는데, 이것을 동적 메서드 디스패치라고 한다.

 

조상 클래스의 메서드를 오버라이드한  메서드가 조상 클래스 참조 변수를 통해 호출될 때,

자바는 호출이 발생할 때 참조되는 객체의 유형에 따라 해당 메서드의 버전(조상 클래스/자식클래스)을 결정한다.

따라서 이 결정은 런타임에 이루어진다.

런타임에서 오버라이딩된 메서드의 버전을 결정하는 것은 참조되는 인스턴스고, 참조 변수 타입에 따라 달라지지 않는다.

 

정리하면 동적 메서드 디스패치는 인터페이스나 추상 클래스를 상속받고 조상 클래스의 추상 메서드를 오버라이딩한 메서드에서만 일어나는 일이고, 컴파일 시점이 아닌 런타임 시점에서 발생한다. 좀만 생각을 해보면 이는 당연하다!!!

추상 클래스

클래스를 설계도라고 설명하면, 추상클래스는 미완성 설계도라고 말할 수 있다.

미완성 설계도라는 뜻은 미완성 메서드(추상 메서드)를 포함한다는 의미다.

추상클래스 자체로는 클래스의 역할을 하지 못한다.

그러나 새로운 클래스를 만들 때 있어서 바탕이 되는 조상클래스로서 중요한 의미를 가진다.

추상 클래스 키워드는 abstract를 붙이기만 하면 된다.

abstract class AbstractObject {
}

이렇게 추상 클래스를 작성하면,
이 클래스를 사용할 때 추상 메서드가 있으니 오버 라이딩을 통해 재정의해 사용하라는 것을 사용자가 알 수 있다.

물론 추상 클래스에도 생성자가 있으며, 멤버변수와 메서드도 가질 수 있다.

이 의미를 해석하면 추상 메서드가 없는 추상 클래스가 존재할 수 있다.

추상 클래스는 추상 메서드를 포함하고 있다는 것을 제외하고는 일반 클래스와 다르지 않다.

그러나 추상 클래스로는 절대 인스턴스를 생성할 수 없다!!!

 

그럼 앞에서 많이 언급한 추상 메서드란 무엇인가??

선언부만 작성하고 구현부는 작성하지 않은 채로 남겨 둔 것이 추상메서드다. 직접 코드로 확인해보자.

abstract void play(String player);

 

추상 메서드는 어떠한 프로그램 로직 없이 absract 키워드와 리턴 값, 메서드 이름, 매개변수만 정의해놓았다.

메서드를 이와 같이 미완성 상태로 남겨 놓은 이유는 메서드의 내용이 상속받는 크래스에 따라 달라질 수 있기 때문에
조상 클래스에서는 선언부만을 작성하고 주석을 덧붙여 어떤 기능을 수행할 목적으로 작성되었는지 알려 주고,

실제 내용은 상속받는 클래스에서 구현하도록 비워 두는 것이다. 즉 설계만 해놓은 것이 추상 메서드다.

이제 예제 코드를 살펴보자.

abstract class Player{
    abstract void playe(int pos);
    abstract void stop();
}

class AudioPlayer extends Player{
    void play(int pos) { //추상 메서드 구현
    	//오디오 프로그램 로직 코드
    }
    void stop(){         //추상 메서드 구현
        //오디오 프로그램 로직 코드
    }
}

class VideoPlayer extends Player{
    void play(int pos) { //추상 메서드 구현
    	//비디오 프로그램 로직 코드
    }
    void stop(){         //추상 메서드 구현
        //비디오 프로그램 로직 코드
    }
}

이런 추상 메서드를 자손 클래스에서 오버라이딩하는 건 정말 강력한 힘을 가지는데,

이는 인터페이스와 다형성에 대해 학습할 때 더 자세히 다루겠다.

final 키워드

final은 '마지막의' 또는 '변경될 수 없는'의 의미를 가지고 있으며 거의 모든 대상에 사용될 수 있다.

변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수 없게 되고,

클래스에 사용되면 자신을 확장하는 자손 클래스를 정의하지 못하게 된다.

즉 상속을 막을 때 자주 사용한다. 대표적인 final class로는 String이 있다.

특히 초기화를 강제하는 아주 좋은 키워드다!

 

final에 대한 자세한 내용은 아래 포스팅에 정리해두었다.

https://devdebin.tistory.com/102?category=1004578 

 

final 제어자

final은 '마지막의' 또는 '변경될 수 없는'의 의미를 가지고 있으며 거의 모든 대상에 사용될 수 있다. 변수에 사용되면 값을 변경할 수 없는 상수가 되며, 메서드에 사용되면 오버라이딩을 할 수

devdebin.tistory.com

Object 클래스

  • Object 클래스는 모든 클래스 상속 계층도의 최상위에 있는 조상 클래스이다.
  • 즉 자바의 모든 클래스는 Object 클래스의 자손 클래스다.
  • extends Object라는 키워드를 붙이지 않아도 컴파일러가 자동적으로 추가한다.
  • Object 클래스에는 모든 인스턴스가 가져야할 기본적인 11개의 메서드가 정의되어 있다.
Object 클래스의 메서드 설명
protected Object clone() 객체 자신의 복사본을 반환한다.
public boolean equals(Object obj) 객체 자신과 객체 obj가 같은 객체인지 알려준다. 
protected void finalize() 객체가 소멸될 때 가비지 컬렉터에 의해 자동으로 호출된다. 이 때 수행되어야하는 코드가 있을 때 오버라이딩한다. (거의 사용하지 않는다.)
public Class getClass() 객체 자신의 클래스 정보를 담고 있는 Class 인스턴스를 반환한다.
public int hashCode() 객체 자신의 해시코드를 반환한다.
public String toString() 객제 자신의 정보를 문자열로 반환한다.
public void notify() 객체 자신을 사용하려고 기다리는 쓰레드를 하나만 깨운다.
public void notifyAll() 객체 자신을 사용하려고 기다리는 모든 쓰레드를 깨운다.
public void wait()
public void wait(long timeout)
public void wait(long timeout, int nanos)
다른 쓰레드가 notify()나 notifyAll()을 호출할 때까지 현재 쓰레드를 무한히 또는 지정된 시간동안 기다리게 한다.

 

이상으로 포스팅을 마칩니다. 감사합니다!!!

 

참고자료 

자바의 정석 (저자 남궁성, 객체지향 프로그래밍 파트 2, 유용한 클래스) 

https://www.geeksforgeeks.org/dynamic-method-dispatch-runtime-polymorphism-java/

반응형

댓글