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

kuku·2023년 10월 16일
0

Java

목록 보기
7/7

추상 클래스와 인터페이스는 비슷한 듯 다르다. 둘의 문법적 특징, 어떤 상황에서 어떤 것을 선택해야 하는지 등을 정리해보고자 한다.

📖추상 클래스와 인터페이스

📁추상 클래스의 문법적 특징

추상 클래스에 대한 예시 코드는 다음과 같다.

public abstract class Shape {

    // 추상 메서드
    public abstract double area();

    public abstract double perimeter();

    // 일반 메서드
    public void displayAreaAndPerimeter() {
        double shapeArea = area();
        double shapePerimeter = perimeter();
        System.out.println("Area: " + shapeArea);
        System.out.println("Perimeter: " + shapePerimeter);
    }
}
  • 클래스 선언에 abstract라는 키워드가 붙는다
  • 일반 클래스와 마찬가지로 메서드 정의가 가능하다
  • 추상 메서드로 선언하고, 메서드에 대한 동작은 정의해주지 않을 수 있다
    • 정의되지 않은 메서드는 추상 클래스를 상속받은 클래스에서 구현한다
  • 추상 클래스의 메서드는 아직 구현되지 않은 추상 클래스의 다른 메서드(추상 메서드)를 호출할 수 있다
    • 추상 메서드가 제공하는 강력한 추상화 문법으로, 공통된 로직은 추상 클래스에 미리 정의해두고, 각 구현 클래스에서 달라져야 하는 부분만 추상 메서드를 오버라이딩하여 작성할 수 있다 (템플릿 메서드 패턴)
  • 추상 클래스는 인스턴스 생성을 할 수 없다
  • 일반적으로 하나 이상의 추상 메서드를 포함한다
    • 그러나, 포함하지 않더라도 클래스 선언에 abstract 키워드가 붙는다면 추상 클래스로 판단한다

앞서 추상 클래스는 인스턴스 생성이 불가하다고 적었으나, 다음과 같이 생성하는 방법이 있긴 하다.

Shape shape = new Shape() {

	@Override
    public double area() {
    	return 1.5;
    }
    
    @Override
    public double perimeter() {
    	return 0.5;
    }
    
};

new 키워드로 인스턴스를 생성하면서 동시에 추상 메서드에 대한 구현을 넣어주는 것이다. 이것은 추상 메서드가 즉석에서 오버라이딩된 것과 같은 효과를 가진다. (찾아보니 이런 식의 구현을 익명 내부 클래스라고 한다고 한다. 추후 내용 정리해볼 것!)

📁인터페이스의 문법적 특징

인터페이스에 대한 예시 코드는 다음과 같다.

public interface Shape {
    double area();
    double perimeter();

    default void displayArea() {
        this.area();
    }
}
  • implements 키워드를 통해 클래스가 인터페이스의 메서드를 구현하여 사용한다. 이때, 인터페이스의 메서드는 반드시 구현해야 한다
  • 클래스는 여러 개의 인터페이스를 구현할 수 있다
  • default 메서드 : 인터페이스에서도 메서드를 정의할 수 있는 기능으로, 자바 8부터 추가되었으며, 아직 구현되지 않은 인터페이스의 다른 메서드를 호출할 수 있다.

📁추상 클래스 vs 인터페이스

자바 인터페이스에서 default 메서드가 도입된 후에는 인터페이스와 추상 클래스 간에 더 이상 차이가 없는 것처럼 보이기도 한다. 그러나 둘 사이에는 몇 가지 차이점이 존재한다.

  1. 추상 클래스는 인스턴스 변수를 가질 수 있으며, 메서드는 인스턴스 변수에 접근할 수 있다.

다음과 같이 CircleClass라는 추상 클래스를 만들었다고 가정해보자. 이 클래스에는 CircleClass 객체의 상태를 나타내는 문자열 color가 포함되어있다.

public abstract class CircleClass {

    private String color;
    private List<String> allowedColors = Arrays.asList("RED", "GREEN", "BLUE");

    public boolean isValid() {
        if (allowedColors.contains(getColor())) {
            return true;
        } else {
            return false;
        }
    }

    // 표준 getter 및 setter
}

위의 추상 클래스에서는 color를 기반으로 CircleClass 객체의 유효성을 검사하는 isValid() 메서드와 같은 일반 메서드가 있다. isValid() 메서드는 CircleClass 객체의 인스턴스 변수에 접근하고, 허용된 색상을 기반으로 CircleClass 인스턴스의 유효성을 검사할 수 있다.

이렇게 추상 클래스 메서드에서는 객체의 상태를 기반으로 어떤 논리든 작성할 수 있다.

다음은 기본 메서드를 갖는 인터페이스를 사용하여 비슷한 작업을 수행한 코드이다.

public interface CircleInterface {
    List<String> allowedColors = Arrays.asList("RED", "GREEN", "BLUE");

    String getColor();
    
    public default boolean isValid() {
        if (allowedColors.contains(getColor())) {
            return true;
        } else {
            return false;
        }
    }
}

