자바의 추상 클래스와 인터페이스

추상 클래스

추상 메서드

  • 메서드의 구체적인 기능을 구현하지 않고 형태만 제공하는 메서드
  • 자식 클래스에서 반드시 재정의하여 사용되기때문에 부모에서의 구현이 무의미한 메서드
  • 구현부가 없다는 의미로 abstract 키워드를 사용하여 표현
  • 메서드의 선언부(리턴타입, 매서드명, 매개변수)만 남기고 구현부({})는 세미콜론(;)으로 대체한 형태

    • abstract void calcArea();

추상 클래스

  • 메서드의 시그니처(리턴타입, 매서드명, 매개변수)만을 정의하고 블럭({}) 내부의 기능은 구현하지 않은 메서드를 추상메서드라고 한다.
  • 추상 메서드를 포함하고 있거나 설계상 의도적으로 추상 클래스로 만든 클래스
  • 추상 메서드를 포함하고 있는 추상 클래스는 직접 new를 통해 객체를 생성할 수 없음
  • 따라서 추상 클래스를 상속받는 자식 클래스를 생성하여, 자식 클래스 내에서 추상 메서드를 오버라이딩(Overriding)하고 이 자식 클래스의 객체를 생성하여 사용
  • 만약 추상 클래스를 상속받는 자식 클래스에서 추상 메서드를 재정의하지 않는다면 자식 클래스 역시 추상 클래스가 되어 객체를 생성할 수 없음.
  • 구현의 강제화
Java
// 추상 클래스
public abstract class Shape {
    protected double area;

    // 추상 메서드
    public abstract void calcArea();
}

// 자식 클래스에서 메서드 재정의
public class Circle extends Shape {
    @Override
    public void calcArea() {
        System.out.println("radius^2 * pi");
    }
}

public class Rectangle extends Shape {
    @Override
    public void calcArea() {
        System.out.println("width * height");
    }
}

인터페이스

  • 사용자 관점 : 사용 방법, 기능에 대한 약속
  • 제공자 관점 : 구현에 대한 책임
  • 추상 메서드와 상수로 이루어진 특별한 타입

개념

  • 추상 클래스보다 더욱 완벽한 추상화를 제공 : 상수형 멤버변수추상 메서드 외 다른 멤버를 갖지 못함

    • 모든 멤버변수 : public static final 이며 생략하여 표현 가능
    • 모든 메서드 : public abstract 이며 생략하여 표현 가능
  • 다중 상속 가능 : 메서드의 구현부가 없으므로 헷갈릴 구현부가 없음
  • 인터페이스 역시 다형성 특징을 활용하여 부모 타입으로 자식 타입 참조 가능
  • 인터페이스를 상속받는 클래스 역시 인터페이스 내의 추상 메서드를 오버라이딩(Overriding)하여 사용해야 한다.

필요성

  • 구현의 강제화로 표준화처리 가능 -> 손쉬운 모듈 교체

    • 부모 타입에서는 보편적이고 표준화된 인터페이스만을 제공
    • 자식 타입이 추가되거나 변경되어도 부모 타입은 수정할 필요 없음
  • 서로 상속관계가 없는 클래스 간 인터페이스를 통한 관계를 부여하여 다형성 확장 가능
  • 독립적인 프로그래밍 -> 개발 시간, 비용 단축
Java
// 인터페이스
public interface Printer {
    void print(String fileName);
}

// 인터페이스를 상속받는 클래스1
public class DotPrinter implements Printer {
    @Override
    public void print(String fileName) {
        System.out.println("Dot 프린터로 출력 - " + fileName);
    }
}

// 인터페이스를 상속받는 클래스2
public class LaserPrinter implements Printer {
    @Override
    public void print(String fileName) {
        System.out.println("Laser 프린터로 출력 - " + fileName);
    }
}

// 클라이언트를 통한 클래스 접근
public class PrintClient {
    private Printer printer;

    public void setPrinter(Printer printer){
        this.printer = printer;
    }

    public void printThis(String fileName){
        printer.print(fileName);
    }
}

// test(main)
public class PrinterTest {
    public static void main(String[] args) {
        PrintClient client = new PrintClient();
        client.setPrinter(new LaserPrinter());
        client.printThis("어떤 프린터로 출력이 되나요?");
    }
}

인터페이스의 Default Method

  • 선언된 구현부가 존재하는 일반 메서드
  • 탄생 배경

    • 인터페이스를 상속받는 자식이 추가되어 인터페이스에도 새로운 메서드가 필요하게됨
    • 만약 새로운 메서드를 abstract method로 만들게 된다면, 기존에 해당 인터페이스를 상속받던 모든 자식에 새로운 메서드에 대한 재정의를 구현해야함.
    • 이를 방지하기위해 재정의가 필요없는 default method가 탄생
  • default method의 경우 구현부가 존재하므로 동일한 이름의 메서드끼리 충돌 발생 가능성이 존재
  • 충돌 발생 시 우선 순위

    • 만약 2개의 인터페이스를 상속받았는데 2개의 부모 인터페이스에 동일한 이름의 default method 존재할 경우 자식 클래스에서는 무조건 해당 default method를 재정의해야함
    • 만약 default method를 포함한 인터페이스를 상속받은(implements) 클래스를 상속받고(extends), 동일한 이름의 default method를 가진 다른 인터페이스를 상속받는(implements) 클래스가 존재할 경우 자식클래스는 클래스 상속(extends)에 우선순위를 둠

패키지

  • 관련된 클래스와 인터페이스를 하나의 폴더에 적절하게 배치하여 관련된 클래스들이 묶여있는 폴더
  • 같은 이름의 클래스를 사용할 때 이름의 충돌을 피할 수 있음
  • 접근 권한을 패키지 단위로 제어할 수 있음
  • API에 제공되는 다양한 클래스들을 패키지화하여 제공