본문 바로가기

JAVA 스터디

[JAVA] 상속(Inheritance)

상속(Inheritance)

'상속'이란 기존의 클래스에 기능을 추가하거나 재정의하여 새로운 클래스를 정의하는 것을 말합니다.

 

상속을 이용하면 기존의 클래스에서 사용하던 필드와 메소드를 그대로 가져와서, 새로운 클래스를 생성할 수 있습니다.

이때 기존의 클래스와 상속받은 클래스를 아래와 같이 부릅니다.

 

기존의 클래스

  • 부모 클래스(parent class)
  • 상위 클래스(super class)
  • 기초 클래스(base class)

상속받은 클래스

  • 자식 클래스(child class)
  • 하위 클래스(sub class)
  • 파생 클래스(derived class)

상속의 장점은 아래와 같습니다.

  • 기존의 클래스를 재활용할 수 있습니다.
  • 자식 클래스 설계 시 중복되는 멤버를 미리 부모 클래스로 정의해 놓으면, 자식 클래스에선 해당 멤버를 정의하지 않아도 됩니다.
  • 클래스 간의 계층적 구조를 형성되므로 다형성의 토대가 마련됩니다.

자식 클래스(child class)

'자식 클래스'란 '부모 클래스'의 모든 멤버를 물려받아 새롭게 정의된 클래스를 말합니다.

자식 클래스에는 부모 클래스의 필드와 메소드만 상속됩니다.

부모 클래스의 생성자와 초기화 블록은 상속되지 않습니다.

그리고 부모 클래스의 'private'이나 'default'로 접근 제어자가 설정된 멤버는 상속은 되지만 접근은 불가합니다.

 

접근 제어자 private은 해당 클래스 내부에서만 접근이 가능하고,

접근 제어자 default는 해당 패키지 내부에서만 접근이 가능합니다.

접근 제어자와 관련된 내용을 정리하여 추후 업데이트 하겠습니다.

 

상속을 위해선 'extend'란 키워드를 사용해서 아래의 예제와 같이 작성해줍니다.

 

class Parent {
    private int a = 10;
    public int b = 20;
}

class Child extends Parent {
    public int c = 30;
    void display() {
        //System.out.println(a);    // 1. 상속받은 private 필드 참조
        System.out.println(b);      // 2. 상속받은 public 필드 참조
        System.out.println(c);      // 3. 자식 클래스에서 선언한 public 필드 참조
    }
}

public class Example {
    public static void main(String[] args) {
        Child ch = new Child();
        ch.display();
    }
}
//실행 결과
20
30

 

예제의 1번 라인은 접근 제어자 private에 의해서 오류가 발생합니다.

같은 클래스 내에서만 접근이 가능한데, Parent와 Child는 서로 다른 클래스이기 때문입니다.

예제의 2번 라인에서는 자식 클래스가 부모 클래스로부터 상속받은 public 필드를 참조하고 있습니다.

자식 클래스에는 존재하지 않는 필드이지만, 상속을 통해서 사용할 수 있습니다.

3번 라인은 자식 스스로의 필드에 접근하고 있습니다.

 

자바에서는 모든 클래스의 부모가 되는 'Object 클래스'가 있습니다.

모든 클래스는 자동으로 Object 클래스를 상속하므로, 따로 extends 키워드를 사용할 필요가 없습니다.

Object 클래스는 'toString()' 혹은 'clone()'과 같은 편리한 메소드를 제공하는 클래스로

프로그램 개발에 도움을 주는 기능들을 제공합니다.

해당 내용은 정리하여 추후에 업데이트하겠습니다.

 

super 키워드 

'super 키워드'는 자식 클래스에서 부모 클래스의 필드나 메소드를 참조할 때 사용하는 참조 변수입니다.

부모 클래스의 멤버와 자식 클래스의 멤버의 이름이 동일할 때, 이를 구별하기 위해 사용합니다.

 

this 키워드와 마찬가지로 super 참조 변수도 인스턴스 메소드에서만 사용할 수 있습니다.

클래스 메소드에서는 사용할 수 없습니다.

 

class Parent {
    int a = 10;
}

class Child extends Parent {
    int a = 20;
    
    public void Info() {
        System.out.println(a);
        System.out.println(this.a);
        System.out.println(super.a);
    }
}

