자바 상속의 특징

 

OOP 이전의 방식

이전 패러다임에서 코드를 재사용하는 방법은 복사한 후 로직 인자에 맞게 수정하는 방식이었다.

이후 기존 로직을 변경해야 할 때, 두 개의 코드다 변경해야 되는 것도 문제고, 하나의 코드를 수정하였다고 하여서 다른 코드를 그렇게 수정하면 된다는 보장이 존재하지 않는다.

 

즉 유지보수에 취약한 코드가 작성, 양산되는 것이다.

 

새롭게 등장한 OOP 패러다임에서는 이러한 부분을 해결하기 위해 도입한 방식이 상속이다.

 

 

상속이란?

기반이 되는 상위 클래스의 특성을 하위 클래스에게 적용하고, 거기에 더해 필요한 특성을 추가, 확장하는 방식을 말한다. 상속의 목적은 기존 기능의 확장과 코드의 재사용이다.

 

 

상위 클래스?

어떠한 속성과 코드를 (하위 클래스들에게) 제공하는 클래스를 말한다.

 

 

하위 클래스?

상위 클래스를 상속받는 클래스를 말한다.

 

 

상속의 장점

하위 클래스에 상위 클래스를 extend만 하여도 코드가 자동적으로 재사용된다는 것이며, 확장성을 위해 변경하여야 할 부분의 로직만 overiding을 사용하여 재정의도 가능하다.

 

 

상속 시 고려해야 할 것, 상속의 단점

상속을 사용하여 코드를 재사용하는 경우에는 기존 상위 클래스가 어떻게 작성되었는지, 어떤 동작을 해야 되는 것인지를 정확히 고려하고 진행해야 한다.

 

그렇지 않고 상속을 과용하게 되면 위에서 고려하지 않은 다른 동작을 하위 클래스가 구현하게 되고, 상속의 특성상 상위 클래스와 하위 클래스가 강한 결합상태로 묶이기 때문에, 추후의 코드의 수정이 어려워짐으로써 상위 클래스의 변경 여파가 모든 하위 클래스에 전파될 수 있는 상황이 발생한다.

 

이를 취약한 기반 클래스 문제라고 한다.

 

 

상속을 사용하는 경우

  • 상위 클래스와 필요한 하위 클래스에 대하여서 잘 알고 있는 경우
  • 프로젝트 구조 자체가 상속을 위해 설계된 경우
  • is - a 관계가 명확한 경우 (동물 → 포유류 → 고래 등, 상위 분류와 하위분류가 명확한 경우)

 

자바의 상속 구조

 

단일 상속 구조

public class 상위 클래스 {

}

public class 하위 클래스 extends 상위 클래스 {

}

 

 

다중 레밸 상속 구조

public class 최상위 클래스 {

}

public class 상위 클래스 extends 최상위 클래스 {

}

public class 하위 클래스 extends 상위 클래스 {

}

 

 

계층적 상속 구조

public class 상위 클래스 {

}

public class 하위 클래스 A extends 상위 클래스 {

}

public class 하위 클래스 B extends 상위 클래스 {

}

 

자바는 다중 상속을 허용하지 않는다.

다중 상속을 허용하지 않는 이유는 하위 클래스의 코드를 복잡하게 만들어 프로그램을 취약하게 만들고, 버그를 유발할 수 있으며, 유지 보수하기 어렵게 만들기 때문이라고 한다.

 

해당 제약을 우회하는 방법으로는 인터페이스를 이용한 다중 상속이 있다.

public class 상위 클래스 {

}

public interface 인터페이스 {

}

public class 하위 클래스 extends 상위 클래스 implements 인터페이스 {

}

 

 

다중 상속 문제 : The Deadly Diamond of Death

이러한 구조를 가지고 있을 경우에 인터페이스 간의 동일한 이름의 메서드가 존재한다면, 어떤 메서드를 사용할지 모르게 되므로 컴파일 에러가 발생하게 된다.

 

이러한 에러를 해결하기 위하여선 해당 메서드를 하위 클래스에서 overriding 할 수밖에 없다.

 

 

살짝 다른 경우!

이러한 경우에는 클래스가 인터페이스에 비해서 우선순위를 가진다.

(동일한 메서드 시그니처가 존재하는 경우 클래스의 메서드를 먼저 호출한다. ) 

 

 

