Java

[ Java ] 추상클래스 vs 인터페이스

walwal_ 2023. 12. 22. 00:24

 

 

 

 

추상클래스와 인터페이스? 차이점은 무엇이며 언제 적절히 사용해야 할까요?

 

자세히 살펴 보도록 하겠습니다.

 

 

 

 


 

 

 

추상 클레스

 

추상클레스란?

 

일부 메서드가 추상 메서드로 선언된 클래스입니다.

추상 클레스에는 구현부가 있는 일반 메서드가 존재할 수 있습니다.

 

코드 예시)

abstract class Singer {

    private String name;
    
    abstract void sing(); // 추상 메서드

    public String getName() { // 일반 메서드
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
}

 

 

추상클래스의 특징

 

 

1. 클래스와 추상 메서드에 abstract 키워드를 사용합니다.

2. 로직이 있는 메서드와 추상 메서드를 모두 가질 수 있습니다.

3. 밀접하게 관련된 클래스 끼리 코드를 공유할 때 사용합니다.

    공통된 기능이나 행위를 가지고 있을 때 추상 클래스를 사용하여 중복 코드를 줄이고 구조를 일관성 있게 유지할 수 있습니다.

4. public이 아닌 다른 접근 제어자를 사용할 때 사용가능합니다.

 

5. static , final 등을 사용할 수 없습니다.

 

 

 

 

 

인터페이스

 

 

인터페이스란?

 

 

인터페이스는 추상클래스와 비슷합니다. 추상클래스와 같이 추상 메서드를 가집니다.

하지만 구현부가 있는 일반 메서드가 존재할 수 없습니다.

 

모든 멤버 변수는 public static final 이며 생략 가능하다.

모든 메서드는 public abstract 며 생략 가능함.

단, static메서드와 디폴트 메서드는 예외로 생략이 불가능하다.

 

 

코드 예시 ↓

 

interface Sound {

    String sound = null; // 상수,  public static final String sound = null

    void makeSound(); // 추상 메서드, public abstract void makeSound();

    default void defaultMethod(){ // default 메서드
        System.out.println("A default Method");
    };

    static void staticMethod(){ // static 메서드
        System.out.println("A static Method");
    };
}

 

 

 

 

 

인터페이스 특징

 

 

1. 인터페이스는 interface 키워드를 사용합니다.

2. 인스턴스를 생성할 수 없고 상수만 가질 수 있습니다.

3. 추상메서드의 접근 제한자는 public과 default만 가능합니다.

 

4. 메서드 구현부가 없는 추상 메서드를 가집니다.

     (JDK 1.8 이상) 하지만, 디폴트 메소드, static 메서드를 가질 수 있습니다.

     

    default 메서드란? 

    - 접근 제어자의 default 와 다른 개념입니다.

    - 인터페이스에서 메서드 body 를 가지는 메서드입니다.

    - 이미 작성된 인터페이스에서 공통된 기능을 추가하고자 할때 사용합니다.

    - 인터페이스에 정의된 메소드들을 구체 클레스가 모두 override 해야 했다면

       default 메서드는 구체클레스에서 override 없이 사용 가능합니다.

5. 다중 상속이 가능합니다.

 

 

 

default 메서드와  static 메서드 사용 예시

 

public interface A {

    void abstractMethod(); // 추상 메서드

    default void defaultMethod(){ // 디폴트 메서드
        System.out.println("A default Method");
    };

    static void staticMethod(){ // static 메서드
        System.out.println("A static Method");
    };
}
class B implements A{

    @Override
    public void abstractMethod() { // 추상 메서드를 override 함.
        System.out.println("B abstractMethod");
    }
    
