본문으로 건너뛰기

7.4 추상화와 인터페이스 (Abstraction & Interface)

객체지향 설계에서 추상화(Abstraction) 란 복잡한 현실 세계를 프로그램으로 모델링할 때, 불필요한 세부 사항은 숨기고 핵심적인 공통 속성과 기능만을 추출 하여 뼈대를 만드는 작업입니다.

자바에서는 추상화를 두 가지 도구로 구현합니다.

  1. 추상 클래스 (Abstract Class)
  2. 인터페이스 (Interface)

1. 추상 클래스 (Abstract Class)

추상 클래스는 미완성 설계도 입니다. 구체적인 구현이 아직 결정되지 않은 메서드(추상 메서드)를 포함하는 클래스입니다.

추상 클래스 선언

// abstract 키워드로 추상 클래스 선언
public abstract class Shape {
protected String color;

// 일반 생성자 가질 수 있음
public Shape(String color) {
this.color = color;
}

// 추상 메서드: 선언부만, 구현부({}) 없음
// → 자식 클래스가 반드시 구현해야 함
public abstract double area();
public abstract double perimeter();

// 일반 메서드: 구현이 완료된 공통 기능
public void displayInfo() {
System.out.printf("도형: %s, 색상: %s, 넓이: %.2f, 둘레: %.2f%n",
getClass().getSimpleName(), color, area(), perimeter());
}

public String getColor() { return color; }
}

추상 클래스 구현 (자식 클래스)

// 원
public class Circle extends Shape {
private double radius;

public Circle(String color, double radius) {
super(color); // 부모 생성자 호출
this.radius = radius;
}

@Override
public double area() {
return Math.PI * radius * radius;
}

@Override
public double perimeter() {
return 2 * Math.PI * radius;
}
}

// 직사각형
public class Rectangle extends Shape {
private double width;
private double height;

public Rectangle(String color, double width, double height) {
super(color);
this.width = width;
this.height = height;
}

@Override
public double area() {
return width * height;
}

@Override
public double perimeter() {
return 2 * (width + height);
}
}

// 삼각형
public class Triangle extends Shape {
private double a, b, c; // 세 변의 길이

public Triangle(String color, double a, double b, double c) {
super(color);
this.a = a; this.b = b; this.c = c;
}

@Override
public double area() {
// 헤론의 공식
double s = (a + b + c) / 2;
return Math.sqrt(s * (s-a) * (s-b) * (s-c));
}

@Override
public double perimeter() {
return a + b + c;
}
}

추상 클래스의 특징

public class AbstractTest {
public static void main(String[] args) {
// Shape shape = new Shape("빨강"); // 컴파일 에러! 추상 클래스 인스턴스화 불가

// 자식 클래스로 인스턴스 생성
Shape circle = new Circle("빨강", 5.0);
Shape rectangle = new Rectangle("파랑", 4.0, 6.0);
Shape triangle = new Triangle("초록", 3.0, 4.0, 5.0);

// 다형성: 부모 타입 배열로 관리
Shape[] shapes = { circle, rectangle, triangle };

for (Shape s : shapes) {
s.displayInfo(); // 각 자식의 area(), perimeter() 호출
}
}
}

출력:

도형: Circle, 색상: 빨강, 넓이: 78.54, 둘레: 31.42
도형: Rectangle, 색상: 파랑, 넓이: 24.00, 둘레: 20.00
도형: Triangle, 색상: 초록, 넓이: 6.00, 둘레: 12.00
추상 클래스의 목적

추상 클래스는 자식 클래스들에게 공통 틀(템플릿) 을 제공합니다. 공통 필드와 구현된 메서드를 재사용하면서, 구체적인 구현이 필요한 메서드는 자식에게 강제합니다. "IS-A 관계"를 표현할 때 사용합니다.

2. 인터페이스 (Interface)

인터페이스는 추상 클래스보다 더 강력한 추상화를 제공합니다. 클래스들이 반드시 구현해야 할 동작의 규격(계약) 을 정의합니다.

기본 인터페이스 선언

public interface Printable {
// 모든 상수는 자동으로 public static final
int MAX_SIZE = 1000; // == public static final int MAX_SIZE = 1000

// 모든 메서드는 자동으로 public abstract
void print(); // == public abstract void print()
String getContent(); // == public abstract String getContent()
}

// 인터페이스 구현: implements 키워드
public class Document implements Printable {
private String content;

public Document(String content) {
this.content = content;
}

@Override
public void print() {
System.out.println("[문서 출력]: " + content);
}

@Override
public String getContent() {
return content;
}
}

다중 구현 (Multiple Implementation)

