본문 바로가기

자바

자바의 상속에 대하여 - 스터디 6차

이번 주차는 상속에 대한 학습입니다.

이전 포스트와 겹치는 부분도 있고, 연관해서 생각 할 부분이 많아서 백기선님 과제가 굉장히 체계적으로 보이네요.

oracle java document 순서로 내주시는 것 같아서, 요거보고 과제를 내시나 싶기도 하구요.

제 학습방법이 제대로된 방향으로 가고있는 것 같아요. ㅎㅎ

 

 

1.  상속의 특징은?


What is Inheritance?

상속은 객체지향 프로그래밍의 특징 중 하나입니다.

자바 컨셉문서를 보면, 자전거를 예를 들어서 설명하고 있네요.

현세계에서 물체는 종종 서로 일정한 공통점을 가지고 있는데요. 같은 공통점을 한데 묶어서 사용할 수 있으면 하는 생각에서 비롯된 개념이 상속인 것 같아요.

 

상속은 말그대로 상위 클래스의 속성, 기능을 사용할 수 있게 만드는 개념입니다.

같은 공통점은 상위 클래스에서 관리하고, 다른점들만 상속받은 객체들에서 관리할 수 있도록 말이죠.

만약 상속이라는 개념이 존재하지 않는다면, 비슷한 기능을 구현할 때마다 동일한 코드를 작성해야 할 것 같네요.

 

 

또다른 특징을 찾아 볼게요.

 

상속은 다중 상속이 불가능하다고 나오네요. 이유로는 상태의 다중 상속 문제, 즉 여러 클래스에서 필드를 상속하는 기능을 방지하기 위한 것이라고 합니다. 예를 들어, 다중 상속이 가능하다고 가정하면 해당 클래스를 인스턴스화하여 객체를 생성하면 해당 객체는 상위 클래스의 모든 필드를 상속받게 됩니다. 이렇게 되면 서로 다른 상위 클래스의 메서드나 생성자가 중복인게 있다면, 어떤걸 상속받게 될 지 정해야 하잖아요.? 이러한 고민을 하지 않게 한 것 같네요. 다른 c계열 언어중에서는 다중 상속을 지원하는 게 있다고 들었는데 이러한 모호함의 문제들을 고민하지 않는 것으로 자바는 결정한 것 같다는 생각이 들어요. 굳이 다중상속 쓰지마.!! 다른걸 더 고민하자.!! 이런 철학을 이야기 하는것 같아요.

 

2.  super 키워드는 언제 사용하지?

 

super 키워드는 자식 클래스에서 상위클래스에 접근하기 위해 사용됩니다.

this는 자기 자신, super는 부모를 지칭한다고 보시면 됩니다.

사용방법 예시를 작성해볼게요.

public class Parants {

  protected String firstName = "Lee";
  
  protected String lastName = "daddy";

}
public class Child extends Parants {

  private String firstName;
  
  private String lastName;

  public Child(String lastName) {
    this.firstName = super.firstName;
    this.lastName = lastName;
  }

  public String getFullName() {
    return this.firstName + " " + this.lastName;
  }

  public String getParantsFullName() {
    return super.firstName + " " + super.lastName;
  }

  public static void main(String[] args) {
    Child child = new Child("jaehoon");
    System.out.println("ChildFullName: " + child.getFullName());
    System.out.println("ParentsFullName: " + child.getParantsFullName());
  }
}

 

Parants 클래스를 상속받은 Child 클래스 입니다.

firstName은 부모의 성을 따라가죠. Child가 생성될 때 Parants의 firstName을 가지고 Child 객체가 생성될 수 있습니다.

그리고 Child 클래스에도 lastName필드가 존재하고, Parants 부모 클래스에도 lastName이 존재하죠.

부모 클래스의 이름을 가져오고 싶으면 super.lastName을 하면 부모의 lastName필드에 접근할 수가 있습니다.

this는 자기 자신 클래스, super는 부모 클래스를 지칭한다고 보시면 됩니다.

 

좀 더 다양한 시선에서 본 것들

this, super 키워드에 대해서 알아봤는데, this(), super() 키워드도 존재합니다.

생성자를 뜻하게 되는데, this() 자기 자신의 생성자를 호출, super()는 부모클래스의 생성자를 호출할 수 있습니다.

예시를 작성해볼게요.

 

공식문서에서 자전거를 예시로 들었으니 자전거와 산악자전거를 예로 들어볼게요.

this()

public class Bicycle {
    
    protected int gear;
    
    protected int speed;

    // Default 생성자
    public Bicycle() {
        this(1, 10); // 기본 기어는 1, 스피드는 10으로 설정
    }

    public Bicycle(int gear, int speed) {
        this.gear = gear;
        this.speed = speed;
    }