public class Example {
    public static void main(String[] args) {
        Child ch = new Chile();
        ch.Info();
    }
}
// 실행결과
20
20
10

 

클래스 메소드에서 super를 사용하게 되면 다음과 같은 오류가 발생합니다.

'com.company.Child.super' cannot be referenced from a static context

 

...

class Child extends Parent {
    int a = 20;
    
    public static void Info() {
        System.out.println(a);            // error
        System.out.println(this.a);       // error
        System.out.println(super.a);      // error
    }
}

 

super() 메소드

'super() 메소드'는 부모 클래스의 생성자를 호출하기 위해 사용합니다.

 

자바에서 자식 클래스가 부모 클래스의 멤버를 참조할 수 있는 이유는

자식 클래스의 인스턴스를 생성할 때, 부모 클래스의 생성자도 함께 호출되어 초기화가 진행되기 때문입니다.

부모가 존재하지 않으면 자식이 없는 것과 같은 이치입니다.

이러한 부모 클래스의 생성자 호출은 Object 클래스에도 동일하게 작용합니다.

 

따라서 자바 컴파일러는 자식 클래스의 생성자에서 부모 클래스의 생성자를 명시하지 않으면,

자식 클래스 생성자의 첫 줄에 아래와 같은 코드를 추가하여 부모 클래스의 멤버를 초기화합니다.

 

super();

 

이때 주의할 점이 있습니다.

위의 코드는 부모 클래스의 생성자를 호출하는 코드인데 인자값이 명시되지 않았습니다.

즉, 부모 클래스의 기본 생성자를 호출하고 있음을 알 수 있습니다.

 

클래스에서 사용자 정의 생성자만 정의되어 있다면 클래스에서 기본 생성자는 자동으로 추가되지 않습니다.

따라서 위의 코드는 존재하지 않는 부모 클래스의 기본 생성자를 호출하는 것이므로 오류가 발생합니다.

오류 내용은 다음과 같습니다.

'Parent(int)' in 'com.company.Parent' cannot be applied to '()'

 

그러므로 사용자 정의 생성자를 정의할 때에는 기본 생성자도 명시해주는 습관을 가지는 것이 좋습니다.

 

class Parent {
    int a;
    public Parent() {
        a = 10;
    }
    public Parent(int a) {
        this.a = a;
    }
}

class Child extends Parent {
    int b;
    public Child() {
        //super(30);
        b = 20;
    }
    public void Info() {
        System.out.println(a);
        System.out.println(b);
    }
}

public class Example {
    public static void main(String[] args) {
        Child ch = new Child();
        ch.Info();
    }
}

 

위의 코드를 실행하면 부모 클래스의 기본 생성자가 호출되었습니다.

 

// 실행결과
10
20

 

만약 자식 클래스 생성자 안에서 주석을 제거하면 부모 클래스의 사용자 정의 생성자를 호출하게 됩니다.

 

// 실행결과
30
20

메소드 오버라이딩(method overriding)

오버라이딩은 상속 관계에 있는 부모 클래스에서 이미 정의되어 있는 메소드를

자식 클래스에서 같은 이름을 갖는 메소드로 '재정의'하는 것입니다.

 

오버라이딩의 조건은 다음과 같습니다.

  • 오버라이딩이란 메소드의 동작만을 재정의하는 것이므로 기존 메소드의 선언부는 동일해야 합니다. 그러나 메소드의 반환 타입은 부모 클래스의 반환 타입으로 형변환을 할 수 있는 타입이라면 변경 가능합니다.
  • 부모 클래스의 메소드보다 접근 제어자를 더 좁은 범위로 변경할 수 없습니다.
  • 부모 클래스의 메소드보다 더 큰 범위의 예외를 선언할 수 없습니다.
class Parent {
    int a = 10;
    String b = "JAVA";
    
    public void setAge(int a) {
        this.a = a;
        System.out.println("Set parent's a : " + a);
    }
    public String Info() throws IOException, SQLException {
        return a + " " + b;
    }
}

