자바의 추상 클래스와 인터페이스
추상 클래스
추상 메서드
- 메서드의 구체적인 기능을 구현하지 않고 형태만 제공하는 메서드
- 자식 클래스에서 반드시 재정의하여 사용되기때문에 부모에서의 구현이 무의미한 메서드
- 구현부가 없다는 의미로
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에 제공되는 다양한 클래스들을 패키지화하여 제공