클래스는 단일 상속만 가능하지만, 인터페이스는 여러 개를 동시에 구현할 수 있습니다.

public interface Flyable {
void fly();
default String getTransportType() { return "공중"; }
}

public interface Swimmable {
void swim();
default String getTransportType() { return "수상"; }
}

public interface Drivable {
void drive();
}

// 여러 인터페이스를 동시에 구현
public class AmphibiousVehicle implements Flyable, Swimmable, Drivable {
@Override
public void fly() { System.out.println("하늘을 납니다!"); }

@Override
public void swim() { System.out.println("물 위를 달립니다!"); }

@Override
public void drive() { System.out.println("도로를 달립니다!"); }

// 두 인터페이스에 동일한 default 메서드가 있으면 반드시 오버라이딩
@Override
public String getTransportType() {
return "육·해·공";
}
}

3. Java 8/9+ 인터페이스 신기능

Java 8부터 인터페이스에 구현을 포함할 수 있게 되었습니다.

default 메서드 (Java 8+)

public interface Collection<E> {
// abstract 메서드: 구현 클래스가 반드시 구현
void add(E element);
void remove(E element);
int size();

// default 메서드: 기본 구현 제공 (오버라이딩 선택)
default boolean isEmpty() {
return size() == 0;
}

default void printAll() {
System.out.println("Collection with " + size() + " elements");
}
}

static 메서드 (Java 8+)

public interface Validator<T> {
boolean validate(T value);

// static 메서드: 인터페이스 이름으로 직접 호출
static <T> boolean validateNotNull(T value) {
return value != null;
}
}

// 호출
boolean result = Validator.validateNotNull("hello"); // true

private 메서드 (Java 9+)

public interface Logger {
default void info(String message) { log("INFO", message); }
default void error(String message) { log("ERROR", message); }
default void warn(String message) { log("WARN", message); }

// private 메서드: default/static 메서드에서만 사용 가능 (외부 노출 없음)
private void log(String level, String message) {
System.out.printf("[%s] %s%n", level, message);
}
}

4. 함수형 인터페이스와 람다

함수형 인터페이스(Functional Interface)추상 메서드가 정확히 하나 인 인터페이스입니다. @FunctionalInterface 어노테이션으로 명시합니다.

// 함수형 인터페이스 선언
@FunctionalInterface
public interface Calculator {
int calculate(int a, int b);
// 추상 메서드는 반드시 1개만! (default/static은 여러 개 가능)
}

public class LambdaExample {
public static void main(String[] args) {
// 기존 방식: 익명 클래스
Calculator add = new Calculator() {
@Override
public int calculate(int a, int b) {
return a + b;
}
};

// 람다 방식: 훨씬 간결
Calculator addLambda = (a, b) -> a + b;
Calculator subtractLambda = (a, b) -> a - b;
Calculator multiplyLambda = (a, b) -> a * b;

System.out.println(addLambda.calculate(10, 5)); // 15
System.out.println(subtractLambda.calculate(10, 5)); // 5
System.out.println(multiplyLambda.calculate(10, 5)); // 50

// Java 표준 함수형 인터페이스 활용
java.util.function.Predicate<String> isLong = s -> s.length() > 5;
java.util.function.Function<String, Integer> length = String::length;

System.out.println(isLong.test("Hello")); // false
System.out.println(isLong.test("Hello World!")); // true
System.out.println(length.apply("Java")); // 4
}
}

5. 추상 클래스 vs 인터페이스 비교

구분추상 클래스 (abstract class)인터페이스 (interface)
상속/구현extends (단일 상속)implements (다중 구현)
변수일반 멤버 변수 선언 가능public static final 상수만
메서드추상 + 일반 메서드 모두 가능기본적으로 추상, default/static 가능
생성자있음없음
접근 제어자모든 접근 제어자 사용 가능public 또는 private (Java 9+)
목적IS-A 관계, 공통 코드 공유CAN-DO 관계, 동작 규격 정의
사용 시기관련 클래스의 공통 부모서로 다른 클래스에 공통 기능 부여

6. 실전 예제: 결제 시스템

인터페이스를 활용한 실제적인 결제 시스템을 구현합니다.

// 결제 처리 인터페이스
public interface PaymentProcessor {
boolean processPayment(double amount);
void refund(double amount);
String getPaymentMethod();

default void printReceipt(double amount) {
System.out.printf("[영수증] 결제 수단: %s, 금액: %.0f원%n",
getPaymentMethod(), amount);
}
}