상속시 메모리 구조

 

이러한 코드를 작성하였다면?

public static void main(String[] args) {
        Dog dog = new Dog();
        dog.name = "푸들";
        dog.habit = "멍멍";
        dog.showHabit();
        dog.showName();


        Animal animal = new Dog();
        animal.name = "리트리버";
        animal.showName();
}

 

메모리 상으로 이러한 구조를 가지게 된다.

 

Super 키워드

상위 클래스로부터 상속받은 필드나 메서드를 하위 클래스에서 참조하는 데 사용하는 참조 변수이다.

public class A {
    protected String property = "properties";

    public void printProperties() {
        System.out.println("A.printProperties");
        System.out.println(property);
    }
}

public class B extends A {
    protected String property = "properties of Sub Class";

    @Override
    public void printProperties() {
        super.printProperties();
        System.out.println("B.printProperties");
        System.out.println(super.property);
        System.out.println(property);
    }
}

 

super() 메서드

부모 클래스의 생성자를 호출할 때 사용되는 메서드이다.

상속 구조 관계에서 하위 클래스를 생성한 경우 내부적으로 super를 호출하여 상위 클래스부터 생성한다.

public class A {
        public String name;
        public Long age;
        public A() {
            System.out.println("A.A constructor");
        }

        public A(String name) {
            this.name = name;
        }

        public A(String name, Long age) {
            this.name = name;
            this.age = age;
        }
    }

    public class B extends A {

        public B() {
            // super() 내부적으로 동작한다.
            System.out.println("B.B constructor");
        }

                public B(String name, Long age) {
            super(name, age);
            System.out.println("B.B args constructor");
        }
    }

        // 실행 함수
    public void run() {
        B b = new B();
                B b1 = new B("name", 20L);
    }

출력 결과

A.A constructor
B.B constructor
A.A args constructor
B.B args constructor

 

메서드 오버 라이딩

상속 구조 관계에서 상위 클래스의 메서드를 하위 클래스에서 다시 정의하는 것을 말한다.

상위 클래스에서 선언된 메서드 시그니처와 동일하게 작성하여야 한다. (메서드 명, 인자 + 반환 값)

→ 리턴 값을 다르게 설정할 경우 attempting to use incompatible return type 컴파일 에러가 발생

public class SuperClass {

    public SuperClass() {
        System.out.println("SuperClass constructor");
    }

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

public class SubClass extends SuperClass{

    public SubClass() {
        System.out.println("SubClass constructor");
    }

    @Override
    public void showPrint() {
        System.out.println("SubClass.showPrint");
    }
}

@Test
public void extendTest(){
    SuperClass superClass = new SubClass();
    superClass.showPrint(); // 하위 클래스의 메서드가 호출된다.
}

출력 결과

SuperClass constructor
SubClass constructor
SubClass.showPrint

 

다이내믹 메서드 디스패치

오버 라이딩된 메서드들이 있는 경우 어떤 것을 호출할지를 런타임에서 결정하는 것을 말한다.

해당 메소드 참조에 의해 호출될 때 객체 유형에 따라서 실행할 메서드를 결정하게 된다.

 

자바는 묵시적으로 메서드를 호출한 객체를 인자로 넘기기 때문에 메서드 내부에서 호출 객체를 참조할 수 있다.

-> 이는 런타임에서 호출하는 객체도 결정된 것을 말한다.

public class SuperClass {

    public SuperClass() {
        System.out.println("SuperClass constructor");
    }

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

public class SubClass extends SuperClass{

    public SubClass() {
        System.out.println("SubClass constructor");
    }

    @Override
    public void showPrint() {
        System.out.println("SubClass.showPrint");
    }
}

public class SubClazz extends SuperClass{

    public SubClazz() {
        System.out.println("SubClazz constructor");
    }

    @Override
    public void showPrint() {
        System.out.println("SubClazz.showPrint");
    }
}

@Test
public void extendTest(){
    SuperClass clazz = new SubClazz();
    clazz.showPrint();
}

출력 결과

SuperClass constructor
SubClazz constructor
notefive.oop.SubClazz@eafc191
SubClazz.showPrint

 

추상 클래스

하나의 추상 메서드를 지니는 클래스로 구현할 때에는 유사한 여러 클래스의 동일한 부분을 잘라내어 추상화하고, 공통화한 클래스를 말한다.

 

추상 클래스는 주로 클래스의 기반 뼈대나 공통 처리(재사용)에 사용한다.

 

추상 클래스의 장점