    public void printDescription() {
        System.out.println("Bike is in gear " + this.gear + " with a speed of " + this.speed + ".");
    }
    
}

this()는 자기 자신의 생성자를 호출

Bicycle은 gear와 speed라는 상태가 존재 합니다. Bicycle을 생성할 때는 gear와 speed를 설정할 수 있습니다.

그러나, 기본값으로 세팅하고 싶으면? 코드에 작성된데로 매개변수 없이 객체를 생성하면 기본세팅인 1, 10으로 만들어지게 됩니다.

new Bicycle() -> Bicycle() 생성자 호출 -> this(1, 10) gear와 speed를 매개변수로 받는 생성자를 인식해서 호출하게 할 수 있습니다. 빌더패턴을 많이 사용하게 되면서 점층적 생성자 패턴은 자주 사용하지는 않지만, 점층적 생성자 패턴으로 사용되니 알고 계시면 좋습니다.

 

점층적 생성자 패턴 예시
public class Person {

    private String name;
    
    private int age;
    
    private String email;

    public Person(String name) {
        this(name, 0);
    }

    public Person(String name, int age) {
        this(name, age, "");
    }

    public Person(String name, int age, String email) {
        this.name = name;
        this.age = age;
        this.email = email;
    }
}

 

super()

public class MountainBicycle extends Bicycle {

  private int seatHeight;

  public MountainBicycle(int gear, int speed, int seatHeight) {
    super(gear, speed);
    this.seatHeight = seatHeight;
  }

  @Override
  public void printDescription() {
    super.printDescription();
    System.out.println("The mountain bike has a seat height of " + this.seatHeight);
  }

}

 

super() 키워드는 부모 생성자를 호출할 때 사용됩니다.

위 예시에서 super(gear, speed)가 사용되서 부모 Bicycle 생성자가 호출되어, 부모 객체의 상태 필드인 gear, speed를 채워지도록 사용할 수 있습니다.

public static void main(String[] args) {
    MountainBicycle mb = new MountainBicycle(3, 21, 15);
    mb.printDescription();
  }
  
  // 출력값: 
  // Bike is in gear 3 with a speed of 21.
  // The mountain bike has a seat height of 15

 

3.  메소드 오버라이딩

메서드 오버라이딩에 대해서는 이전 포스트에서 설명했기에 생략합니다.

https://jh2021.tistory.com/25

 

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

 

다이나믹 메서드 디스패치라는 말은 생소한 것 같네요.

런타임 시점(실행)에 어떤 메서드를 호출할지 고르는걸로 대략 알고 있는데, 자세하게 찾아봐야 겠어요.

dispatch의 뜻은 1. (특별한 목적을 위해) 보내다. 2. 급보, 파견 이런 뜻이 있네요.

뭔가 뜻이 더 이상한데요? ㅎㅎ

 

프로그래밍 용어에서는 어떤 메서드를 호출할 것인가를 결정하여 그것을 실행하는 과정을 Dynamic Dispatch 라고 하네요.

 

geeksforgeeks 사이트에서 더 자세하게 설명하고 있습니다. 아래 링크 첨부했으니 참고 하시면 좋을 것 같아요.

 

Dynamic Method Dispatch는 재정의된 메서드에 대한 호출이 컴파일 타임이 아닌 런타임에 해결되는 메커니즘 이라는 내용입니다. 참조되는 객체에 따라서 컴파일러가 상위 객체의 메서드를 호출할 것이냐? 하위 객체에서 오버라이딩 된 메서드를 호출할 것이냐? 모를 경우 런타임 시점에 결정하게 된다는 것이 핵심이네요.

 

런타임 환경 참조

 

Dynamic Method Dispatch의 예시코드를 작성해볼게요.

class A {
  void callme() {
    System.out.println("Inside A's callme method");
  }
}

class B extends A {

  @Override
  void callme() {
    System.out.println("Inside B's callme method");
  }
}

class C extends A {

  @Override
  void callme() {
    System.out.println("Inside C's callme method");
  }
}

public class DynamicDispatch {

  public static void main(String[] args) {
    A a = new A();
    A b = new B();
    A c = new C();

    a.callme();
    // Inside A's callme method
    b.callme();
    // Inside B's callme method
    c.callme();
    // Inside C's callme method
  }

}

 

A라는 상위 수퍼클래스가 있고 B, C 클래스는 A를 상속받는 하위 서브 클래스입니다.

모두 A 클래스를 참조 변수로 가지지만 (다형성), 실제 참조된 객체에 따라서 참조된 객체의 메서드가 호출 되는 것을 알 수 있습니다.

 

5.  추상클래스와 상속은 연관된 것 같아

 

우선 추상클래스에 대해 먼저 이야기 해볼게요.

자바에서는 미완성된 클래스로 하나 이상의 추상 메소드를 포함하는 클래스를 가리켜 추상 클래스(abstract class)라고 합니다.

