프로그래밍 언어/Java

[Java] 추상 클래스와 인터페이스, 그리고 둘의 차이점

happy_life 2022. 6. 24. 16:54

목차

1. 추상클래스란?

2. 인터페이스란?

3. 추상 클래스와 인터페이스 차이점

 

 

1. 추상클래스란?

클래스를 설계도에 비유한다면, 추상클래스는 미완성 설계도에 비유할 수 있다. 클래스가 미완성이라는 것은 멤버의 개수와 관계된 것이 아니라, 미완성 메서드(추상메서드)를 가지고 있다는 것이다. 미완성이기 때문에 추상 클래스는 인스턴스를 생성할 수 없다. 추상 메서드는 상속으로 구현해야 인스턴스를 생성하고 사용할 수 있다.

 

 

코드 예제

/**
 * 추상 클래스
 */
abstract class Fruit {
    abstract void Print();
}

class Apple extends Fruit {
    @Override
    void Print() {
        System.out.println("나는 사과입니다."); // 추상 메서드 구현  
    }
}

class Peach extends Fruit {
    @Override
    void Print() {
        System.out.println("나는 복숭아입니다."); // 추상 메서드 구현
    }
}

 

 

2. 인터페이스란?

인터페이스도 일종의 추상 클래스이다. 하지만 추상화정도가 추상 클래스보다 높아 일반 메서드는 사용할 수 없다는 점에서 차이가 있다. 추상 클래스는 추상 메서드를 가질 수 있으므로, 일반 메서드도 포함할 수 있지만, 인터페이스는 일반 메서드를 포함할 수 없다. (최근 jdk 1.8 이상에서는 static default 메서드를 허용해주니 자바 8이상이면 참고하자)

 

인터페이스에서는 추가적인 제약사항이 더 있는데 다음과 같다.

모든 멤버변수는 public static final 이어야 하며 이를 생략할 수 있다.

2.1 인터페이스의 구현

인터페이스도 추상클래스처럼 그 자체로 인스턴스를 생성할 수 없고 구현 후에만 인스턴스를 생성할 수 있다.

 

코드예제

 

Movable.interface

public interface Movable {
    public void move(int x, int y);
}

 

Attackable.interface

public interface Attackable {
    public void attack(Solider solider);
}

 

ImplementationEx1.class

public class ImplementationEx1{
    public static void main(String[] args) {
        Solider solider1 = new Solider("Kim");
        Solider solider2 = new Solider("Park");

        solider1.move(3,4); 
        //x: 3만큼 이동하였습니다.
        //y: 4만큼 이동하였습니다.
        solider1.attack(solider2);
        //Park을 공격했습니다.
    }
}

class Solider implements Movable, Attackable {
// Solider의 멤버변수
    String name;
    int x = 0;
    int y = 0;
// 생성자
    Solider(String name) {
        this.name = name;
    }

    @Override
    public void attack(Solider solider) {
        System.out.println(solider.name + "을 공격했습니다.");
    }

    @Override
    public void move(int x, int y) {
        this.x += x;
        this.y += y;
        System.out.println("x: " + x + "만큼 이동하였습니다.");
        System.out.println("y: " + y + "만큼 이동하였습니다.");
    }
}

Solider 클래스에 Movable 인터페이스와 Attackable 인터페이스를 구현해주었다. 

 

2.2 인터페이스 간 상속

인터페이스 간에 상속을 할 수 있다.

 

코드 예제

Fightable.interface

public interface Fightable extends Attackable, Movable{
}

 

2.3 인터페이스를 이용한 다형성

다형성을 공부할 때 자식 클래스의 인스턴스를 부모 타입의 참조변수로 참조하는 것이 가능하다고 배웠다. 인터페이스 또한 구현 클래스의 부모라고 볼 수 있으므로, 다형성을 활용할 수 있다.

 

이런 다형성을 활용하여 메서드의 리턴 타입으로 인터페이스의 타입을 지정하는 것이 가능하다.

 

코드예제

Fightable method() {
        Solider solider = new Solider("Min");
        return solider;
    }

리턴타입이 인터페이스라는 것은 메서드가 해당 인터페이스를 구현한 클래스의 인스턴스를 반환한다는 것을 의미한다.

이는 반드시 알아야하니 꼭 기억하자.

 

 

2.4 인터페이스의 장점

1. 개발시간을 단축시킬 수 있다.

