본문 바로가기
  • 시 쓰는 개발자
1일 1개념정리 (24년 8월~)/Java

1일1개 (18) - 상속과 구현

by poetDeveloper 2024. 8. 27.

1일 1개념정리 24.08.09.금 ~ 

 

큰 결정에 큰 동기가 따르지 않을 때도 있다. 하지만 큰 결심이 따라야 이뤄낼 수 있다.

무조건 무조건 1일 1개의 개념 정리하기 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!


#18. extends VS implements

둘 다 상속에 대한 내용이라 좀 헷갈릴 수 있다. 차이를 알아봅시다. 영단어로만 따지면 extend는 확장, 연장이고 implement는 구현인데 어떤 공통점과 차이가 있을까?

 

extends 상속

클래스가 클래스를, 인터페이스가 인터페이스를 상속받을 때 사용한다.

  • 클래스가 다른 클래스를 상속받을 때 사용하고, 부모 클래스의 멤버와 메소드를 자식 클래스에서 "그대로" 사용할 수 있다. 추가 오버라이딩 할 필요 없이 부모 클래스에서 구현된 것은 직접 사용 가능하다.
  • 클래스가 인터페이스를 상속할 수는 없음. 클래스는 인터페이스를 implements로 구현할 수 있을 뿐임.
  • 단, private이 붙은 필드는 자식에서 사용할 수 없다.
  • 클래스는 하나의 클래스만 상속받을 수 있다. 다중 상속 X.
  • 인터페이스가 다른 인터페이스를 상속받을 때도 extends를 사용한다. 인터페이스는 다중 상속이 가능해서 여러 인터페이스를 동시에 상속받을 수 있다. (인터페이스는 인터페이스만 상속 가능)
  • 주의) 클래스 상속은 부모 것을 그대로 받는 것이고, 인터페이스 상속은 선언된 메소드와 필드를 물려받는 것.
// 부모 클래스
class Animal {
    String name;
    
    void eat() {
        System.out.println(name + " is eating.");
    }
}

// 자식 클래스 - Animal 클래스를 상속받음
// Dog에는 name 변수도 없고, eat 메소드도 없음 !! ★★★★★
class Dog extends Animal {
    void bark() {
        System.out.println(name + " is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        
        // Dog에 없는 name, eat 사용 가능
        dog.name = "쪼꼬";
        dog.eat();
        
        dog.bark();
    }
}

 

<출력>

쪼꼬 is eating.
쪼꼬 is barking.

 

implements 구현

클래스가 인터페이스를 "구현"할 때 사용한다.

  • 인터페이스는 메소드의 선언만 포함하고 있고 실제 구현이 없으니까 클래스에 의해 메소드를 구현할 때 사용한다. 이때, 인터페이스에 선언된 모든 메소드를 구현해야한다.
  • 하나의 클래스는 여러 인터페이스를 implements로 구현할 수 있다. 자바에서 다중 상속을 지원하지 않기 때문에, 인터페이스를 통해 다중 구현을 허용하는 방식이다.
// 인터페이스
interface Animal {
    void sound();
    void eat();
}

// 다중 구현 예시
interface Runnable {
    void run();
}

// 인터페이스를 구현하는 클래스
class Dog implements Animal, Runnable {

	// sound, eat, 그리고 run까지 셋 다 구현해야함
    public void sound() {
        System.out.println("Woof");
    }
    
    public void eat() {
        System.out.println("Dog is eating.");
    }
    
    public void run() {
        System.out.println("Dog is running.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound(); // 인터페이스에서 구현된 메소드
        dog.eat();   // 인터페이스에서 구현된 메소드
        dog.run();   // 인터페이스에서 구현된 메소드
    }
}

 

<출력>

Woof
Dog is eating.
Dog is running.

 

extends   VS   implements 차이점 비교

extends (상속)

  • 클래스 상속 : 하위 클래스가 상위 클래스의 모든 필드와 메소드를 상속받아 재사용할 수 있다. 필요에 따라 상위 클래스의 메소드를 오버라이딩 할 수도 있다.(필수는 아님) 클래스 상속은 부모 클래스와 자식 클래스 간 "is-a" 관계인데, 예를 들어 Dog extends Animal이라고 하면 "Dog는 Animal이다"라는 의미라서 Dog 클래스가 Animal 클래스의 속성과 동작을 물려받는다.
  • 인터페이스 상속 : 인터페이스가 다른 인터페이스를 상속받아 기존 인터페이스의 메소드를 상속받고, 추가로 더 많은 메소드를 정의할 수 있다. 인터페이스 상속에서는 구현이 없고, 선언된 메소드만 물려받는다.

implements (구현)

