참조 타입 변환과 instanceof 문서의 구현 코드다.
// 참조 타입 변환과 instanceof: 자동 변환, 강제 변환, ClassCastException
// 실행: docker run --rm -v "$PWD":/w -w /w eclipse-temurin:21-jdk java 28_java-casting_impl.java
// 문서(28.참조 타입 변환과 instanceof.md)에 실은 출력과 카드의 답을 직접 비교한다.
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
class CastingCheck {
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);
}
static void ride(Vehicle vehicle) {
vehicle.run();
if (vehicle instanceof Bus) {
Bus bus = (Bus) vehicle;
bus.checkFare();
}
}
static void rideNew(Vehicle vehicle) { // Java 16부터: 검사와 변환을 한 번에
vehicle.run();
if (vehicle instanceof Bus bus) {
bus.checkFare();
}
}
public static void main(String[] args) {
// 자동 변환: 문서의 A, B, C, D, E
A a;
B b = new B(); C c = new C(); D d = new D(); E e = new E();
a = b; a = c; a = d; a = e;
if (!(a instanceof E && a instanceof C && !(a instanceof B))) throw new AssertionError("a에는 E 객체가 들어 있다");
System.out.println("ok: B, C, D, E 객체 모두 A 변수에 들어간다");
// 자동 변환은 객체를 바꾸지 않는다
Bus original = new Bus();
Vehicle v = original;
if (v != original) throw new AssertionError("같은 객체여야 한다");
System.out.println("ok: 자동 변환 뒤에도 같은 객체");
// 강제 변환: 문서의 Vehicle과 Bus
check("강제 변환", capture(() -> {
Vehicle vehicle = new Bus();
vehicle.run();
Bus bus = (Bus) vehicle;
bus.run();
bus.checkFare();
System.out.println(bus == vehicle);
}), "Bus is running\nBus is running\nCheck fare\ntrue\n");
// 들어 있는 객체가 Bus가 아니면 실행 중에 ClassCastException
String msg = null;
try {
Vehicle vehicle = new Taxi();
Bus bus = (Bus) vehicle;
} catch (ClassCastException ex) { msg = ex.getMessage(); }
if (msg == null || !msg.contains("Taxi cannot be cast to class Bus")) throw new AssertionError("ClassCastException: " + msg);
System.out.println("ok: Taxi를 Bus로 바꾸면 ClassCastException");
// instanceof
check("instanceof 표", capture(() -> {
Vehicle v1 = new Bus();
Vehicle v2 = new Taxi();
Vehicle v3 = null;
System.out.println(v1 instanceof Bus);
System.out.println(v1 instanceof Vehicle);
System.out.println(v2 instanceof Bus);
System.out.println(v3 instanceof Bus);
}), "true\ntrue\nfalse\nfalse\n");
check("ride(Bus)", capture(() -> ride(new Bus())), "Bus is running\nCheck fare\n");
check("ride(Taxi)", capture(() -> ride(new Taxi())), "Taxi is running\n");
check("rideNew(Bus)", capture(() -> rideNew(new Bus())), "Bus is running\nCheck fare\n");
check("rideNew(Taxi)", capture(() -> rideNew(new Taxi())), "Taxi is running\n");
// 카드 C2
check("카드 C2", capture(() -> {
Vehicle[] list = { new Taxi(), new Bus(), new Taxi() };
int count = 0;
for (Vehicle x : list) {
if (x instanceof Bus) {
((Bus) x).checkFare();
count++;
}
}
System.out.println(count);
}), "Check fare\n1\n");
// 카드 C3: (가)는 통과, (나)는 ClassCastException
A x = new D();
B ok = (B) x; // D는 B의 자식이라 통과
boolean cce = false;
try { C bad = (C) x; } catch (ClassCastException ex) { cce = true; }
if (!cce) throw new AssertionError("D를 C로 바꾸면 ClassCastException이어야 한다");
System.out.println("ok: 카드 C3");
System.out.println("ALL CHECKS PASSED");
}
}
interface A { }
class B implements A { }
class C implements A { }
class D extends B { }
class E extends C { }
interface Vehicle { void run(); }
class Bus implements Vehicle {
@Override
public void run() { System.out.println("Bus is running"); }
public void checkFare() { System.out.println("Check fare"); }
}
class Taxi implements Vehicle {
@Override
public void run() { System.out.println("Taxi is running"); }
}