본문 바로가기
JAVA

상속

by mjjang 2020. 12. 25.

1. 자바 상속의 특징

  1. 단일 상속만 가능합니다.
  • 여러 조상들 중에서 이름이 같은 함수가 있을 경우, 문제가 될 수 있습니다.
  • 그러나 다중 상속의 장점도 많기 때문에, 자바는 interface 다중 구현을 제공합니다.
  1. 생성자는 상속되지 않습니다.

2. super 키워드

상위 객체(super)의 reference를 가지고 있습니다. 여기서 상위 객체란 부모 객체를 가리키고, 자식 객체에서 super키워드를 통해 부모 객체에 접근을 할 수 있다는 뜻입니다.

public class Parent {
    int age;
    String name;

    Parent(){}

    Parent(int age, String name) {
        this.age = age;
        this.name = name;
    }

    protected String getName() {
        return this.name;
    }
}

public class Child extends Parent{

    int age;
    String name;

    Child(){}

    Child(int age, String name) {
        super(100, "부모"); // 1. 부모 클래스 생성자 호출
        this.age = age;
        this.name = name;
    };

    public void printParentInfo() {
        System.out.println("부모의 나이는 ? " +  super.age);// 멤버 변수접근
        System.out.println("부모의 이름은 ? " +  super.getName()); //메소드 접근
    }

}

Child클래스에서 super키워드를 이용해 Parent 클래스의 멤버 변수와 메소드에 접근할 수 있고, super()를 이용해 부모 클래스의 생성자를 호출할 수 있습니다.

당연한 얘기지만, 부모 클래스에서 private으로 지정된 변수는 상속은 받지만 자식 객체에서 바로 접근할 수 없고 private 메소드는 상속을 받지 않으므로 호출할 수 없습니다.

또한 자식 클래스에서 super() 생성자는 항상 첫 번째 구문에 써야 합니다.

3. 메소드 오버라이딩

오버라이딩은 부모 클래스로부터 상속받은 메소드를 자식 클래스에서 재정의하여 사용하는 것입니다.

오버라이딩의 조건 및 방법

class Parent {
    public void introduce() {
        System.out.println("부모 클래스입니다.");
    }
}

class Child extends Parent{
    public void introduce() {
        System.out.println("자식 클래스입니다.");
    }
}

이런 식으로 부모 클래스의 메소드를 재정의하면 자식 객체에서 introduce()를 호출했을 때 "자식 클래스입니다."가 출력됩니다. 만약, 재정의 하지 않고 introduce()를 호출한다면 부모 클래스 메서드의 정의된 것처럼 "부모 클래스입니다."가 출력됩니다.

오버 라이딩을 할 때는 자식 클래스에서 부모 메소드의 이름, 리턴 타입, 매개변수의 개수, 자료형의 순서를 동일하게 하여 메소드를 작성해야 합니다.

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

method dispatch에는 static method dispatch와 dynamic method dispatch가 있는데 static은 컴파일 타임에 어떤 메서드가 호출될지 정해져 있고 dynamic은 런타임에 호출되는 메소드가 동적으로 정해지는 걸 말합니다.

static dispatch

public class Dispatch {
    public static void main(String[] args) {
        Dispatch dispatch = new Dispatch();
        dispatch.run();
    }

    public void run() {
        System.out.println("run");
    }
}

static dispatch는 컴파일 시점에 호출될 메소드를 결정하는 방식입니다. 자바에서 객체 생성은 런타임 시에 호출됩니다. 즉, new Dispatch()는 런타임시에 호출되고 컴파일 시점에서는 Dispatch dispatch라는 참조 변수만 생성됩니다. Dispatch는 구현 클래스이기 때문에 구현 클래스의 메소드를 호출하는 것을 컴파일 시점에 알 수 있게 됩니다.