간단한 특징에 대해서 설명해볼게요.

1. abstract 키워드를 사용해서 클래스를 생성

2. 추상클래스 자체는 new 키워드를 이용해서 인스턴스화 할 수 없음

3. 구현되지 않는 abstract 추상 메서드를 지님

4. 상속을 통해서 추상 메서드를 완성시켜야 함

 

추상클래스는 무엇이고 왜 사용하는지 본질적인 개념에 대해 초점을 두어야 한다고 생각합니다.

 

우선 추상메서드가 있는 클래스가 추상클래스라고 했는데, 추상메서드는 무엇일까요?

추상메서드는 꼭 필요한 동작이지만, 각 객체마다 다르게 구현되어야 하는 경우에 정의 하는 것을 말해요.

간단한 예시를 작성해보면, Player라는 미완성 클래스에 play(실행), stop(정지)가 꼭 필요하다면 이렇게 구현할 수 있게 됩니다.

public abstract class Player {

  abstract void play();

  abstract void stop();

}


추상 클래스를 완성 시켜보면,

public class AudioPlayer extends Player {

  void play() {
    System.out.println("AudioPlayer play");
  };

  void stop(){
    System.out.println("AudioPlayer stop");
  };

  public static void main(String[] args) {
    Player audioPlayer = new AudioPlayer();
    audioPlayer.play();
    audioPlayer.stop();
  }
}

Player를 상속받아서 완성된 AudioPlayer 클래스를 인스턴스(객체)화 할 수 있게 되는 거죠.

주의할 점은 추상클래스의 모든 추상 메서드를 구현 & 완성 시켜줘야 인스턴스(객체)화 할 수 있습니다.

 

추상클래스를 만들어봤는데요. 왜 추상화의 본질이 보이시나요?

구조적이게 객체를 설계하게 도와주어, 각 객체들을 유연하게 수정할 수 있게 도와주는 것입니다.

저는 추상화를 사용하면, 잘 정리된다는 느낌이 드는 것 같아요.

 

좀 더 다양한 시선에서 본 것들

추상클래스를 완성된 클래스로 만들기위해서 상속을 해서 구현한다고 했죠.

그러면 그냥 일반 클래스를 상속해서 메서드를 재정의하는 구현과 비슷하지 않나? 이런 생각이 들더라구요.

추상클래스 상속과 일반 클래스의 상속 차이를 이야기 해볼까 싶어요.

일반 클래스, 추상 클래스 모두 상속이 가능합니다. 추상클래스는 자체만으로 인스턴스화가 되지 못하기 때문에 무조건 extends 키워드로 완성된 크래스를 구현해야 하죠. 일반클래스도 오버라이딩을 통해 모든 동작을 재정의해서 사용할 수 있죠.

 

7년전 스택오버플로우에서 누군가가 질문을 올렸네요. ㅎㅎ

주요 차이점은 추상화를 사용하면 내부 정보를 숨기고 사용자에게 추상메서드를 구현해야만 한다고 알려줄 수 있다는 것이고

일반클래스의 상속을 사용하면 이미 존재하는 클래스의 속성과 메서드를 모두 물려받고 재정의하지 않으면 그대로 사용하게 됩니다.

 

추상 클래스
1. 추상화는 구현 세부 사항을 숨기고 사용자에게 동작(메서드)만을 표시
2. 추상 클래스의 객체를 생성 X

상속
1. 상속은 기존 클래스의 속성과 메서드를 모두 물려받아 새로운 클래스를 만듬
2. 상위 클래스의 객체를 생성할 수 있습니다.

 

정리

상속은 코드 재사용을 용이하게 하고 계층 구조를 만들기 위해, 추상화는 복잡한 구조를 단순화하고 보다 나은 설계를 위한 목적이 조금 다르다는 생각입니다. 그러나 상속과 추상화는 결국 객체지향 프로그래밍에 핵심 개념이며, 뭐가 더 좋다가 아니라 목적에 맞게 사용하는 것이 중요하다는 생각이 드네요.

6.  final 키워드

final 키워드는 변하지 않는 것을 의미해요.

공식문서에서는 final로 선언된 클래스는 하위 클래스로 분류할 수 없다, final 메서드는 하위 클래스로 재정의될 수 없다라고 나옵니다.

 

final class

클래스에 final 키워드를 붙이고 상속하여 하위클래스를 만들어보면 IDE에서 상속할 수 없다고 나오네요.

 

final method

메서드에 final을 붙이면 오버라이드를 하지 못하게 하네요.

final field

final 필드는 객체 생성시 초기화가 필수로 필요하고, 해당 필드(인스턴스 변수)가 변하지 못하게 합니다.