인터페이스 자체는 인스턴스 변수를 가질 수 없다. 따라서 구현 객체의 상태 정보를 제공하기 위해 getColor() 메서드를 정의하고, 다음과 같이 구현 클래스가 getColor() 메서드를 오버라이딩하여 런타임에 인스턴스의 상태를 제공한다.

public class ChildCircleInterfaceImpl implements CircleInterface {
    private String color;

    @Override
    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }
}
  1. 추상 클래스는 생성자를 가질 수 있으므로 생성 시 상태를 초기화할 수 있다. 반면, 인터페이스는 생성자를 가질 수 없다.

앞서 추상 클래스의 문법적 특징에서도 언급하였듯이, 추상 클래스는 new 키워드로 인스턴스를 생성할 수 있다.

  1. 몇 가지 문법적 차이가 존재한다.
  • 추상 클래스는 Object 클래스의 메서드를 오버라이딩할 수 있지만, 인터페이스는 불가하다.

자바의 Object 클래스는 모든 클래스의 부모 클래스이며, 다른 클래스에서 오버라이딩이 가능한 메서드를 가지고 있다. 자주 오버라이딩 되는 메서드에는 toString(), equals(), hashCode() 등이 있다.

추상 클래스는 다음과 같이 Object 클래스의 메서드를 오버라이딩 할 수 있다.

public abstract class MyAbstractClass {

    @Override
    public String toString() {
        // 객체의 문자열 표현을 사용자 지정 방식으로 반환
        return "This is a custom string representation of the object.";
    }

    @Override
    public boolean equals(Object obj) {
        // 객체 간의 동등성을 사용자 지정 조건에 따라 판단
        // 사용자가 정의한 비교 로직을 여기에 구현
    }
    
}
  • 추상 클래스는 모든 가능한 접근 제어자를 가진 인스턴스 변수를 선언할 수 있으며, 하위 클래스에서 액세스할 수 있다.

  • 추상 클래스는 인스턴스 및 정적 블록을 선언할 수 있지만, 인터페이스는 선언할 수 없다.

정적 블록(Static Block)은 클래스를 초기화하는 블록이다. 특정 클래스가 로드될 때 실행되는 블록으로, 해당 클래스에 대한 정적 초기화를 수행할 수 있다.

정적 블록은 클래스가 로드될 때 한 번만 실행되며, 그 이후로는 다시 실행되지 않는다. 주로 클래스 변수(static 변수)의 초기화나 외부 리소스에 대한 연결 설정과 같은 초기화 작업에 유용하게 활용된다.

예시 코드는 다음과 같다.

public class StaticBlockExample {
    static {
        // 정적 블록 내에서 초기화 작업 수행
        System.out.println("This is a static block.");
    }

    public static void main(String[] args) {
        // 클래스를 사용하는 메인 메서드
        System.out.println("Main method is called.");
    }
}
  • 추상 클래스는 람다 표현식을 참조할 수 없지만, 인터페이스는 람다 표현식을 참조하는 단일 추상 메서드를 가질 수 있다.

람다 표현식은 자바 8부터 도입된 기능으로, 주로 함수형 인터페이스(Functional Interface)와 함께 사용된다. 함수형 인터페이스는 하나의 추상 메서드만을 가지며, 람다 표현식은 이 추상 메서드의 구현을 제공한다.

예시 코드는 다음과 같다.

@FunctionalInterface
interface Calculator {
    int calculate(int a, int b); // 단일 추상 메서드
}

public class LambdaExample {
    public static void main(String[] args) {
    
        Calculator addition = (a, b) -> a + b; // 람다 표현식으로 추상 메서드 구현
        
        System.out.println("Addition: " + addition.calculate(5, 3));
    }
}

❓언제 사용할까?

일반적으로, 추상 클래스를 사용해야만 하는 상황이 아니라면 인터페이스 사용을 권장한다고 한다. 이때, 추상 클래스를 사용해야하는 상황이란

  1. 인스턴스 변수(필드)가 필요한 경우
  2. 생성자가 필요한 경우
  3. Object 클래스의 메서드를 오버라이딩 하고 싶은 경우

라고 한다.

이외에는 추상 클래스보다 인터페이스가 더 추상적인 존재이기 때문에 인터페이스의 사용이 권장된다.

❓인터페이스가 더 추상적?

  • 인터페이스는 다중 상속을 지원하고 클래스에서 여러 인터페이스를 구현할 수 있지만, 추상 클래스는 단일 상속만 지원한다.
  • 추상 클래스는 인스턴스 변수와 구현이 있는 메서드를 가질 수 있기 때문에 상대적으로 구체적인 개념으로 여겨진다.

참고 : https://www.baeldung.com/java-interface-default-method-vs-abstract-class, https://school.programmers.co.kr/learn/courses/17778/17778-%EC%8B%A4%EB%AC%B4-%EC%9E%90%EB%B0%94-%EA%B0%9C%EB%B0%9C%EC%9D%84-%EC%9C%84%ED%95%9C-oop%EC%99%80-%ED%95%B5%EC%8B%AC-%EB%94%94%EC%9E%90%EC%9D%B8-%ED%8C%A8%ED%84%B4

0개의 댓글