Dynamic dispatch

    static interface Super { void run(); }

    static class Sub1 implements Super {
        public void run() {
            System.out.println("Sub1");
        }
    }

    static class Sub2 implements Super {
        public void run() {
            System.out.println("Sub2");
        }
    }

    public static void main(String[] args) {
        Super s = new Sub1();
        s.run(); // Sub1
    }

Super라는 인터페이스와 그 인터페이스를 구현한 Sub1, Sub2 클래스가 있습니다. main메소드에는 Super타입의 s라는 참조 변수가 있습니다. 컴파일 시점에는 s가 Super타입인 사실만 알기 때문에 s.run()에서 Sub1과 Sub2중에서 어떤 구현체의 메소드인지 알지 못합니다. 런타임에 new Sub1()이라는 객체가 생성이 되고 나서 run()메소드가 호출될 때 묵시적으로 receiver parameter로 this가 전달될 때 Sub1의 메소드인 것을 알게 됩니다.

Method Signature, Method Type

Method Signature

Method Signature는 name, parameter Types이다.
Method Signature만으로 메서드를 구분 지을 수 있어야 하므로 return type은 포함되지 않습니다.

    public int sign(String name) {
        return 0;
    }

    public String sign(String name) {
        return "";
    }

return type이 Method Signature에 포함된다면 위에 코드는 에러가 나지 않아야 하는데 "sign(String) is already defined"이라는 컴파일 에러가 발생합니다. 그래서 Method는 name과 parameter types으로 구분됩니다.

Method Type

Java8에 추가된 Method Reference로 인해 등장했다. Method Reference는 람다식을 간결하게 표현해주는 방식이다. Method Type에는 return type, parameter type, parameter argument, exception이 있다.

public static void main(String[] args) {
        List<Super> list = Arrays.asList(new Sub1(), new Sub2());

        for (Super s : list) {
            s.run();
        }

        list.forEach(s -> s.run()); // 람다식
        list.forEach(Super::run); // method reference

    }

위에 3개의 코드는 같은 의미입니다. List의 parameter type이 Super이기 때문에 람다식으로 Super를 s로 쓸 수 있고 s.run()은 Super.run()과 같다. 람다식을 method reference로 Super::run으로 간단하게 표현할 수 있습니다. method reference는 Method Type이 같은 경우에만 사용할 수 있습니다.

5. 추상 클래스

추상의 뜻은 "여러 사물 또는 개념 따위의 개별자들로부터 공통점을 파악하고 추려내는 것"입니다.
즉, 추상 클래스란 여러 클래스의 공통점을 추출하여 만든 클래스이고 자바에서는 abstract라는 예약어를 클래스 앞에 붙입니다.

예를 들어, 소방차라는 클래스가 이미 있고 경찰차와 구급차 클래스를 추가하려 합니다. 그럴 때 개별적으로 경찰차 따로 구급차 따로 구현할 수 있지만 그러면 구현하는 시간도 오래 걸리고 중복되는 코드가 발생할 수 있습니다. 자동차는 바퀴가 4개이고 달릴 수 있는 특성이 있습니다. 이 공통점을 추출하여 자동차라는 추상 클래스를 만든다면 다른 유형의 자동차를 구현할 때 빠르고 편리하게 만들 수 있습니다.

abstract class Car {
    int wheel = 4;

    abstract void run();
}

public class FireTruck extends Car {
    @Override
    void run() {
        System.out.println("소방차가 달립니다.");
    }
}

public class PoliceCar extends Car {
    @Override
    void run() {
        System.out.println("경찰차가 달립니다.");
    }
}

이런 식으로 추상 클래스를 상속받는 소방차와 경찰차 클래스를 만들 수 있습니다. 추상 클래스는 여러 클래스들의 공통점을 추출하여 만들었기 때문에 공통된 메소드가 있을 것입니다. Car클래스에서는 run을 공통 메소드로 정의하고 있고 abstract라는 예약어를 사용하고 있습니다. abstract메소드는 상속받은 클래스에서 반드시 오버 라이딩해야 하는 메소드입니다.