// 신용카드 결제
public class CreditCardProcessor implements PaymentProcessor {
private String cardNumber;
private double limit;
private double balance;

public CreditCardProcessor(String cardNumber, double limit) {
this.cardNumber = cardNumber;
this.limit = limit;
this.balance = 0;
}

@Override
public boolean processPayment(double amount) {
if (balance + amount > limit) {
System.out.println("[신용카드] 한도 초과! 결제 실패");
return false;
}
balance += amount;
System.out.printf("[신용카드] 카드 %s으로 %.0f원 결제 완료%n",
cardNumber.substring(cardNumber.length() - 4), amount);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
balance -= amount;
System.out.printf("[신용카드] %.0f원 환불 완료. 현재 사용액: %.0f원%n",
amount, balance);
}

@Override
public String getPaymentMethod() { return "신용카드"; }
}

// 카카오페이 결제
public class KakaoPayProcessor implements PaymentProcessor {
private String userId;
private double walletBalance;

public KakaoPayProcessor(String userId, double walletBalance) {
this.userId = userId;
this.walletBalance = walletBalance;
}

@Override
public boolean processPayment(double amount) {
if (walletBalance < amount) {
System.out.println("[카카오페이] 잔액 부족! 결제 실패");
return false;
}
walletBalance -= amount;
System.out.printf("[카카오페이] %s님 %.0f원 결제 완료. 잔액: %.0f원%n",
userId, amount, walletBalance);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
walletBalance += amount;
System.out.printf("[카카오페이] %.0f원 환불. 잔액: %.0f원%n",
amount, walletBalance);
}

@Override
public String getPaymentMethod() { return "카카오페이"; }
}

// 네이버페이 결제
public class NaverPayProcessor implements PaymentProcessor {
private double points;

public NaverPayProcessor(double points) {
this.points = points;
}

@Override
public boolean processPayment(double amount) {
if (points < amount) {
System.out.println("[네이버페이] 포인트 부족! 결제 실패");
return false;
}
points -= amount;
System.out.printf("[네이버페이] %.0f 포인트 사용. 잔여 포인트: %.0f%n",
amount, points);
printReceipt(amount);
return true;
}

@Override
public void refund(double amount) {
points += amount;
System.out.printf("[네이버페이] %.0f 포인트 환불. 잔여 포인트: %.0f%n",
amount, points);
}

@Override
public String getPaymentMethod() { return "네이버페이"; }
}

// 결제 서비스 (어떤 결제 수단이든 동일한 코드로 처리 — 다형성!)
public class PaymentService {
private PaymentProcessor processor;

public PaymentService(PaymentProcessor processor) {
this.processor = processor;
}

// 결제 수단을 교체 가능 (OCP: 확장에 열려 있음)
public void setProcessor(PaymentProcessor processor) {
this.processor = processor;
}

public void checkout(double amount) {
System.out.println("\n=== 결제 진행 ===");
boolean success = processor.processPayment(amount);
if (success) {
System.out.println("결제가 성공적으로 완료되었습니다.");
} else {
System.out.println("결제에 실패하였습니다. 다른 결제 수단을 이용해주세요.");
}
}
}

// 실행 테스트
public class PaymentTest {
public static void main(String[] args) {
PaymentProcessor creditCard = new CreditCardProcessor("1234-5678-9012-3456", 500000);
PaymentProcessor kakaoPay = new KakaoPayProcessor("user123", 50000);
PaymentProcessor naverPay = new NaverPayProcessor(30000);

PaymentService service = new PaymentService(creditCard);
service.checkout(150000); // 신용카드 결제

service.setProcessor(kakaoPay);
service.checkout(30000); // 카카오페이 결제

service.setProcessor(naverPay);
service.checkout(50000); // 네이버페이 결제 실패 (잔액 부족)
}
}

출력:

=== 결제 진행 ===
[신용카드] 카드 3456으로 150000원 결제 완료
[영수증] 결제 수단: 신용카드, 금액: 150000원
결제가 성공적으로 완료되었습니다.

=== 결제 진행 ===
[카카오페이] user123님 30000원 결제 완료. 잔액: 20000원
[영수증] 결제 수단: 카카오페이, 금액: 30000원
결제가 성공적으로 완료되었습니다.

=== 결제 진행 ===
[네이버페이] 포인트 부족! 결제 실패
결제에 실패하였습니다. 다른 결제 수단을 이용해주세요.

요약

개념핵심 내용
추상화공통 속성/기능 추출, 불필요한 세부사항 은닉
추상 클래스abstract 선언, 인스턴스화 불가, 추상 메서드 강제 구현
인터페이스완전한 추상화, 다중 구현, 동작 규격 정의
default 메서드Java 8+, 인터페이스에 기본 구현 추가
함수형 인터페이스추상 메서드 1개, @FunctionalInterface, 람다와 사용
추상 클래스 vs 인터페이스IS-A 관계 vs CAN-DO 관계