    public static void main(String[] args) {
        B b = new B();
        b.abstractMethod(); // B abstractMethod
        b.defaultMethod(); // A default Method  디폴트 메서드를 override 하지 않고 사용
        A.staticMethod(); // A static Method    static 메서드를 override 하지 않고 사용
    }
}

 

 

 

 

 

추상 클레스와 인터페이스의 상속

 

 

추상 클레스와 인터페이스의 상속에 대해 코드를 통해 비교해 보도록 하겠습니다.

 

추상 클레스의 단일 상속

 

 

상속을 받는 자식 클래스 입장에서 추상 클래스는 단 하나만 상속받을 수 있습니다.

추상 클래스를 상속받을 때는 부모 클래스에 정의된 추상 메서드를 반드시 구현해야 합니다.

 

 

 

 

↓  코드 예시 ↓

 

 

 

1. 추상 클레스를 이용한 코드 작성

 

 

노래와 작곡을 잘하는 싱어송라이터를 표현하기 위한 코드입니다.

Singer, SongWriter 에 해당하는 추상 클레스를 작성했고, SingerSongWriter class 에게 상속해 사용하려 합니다.

 

 

abstract class Singer {
    abstract void sing();
}

abstract class SongWriter {
    abstract void songWriter();
}

class SingerSongWriter extends Singer {

    @Override
    public void sing() { // 추상 클래스 Singer 의 sing 메서드를 구현.
        System.out.println("노래 잘해용");
    }

    public static void main(String[] args) {
        SingerSongWriter singerSongWriter = new SingerSongWriter();
        singerSongWriter.sing();
    }
}

 

 

Singer 를 상속 받았지만 SongWriter 를 아직 상속받지 못한 상태입니다. 

 

 

 

 

2. 다중 상속으로 인한 예외 발생

 

SongWriter를 상속 받으려 했으나 추상 클레스 특성상 다중 상속이 불가 한 상황입니다.

 

하나 이상 상속 받을 수 없습니다.

 

 

 

3. 거듭 상속? 을 이용해 해결?

 

 

sing 이 songwriter 를 상속받고, SingerSongWriter 를 상속받는 구조라면 모두 나타낼 수 있지 않을까요?

 

abstract class Singer {
    abstract void sing();
}

abstract class SongWriter extends Singer{ // SongWriter가 Singer를 상속받음.
    abstract void songWriter();
}

class SingerSongWriter extends SongWriter { // SingerSongWriter가 Singer를 상속받은 SongWriter를 상속받음.

    @Override
    public void sing() {
        System.out.println("노래 잘해용");
    }
    @Override
    void songWriter() {
    	System.out.println("작곡 잘해용");
    }
    
    public static void main(String[] args) {
        SingerSongWriter singerSongWriter = new SingerSongWriter();
        singerSongWriter.sing(); // 노래 잘해용
        singerSongWriter.songWriter(); // 작곡 잘해용
    }
}

 

 

성공했습니다.

 

 

 

4. Sing 과 SongWriter 에 Dance 기능도 추가된다면?

 

추상클래스로 나타내려면, 아마도 Sing + Dance , SongWriter + Dance, Sing + SongWriter + Dance 등의 조합의 새로운 코드를 작성해야 할 것입니다. 

 

 

 

 

그렇다면 인터페이스는 어떨까요? 

 

 

인터페이스의 다중 상속

 

 

인터페이스는 여러 개를 동시에 상속받을 수 있습니다.

 

 

 

↓  코드 예시 ↓

 

 

 

1.  인터페이스를 통해 다중 상속을 받습니다.

 

 

SingerSongWriter를 먼저 구현해 보았습니다.

 

 

interface Singer {
    void sing();
}
interface SongWriter {
    void songWriter();
}

class SingerSongWriter implements Singer, SongWriter{ // Singer, SongWriter를 동시에 상속받음.

    @Override
    public void sing() {
        System.out.println("노래 잘해용");
    }

    @Override
    public void songWriter() {
        System.out.println("작곡 합니당");
    }