6. final 키워드

final 키워드는 확장성을 제한할 때 사용합니다. final을 사용하여 한번 할당한 entity(변수, 메소드, 클래스)는 재할당 할 수 없습니다. final이 변수, 메소드, 클래스에서 어떻게 쓰이는지 알아보겠습니다.

final 변수

지역 변수

public void finalVariable() {
        final int i = 1;
        // i = 2; 할당할 수 없다는 에러가 발생한다.
    }

객체 변수

public class FinalExample {

    int value = 1;

    public void finalVariable() {
        final FinalExample finalExample = new FinalExample();
        // finalExample = new FinalExample(); 할당할 수 없다는 에러가 발생한다.

        finalExample.value = 3;
    }
}

객체 변수도 마찬가지로 재할당 할 수 없지만 이것은 객체가 불변하다는 것을 의미하지는 않습니다. 객체의 속성은 여전히 바꿀 수 있습니다.

멤버 변수

멤버 변수에는 static 변수가 있고 인스턴스 변수가 있는데
static 변수는 처음 선언할 때와 정적 초기화 블록에서 초기화할 수 있고

인스턴스 변수는 선언 시, 인스턴스 초기화 블록, 생성자에서 초기화할 수 있습니다.

final 메소드

final로 표시된 메소드는 오버라이딩이 불가능합니다.

final 클래스

final로 표시된 클래스는 상속이 불가능합니다. 자바에서는 String클래스가
final 클래스인데 String클래스는 모든 곳에서 사용될 수 있으므로 그것을 상속하여 기능을 변경한다면 String객체에 대한 작업 결과의 일관성을 보장할 수 없기 때문에 final로 만들었습니다.

7. Object 클래스

java.lang 패키지

java.lang 패키지는 자바에서 가장 기본적인 동작을 수행하는 클래스들의 집합입니다. 따라서 import문을 사용하지 않아도 사용할 수 있습니다.

Object클래스는 java.lang 패키지 중에서 가장 많이 사용되는 클래스이고 모든 자바 클래스의 최고 조상 클래스입니다. 따라서 자바의 모든 클래스는 Object 클래스의 메소드를 사용할 수 있습니다.

Object클래스는 11개의 메소드가 있고 그중 toString(), eqauls(), hashcode()에 대해 자세히 알아보겠습니다.

toString() 메소드

toString() 메소드는 해당 인스턴스에 대한 정보를 문자열로 반환해줍니다.
이때 반환되는 문자열은 클래스이름@16진수 해시 코드로 이루어집니다.

public class ObjectExample {

    public static void main(String[] args) {
        ObjectExample example1 = new ObjectExample();
        ObjectExample example2 = new ObjectExample();

        System.out.println(example1.toString()); // FinalExample@4554617c
        System.out.println(example2.hashCode()); // FinalExample@74a14482
    }
}

toString()메소드를 오버라이딩하면 인스턴스의 정보를 원하는 대로 표현할 수 있습니다.

public class ObjectExample {

    int value;

    ObjectExample() {}

    ObjectExample(int value) {
        this.value = value;
    }

    public static void main(String[] args) {
        ObjectExample example1 = new ObjectExample(1);
        ObjectExample example2 = new ObjectExample(2);

        System.out.println(example1.toString()); // value = 1입니다.
        System.out.println(example2.toString()); // value = 2입니다.
    }

    @Override
    public String toString() {
        return "value = " + this.value + "입니다.";
    }
}

equals() 메소드

equals() 메소드는 해당 인스턴스와 매개변수로 전달받는 참조 변수를 비교하여 boolean값으로 결과를 반환합니다.
이때 참조 변수가 가리키는 값을 비교하므로, 서로 다른 두 객체는 언제나 false를 반환하게 됩니다.


public class ObjectExample {

    int value;

    ObjectExample() {}

    ObjectExample(int value) {
        this.value = value;
    }