인터페이스를 구현해 코드를 작성하는 쪽에서는 메서드의 선언부만 알면 바로 작업을 진행할 수 있다. 또한 인터페이스를 만드는 쪽에서는 구현하는 클래스가 모두 작성될 때까지 기다리지 않고도 인터페이스를 만들 수 있다.

 

2. 표준화가 가능하다.

프로젝트에 사용되는 기본 틀을 인터페이스로 작성한 다음, 개발자들에게 인터페이스를 구현해 각각 코드를 작성하게 하여, 일관된 기준안에서 정형화된 프로그램을 개발할 수 있다.

 

3. 서로 관련없는 클래스에 관계를 맺어줄 수 있다.

A,B 라는 관련 없는 클래스에 똑같은 interface를 달면 그 interface를 공통적으로 구현하도록 함으로써 관계를 맺어줄 수 있다.

 

4. 독립적인 프로그래밍이 가능하다.

인터페이스를 활용해 클래스와 클래스간 직접적인 연관관계를 간접적인 관계로 변경해, 하나의 클래스가 변경되어도 다른 클래스를 변경하지 않고 독립적으로 코드를 작성할 수 있게 된다.

 

예를 들어, 저장소 클래스와 서비스 클래스가 있다고 하자. 서비스 클래스에서 저장소 클래스에 데이터를 저장하는 코드를 작성해놨다고 하자. 만약 저장소의 종류를 바꾸게 된다면 서비스 클래스에서도 저장소와 관련된 코드를 모두 수정해야 한다. 하지만 인터페이스를 통해 저장소 인터페이스와 서비스 클래스를 간접적으로 연결해두면 서비스 클래스에서 코드를 수정하지 않아도 된다. 다형성을 활용하여 인터페이스에서 저장소 구현체를 종류별로 return해주면 되고, 서비스 클래스에서는 그 구현체만을 받아 사용하기만 하면 되는 것이다.

 

2.5 인터페이스의 이해

인터페이스를 좀 더 깊게 이해하기 위해서 아래의 사항을 염두에 두자.

- 클래스를 사용하는 쪽(User)와 클래스를 제공하는 쪽(Provider)가 있다.
- 메서드를 사용(호출)하는 쪽(User)에서는 사용하려는 메서드(Provider)의 선언부만 알면 된다. ( 내용은 몰라도 된다.)

코드예제

public class InterfaceEx {
    public static void main(String[] args) {
        A a = new A();
        a.methodA(new B());
    }
}

class A {
    public void methodA(B b) {
        b.methodB();
    }
}

class B {
    public void methodB() {
        System.out.println("methodB()");
    }
}

클래스 A는 B의 인스턴스를 생성해 b 메서드를 호출하게 된다. 이 때 A는 User, B는 Provider라고 한다.  이 둘은 직접적으로 연결되어 있는데, 따라서 만약 Provider가 다른 클래스로 변경되면 A의 메서드 부분도 변경되어야 한다.

하지만 클래스 A가 B를 직접 호출하지 않고, 인터페이스를 매개로 클래스 B에 접근하도록 하면 클래스 B가 변경되거나 다른 클래스로 변경되어도 클래스 A는 전혀 영향을 받지 않게 된다.

 

코드예제

public class InterfaceEx {
    public static void main(String[] args) {
        A a = new A();
        I i = new B();
        I i2 = new C();

        a.methodA(i);//methodB
        a.methodA(i2);//methodC
    }
}

interface I {
    public abstract void method();
}

class A {
    public void methodA(I i) {
        i.method();
    }
}

class B implements I{

    @Override
    public void method() {
        System.out.println("methodB");
    }
}

class C implements I {
    @Override
    public void method() {
        System.out.println("methodC");
    }
}

클래스 A는 여전히 B를 호출하지만, 인터페이스 I와만 직접적인 관련이 있으므로, 클래스 B가 C로 변경되어도 전혀 영향을 받지 않는다.

 

3. 추상 클래스와 인터페이스 차이점

1. 인터페이스는 인터페이스로부터만 상속받을 수 있으며, 클래스와 달리 다중상속이 가능하다.

 

클래스를 다중 상속하려는 경우 에러 발생

 

인터페이스 다중 상속 가능

인터페이스로 다중 상속은 가능하긴 하지만, 자바에서 인터페이스로 다중 상속을 구현하는 경우는 거의 없으니, 참고로 알아만 두자.