class Child extends Parent {
//  private void setAge(int a)                더 좁은 범위의 접근 제어자 설정으로 error
    public void setAge(int a) {
        this.a = a + 20;
        System.out.println("Set child's a : " + this.a);
    }
/*
    public int Info() throws Exception {      더 적은 개수의 에러 설정으로 error
        return a;
    }
*/

 

주석을 제거하면 앞서 말한 조건들에 의해서 오류가 발생합니다.

아래 예제는 직접 인스턴스를 생성하여 실행한 결과입니다.

 

public class Example {
    public static void main(Stringp[] args) {
        Parent pa = new Parent();
        pa.setAge(10);
        Child ch = new Child();
        ch.setAge(10);
    }
}

// 실행결과
Set parent's a : 10
Set child's a : 30

 

오버로딩(overloading)과 오버라이딩(overriding)

오버로딩과 오버라이딩의 개념이 헷갈릴 수 있습니다.

그러나 둘은 확실히 다른 개념이기 때문에 그 차이를 알고 있어야 합니다.

 

오버로딩은 새로운 메소드를 정의하는 것입니다.

오버라이딩은 상속받은 기존의 메소드를 재정의하는 것입니다.

 

class Parent {
    void Info() {
        System.out.println("부모 클래스의 Info() 메소드");
    }
}

class Child extends Parent {
    void Info() {                      // 오버라이딩된 Info()
        System.out.println("자식 클래스의 Info() 메소드");
    }
    void display(String str) {         // 오버로딩된 Info()
        System.out.println(str);
    }
}

public class Example {
    public static void main(String[] args) {
        Child cd = new Child();
        ch.Info();
        ch.Info("오버로딩된 Info() 메소드");
    }
}
// 실행결과
자식 클래스의 Info() 메소드
오버로딩된 Info() 메소드

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

다이나믹 메소드 디스패치를 설명하기 앞서 '다형성(Ploymorphism)'에 대해 설명하겠습니다.

 

다형성이란 여러 가지 형태를 가질 수 있는 능력을 말합니다.

자바에서는 한 타입의 참조 변수로 여러 타입의 객체를 다룰 수 있도록 해줍니다.

상속관계에 있는 Parent(부모 클래스)와 Child(자식 클래스)가 있습니다.

이때 자바에서 말하는 다형성이란 다음과 같이 선언 및 초기화할 수 있음을 의미합니다.

 

Parent p = new Child()

 

다이나믹 메소드 디스패치는 런타임 시간에 어떤 메소드를 호출할지 결정하는 것을 말합니다.

Parent를 상속하는 Child1과 Child2를 만들고, print() 메소드를 오버 라이딩해보겠습니다.

 

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

class Child1 extends Parent {
    ...
    public void print() {
        System.out.println("I am child1");
    }
}

class Child2 extends Parent {
    ...
    public void print() {
        System.out.println("I am child2");
    }
}

public class Example {
    public static void main(String[] args) {
        Parent p = new Parent();
        p.print();
        p = new Child1();
        p.print();
        p = new Child2();
        p.print();
    }
}

 

위의 코드를 수행해서 print() 메서드를 차례대로 호출한다면 실행결과는 다음과 같이 됩니다.

 

// 실행결과
I am parent
I am child1
I am child2

 

Parent의 참조 변수인데 실행되는 메소드가 서로 다른 이유는 자바에선 초기화된 인스턴스를 참조하기 때문입니다.

Parent의 참조 변수를 정의한 클래스들로 차례대로 인스턴스화 해주었으므로 이러한 결과가 나왔습니다.

 

그러나, 상속관계에 있다고 해서 Child 참조 변수가 Parent 인스턴스를 참조할 수는 없습니다.

참조 변수가 사용할 수 있는 멤버의 개수가 실제 인스턴스의 멤버 개수가 많기 때문에 발생하는 오류입니다.

 

Child1 c = new Parent()        // error

 

참조 변수의 타입 변환은 다음과 같은 조건에 따라 가능합니다.