  • 인터페이스 구현 : 클래스가 인터페이스를 구현할 때는 인터페이스에 선언된 모든 메소드를 반드시 구현해야 한다. implements는 클래스와 인터페이스 간 "can-do" 또는 "can-act-as" 관계로 표현된다. 예를 들어, Dog implements Animal은 "Dog는 Animal처럼 행동할 수 있다"라는 의미로, Dog 클래스가 Animal 인터페이스에서 정의된 행동을 실제로 구현해야 한다는 뜻이다.

 

정리) 그냥 다중 상속 허용하면 안되나 ? implements가 왜 필요한 것인가 ?

1. 다중 상속의 문제점 - 다이아몬드 문제

다중 상속을 허용하면 복잡하고 모호해질 수 있다. 특히 이를 허용할 경우 "다이아몬드 문제"가 발생할 수 있다.

class A {
    void display() {
        System.out.println("A");
    }
}

class B extends A {
    void display() {
        System.out.println("B");
    }
}

class C extends A {
    void display() {
        System.out.println("C");
    }
}

// 다중 상속이 허용된다면...
class D extends B, C {
    // display() // 무슨 display를 써야함 ??
}

위의 예시에서 D 클래스가 B와 C를 동시에 상속받으면, display() 메소드를 호출할 때 B의 display()를 사용할지 C의 display()를 사용할지 자바 컴파일러가 결정할 수 없다. 이런 모호성 때문에 자바는 다중 상속을 허용하지 않는다.

 

2. 인터페이스

그래서 다중상속의 모호함을 해소하고, 다중 구현을 가능하게 하는 우회방식인 인터페이스를 사용한다. 인터페이스는 클래스가 다양한 "행동 계약"을 준수하도록 하는 방법이다. 인터페이스는 구현 없이 메소드만 있기에 이를 구현하는 클래스가 메소드를 구체적으로 오버라이딩으로 정의해야 한다.

 

3. extends와 implements는 왜 나눠놓았는가?

  • 명확성 : extends는 클래스 간의 명확한 상속 관계를 표현할 수 있다. 자식 클래스는 부모 클래스의 특성과 동작을 물려받아서 확장한다.
  • 유연성 : implements를 사용한 인터페이스 구현은 다양한 클래스가 특정 행동을 구현할 수 있게 한다. 여러 인터페이스를 동시에 구현할 수 있기 때문에 다중 상속의 모호성을 피하면서도 여러 기능을 클래스에 추가할 수 있다.

 

4. 결론 - 왜 다중 상속 대신 인터페이스를 사용하는가?

  • 복잡성 감소 : 다중 상속의 모호성을 피하고 더 명확하고 직관적으로 유지 가능
  • 재사용성 향상 : 인터페이스를 통해 다양한 클래스가 특정 행동을 구현할 수 있으므로 코드 재사용성과 확장성 향상
  • 유연성 보장 : 클래스가 여러 인터페이스를 구현할 수 있으므로 다양한 기능을 가진 객체를 만들 수 있다.

 

참고

클래스-클래스 extends : 가능

→ 다른 클래스를 상속받아 필드와 메소드 재사용 가능

클래스-클래스 implements : 불가능

애당초 implements 정의상 관계 성립 X

 

클래스-인터페이스 extends : 불가능

 extends 특성상 애당초 상속 X. 인터페이스 구현한 것도 없으니 물려줄 것도 없음. 인터페이스를 extends로 물려받을 수 있는 것은 인터페이스뿐임.

클래스-인터페이스 implements : 가능

클래스는 인터페이스를 implements로 구현하여 인터페이스에 정의된 모든 메소드를 정의한다.

 

인터페이스-인터페이스 extends : 가능

인터페이스는 다른 인터페이스를 extends로 상속받아 메소드 선언을 추가하거나 변경할 수 있다.

인터페이스-인터페이스 implements : 불가능

애당초 인터페이스에선 구현 안하니까 구현을 꼭 해야하는 implements 못씀.

 

인터페이스-클래스 extends : 불가능

인터페이스는 클래스를 상속받을 수 없음. 애당초 역할이 다름

인터페이스-클래스 implements : 불가능

반대가 되어야함. 클래스가 인터페이스를 implements로 구현하는 것이 맞음.