객체가 생성된 이후 변하지 않는 속성을 가진 불변 객체(Immutable Object)는 동시성을 고려하지 않아도 되는 Thread-Safe의 이점을 가지기 때문에, 저는 실무에서도 불변객체를 만들수 있다면 최대한 불변객체로 가지려고 노력하는 것 같아요.

 

7.  Object 클래스를 만든 이유가 뭘까?

Object 클래스는 Java 프로그래밍 언어의 다른 모든 클래스의 슈퍼클래스 조상 역할을 가집니다.

자바 컴파일러는 일반 클래스를 Object의 하위 클래스로 자동으로 설정하기 때문에 모든 객체는 Object를 상속받아서 만들어지게 됩니다.

 

처음 자바를 입문할때는 Object는 가장 맨위의 조상 클래스라는 이야기 많이 들어 보셨을 것 같아요.

그러면 이제는 왜 자바가 Object를 이용해서 계층화된 클래스를 정의하였는가를 살펴볼때 인 것 같네요.

 

 

왜 자바는 Object 클래스로 계층화 하였을까?

Java 객체 계층 구조의 최상위

앞서 Java의 모든 클래스는 명시적으로 다른 클래스를 확장하지 않는 경우 Object 클래스를 상속 받는다고 이야기 했습니다.

이는 Object 클래스가 계층 구조의 최상위에 위치하여 모든 객체가 Object 유형으로 처리될 수 있게 하고 다형성을 활용할 수 있게 합니다.

 

공통 메소

Object 클래스는 equals(), hashCode(), toString(), clone() 등 Java의 모든 객체에 공통적인 동작(메서드)를 제공합니다.

또한 스레딩을 위한 notify(), notifyAll() 및 wait() 메서드도 제공되고요. 이러한 메서드는 개체 비교, 해시 코드 생성, 개체를 문자열 표현으로 변환, 개체 복제, 개체의 런타임 클래스 가져오기, 객체 동기화에 각각 유용한 기본 동작으로 공통되게 사용될 수 있기 때문에 생성되는 모든 객체들이 가지면 좋은 기능들을 기본적으로 물려 받을 수 있게 하는거죠.

 

다형성

모든 Java 클래스는 Object에서 상속되므로 모든 Java 객체는 Object 유형의 변수에 할당될  있습니다이는 Java 다형성을 위한 초석으로모든 유형의 객체를 Object 유형으로 전달할  습니다이는 일반 개체에서 작동하는 컬렉션  프레임워크에 특히 유용하며 높은 수준의 유연성과 재사용을 만들어 낼 수 있습니다.

 

가비지 컬렉터의 식별

Object 클래스 자체는 가비지 수집을 직접 처리하지 않지만 객체 수명 주기 관리를 뒷받침하는 메커니즘은 모든 객체를 균일하게 처리하는 기능에 의존합니다. 'Object'를 상속함으로써 모든 Java 객체는 가비지 컬렉터에 의해 일관된 방식으로 관리될  있습니다.

 

Object 클래스까지 내용이 많네요. ㅎㅎ
저는 실무에서 추상화 해서 구현하려고 항상 노력하는데, 구현시간이 길어지거나 오히려 더 복잡해지는 문제가 많더라구요.

자바를 사용하면서 내가 자바의 핵심 개념을 얼마나 알고 있지? 라는 생각과 함께 부끄러운 마음도 드네요.
꾸준히 공부하면서 계속 더 좋은 코드와 설계를 만들도록 노력해야 할 것 같아요. ㅎㅎ
혹 잘못된 내용 있으면 지적해주시면 감사하겠습니다. 🙏

 

하얀종이 개발자

Reference

[백기선님 과제]

https://github.com/whiteship/live-study/issues/6

 

[상속 참고 링크]

https://docs.oracle.com/javase/tutorial/java/concepts/inheritance.html

https://docs.oracle.com/javase/tutorial/java/IandI/subclasses.html

 

[다이나믹 메서드 디스패치 참고 링크]

https://www.youtube.com/watch?v=8C_YRYXCuwc

https://www.geeksforgeeks.org/difference-between-compile-time-and-run-time-polymorphism-in-java/

https://www.geeksforgeeks.org/dynamic-method-dispatch-runtime-polymorphism-java/?ref=lbp

 

[추상클래스와 상속의 차이]

https://stackoverflow.com/questions/40626800/what-is-exact-difference-between-inheritance-and-abstract-class

https://pediaa.com/what-is-the-difference-between-abstraction-and-inheritance

 

[추상 클래스]

https://tcpschool.com/java/java_polymorphism_abstract

 

[남궁성님의 자바의 정석]

https://www.youtube.com/user/MasterNKS

 

[Object 클래스]

https://www.javatpoint.com/object-class

https://medium.com/javarevisited/5-key-questions-about-java-object-class-39b16986d3e0