본문 바로가기

Programming/Java

[기본-6] 상속

목표

자바의 상속에 대해 학습하세요.

학습할 것 (필수)

  • 자바 상속의 특징
  • super 키워드
  • 메소드 오버라이딩
  • 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)
  • 추상 클래스
  • final 키워드
  • Object 클래스

1. 자바 상속의 특징

자바 상속의 특징으로는 C++언어와 비교했을 때 다중 상속을 허용하지 않는다. 즉, 하나의 super 클래스만을 상속 받을 수 있고, 두 개 이상의 클래스를 동시에 상속 받을 수 없다. 대신 인터페이스를 implements 키워드를 이용하여 구현할 수 있는데 인터페이스는 다음에 설명하도록 하겠다. 자바의 상속은 extends 키워드를 통하여 부모 클래스를 지정하면 된다. 

 

문법은 다음과 같다.

접근제어자 class 클래스명 extends 부모클래스명 {
    필드;
    메소드;
}

 

다음은 간단한 클래스 예제이다. 부모 클래스 Bear는 다음과 같다.

public class Bear {

    private int age;
    private String comments;

    public void print() {
        System.out.println("저는 그냥 곰입니다.");
    }

    public int getAge() {
        return age;
    }

    public String getComments() {
        return comments;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setComments(String comments) {
        this.comments = comments;
    }
}

 

Bear 클래스를 상속받는 Grizzly 클래스는 다음과 같다.

public class Grizzly extends Bear {

    // 상속받은 클래스에서 필드와 메소드를 필요에 따라 작성할 수 있다.
    // 이 때 필드의 경우 이름이 동일하면 부모클래스의 변수는 가려지게 된다. 
    // 이경우 super 키워드로 접근해야 하고 만약 부모 변수의 접근지시자가 private이면 
    // getter나 setter 메소드로 접근할 수 있다.
    private int age; 

    // 메소드의 경우는 동일한 이름에 반환타입과 매개변수 타입이 동일하면 오버라이드(재정의)할 수 있다.
    // 오버라이드 유무를 검사하는 어노테이션은 @Override 이다.
    @Override
    public void print() {
        System.out.println("곰 중에 그리즐리");
    }
    
}

 

메인 메소드를 작성한 후 다음과 같이 객체를 생성해보자

Grizzly grizzly = new Grizzly();
// 상속을 받았기 때문에 Bear 클래스에서 정의한 메소드를 사용할 수 있다.
grizzly.setComments("잠이 옵니다.");
grizzly.setAge(32);

2. super 키워드

위에서 설명했듯이 부모클래스의 필드에 접근하거나 메소드를 호출하고자 할 때는 super 키워드를 사용한다. 

이전 포스팅에서 나온 키워드인 this 키워드와 비교하면, this는 현재 클래스에 정의된 필드나 메소드를 참조할 때 사용하고, super는 부모 클래스에 정의된 필드나 메소드를 참조할 때 사용한다. 

 

한가지 참고할 사항은 static 메소드 내에서 this 키워드와 super 키워드를 사용할 수 없다.

this나 super는 생성된 객체에 대해 참조하지만 static 메소드는 객체의 생성 이전에 로딩되어 static 메소드의 시점에서 봤을 때 당연히 생성되지 않은 객체에 대해서 this나 super 를 통해 아직 메모리에 로딩되지 않은 객체에 접근한다는 것 자체가 모순인 것이다.

 

public class Main {
    public static void main(String[] args) {
        Panda panda = new Panda(20); // super를 이해하기 위한 예제이다.
        System.out.println(panda.age = 3); // 3 출력. 부모 Bear의 age는 자식 Panda의 age에 가려짐
        panda.subPanda(); // 3 출력
        panda.superBear(); // 20 출력
    }
}

class Bear {
    public int age;

    public Bear(int age) {
        this.age = age;
    }
}

class Panda extends Bear {

    public int age; // super 키워드를 사용하지 않으면 Bear의 age에 접근불가

    public Panda(int age) {
        //super(); 가 생략되어 있다. super 클래스에서 기본생성자가 없는 경우 sub 클래스에서 super 클래스의 생성자를 호출하게끔 수정해야 한다.
        super(age); // super 클래스의 생성자 호출하여 처리할 수도 있다.
    }

    public void subPanda() {
        System.out.println("나는 서브 판다 나이 : " + age);
    }

    public void superBear() {
        System.out.println("나는 슈퍼 베어. 나이 : " + super.age);
    }

}

 

3. 메소드 오버라이딩

super 클래스에서 정의한 메소드를 재정의 할 수 있다. 방법은 다음과 같다.

public class Grizzly extends Bear {

    // 오버라이드 어노테이션을 통하여 재정의가 가능한지 검사할 수 있다. IDE에서 부모클래스에 없는 메소드이면 오류가 발생한다.
    @Override
    public void print() {
        System.out.println("그리즐리가 재정의 하는 메소드");
    }

}

 

메소드 오버라이딩은 접근지시자 리턴타입 메소드명 파라메터형이 전부 일치해야 한다.

@Override 어노테이션을 통하여 오버라이드가 가능한 지 확인하자

 

4. 다이나믹 메소드 디스패치 (Dynamic Method Dispatch)

메소드 디스패치는 호출할 메소드를 결정 및 실행시키는 과정을 의미한다. 디스패치는 다음과 같이 나뉜다.