  • 유사한 동작을 가진 클래스들의 코드를 공통적으로 모아 추상화 하기에 하나의 추상 클래스에서 해당 코드를 관리할 수 있습니다.
  • 기능 확장 시 유사한 코드를 더 작성하지 않고 상위 클래스를 상속 받음으로써 간단하게 확장할 수 있습니다.

 

추상 클래스의 단점

  • 추상 클래스를 도입하고 상속을 진행하다 보면 전체 클래스의 수가 많아지게 된다.
  • 상속 계층이 깊어질수록 해당 부분을 개발하지 않고 하위 클래스를 사용하는 개발자는 로직의 이해를 위해 해당 계층의 상위부터 동작을 이해하여야 한다. (유지 보수가 어려워진다.)

 

Final 키워드

final : 해당 지정자가 작성된 것에 대해 상속이나 변경을 금지한다는 의미이다.

  • 객체 변수 : 그 필드의 값이 변경되는 것을 금지한다.

    → 값을 해당 필드에서 초기화하지 않는다면, 생성자를 통한 초기화가 강제된다.

      private final String name = "name";
    
      혹은
    
      private final String name;
    
      public Example(String name) {
              this.name = name;
      }
  • 객체 메서드 : 해당 클래스 타입을 상속받는 서브, 하위 클래스에서 오버라이드를 금지한다.

      public class A {
    
          public final void someMethod() {
              // Do something...
              System.out.println("super class!");
          }
      }
    
      public class B extends A {
    
                      // 컴파일 에러가 발생한다.
                      // cannot override someMethod() ... overridden method is final
                      @Override 
                      public final void someMethod() {
              }
      }
    
          public void run() {
              B b = new B();
              b.someMethod();
          }
  • 클래스 자신 : 해당 클래스의 상속을 금지한다. (String, Integer.... 등)

      public final class A {
    
          public final void someMethod() {
              // Do something...
              System.out.println("super class!");
          }
      }
    
      // 컴파일 에러가 발생한다.
      // cannot inherit from final
      public class B extends A { 
      }
    
          public void run() {
              B b = new B();
              b.someMethod();
          }
      }

 

Object 클래스

최상위 클래스이며, 모든 클래스는 묵시적으로 Object 클래스를 상속받게 된다.

  • equals(), toString(), clone() 등 유틸 메서드
  • wait(), notify(), notifyAll() 등 스레드 관련 메서드
  • finalize() : GC 전 리소스를 반환하기 위한 메서드
  • hashcode() : 해당 객체의 참조 값을 나타내는 메서드

들을 제공한다.

 

equals, toString, clone, getClass

public static void main(String[] args) throws CloneNotSupportedException {

    ObjectExample example = new ObjectExample();

    System.out.println(example.toString());

    System.out.println(example.equals(example));

    System.out.println(example.hashCode());

    System.out.println(example.getClass());

}

출력 결과

notefive.anonymous.ObjectExample@43a25848
true
1134712904
class notefive.anonymous.ObjectExample

 

스레드 관련 메서드

 

wait

  • 해당 Object를 선점하고 있다가 접근 권한을 다른 쓰레드에게 넘기고 notify, notifyAll 이 호출될 때까지 해당 쓰레드를 대기하게끔 설정하는 메서드이다.
    • 스레드는 Wait Set에 들어가게 된다.

notify

  • 해당 Object의 선점을 위해 Wait Set 에 대기중인 하나의 쓰레드를 실행 대기 상태로 만든다.
    • Entry Set의 쓰레드들과 같이 경합에 참여한다.

 

notifyAll

  • 해당 Obejct의 선점을 위해 Wait Set 에 대기중인 모든 쓰레드를 실행 대기 상태로 만든다.
    • notify와 동일한 경합상태가 발생한다.

 

참고자료

'Live Study' 카테고리의 다른 글

Live Study_Week 08. 인터페이스  (0) 2021.01.05
Live Study_Week 07. 패키지  (0) 2020.12.28
Live Study_Week 05. 클래스  (0) 2020.12.15
Live Study_Week 04. 제어문 + 과제  (0) 2020.12.01
Live Study_Week 03. 연산자  (0) 2020.11.24

+ Recent posts