    public static void main(String[] args) {
        SingerSongWriter singerSongWriter = new SingerSongWriter();
        singerSongWriter.sing(); // 노래 잘해용
        singerSongWriter.songWriter(); // 작곡 합니당
    }
}

 

 

 

 

다중 상속이 가능해 다중 상속 상황에 인터페이스가 유리함을 알 수 있습니다.

 

 

 

 

2. Dance 기능 추가

 

interface Dancer {
    void dence();
}
... 생략 ...
class SingerSongWriter implements Singer, SongWriter{
	... 생략 ...
}
class SingerSongWriterAndDancer implements Singer, SongWriter, Dancer{
	... 생략 ...
}

 

 

마찬가지로 손쉽게 Dance 기능을 추가 하는데 성공했습니다.

 

 

만약 다중 상속 시 서로 다른 인터페이스에 같은 이름의 메서드가 있다면 어떤 클래스의 매서드를 상속받을까요? 궁금하다면.. 

 

더보기

 

 

해당 문제는 ' 다이아 몬드 문제'라 부른다고 합니다.

다이아몬드 문제란 다중 상속에서 여러 클래스가 같은 클래스를 상속할 때 발생하는 모호성입니다.

자바에서는 다중 상속을 클래스 단위로 지원하지 않습니다.

하지만 인터페이스에서는 여러 개의 인터페이스를 구현할 수 있어 다이아몬드 문제가 발생할 수 있다고 봅니다.

쉽게 말해 다중 상속 시 같은 이름의 추상메서드가 있을 때 어떤 인터페이스의 메서드를 상속받아야 하는지 판별할 수 없는 문제입니다.

 

하지만 문제될것이 없습니다!

 

1. default 메서드와 static 메서드부모 클래스 명이 명시되어야 하기때문상관없으며 충돌시 오버라이드로 해결가능합니다.

2. abstractMethod 역시 이름이 같다 해도, 각 부모 클래스에 구체적인 선언부가 없어 자식 클래스에서 재 정의해 사용하기 때문에 자식 클래스가 오버라이드한 내용 대로 출력됩니다. 때문에 문제되지 않습니다.

 

 

↓  코드 예시 ↓

 

 

 

메서드 이름이 같은 인터페이스 A와 ATemp 코드입니다.

이름이 같다해도, 자식 클래스에서 재정의가 이루어 지거나, 부모 클래스 이름이 명시되기 때문에 문제되지 않음을 확인할 수 있습니다.

interface A {
    void abstractMethod();

    default void defaultMethod(){
        System.out.println("A default Method");
    };

    static void staticMethod(){
        System.out.println("A static Method");
    };
}

interface ATemp {
    void abstractMethod();

    default void defaultMethod(){
        System.out.println("ATemp default Method");
    };

    static void staticMethod(){
        System.out.println("ATemp static Method");
    };
}

class B implements A, ATemp{

    @Override
    public void abstractMethod() {
        System.out.println("B abstractMethod");
    }

    @Override
    public void defaultMethod() {
        A.super.defaultMethod();
        ATemp.super.defaultMethod();
    }

    public static void main(String[] args) {
        B b = new B();
        b.abstractMethod(); // B abstractMethod
        b.defaultMethod(); // A default Method, ATemp default Method
        A.staticMethod(); // A static Method
    }
}

 

 

 

 

인터페이스의 활용

 

 

 

인터페이스는 공통된 관계를 나타내기 위해 인터페이스를 사용하기도 합니다.

 

자동차의 경적 소리와 동물의 울음소리에 대해 ‘소리’라는 인터페이스를 만들어 관계를 나타내보았습니다.

 

 

 

 

 

↓  코드 예시 ↓

 

interface Sound { //  관계를 나타내기 위한 인터페이스
    void makeSound();
}

interface Animal{
    String getName();
    int getAge();
}

class Dog implements Animal, Sound{ // 인터페이스의 다중 상속

    private String name;
    private int age;

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

    @Override
    public void makeSound() {
        System.out.println("왈왈");
    }

    @Override
    public String getName() {
        return name;
    }
    
    @Override
    public int getAge() {
        return age;
    }
}
class Car implements Sound{ 

