인터페이스 문서의 구현 코드다.
// 인터페이스: 선언, 구현 클래스, 상수 필드, 추상 메서드, 인터페이스 타입 변수
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 23_java-interface_impl.java
// 문서(23.인터페이스.md)에 실은 출력과 카드의 답을 직접 비교한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
class InterfaceCheck {
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);
}
// 리모컨을 쓰는 쪽. RemoteControl만 안다.
static void pressAll(RemoteControl rc) {
rc.turnOn();
rc.turnOff();
}
public static void main(String[] args) throws Exception {
// 예시로 보기: 같은 변수에 다른 객체를 넣으면 같은 호출이 다른 일을 한다
check("예시: 리모컨 바꿔 끼우기", capture(() -> {
RemoteControl rc;
rc = new Television();
rc.turnOn();
rc = new Lights();
rc.turnOn();
}), "Turn on TV\nTurn on lights\n");
// 상수 필드
check("상수 필드", capture(() -> {
System.out.println("Max:" + RemoteControl.MAX_VOLUME);
System.out.println("Min:" + RemoteControl.MIN_VOLUME);
}), "Max:10\nMin:0\n");
// 상수 필드에는 public static final이 자동으로 붙는다
int m = RemoteControl.class.getField("MAX_VOLUME").getModifiers();
if (!(Modifier.isPublic(m) && Modifier.isStatic(m) && Modifier.isFinal(m)))
throw new AssertionError("MAX_VOLUME은 public static final이어야 한다");
System.out.println("ok: MAX_VOLUME은 public static final");
// 추상 메서드에는 public abstract가 자동으로 붙는다
int mm = RemoteControl.class.getMethod("turnOn").getModifiers();
if (!(Modifier.isPublic(mm) && Modifier.isAbstract(mm)))
throw new AssertionError("turnOn은 public abstract여야 한다");
System.out.println("ok: turnOn은 public abstract");
// 쓰는 쪽 코드는 그대로 두고 객체만 바꾼다
check("pressAll(TV)", capture(() -> pressAll(new Television())), "Turn on TV\nTurn off TV\n");
check("pressAll(Lights)", capture(() -> pressAll(new Lights())), "Turn on lights\nTurn off lights\n");
// null이 든 인터페이스 변수로 메서드를 부르면 NullPointerException
boolean npe = false;
try {
RemoteControl rc = null;
rc.turnOn();
} catch (NullPointerException e) { npe = true; }
if (!npe) throw new AssertionError("null 변수 호출은 NullPointerException이어야 한다");
System.out.println("ok: null 변수로 호출하면 NullPointerException");
// 카드 C4
check("카드 C4", capture(() -> {
RemoteControl[] rcs = { new Lights(), new Television(), new Lights() };
for (RemoteControl rc : rcs) rc.turnOn();
System.out.println(RemoteControl.MAX_VOLUME - RemoteControl.MIN_VOLUME);
}), "Turn on lights\nTurn on TV\nTurn on lights\n10\n");
System.out.println("ALL CHECKS PASSED");
}
}
interface RemoteControl {
int MAX_VOLUME = 10; // public static final이 자동으로 붙는다
int MIN_VOLUME = 0;
void turnOn(); // public abstract가 자동으로 붙는다
void turnOff();
}
class Television implements RemoteControl {
@Override
public void turnOn() { System.out.println("Turn on TV"); }
@Override
public void turnOff() { System.out.println("Turn off TV"); }
}
class Lights implements RemoteControl {
@Override
public void turnOn() { System.out.println("Turn on lights"); }
@Override
public void turnOff() { System.out.println("Turn off lights"); }
}