추상 클래스와 인터페이스 비교 문서의 구현 코드다.
// 추상 클래스와 인터페이스 비교: 상태를 물려주는 부모 하나 + 할 수 있는 일을 적은 인터페이스 여럿
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 25_abstract-class-vs-interface_impl.java
// 문서(25.추상 클래스와 인터페이스 비교.md)의 표와 카드의 답을 직접 비교한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
class ContrastCheck {
static String capture(Runnable r) {
PrintStream old = System.out;
ByteArrayOutputStream buf = new ByteArrayOutputStream();
System.setOut(new PrintStream(buf));
try { r.run(); } finally { System.setOut(old); }
return buf.toString();
}
static void check(String name, String got, String want) {
if (!got.equals(want)) throw new AssertionError(name + "\n--- got ---\n" + got + "--- want ---\n" + want);
System.out.println("ok: " + name);
}
public static void main(String[] args) throws Exception {
// 오리는 Bird 하나를 물려받고, 인터페이스 둘을 구현한다
check("Duck: 부모 하나 + 인터페이스 둘", capture(() -> {
Duck d = new Duck("도널드");
Bird b = d; // 부모 타입
Flyable f = d; // 인터페이스 타입 1
Swimmable s = d; // 인터페이스 타입 2
b.eat();
f.fly();
s.swim();
}), "도널드: 먹는다 (1번째)\n도널드: 난다\n도널드: 헤엄친다\n");
// 추상 클래스는 객체마다 다른 값을 담는 필드를 둔다
Duck a = new Duck("A"), b = new Duck("B");
String out = capture(() -> { a.eat(); a.eat(); b.eat(); });
check("인스턴스 필드는 객체마다 따로", out, "A: 먹는다 (1번째)\nA: 먹는다 (2번째)\nB: 먹는다 (1번째)\n");
// 인터페이스의 필드는 모두 public static final
int m = Flyable.class.getField("MAX_HEIGHT").getModifiers();
if (!(Modifier.isPublic(m) && Modifier.isStatic(m) && Modifier.isFinal(m)))
throw new AssertionError("인터페이스 필드는 public static final이어야 한다");
System.out.println("ok: 인터페이스 필드는 public static final");
// 추상 클래스에는 생성자가 있고 인터페이스에는 없다
if (Bird.class.getDeclaredConstructors().length != 1) throw new AssertionError("Bird 생성자 1개");
if (Flyable.class.getDeclaredConstructors().length != 0) throw new AssertionError("Flyable 생성자 0개");
System.out.println("ok: 추상 클래스 생성자 1개, 인터페이스 생성자 0개");
// 부모 클래스는 하나, 구현한 인터페이스는 둘
if (Duck.class.getSuperclass() != Bird.class) throw new AssertionError("부모는 Bird");
if (Duck.class.getInterfaces().length != 2) throw new AssertionError("인터페이스 2개");
System.out.println("ok: Duck의 부모 클래스 1개, 인터페이스 2개");
System.out.println("ALL CHECKS PASSED");
}
}
abstract class Bird {
String name; // 객체마다 다른 값
int meals = 0; // 객체마다 다른 값
Bird(String name) { this.name = name; }
void eat() {
meals++;
System.out.println(name + ": 먹는다 (" + meals + "번째)");
}
}
interface Flyable {
int MAX_HEIGHT = 100;
void fly();
}
interface Swimmable {
void swim();
}
class Duck extends Bird implements Flyable, Swimmable {
Duck(String name) { super(name); }
@Override
public void fly() { System.out.println(name + ": 난다"); }
@Override
public void swim() { System.out.println(name + ": 헤엄친다"); }
}