F-LAB JAVA · 3주차 · Phase 4 · 추상화의 두 도구
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
interface 키워드 의 정확한 의미는?Comparable, Iterable, Runnable, Serializable 등의 활용은?인터페이스는 "할 수 있는 능력 (can-do)" 을 표현하는 순수한 추상화 도구다.
모든 메서드는 자동으로public abstract, 모든 필드는 자동으로public static final.
다중 구현이 가능해서 "어느 분류에도 속할 수 있는 능력" 을 자유롭게 부여 가능.
Java 8+ 부터는default와static메서드로 구현도 가능 (다음 Unit).
Comparable,Iterable,Runnable,Serializable같은 자바 표준 인터페이스가 모든 코드의 토대.
일반 클래스 (Concrete Class):
완성된 사람 — 직업, 능력 모두 갖춤
Dog → 개로 태어남
추상클래스 (Abstract Class):
부분 완성 — 일부 능력은 자식이 채워야
Animal → 동물이지만 sound() 는 자식이
인터페이스 (Interface):
자격증 / 능력 인증서
- "수영 가능 (Swimmer)"
- "비행 가능 (Flyer)"
- "비교 가능 (Comparable)"
한 사람이 여러 자격증 보유 가능
Duck = Animal + Swimmer + Flyer
→ 인터페이스 = 능력 표현 + 다중 부여 가능.
1. interface 키워드와 본질
2. 멤버의 자동 수식자
3. 다중 구현 (Multiple Implementation)
4. 인터페이스의 제약 — 생성자/필드 없음
5. 마커 인터페이스 (Marker Interface)
6. 함수형 인터페이스와 람다
7. 자바 표준 인터페이스 활용
8. 인터페이스의 진화 — 역사
9. 면접 + 자기 점검
interface:
"객체가 가져야 할 메서드의 명세 (specification)"
- 메서드 시그니처만 정의 (Java 7 까지)
- 구현은 없음
- implements 키워드로 구현
// 인터페이스 정의
public interface Drawable {
void draw();
void erase();
}
// 인터페이스 구현
public class Circle implements Drawable {
@Override
public void draw() {
System.out.println("Drawing circle");
}
@Override
public void erase() {
System.out.println("Erasing circle");
}
}
// 사용
Drawable d = new Circle();
d.draw();
// 클래스: class
public class Circle { ... }
// 추상클래스: abstract class
public abstract class Shape { ... }
// 인터페이스: interface (abstract 안 붙임)
public interface Drawable { ... }
// 인터페이스끼리 확장: extends
public interface ColoredDrawable extends Drawable {
void setColor(Color c);
}
// 클래스가 인터페이스 구현: implements
public class Circle implements Drawable { ... }
// 추상클래스도 인터페이스 구현 가능
public abstract class Shape implements Drawable { ... }
// 클래스 상속 + 인터페이스 구현
public class Circle extends Shape implements Drawable, Cloneable {
}
인터페이스는:
- "is-a" 관계 X
- "can-do" 능력 표현 ✓
예:
Cat extends Animal (is-a: 고양이는 동물)
Cat implements Swimmer (can-do: 고양이는 수영 가능)
다른 시각:
추상클래스: "어떤 종류인가?"
인터페이스: "무엇을 할 수 있는가?"
| 항목 | 추상클래스 | 인터페이스 |
|---|---|---|
| 키워드 | abstract class | interface |
| 구현 키워드 | extends | implements |
| 다중 상속/구현 | ❌ (단일) | ✓ (다중) |
| 메서드 구현 | 가능 (구현 + 추상) | Java 7 X, Java 8+ default |
| 필드 | 인스턴스 + static | static final 만 |
| 생성자 | 가능 | ❌ |
| 접근 제어자 | 모두 가능 | public 만 (자동) |
| 의도 | "is-a" 관계 | "can-do" 능력 |
// 인터페이스: 운송 가능 능력
public interface Shippable {
void ship();
BigDecimal getShippingCost();
}
// 인터페이스: 추적 가능 능력
public interface Trackable {
String getTrackingNumber();
TrackingStatus getStatus();
}
// 클래스: 두 능력 모두
public class Shipment implements Shippable, Trackable {
@Override
public void ship() {
// ...
}
@Override
public BigDecimal getShippingCost() {
// ...
}
@Override
public String getTrackingNumber() {
// ...
}
@Override
public TrackingStatus getStatus() {
// ...
}
}
// 다형성 활용
public void processShippable(Shippable s) {
s.ship();
}
public void processTrackable(Trackable t) {
System.out.println(t.getTrackingNumber());
}
Shipment shipment = new Shipment();
processShippable(shipment); // Shippable 로 사용
processTrackable(shipment); // Trackable 로 사용
인터페이스와 추상클래스의 가장 본질적 차이는?
답:
인터페이스:
추상클래스:
→ "어떤 종류인가" vs "무엇을 할 수 있는가".
public interface Drawable {
// 작성한 코드
void draw();
// 실제로는 컴파일러가:
// public abstract void draw();
// 자동 부여:
// - public
// - abstract (Java 7 까지)
}
자동 부여되는 수식자:
public: 모든 메서드는 publicabstract: 본체 없으면 자동 abstract (Java 7)public interface Drawable {
// ✓ public 명시 (불필요하지만 합법)
public void draw();
// ✓ abstract 명시 (불필요하지만 합법)
abstract void erase();
// ✓ public abstract 모두 명시
public abstract void resize();
// ❌ 다른 수식자
// private void hidden(); ← Java 8 까지 에러, 9+ 부터 가능
// protected void inner(); ← 에러
// package void check(); ← 에러 (자동 public)
// final void cannot(); ← 에러 (abstract 와 모순)
// static void util(); ← Java 7 까지 에러, 8+ 가능
}
public interface Constants {
// 작성한 코드
int MAX_VALUE = 100;
String NAME = "Default";
// 실제로는 컴파일러가:
// public static final int MAX_VALUE = 100;
// public static final String NAME = "Default";
// 자동 부여:
// - public
// - static
// - final
}
핵심:
public: 외부에서 접근 가능static: 클래스 레벨 (인스턴스 무관)final: 초기화 후 변경 불가 (상수)public interface Constants {
// ✓ 명시 (불필요하지만 합법)
public static final int A = 1;
// ✓ 일부 생략 (자동 추가됨)
static final int B = 2; // public 자동
public final int C = 3; // static 자동
public static int D = 4; // final 자동
int E = 5; // 모두 자동
// ❌ 다른 수식자
// private int F = 6; // 에러 (public 자동)
// int G; // 에러 (초기화 필수, final 이라)
}
public interface Counter {
// ❌ 인스턴스 필드 시도
int count = 0;
// 실제로는:
// public static final int count = 0;
// → 모든 구현체가 공유 + 변경 불가
// → 인스턴스별 count 가 아님
}
// 구현체에서 인스턴스 필드 가능
public class CounterImpl implements Counter {
private int instanceCount = 0; // 인스턴스 필드 OK
}
→ 인터페이스에는 상수만.
→ 인스턴스 상태는 구현 클래스에서.
왜 public 자동?
- 인터페이스는 공개 명세
- 외부에서 접근하기 위한 것
- 다른 가시성은 의미 없음
왜 abstract 자동? (Java 7 까지)
- 인터페이스의 본질
- 구현 없는 명세
왜 static 자동? (필드)
- 인터페이스는 인스턴스 없음
- 상수만 가능
- 클래스 레벨 데이터
왜 final 자동? (필드)
- 모든 구현체가 공유
- 변경되면 일관성 깨짐
- 상수로 강제
인터페이스 메서드와 필드의 자동 수식자는?
답:
메서드: public abstract (Java 7 까지)
필드: public static final
→ 인터페이스의 본질이 키워드에 반영됨.
다중 구현 (Multiple Implementation):
한 클래스가 여러 인터페이스를 동시에 구현.
문법:
public class MyClass implements A, B, C { ... }
효과:
- MyClass 는 A 이면서 B 이면서 C
- 어떤 메서드도 모호함 없음 (인터페이스는 구현 없으니까)
클래스 다중 상속 (자바엔 없음):
class Bat extends Mammal, Bird { ... } // ❌
문제: Diamond Problem
- Mammal.move() 와 Bird.move() 중 어느 것?
인터페이스 다중 구현:
class Bat implements Flyer, Mammal_Interface { ... } // ✓
모호함 없음:
- 인터페이스 메서드는 구현 없음
- 모두 클래스가 직접 구현
- 메서드 해결 명확
public interface Swimmer {
void swim();
}
public interface Flyer {
void fly();
}
public interface Walker {
void walk();
}
// 단일 구현
public class Fish implements Swimmer {
@Override
public void swim() { ... }
}
// 다중 구현
public class Duck implements Swimmer, Flyer, Walker {
@Override
public void swim() { ... }
@Override
public void fly() { ... }
@Override
public void walk() { ... }
}
// 사용
Swimmer s = new Duck();
s.swim(); // Duck.swim()
Flyer f = new Duck();
f.fly(); // Duck.fly()
// 같은 객체, 다른 타입으로 사용 가능
Duck duck = new Duck();
swim(duck); // Swimmer 매개변수
fly(duck); // Flyer 매개변수
// 인터페이스끼리는 extends + 다중 가능
public interface SwimFly extends Swimmer, Flyer {
void splash(); // 추가 메서드
}
// 구현
public class Duck implements SwimFly {
@Override
public void swim() { ... }
@Override
public void fly() { ... }
@Override
public void splash() { ... }
}
// 자바 표준 라이브러리의 예
public class ArrayList<E>
extends AbstractList<E> // 추상클래스 단일 상속
implements
List<E>, // 리스트 능력
RandomAccess, // 인덱스 빠른 접근 (마커)
Cloneable, // 복제 가능
java.io.Serializable { // 직렬화 가능
// ...
}
ArrayList 는:
// ❌ 같은 메서드 시그니처에 다른 반환 타입
public interface A {
int getValue();
}
public interface B {
String getValue(); // 같은 이름, 다른 반환
}
public class C implements A, B {
// 어느 것 구현?
// 컴파일 에러
}
// ✓ 같은 시그니처면 OK
public interface A {
void process();
}
public interface B {
void process(); // 같은 시그니처
}
public class C implements A, B {
@Override
public void process() {
// 두 인터페이스 모두 만족
}
}
다중 구현의 가치:
1. 능력의 자유로운 조합
Duck = Swimmer + Flyer + Walker
각 능력은 독립적
2. 다양한 시각으로 객체 사용
같은 객체를 여러 매개변수 타입으로
3. 코드 분류의 유연성
하나의 분류 (extends) + 여러 능력 (implements)
4. 표준 인터페이스 활용
Comparable, Iterable 등을 부여
클래스는 단일 상속만 되지만 인터페이스는 다중 구현이 가능한 이유는?
답:
클래스 다중 상속의 문제:
인터페이스 다중 구현이 가능한 이유:
Java 8+ default 메서드 등장 후:
→ "구현이 없으면 충돌도 없다".
public interface Drawable {
// ❌ 생성자 시도
public Drawable() {
// 에러: 인터페이스에 생성자 불가
}
}
이유:
1. 인터페이스는 인스턴스화 불가
- 추상클래스도 인스턴스화 불가지만 자식이 super() 호출
- 인터페이스는 그런 호출 자체가 없음
2. 인터페이스는 상태 없음
- 인스턴스 필드 없음
- 초기화할 게 없음
3. 다중 구현의 모호함
- 여러 인터페이스 생성자 호출 순서?
- 다중 상속 Diamond 와 같은 문제
4. 의도의 차이
- 인터페이스 = 능력 명세
- 생성자 = 객체 생성 (의미 다름)
public interface Counter {
// ❌ 인스턴스 필드 시도
private int count;
// 실제로는:
// public static final int count = ???;
// → static, 초기화 필수, 변경 불가
// 인터페이스에 인스턴스 필드 불가능
}
이유:
1. 다중 구현의 모호함
- 여러 인터페이스에 같은 이름 필드?
- 인스턴스 메모리에 어떻게 배치?
2. 상태와 동작의 분리
- 인터페이스 = 동작 명세
- 상태는 구현 클래스에서
3. 자바 설계 철학
- 인터페이스는 행위 위주
- 상태는 클래스의 영역
public interface FreightPolicy {
// ✓ 상수 (public static final 자동)
BigDecimal FUEL_RATE = new BigDecimal("0.05");
int MAX_WEIGHT = 10000;
String DEFAULT_CURRENCY = "USD";
Set<String> SUPPORTED_PORTS = Set.of(
"BUSAN", "INCHEON", "GWANGYANG"
);
}
// 사용
BigDecimal rate = FreightPolicy.FUEL_RATE;
int max = FreightPolicy.MAX_WEIGHT;
// 구현체에서도 사용
public class StandardPolicy implements FreightPolicy {
public void check(int weight) {
if (weight > MAX_WEIGHT) { // 상수 접근 (this 없이)
throw new IllegalArgumentException();
}
}
}
// ❌ 안티패턴: 상수만 모아둔 인터페이스
public interface Constants {
int MAX = 100;
String NAME = "default";
// ...
}
// 사용
public class MyClass implements Constants {
void method() {
System.out.println(MAX); // implements 만으로 접근
}
}
문제:
해결:
// ✓ 상수 클래스
public final class Constants {
private Constants() {} // 인스턴스화 방지
public static final int MAX = 100;
public static final String NAME = "default";
}
// 사용
int max = Constants.MAX;
→ Java 5+ 의 static import 도 활용 가능.
public interface MyInterface {
// 1. 추상 메서드 (Java 7 부터)
void method1();
// 2. 상수 (public static final)
int CONSTANT = 100;
// Java 8+ 추가:
// 3. default 메서드 (구현 가능)
default void method2() {
System.out.println("Default");
}
// 4. static 메서드
static void method3() {
System.out.println("Static");
}
// Java 9+ 추가:
// 5. private 메서드
private void method4() {
System.out.println("Private");
}
// 6. private static 메서드
private static void method5() {
System.out.println("Private static");
}
// 가능한 멤버:
// - 추상 메서드
// - 상수
// - default 메서드 (Java 8+)
// - static 메서드 (Java 8+)
// - private 메서드 (Java 9+)
// - private static 메서드 (Java 9+)
// 불가능:
// - 인스턴스 필드
// - 생성자
// - 인스턴스 초기화 블록
// - protected, package-private 메서드 (default 키워드와 다름)
}
public interface Container {
void add(Object item);
// 중첩 인터페이스
interface Listener {
void onAdded(Object item);
}
// 중첩 클래스
class Helper {
static void log(Object item) {
System.out.println(item);
}
}
// 중첩 enum
enum Type {
SMALL, MEDIUM, LARGE
}
}
// 사용
Container.Listener listener = ...;
Container.Type type = Container.Type.LARGE;
Container.Helper.log("hello");
중첩 타입은 자동으로 public static.
인터페이스에 생성자가 없는 이유 4가지는?
답:
1. 인스턴스화 불가 — 생성자 호출 자체가 없음
2. 상태 없음 — 인스턴스 필드 없으니 초기화할 게 없음
3. 다중 구현의 모호함 — 여러 인터페이스 생성자 호출 순서?
4. 의도의 차이 — 인터페이스는 능력 명세, 생성자는 객체 생성
추가:
마커 인터페이스 (Marker Interface):
메서드가 하나도 없는 인터페이스.
단지 "이 클래스는 X 의 성질을 가진다" 를 표시.
영어:
"Tagging Interface" 라고도 함.
// 1. Serializable
public interface Serializable {
// 메서드 없음
// 의미: "이 객체는 직렬화 가능"
}
// 2. Cloneable
public interface Cloneable {
// 메서드 없음
// 의미: "Object.clone() 호출 가능"
}
// 3. RandomAccess
public interface RandomAccess {
// 메서드 없음
// 의미: "인덱스 접근이 빠름 (O(1))"
}
// 4. Remote (RMI)
public interface Remote {
// 메서드 없음
// 의미: "원격 호출 가능 객체"
}
// 마커 사용 예 — Serializable
public class Shipment implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String blNo;
// ...
}
// 직렬화 로직
public void serialize(Object obj, OutputStream out) throws IOException {
if (!(obj instanceof Serializable)) {
throw new NotSerializableException(obj.getClass().getName());
}
// 실제 직렬화
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(obj);
}
핵심:
instanceof 로 마커 검사// Collections.binarySearch 내부
public static <T> int binarySearch(List<T> list, T key, Comparator<? super T> c) {
if (list instanceof RandomAccess) {
// 인덱스 접근 빠름 → 일반 이진 검색
return indexedBinarySearch(list, key, c);
} else {
// 인덱스 접근 느림 → Iterator 기반 이진 검색
return iteratorBinarySearch(list, key, c);
}
}
implements RandomAccess → 빠른 경로→ 마커가 런타임 알고리즘 분기.
1. 컴파일 타임 검증
- instanceof 로 명확
- 잘못된 사용 사전 차단
2. 단순성
- 메서드 없음 → 구현 부담 X
- 단지 표시만
3. 다중 마커 가능
- 한 클래스가 여러 마커 보유
- implements Serializable, Cloneable, ...
4. 동적 검사
- 런타임에 if (obj instanceof Marker) ...
- 유연한 처리
1. 정적 검사만 가능
- 추가 정보 (메타데이터) X
- "표시" 외 의미 없음
2. 어노테이션 등장 (Java 5+)
- 더 풍부한 메타데이터
- @Deprecated, @Override, @SuppressWarnings 등
- 마커 인터페이스 일부 대체
3. 런타임 의존
- instanceof 검사 필요
- 컴파일 타임 다형성 활용 X
// 마커 인터페이스
public class Shipment implements Serializable {
// ...
}
// 어노테이션
@Entity
@Table(name = "shipments")
public class Shipment {
// ...
}
비교:
마커 인터페이스:
✓ 컴파일 타임 타입 검사
✓ instanceof 사용
✗ 메타데이터 제한 (단순 표시)
어노테이션:
✓ 풍부한 메타데이터 (속성, 값)
✓ 메서드, 필드 등 다양한 위치
✗ 런타임 검사만 (보통)
✗ 타입 시스템 통합 X
선택:
// 도메인 객체
public class Shipment implements Serializable {
private static final long serialVersionUID = 20251019L;
private Long id;
private String blNo;
private BigDecimal weight;
// ...
}
// 사용
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ship.dat"));
oos.writeObject(shipment); // Serializable 이므로 OK
// 비 Serializable 객체
public class NotSerializable {
private int value;
}
oos.writeObject(new NotSerializable()); // ❌ NotSerializableException
// 도메인 마커
public interface AuditableEntity {
// 메서드 없음
// 의미: "이 엔티티는 변경 감사를 받는다"
}
public interface CacheableEntity {
// 의미: "이 엔티티는 캐시 가능"
}
// 구현
public class Shipment implements AuditableEntity, CacheableEntity {
// ...
}
// 일반 처리 로직
public void save(Object entity) {
if (entity instanceof AuditableEntity) {
recordAudit(entity);
}
if (entity instanceof CacheableEntity) {
cache.put(getId(entity), entity);
}
repository.save(entity);
}
마커 인터페이스와 어노테이션 중 어느 것을 언제 쓰나?
답:
마커 인터페이스 선택:
instanceof 로 분기어노테이션 선택:
자바 라이브러리의 변화:
함수형 인터페이스 (Functional Interface):
추상 메서드가 정확히 하나인 인터페이스.
(default, static 메서드는 무관)
영어:
"Single Abstract Method (SAM)" 인터페이스
@FunctionalInterface
public interface Runnable {
void run();
}
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
// default 메서드 — 무관
default Comparator<T> reversed() {
return (a, b) -> compare(b, a);
}
// static 메서드 — 무관
static <T> Comparator<T> naturalOrder() { ... }
}
@FunctionalInterface:
// Java 7 까지: 익명 클래스
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello");
}
};
// Java 8+: 람다
Runnable r = () -> System.out.println("Hello");
// 더 자세한 람다
Runnable r = () -> {
System.out.println("Hello");
System.out.println("World");
};
핵심:
// Comparator 사용
List<String> list = new ArrayList<>(List.of("banana", "apple", "cherry"));
// 익명 클래스
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return a.length() - b.length();
}
});
// 람다
Collections.sort(list, (a, b) -> a.length() - b.length());
// 메서드 참조
Collections.sort(list, Comparator.comparingInt(String::length));
// java.util.function 패키지
// 1. Function<T, R> — 입력 T, 출력 R
Function<String, Integer> length = s -> s.length();
length.apply("hello"); // 5
// 2. Predicate<T> — 입력 T, 출력 boolean
Predicate<String> isEmpty = String::isEmpty;
isEmpty.test(""); // true
// 3. Consumer<T> — 입력 T, 출력 없음
Consumer<String> printer = System.out::println;
printer.accept("hello"); // 출력: hello
// 4. Supplier<T> — 입력 없음, 출력 T
Supplier<List<String>> listSupplier = ArrayList::new;
List<String> list = listSupplier.get();
// 5. BiFunction<T, U, R> — 입력 T, U, 출력 R
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
add.apply(2, 3); // 5
// 6. UnaryOperator<T> — Function<T, T> 의 특수
UnaryOperator<String> toUpper = String::toUpperCase;
// 7. BinaryOperator<T> — BiFunction<T, T, T>
BinaryOperator<Integer> max = Integer::max;
// 도메인 함수형 인터페이스
@FunctionalInterface
public interface FareCalculator {
BigDecimal calculate(int weight, int distance);
}
@FunctionalInterface
public interface ShipmentValidator {
boolean validate(Shipment s);
}
// 사용
FareCalculator basicFare = (w, d) -> new BigDecimal("0.5").multiply(BigDecimal.valueOf(w));
FareCalculator premiumFare = (w, d) ->
new BigDecimal("1.0").multiply(BigDecimal.valueOf(w))
.multiply(BigDecimal.valueOf(1 + d / 1000.0));
BigDecimal basicCost = basicFare.calculate(1000, 5000);
BigDecimal premiumCost = premiumFare.calculate(1000, 5000);
인스턴스화 불가의 우회:
인터페이스는 인스턴스화 불가.
하지만 람다로 "마치 인스턴스 같은" 객체 생성:
Runnable r = () -> { ... };
// 이는:
// Runnable r = new Runnable() {
// @Override public void run() { ... }
// };
람다는 익명 클래스의 syntactic sugar 와 비슷.
실제로는 invokedynamic 명령어로 더 효율적.
// 4가지 메서드 참조
// 1. static 메서드 참조
Function<String, Integer> f1 = Integer::parseInt;
// 동등: s -> Integer.parseInt(s)
// 2. 특정 인스턴스의 메서드 참조
String prefix = "Hello, ";
Function<String, String> f2 = prefix::concat;
// 동등: s -> prefix.concat(s)
// 3. 임의 객체의 메서드 참조
Function<String, Integer> f3 = String::length;
// 동등: s -> s.length()
// 4. 생성자 참조
Supplier<List<String>> f4 = ArrayList::new;
// 동등: () -> new ArrayList<>()
Function<Integer, List<String>> f5 = ArrayList::new;
// 동등: capacity -> new ArrayList<>(capacity)
List<Shipment> shipments = ...;
// 함수형 인터페이스 활용
List<String> activeBlNos = shipments.stream()
.filter(s -> s.getStatus() == ShipmentStatus.ACTIVE) // Predicate
.map(Shipment::getBlNo) // Function
.collect(Collectors.toList());
shipments.forEach(System.out::println); // Consumer
Optional<BigDecimal> maxWeight = shipments.stream()
.map(Shipment::getWeight) // Function
.max(Comparator.naturalOrder()); // Comparator
BigDecimal total = shipments.stream()
.map(Shipment::getFare)
.reduce(BigDecimal.ZERO, BigDecimal::add); // BinaryOperator
함수형 인터페이스가 람다와 어떻게 연결되나?
답:
1. 함수형 인터페이스: 추상 메서드가 정확히 1개
2. 람다: 그 1개 메서드의 구현을 간결하게 표현
3. 사용:
Runnable r = () -> {...} → run() 구현Comparator<T> c = (a, b) -> ... → compare() 구현→ 함수형 프로그래밍의 핵심.
public interface Comparable<T> {
int compareTo(T other);
}
// 구현
public class Shipment implements Comparable<Shipment> {
private LocalDate createdAt;
@Override
public int compareTo(Shipment other) {
return this.createdAt.compareTo(other.createdAt);
}
}
// 활용
List<Shipment> list = ...;
Collections.sort(list); // Comparable 자동 사용
TreeSet<Shipment> set = new TreeSet<>(list); // 자동 정렬
Comparable 의 의미:
public interface Comparator<T> {
int compare(T o1, T o2);
// Java 8+ default + static 메서드들
static <T extends Comparable<? super T>> Comparator<T> naturalOrder() { ... }
default Comparator<T> reversed() { ... }
default Comparator<T> thenComparing(Comparator<? super T> other) { ... }
static <T, U extends Comparable<? super U>>
Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor) { ... }
}
// 활용
List<Shipment> list = ...;
// 1. 람다
list.sort((s1, s2) -> s1.getWeight().compareTo(s2.getWeight()));
// 2. Comparator.comparing
list.sort(Comparator.comparing(Shipment::getWeight));
// 3. 복합 정렬
list.sort(
Comparator.comparing(Shipment::getCreatedAt)
.thenComparing(Shipment::getWeight)
.reversed()
);
Comparator 의 의미:
public interface Iterable<T> {
Iterator<T> iterator();
// Java 8+ default
default void forEach(Consumer<? super T> action) {
for (T t : this) action.accept(t);
}
default Spliterator<T> spliterator() { ... }
}
// 구현 예
public class MyCollection<T> implements Iterable<T> {
private List<T> items = new ArrayList<>();
@Override
public Iterator<T> iterator() {
return items.iterator();
}
}
// for-each 자동 사용
MyCollection<String> collection = ...;
for (String s : collection) { // Iterable 덕분에 가능
System.out.println(s);
}
Iterable 의 의미:
// Runnable — 결과 없는 작업
@FunctionalInterface
public interface Runnable {
void run();
}
Runnable task = () -> {
System.out.println("Running");
};
new Thread(task).start();
// Callable — 결과 있는 작업 (제네릭)
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Callable<String> task = () -> {
Thread.sleep(1000);
return "Done";
};
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> future = executor.submit(task);
String result = future.get();
public interface AutoCloseable {
void close() throws Exception;
}
// try-with-resources 와 함께
public class FileResource implements AutoCloseable {
private final BufferedReader reader;
public FileResource(String filename) throws IOException {
this.reader = new BufferedReader(new FileReader(filename));
}
public String readLine() throws IOException {
return reader.readLine();
}
@Override
public void close() throws IOException {
reader.close();
}
}
// 사용 — close() 자동 호출
try (FileResource res = new FileResource("file.txt")) {
String line = res.readLine();
} catch (IOException e) {
// ...
}
// res.close() 자동 호출
public interface CharSequence {
int length();
char charAt(int index);
CharSequence subSequence(int start, int end);
String toString();
}
// 구현체:
// - String
// - StringBuilder
// - StringBuffer
// - CharBuffer
// 활용
public static void process(CharSequence cs) {
for (int i = 0; i < cs.length(); i++) {
System.out.println(cs.charAt(i));
}
}
process("hello"); // String
process(new StringBuilder("ab")); // StringBuilder
컬렉션:
- Collection<E>
- List<E>, Set<E>, Queue<E>
- Map<K, V>
- Iterator<E>, Iterable<E>
- Comparator<T>
함수형:
- Function<T, R>
- Predicate<T>
- Consumer<T>
- Supplier<T>
- Runnable, Callable<V>
비교:
- Comparable<T>
- Comparator<T>
I/O:
- Closeable, AutoCloseable
- Readable, Appendable
리소스:
- Serializable (marker)
- Cloneable (marker)
- RandomAccess (marker)
기타:
- CharSequence
- Number
- EventListener
// 1. Comparable + Comparator
public class Shipment implements Comparable<Shipment> {
@Override
public int compareTo(Shipment other) {
return this.getId().compareTo(other.getId());
}
}
List<Shipment> list = ...;
Collections.sort(list); // 자연 순서
// 다른 기준
list.sort(Comparator.comparing(Shipment::getCreatedAt));
// 2. Iterable + for-each
public class ShipmentBatch implements Iterable<Shipment> {
private List<Shipment> shipments;
@Override
public Iterator<Shipment> iterator() {
return shipments.iterator();
}
}
for (Shipment s : batch) { ... }
// 3. Runnable + Thread
Runnable task = () -> processShipment(shipment);
new Thread(task).start();
// 4. AutoCloseable + try-with-resources
public class ShipmentLock implements AutoCloseable {
private final ReentrantLock lock = new ReentrantLock();
public ShipmentLock() {
lock.lock();
}
@Override
public void close() {
lock.unlock();
}
}
try (ShipmentLock l = new ShipmentLock()) {
// 동기화 작업
}
// 5. 함수형 인터페이스
shipments.stream()
.filter(Shipment::isActive)
.map(Shipment::getBlNo)
.forEach(System.out::println);
가장 자주 쓰는 자바 표준 인터페이스 5가지는?
답:
1. Collection / List / Map — 컬렉션의 표준
2. Comparable / Comparator — 정렬
3. Iterable / Iterator — 순회
4. Runnable / Callable — 동시성
5. 함수형 인터페이스 (Function, Predicate, Consumer, Supplier) — 람다
추가:
→ 거의 모든 자바 코드의 토대.
초기 자바 (1996 ~ 2011):
인터페이스 = 메서드 시그니처 + 상수
규칙:
- 모든 메서드 추상
- 모든 필드 public static final
- 생성자 X
- 구현 X
- 다중 구현 가능
// Java 5+
@FunctionalInterface // 마커 인터페이스 → 어노테이션
public interface Comparable<T> { // 제네릭
int compareTo(T other);
}
영향:
public interface Comparator<T> {
int compare(T o1, T o2); // 추상
// ★ Java 8 추가: default 메서드
default Comparator<T> reversed() {
return (a, b) -> compare(b, a);
}
// ★ Java 8 추가: static 메서드
static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
return (a, b) -> a.compareTo(b);
}
}
Java 8 의 변화:
public interface Calculator {
default int sumPositives(List<Integer> nums) {
return nums.stream()
.filter(this::isPositive) // private 호출
.mapToInt(Integer::intValue)
.sum();
}
default int sumNegatives(List<Integer> nums) {
return nums.stream()
.filter(this::isNegative) // private 호출
.mapToInt(Integer::intValue)
.sum();
}
// ★ Java 9 추가: private 메서드
private boolean isPositive(int n) {
return n > 0;
}
private boolean isNegative(int n) {
return n < 0;
}
}
Java 9 의 변화:
시간순 변화:
Java 1.0 (1996):
- 인터페이스 도입
- 추상 메서드 + 상수만
Java 1.2 (1998):
- Collection 프레임워크
- List, Set, Map 인터페이스 표준화
Java 5 (2004):
- 제네릭 (Generic)
- 어노테이션
- Enhanced for-each (Iterable)
Java 8 (2014) ★:
- default 메서드
- static 메서드
- 함수형 인터페이스
- 람다 + 메서드 참조
- Stream API
Java 9 (2017):
- private 메서드
- private static 메서드
Java 21 (2023):
- Sealed 인터페이스 (확장 제한)
- Pattern matching for switch
왜 default 메서드가 등장?
문제:
- 기존 인터페이스 (Collection 등) 에 새 메서드 추가 불가
- 추가하면 모든 구현체가 깨짐
- 호환성 문제
해결:
- default 메서드 = 기본 구현 제공
- 기존 구현체는 그대로 사용
- 새 메서드도 자동 사용 가능
예:
Collection.forEach() — Java 8 추가
Collection.removeIf() — Java 8 추가
Collection.stream() — Java 8 추가
→ 기존 ArrayList 코드 그대로 동작
// Sealed: 확장을 명시적으로 제한
public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
}
public final class Circle implements Shape {
private final double radius;
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// Triangle, Rectangle 도 비슷
// Switch + pattern matching
public double describeShape(Shape s) {
return switch (s) {
case Circle c -> "Circle with area " + c.area();
case Rectangle r -> "Rectangle with area " + r.area();
case Triangle t -> "Triangle with area " + t.area();
// default 불필요 — sealed 라 완전
};
}
특징:
Java 8 의 default 메서드가 등장한 이유는?
답:
1. 하위 호환성:
함수형 프로그래밍 지원:
인터페이스의 진화:
→ Java 8 이 자바의 가장 큰 진화.
| Q | 핵심 답변 |
|---|---|
| 인터페이스의 의미? | "can-do" 능력 표현 |
| 메서드 자동 수식자? | public abstract |
| 필드 자동 수식자? | public static final |
| 다중 구현이 가능한 이유? | 구현 없으니 모호함 X |
| 생성자가 없는 이유? | 인스턴스화 불가 + 상태 없음 |
| 인스턴스 필드 불가 이유? | 다중 구현 모호함 회피 |
| 마커 인터페이스의 예? | Serializable, RandomAccess |
| 어노테이션과 마커 차이? | 타입 시스템 vs 메타데이터 |
| 함수형 인터페이스? | 추상 메서드 1개 |
| @FunctionalInterface? | 검증 어노테이션 |
| Comparable vs Comparator? | 자기 비교 vs 외부 비교 |
| Iterable 의 의미? | for-each 가능 |
| 람다와 인터페이스 관계? | 함수형 인터페이스의 단순화 |
| Java 8 default 메서드 이유? | 하위 호환성 |
답:
final class + private 생성자 + public static finalstatic import답:
Runnable r = new Runnable() {...}Runnable r = () -> {...}Runnable r = obj::methodRunnable r = new MyRunnable()답:
interface A extends B, C OK답:
public interface MyInterface {
static void main(String[] args) {
System.out.println("Hello");
}
}
답:
final interface 컴파일 에러)1. 인터페이스 = "can-do" 능력
2. 다중 구현의 자유
3. 진화의 역사
이번 Unit에서 인터페이스의 본질을 봤다면, 다음은 Java 8 의 혁명.
🚀 Phase 4 — 추상화의 두 도구
✅ Unit 4.1 추상클래스의 특징
✅ Unit 4.2 인터페이스의 특징 ← 여기
⏭ Unit 4.3 Java 8 default & static 메서드
⏭ Unit 4.4 추상클래스 vs 인터페이스 선택 기준
✅ Phase 1 — Pass by Value (1.1 ~ 1.3 완주)
✅ Phase 2 — 컬렉션 프레임워크 (2.1 ~ 2.6 완주)
✅ Phase 3 — 해시의 원리 (3.1 ~ 3.4 완주)
🚀 Phase 4 — 추상화의 두 도구 (2/4 진행)
총: 15/43 Unit 작성 (약 35%)