  • 서로 상속 관계에 있는 클래스 사이에만 타입 변환을 할 수 있습니다.
  • 자식 클래스 타입에서 부모 클래스 타입으로의 타입 변환은 생략할 수 있습니다.
  • 부모 클래스 타입에서 자식 클래스 타입으로의 변환은 반드시 명시해야 합니다.

3번째 조건에 의해서 오류가 발생했던 위의 코드는 아래와 같이 사용할 수 있게 됩니다.

 

Parent p = new Parent();
Child1 c = (Child1)p;        

 

만약 참조변수가 실제로 가리키고 있는 인스턴스의 타입을 확인하고 싶다면 'instanceof 연산자'를 사용하면 됩니다.

 

class Parent { }
class Child extends Parent { }
class Brother extends Parent { }
 
public class Polymorphism01 {
    public static void main(String[] args) {
        Parent p = new Parent();
        System.out.println(p instanceof Object); // true
        System.out.println(p instanceof Parent); // true
        System.out.println(p instanceof Child);  // false
        System.out.println();
 
        Parent c = new Child();
        System.out.println(c instanceof Object); // true
        System.out.println(c instanceof Parent); // true
        System.out.println(c instanceof Child);  // true
    }
}

 

추상 메소드(abstract method)

추상 메소드란 자식 클래스에서 반드시 오버라이딩해야만 사용할 수 있는 메소드입니다.

자바에서 추상 메소드를 사용하는 이유는, 특정 클래스를 상속하는 자식 클래스에서 반드시 구현해야 하는

메소드가 존재할 때 구현하도록 하기 위함입니다.

 

추상 메소드는 메소드 선언부만 작성해주면 됩니다.

 

abstract void method_name();

 

추상 클래스(abstract class)

하나 이상의 추상 메소드가 존재하면, 해당 클래스를 추상 클래스라고 합니다.

반드시 사용되어야 하는 메소드를 추상 클래스에 추상 메소드로 선언하면,

상속받는 모든 클래스에서는 이 추상 메소드를 반드시 오버라이딩해야 합니다.

 

이러한 추상 클래스는 동작이 정의되어 있지 않은 추상 메소드를 포함하고 있으므로 인스턴스를 생성할 수 없습니다.

추상 클래스는 먼저 상속을 통해 자식 클래스를 만들고, 만든 자식 클래스에서 추상 클래스의 모든 추상 메소드를

오버라이딩하고 나서야 자식 클래스의 인스턴스를 생성할 수 있습니다.

 

그 외는 일반 클래스와 모두 동일합니다.

 

abstract class Animal { abstract void cry(); }
class Cat extends Animal { void cry() { System.out.println("냐옹냐옹!"); } }
class Dog extends Animal { void cry() { System.out.println("멍멍!"); } }

public class Polymorphism02 {
    public static void main(String[] args) {
        // Animal a = new Animal(); // 추상 클래스는 인스턴스를 생성할 수 없음.
        Cat c = new Cat();
        Dog d = new Dog();

        c.cry();
        d.cry();
    }
}
// 실행결과
냐옹냐옹!
멍멍!

final 키워드

final 키워드는 자바에서 '변경 불가능'을 의미합니다.

변수를 변경 불가능하게 만듦으로써 상수를 표현하기 위한 키워드입니다.

변수 이외에도 메소드와 클래스에서도 사용 가능합니다.

 

1.  변수에 final을 사용

public class Example {
    public static void main(String[] args) {
        // 선언 및 초기화
        final int a = 10;
        
        // 값 변경
        a = 20;
    }
}

 

변수 a를 선언할 때 final을 사용해서 상수임을 보이고 있습니다.

선언과 동시에 10이라는 값을 변수 a에 대입했습니다.

 

다음으로 20이라는 값을 대입하려고 하면 다음과 같은 에러가 발생하는데,

상수로 정의된 값을 변경하려 했기 때문에 에러가 발생합니다.

 

Cannot assign a value to final variable 'a'

 

2. 메소드에 final을 사용

 

class Parent {
    ...
    public final void method() {
        // 내용 정의
    }
}

class Child extends Parent {
    ...
    // error
    public void method() {            
    
    }
}

 

메소드에 final을 사용하면 메소드를 변경하지 못한다는 의미로, 오버라이딩을 막는다는 의미입니다.

이때에는 다음과 같은 오류가 발생합니다.

 

'method()' cannot override 'method()' in 'com.company.Parent'; overridden method is final

 

3. 클래스에 final을 사용

final class Parent {
    ...
}

// error
class Child extends Parent {
    ...
}

 

클래스에 final이 사용되면 상속을 할 수 없습니다.

상속을 허용하면 자식 인스턴스를 통해서 부모 클래스의 멤버를 변경할 수 있게 됩니다.

final이 변경 불가능을 의미하므로, 이를 허용하지 않는 것입니다.

이때에는 다음과 같은 오류가 발생합니다.

 

Cannot inherit from final 'com.company.Parent'

 

final 메소드와 클래스는 주로 라이브러리 형태의 프로젝트를 개발할 때 사용합니다.

라이브러리를 완전히 이해하지 못한 상태에서 오버라이딩을 한다면 에러가 발생할 확률이 높기 때문에

이를 막기 위해 사용합니다.

Object 클래스

Object 클래스는 자바에서 특별한 의미를 가지고 있습니다.

자바에서 모든 클래스는 Object()를 암시적으로 상속받습니다.

왜냐하면, 모든 클래스가 공통으로 포함하고 있어야 하는 기능을 제공하기 위해서입니다.

 

Object 클래스는 매개변수가 없는 생성자를 가지고 있습니다.

 

API 문서에 작성된 Object 클래스가 제공하는 메소드는 다음과 같습니다.

https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

 

 

이 중에서 활용도가 높고, 입문 단계에서 이해할 수 있는 API를 살펴보겠습니다.


toString()

toString() 메소드는 객체를 문자로 표현하는 메소드입니다.

다음은 예제로 활용할 계산기 코드의 일부입니다.

 

class Calculator {
    int left, right;
    
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
    ...
}

public class Example {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        System.out.println(c1);
    }
}

 