  • 스태틱(정적) 메소드 디스패치
  • 다이나믹(동적) 메소드 디스패치

스태틱 메소드 디스패치는 호출할 메소드가 컴파일 시점이 이미 결정되는 것을 뜻한다. 반면, 다이나믹 메소드 디스패치는 인터페이스 또는 추상 클래스의 메소드 처럼 런타임시 호출 시점에 호출할 메소드가 동적으로 결정되는 것을 뜻한다. 컴파일 시점에는 인터페이스나 추상 클래스의 변수에서 메소드를 호출하는 로직을 작성할 수 있으나 실제로는 어떤 타입의 하위 클래스를 전달 받았는지에 따라서 하위 클래스에서 정의된 메소드의 내용이 실행된다.

 

List<String> list = new ArrayList<>();
list.add("HI"); // add 메소드는 인터페이스인 List에서는 추상메소드이며 실제 변수에 매핑된 하위 클래스에서 구현한 메소드가 호출됨

 

5. 추상 클래스

추상 클래스는 객체를 생성할 수 없는 클래스이다. 슈퍼 클래스로 사용하는 용도였는데 하위 클래스에서 반드시 정의할 메소드를 몸체 없이 선언만 하고 상위 클래스에서 하위 클래스에 구현된 클래스도 상속할 수 있게끔 추상 메소드가 아닌 메소드도 작성이 가능한 클래스이다. Java 8부터 인터페이스에 default 메소드가 추가되어 많이 쓰이지는 않는다. 

 

public abstract class Bear {

    public int legs;

    public abstract String myName(); // 베어클래스를 상속 받은 하위 곰 클래스에서 이름을 출력하는 메소드를 구현해야한다.
    
    // 공통적인 메소드를 반환한다.
    public int getLegs() {
        return legs;
    } 
}

6. final 키워드

final 키워드는 클래스, 메소드, 필드 등에 붙일 수 있다.

  • 클래스 : 상속을 허용하지 않는다.
  • 메소드 : 재정의를 허용하지 않는다.
  • 필드 : 지정한 값의 변경을 허용하지 않는다. (객체의 경우는 필드가 참조하고 있는 객체에 대한 참조이며 객체 내부 값은 변경가능) 

 

7. Object 클래스

모든 클래스의 조상은 Object 클래스이다. 클래스에 extends 키워드가 붙어 있지 않으면 extends Object 가 생략되어 있다고 생각하면 된다.

 

다음은 자바에서 기본적으로 제공하는 Object 클래스 이다. (Java 15 기준)

package java.lang;

import jdk.internal.HotSpotIntrinsicCandidate;

public class Object {

    @HotSpotIntrinsicCandidate
    public Object() {}

    @HotSpotIntrinsicCandidate
    public final native Class<?> getClass();

    @HotSpotIntrinsicCandidate
    public native int hashCode();

    public boolean equals(Object obj) {
        return (this == obj);
    }

    @HotSpotIntrinsicCandidate
    protected native Object clone() throws CloneNotSupportedException;

    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }

    @HotSpotIntrinsicCandidate
    public final native void notify();

    @HotSpotIntrinsicCandidate
    public final native void notifyAll();

    public final void wait() throws InterruptedException {
        wait(0L);
    }

    public final native void wait(long timeoutMillis) throws InterruptedException;

    public final void wait(long timeoutMillis, int nanos) throws InterruptedException {
        if (timeoutMillis < 0) {
            throw new IllegalArgumentException("timeoutMillis value is negative");
        }

        if (nanos < 0 || nanos > 999999) {
            throw new IllegalArgumentException(
                                "nanosecond timeout value out of range");
        }

        if (nanos > 0 && timeoutMillis < Long.MAX_VALUE) {
            timeoutMillis++;
        }

        wait(timeoutMillis);
    }

    // 객체의 참조가 해제된 후 GC에 의하여 호출
    @Deprecated(since="9")
    protected void finalize() throws Throwable { }
}

field는 존재하지 않고 메소드만 가지고 있는 것을 알 수 있는데, 우선 눈여겨볼 메소드는 다음과 같다.

clone

  • 객체의 복사를 하기위해 제공되는 메소드
  • 얕은 복사가 일어날 수 있기 때문에 재정의해서 객체의 상태를(필드의 값) 그대로 복사할 수 있게 오버라이드 해야한다.

equals

  • 자바에서 equals 메소드는 객체들의 동등성을 확인할 때 사용하는 메소드이다.
  • 가능하면 IDE에서 지원해주는 기능 등을 이용하여 정확하게 오버라이드를 해야한다.

hashcode

  • 자바에서 hashcode 메소드는 객체의 hashcode 값을 리턴한다.
  • equals 메소드로 동등성이 성립되어 동일한 객체로 판별되는 두 객체는 같은 값의 hashcode int값을 리턴한다.
  • 반면에 동일한 객체로 판별되지 않는 두 객체는 다른 값의 hashcode 혹은 같은 값의 hashcode 를 반환한다.
  • equals 매소드를 오버라이딩 할 때 hashcode 메소드도 같이 오버라이드 한다.(이팩티브 자바 참고)

toString

  • 주로 객체가 가지고 있는 필드를 문자열로 출력하고자 할 때 클래스에서 오버라이드하여 구현한다.

native 키워드 

  • 자바에서 C, C++ 등으로 구현된 소스를 호출할 때 사용하는 키워드이며 JNI를 통하여 사용할 수 있다.

 

 

 

 

 

 

 

'Programming > Java' 카테고리의 다른 글

[기본-8] 인터페이스  (0) 2021.03.15
[기본-5] 클래스  (0) 2021.03.03
[기본-4] 제어문  (0) 2021.03.02
[기본-3]자바 연산자  (0) 2021.03.01
[기본-2]자바 데이터 타입, 변수 그리고 배열  (0) 2021.02.28