    public static void main(String[] args) {
        ObjectExample example1 = new ObjectExample(2);
        ObjectExample example2 = new ObjectExample(2);

        System.out.println(example1.equals(example2)); // false

        example1 = example2;
        System.out.println(example1.equals(example2)); // true
    }
}

value값을 2로 초기화해서 생성한 두 객체를 equals()메소드로 비교했습니다. 두 객체의 참조 변수는 각각 다른 주소 값을 가리키고 있기 때문에 false가 출력되고 example1에 example2의 주소 값을 할당하고 비교하니 같은 주소 값을 가리키고 있어서 true가 출력됩니다. 두 객체가 가리키고 있는 주소 값이 달라도 true를 출력하려면 equals() 메소드를 오버 라이딩해야 합니다.

public static void main(String[] args) {
        ObjectExample example1 = new ObjectExample(2);
        ObjectExample example2 = new ObjectExample(2);

        System.out.println(example1.equals(example2)); // true;

        example2 = new ObjectExample(3);
        System.out.println(example1.equals(example2)); // false;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ObjectExample that = (ObjectExample) o;
        return value == that.value;
    }

equals() 메소드를 value값을 비교하도록 오버라이딩해서 다른 주소 값은 가진 객체라도 같은 value를 가지고 있으면 true를 리턴하도록 할 수 있습니다.

hashcode() 메소드

hashcode()메소드는 객체를 식별하기 위한 하나의 정수 값을 말합니다.
객체를 식별하기 위해서는 유일한 값이어야 하기 때문에 객체의 주소를 이용해서 해시 코드를 만듭니다. 그래서 주소 값이 다른 객체의 해시코드는 항상 다릅니다.

public static void main(String[] args) {
        ObjectExample example1 = new ObjectExample(2);
        ObjectExample example2 = new ObjectExample(2);

        System.out.println(example1.equals(example2)); // true;

        Set<ObjectExample> set = new HashSet();
        set.add(example1);
        set.add(example2);

        System.out.println(set.size()); // 2
    }

HashSet클래스는 중복된 요소를 저장하지 않는 특징을 가집니다. example1과 example2를 equals()메소드로 비교했을 때 true가 나왔는데 HashSet은 왜 중복된 요소로 인식하지 않았을까요?
HashSet은 중복을 검사할 때 hashcode()메소드로 같은 값을 가지는지 확인한 후 hashcode값이 같을 경우에만 equals를 비교합니다. 같은 value값을 가지는 객체를 중복된 객체로 인식하고 싶을 때는 equals()와 hashcode 둘다 오버라이딩 해야 중복된 객체로 인식합니다.

public static void main(String[] args) {
        ObjectExample example1 = new ObjectExample(2);
        ObjectExample example2 = new ObjectExample(2);

        System.out.println(example1.equals(example2)); // true;

        Set<ObjectExample> set = new HashSet();
        set.add(example1);
        set.add(example2);

        System.out.println(set.size()); // 1
    }

    @Override
    public int hashCode() {
        return 133;
    }

hashcode를 133이 반환되도록 오버라이딩 하면 example1과 example2는 같은 객체로 인식되어 example2는 삽입이 되지 않습니다.

참조

https://m.blog.naver.com/PostView.nhn?blogId=heartflow89&logNo=220961515893&proxyReferer=https:%2F%2Fwww.google.com%2F

https://advenoh.tistory.com/13

http://www.tcpschool.com/java/java_api_object

https://multifrontgarden.tistory.com/133

https://www.youtube.com/watch?v=s-tXAHub6vg&t=4141s

'JAVA' 카테고리의 다른 글

JAVA 예외 처리  (0) 2021.01.15
Garbage Collection  (0) 2021.01.08
자바 인터페이스  (0) 2021.01.07
자바 패키지에 관하여  (0) 2020.12.31
JVM 구조와 JAVA 메모리 구조  (0) 2020.12.28

댓글