    @Override
    public void makeSound() {
        System.out.println("빵빵");
    }
}

class Main{
    public static void main(String[] args) {
        Sound dog = new Dog("강아지", 10);
        Sound car = new Car();
        dog.makeSound(); // 왈왈
        car.makeSound(); // 빵빵
    }
}

 

 

 

 

 

 

인터페이스와 추상 클레스를 같이 써보자


인터페이스와 추상 클래스는 다형성을 지원합니다.

다형성을 객체지향 프로그래밍(OOP)의 핵심 개념 중 하나입니다.

다형성은 객체 지향 프로그래밍에서 하나의 인터페이스나 추상 클래스를 상속받아 사용할 수 있음을 나타냅니다.

 

인터페이스와 추상 클래스를 함께 사용해 객체지향적 프로그래밍을 하고 코드의 재사용성을 높일 수 있습니다.

 

 

 

↓  코드 예시 ↓

 

 

1. 인터페이스 + 추상 클래스

 

 

아래 예시 코드는 '학생' 인터페이스가 학생의 행동을 정의합니다.

'학생' 인터페이스를 추상 클래스인 '초등학교 학생', '고등학교 학생'이 상속받아 '학생'의 공통된 행동을 구현합니다.

이때 초등학생, 고등학생에 따라 구현부가 달라짐을 알 수 있습니다.

마지막으로 최종 구현체 클래스에서 알맞는 추상 클래스를 상속받아 메서드를 사용합니다.

 

interface Student { // 인터페이스
    void goToSchool();
    void learnMath();
    void leaveSchool();
}

abstract class ElementarySchoolStudent implements Student{ // 인터페이스를 상속받은 추상클레스
    @Override
    public void goToSchool() {
        System.out.println("초등학교로 등교합니다.");
    }
    @Override
    public void learnMath() {
        System.out.println("덧셈을 배웁니다.");
    }
    @Override
    public void leaveSchool() {
        System.out.println("2시에 하교합니다.");
    }
}

abstract class highSchoolStudent implements Student{// 인터페이스를 상속받은 추상클레스
    @Override
    public void goToSchool() {
        System.out.println("고등학교로 등교합니다.");
    }
    @Override
    public void learnMath() {
        System.out.println("미적분을 배웁니다.");
    }
    @Override
    public void leaveSchool() {
        System.out.println("6시에 하교합니다.");
    }
}

class Suhyeon extends HighSchoolStudent{

    public static void main(String[] args) {
        Student suhyeon = new Suhyeon();
        suhyeon.goToSchool(); 
    }
}

 

 

 

 

 

2. default 메서드를 추가해 보았습니다.

 

 

 

인터페이스에 초등학생, 고등학생의 공통된 행동인 ‘급식을 먹는다.’ 와 ‘수업을 듣는다.’ 를 추가하였습니다.

interface Student {
    void goToSchool();
    void learnMath();
    void leaveSchool();

// 추가
    default void eatLunch(){
        System.out.println("맛있는 점심을 먹습니다.");
    };
    default void takeClasses(){
        System.out.println("지루한 수업을 들어요");
    };
}

... 생략 ...

abstract class HighSchoolStudent implements Student{
    @Override
    public void goToSchool() {
        System.out.println("고등학교로 등교합니다.");
    }
    @Override
    public void learnMath() {
        System.out.println("미적분을 배웁니다.");
    }
    @Override
    public void leaveSchool() {
        System.out.println("6시에 하교합니다.");
    }
}

class Suhyeon extends HighSchoolStudent{

    public static void main(String[] args) {
        Student suhyeon = new Suhyeon();
        suhyeon.goToSchool(); // 고등학교로 등교합니다.
        suhyeon.takeClasses();  // 지루한 수업을 들어요
    }
}

 

 

 

 

인터페이스의 default 메서드를 사용해 공통된 기능을 손쉽게 사용할 수 있도록하였습니다.

default 메서드의 취지는 이미 작성된 코드에서 공통된 기능을 추가하도록하기 위함입니다.

인터페이스 생성시 작성하는것을 지양하는것이 좋다고 생각합니다.

인터페이스는 주로 다중 상속과 관련된 기능을 제공하는 데 집중하도록 작성하는 것이 좋습니다.

 

 

 

 

 

 

 

 

 

💡 결론 💡

 

공통

 

추상 클래스와 인터페이스 모두 상속받은 자식 클래스에게 구현을 위임합니다.

 

차이

 

인터페이스

- 다중 상속이 가능합니다.

- 관련 없는 클래스들 끼리 관계를 맺을 수 있습니다.

- 특정 데이터 타입의 동작을 지정하지만 해당 동작을 누가 구현하는지 중요하지 않을 때 사용하기 좋습니다.

 

추상 클래스

- 다중 상속이 불가능 합니다.

- 인터페이스와 다르게 static 또는 final 을 사용할 수 없습니다.

 

 

 

 

 

참고한 자료

https://youtu.be/T1BJzC9xb0g?si=VUYbm45g5aqA3ofB

책 Java의 정석 - 남궁 성