setOprands()의 매개변수로 인자값을 2개를 받아서 left와 right에 각각 대입해줍니다.

이후 main 함수에서 c1 인스턴스를 출력하면 다음과 같은 출력문이 나타납니다.

(@ 이후는 각자 다를 것입니다.)

 

com.company.Calculator@4554617c

 

이 정보는 인스턴스 c1이 클래스 Calculator의 인스턴스라는 의미입니다.

여기에서 toString() 메소드를 오버라이딩 해보겠습니다.

 

class Calculator {
    int left, right;
    
    public void setOprands(int left, int right) {
        this.left = left;
        this.right = right;
    }
    // toString 오버라이딩
    public String toString() {
        return "left : " + this.left + ", right : " + this.right;
    }
    ...
}

public class Example {
    public static void main(String[] args) {
        Calculator c1 = new Calculator();
        c1.setOprands(10, 20);
        
        // 객체 출력
        System.out.println(c1);
        // toString() 메소드 호출의 결과값 출력
        System.out.println(c1.toString();
    }
}
// 실행결과
left : 10, right : 20
left : 10, right : 20

 

오버라이딩을 수행해줬고 인스턴스를 출력해주었습니다.

이때 toString() 메소드를 호출하지도 않았는데, toString() 메소드를 호출했을 때와 동일한 효과가 나고 있습니다.

toString() 메소드는 자바에서 직접 호출하지 않아도 어떤 객체를 System.out.print(혹은 println)로 호출하면

자동으로 toString() 메소드가 호출되도록 약속했습니다.

 

덕분에 인스턴스 c1의 상태를 쉽게 알 수 있습니다.


equals

equals() 메소드는 객체와 객체가 서로 같은지 비교해주는 API입니다.

객체의 다름은 객체의 필드로 나타내 집니다.

 

class Fruit {
    String name;
    
    public Fruit(String name) { 
        this.name = name;
    }
    public boolean equals(Object obj) {
        Student _obj = (Student) obj;
        return name == _obj.name;
    }
}

public class Example {
    public static void main(String[] args) {
        Fruit f1 = new Fruit("apple");
        Fruit f2 = new Fruit("apple");
        
        // 1. 단순 객체 비교
        System.out.println(f1 == f2);
        // 2. equals() 메소드를 통한 비교
        System.out.println(f1.equals(f2));
    }
}

// 실행결과
false
true

 

인스턴스 f1과 인스턴스 f2는 서로 다른 인스턴스입니다.

따라서 1번의 경우 false가 나오는 것은 당연하다 할 수 있습니다.

 

그러나 f1과 f2 모두 name 필드에 'apple'이라는 동일한 값이 대입되어 있습니다.

이 둘을 논리적으로 같은 인스턴스임을 보이기 위해서 equals() 메소드를 오버라이딩해서 호출해주었습니다.

두 인스턴스의 name 필드값을 직접 비교했으므로 true가 반환되었습니다.


clone

clone() 메소드는 '복제'라는 뜻에 맞게 어떤 객체를 똑같은 객체로 복사해주는 메소드입니다.

예제를 살펴보겠습니다.

 

class Fruit implements Cloneable {
    public String name;
    
    public Fruit(String name) { 
        this.name = name;
    }
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class Example {
    public static void main(String[] args) {
        Fruit f1 = new Fruit("apple");
        try {
            // f1을 f2로 복사
            Fruit f2 = (Fruit)f1.clone();
            System.out.println(f1.name);
            System.out.println(f2.name);
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
    }
}

// 실행결과
apple
apple

 

위의 코드에서 인터페이스 Cloneable의 실제 코드는 다음과 같습니다.

 

public interface Cloneable {}

 

구현되지 않은 인터페이스임을 알 수 있습니다.

그럼에도 이 메소드를 사용하는 이유는 클래스 Fruit이 복제 가능하다는 것을 표시하기 위해서입니다.

이 인터페이스를 구현하지 않고 호출하면, 에러가 발생할 것입니다.


다음은 API 문서에 있지만 다루지 않은 다른 메소드들입니다.

 

finalize() 메소드

가비지 컬렉터가 객체에 대한 참조가 없을 때 호출하는 메소드입니다.

아무도 참조하지 않기 때문에 방치해두면 문제가 발생할 수 있습니다.

 

getClass() 메소드

객체의 런타임 클래스를 호출합니다.

즉, 객체가 현재 어떤 클래스의 인스턴스를 가지고 있는지 알 수 있습니다.

 

class Parent {
    ...
}

class Child extends Parent {
    ...
}

public class Example {
    public static void main(String[] args) {
        Parent p = new Child();
        System.out.println(p.getClass());
    }
}

// 실행결과
class com.company.Child

 

hashCode() 메소드

객체의 해시 코드 값을 반환합니다.

두 객체가 동일하면 해시 코드도 동일해야 합니다.

때문에 equals() 메소드를 오버라이딩 할 때, hashCoid()도 같이 오버라이딩해야 합니다.

 

final로 선언된 notify(), notifyAll(), wait() 메소드

해당 메소드들은 독립적으로 실행되는 스레드 간의 동기화에 사용됩니다.

 


추가

1.

final 키워드는 static과 함께 사용하는 경우가 있습니다.

final은 '최종'의 의미로 더 이상 변경이 불가능하도록 만들고, static은 '정적'이란 의미로

인스턴스 간에 데이터 공유가 필요해서 클래스 멤버로 선언할 때 사용합니다.

 

즉, 클래스를 이용해서 여러 인스턴스를 생성해도 절대 변하지 않는 값을 만들기 위해 static final로 지정해줍니다.

 

이 두 키워드를 함께 사용하면 아래와 같이 2가지 경우로 나뉩니다.

  • final static
  • static final

둘의 차이는 없으며 동일하게 작동합니다. 그러나 자바에서는 'static final'로 사용할 것을 권장합니다.

 

2.

클래스 간의 상속관계가 너무 깊으면 오히려 단점으로 작용해 캡슐화를 깨뜨릴 수 있습니다.

캡슐화 : 만일의 상황(타인이 외부에서 조작)에 대비해 외부에서 특정 속성이나 메소드를 사용할 수 없도록 숨김.

부모 클래스의 구현이 자식 클래스에게 노출되는 상속은 캡슐화를 깨뜨립니다.

캡슐화가 깨짐으로써 자식 클래스가 부모 클래스에게 강하게 결합, 의존하게 되고

강한 결합, 의존은 변화에 유연하게 대처하기 어려워집니다.

 

자식 클래스에서 필요 없는 기능인데 상속받아야 하는 경우도 발생합니다.

 

부모 클래스의 멤버에서 버그가 발생해서 코드를 수정하는 과정에서 자식 클래스에 악영향을 끼치는 경우도 있습니다.

 

따라서 상속은 하위 자료형이 확실한 경우에 사용하는 것이 좋습니다.

이를 두고, 'is-a 관계'라고 부릅니다.


참고

https://opentutorials.org/course/1223/6241

 

Object 클래스 - 생활코딩

상속 자바에서 상속이란 필수적이다. 여러분이 상속하건 하지 않았건 기본적인 상속을 하게 된다. package org.opentutorials.javatutorials.progenitor; class O {} 위의 코드는 아래와 코드가 같다. package org.opent

opentutorials.org

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

 

Object as a Superclass (The Java™ Tutorials > Learning the Java Language > Interfaces and Inheritance)

The Java Tutorials have been written for JDK 8. Examples and practices described in this page don't take advantage of improvements introduced in later releases and might use technology no longer available. See Java Language Changes for a summary of updated

docs.oracle.com