F-lab Java 1주차 / Phase 2 / Unit 2.5 본격 학습 자료
9-섹션 마스터 프롬프트 형식으로 깊이 파헤친다.선수 지식: Unit 2.4 (다형성)
다음 Unit: 2.6 — Nested/Inner/Anonymous 클래스이 Unit의 의미: 다형성의 안전장치. ClassCastException을 방지하고, 자식의 고유 메서드를 안전하게 호출하는 도구.
당신이 어른용 행사에 들어가려고 합니다. 입구의 보안 직원이 묻습니다:
"이분이 정말 성인이 맞는지 확인하겠습니다."
직원이 신분증을 보고 확인:
핵심:
→ instanceof 가 자바에서 하는 역할.
야구 선수가 경기 후 유니폼을 갈아입는다고 합시다.
상황:
같은 사람 인데:
핵심:
→ 이게 형변환 (Type Casting).
응급실에 환자가 도착합니다. 의사가:
1. instanceof — 신원 확인
"이 환자는 성인인가? 어린이인가?"
↓
2. 형변환 — 적절한 도구 준비
"어린이라면 → 소아과 도구"
"성인이라면 → 성인 도구"
↓
3. 자식 클래스만의 메서드 호출
"어린이에게는 어린이용 약 처방"
잘못된 처리:
→ instanceof로 먼저 확인 → 안전한 형변환 → 자식의 고유 메서드 사용
Unit 2.4 다형성을 복습:
public abstract class Animal {
public abstract void makeSound(); // 모든 동물 공통
}
public class Dog extends Animal {
@Override
public void makeSound() { System.out.println("멍멍"); }
public void bark() { System.out.println("BARK!"); } // Dog만의 메서드
public void wagTail() { System.out.println("꼬리 흔들기"); } // Dog만의 메서드
}
다형성의 강점:
Animal a = new Dog();
a.makeSound(); // ✅ "멍멍"
다형성의 한계 ⚠️ :
Animal a = new Dog();
a.bark(); // ❌ 컴파일 에러 — Animal에 bark() 없음
a.wagTail(); // ❌ 컴파일 에러 — Animal에 wagTail() 없음
→ 부모 타입으로는 자식만의 메서드 사용 불가. 컴파일러는 변수 타입(Animal) 만 봄.
자식의 메서드를 쓰려면 자식 타입으로 형변환 필요:
Animal a = new Dog();
Dog d = (Dog) a; // 명시적 캐스팅
d.bark(); // ✅ 가능
d.wagTail(); // ✅ 가능
Animal a = new Cat(); // 실제는 Cat
Dog d = (Dog) a; // ⚠️ Cat을 Dog로 캐스팅 시도
d.bark(); // 💥 ClassCastException (런타임 에러)
문제:
instanceof 의 등장이 문제를 해결하기 위한 안전장치:
Animal a = new Cat();
if (a instanceof Dog) { // ← 진짜 Dog인지 확인
Dog d = (Dog) a; // 안전한 캐스팅
d.bark();
} else {
System.out.println("Dog 아님");
}
효과:
"다형성은 강력하지만 자식의 고유 능력을 쓰려면 형변환이 필요하다.
그 형변환은 위험하므로instanceof라는 안전장치를 거쳐야 한다."
다형성, instanceof, 형변환은 세 가지가 함께 가는 한 세트.
instanceof 없이 형변환을 남발하면 어떤 문제가 생기는지 보겠습니다.
다양한 운임 종류 (StandardFare, UrgentFare, InternationalFare) 가 있고, 각자 고유 메서드를 가집니다.
public abstract class Fare { ... }
public class StandardFare extends Fare { ... }
public class UrgentFare extends Fare {
public int getUrgentFee() { return urgentFee; }
}
public class InternationalFare extends Fare {
public String getCurrency() { return currency; }
public double getExchangeRate() { return exchangeRate; }
}
public class FareReportService {
public void printReport(List<Fare> fares) {
for (Fare fare : fares) {
// 모든 운임을 UrgentFare로 캐스팅 시도 ❌
UrgentFare urgent = (UrgentFare) fare;
System.out.println("긴급비: " + urgent.getUrgentFee());
}
}
}
List<Fare> fares = List.of(
new StandardFare(1L, 50000),
new UrgentFare(2L, 50000, 10000),
new InternationalFare(3L, 100, "USD", 1300.0)
);
reportService.printReport(fares);
// 첫 번째 항목 처리 시도:
// 💥 ClassCastException — StandardFare를 UrgentFare로 캐스팅 불가
// → 프로그램 죽음
문제:
1. 첫 다른 타입 만나면 즉시 크래시
2. 의도하지 않은 객체 처리 위험
3. 고객 데이터 처리 중 다운 → 서비스 장애
대안으로 모든 메서드를 부모에 정의하는 사람이 있습니다:
// ❌ 잘못된 설계
public abstract class Fare {
// 모든 자식의 메서드를 다 가짐
public int getUrgentFee() { return 0; } // 일반 운임은 0
public String getCurrency() { return "KRW"; } // 일반은 KRW
public double getExchangeRate() { return 1.0; } // 일반은 1.0
// ... 모든 자식의 메서드 ❌
}
문제:
getUrgentFee() 가 항상 0)public class FareReportService {
public void printReport(List<Fare> fares) {
for (Fare fare : fares) {
// 공통 정보
System.out.println("운임 ID: " + fare.getId());
System.out.println("총액: " + fare.calculateTotal());
// 타입별 추가 정보
if (fare instanceof UrgentFare) {
UrgentFare urgent = (UrgentFare) fare;
System.out.println("긴급비: " + urgent.getUrgentFee());
} else if (fare instanceof InternationalFare) {
InternationalFare intl = (InternationalFare) fare;
System.out.println("통화: " + intl.getCurrency());
System.out.println("환율: " + intl.getExchangeRate());
}
}
}
}
reportService.printReport(fares);
// 모든 운임 안전하게 처리 ✅
효과:
// ⚠️ 너무 많은 instanceof — 다형성을 활용 못함
public void process(Fare fare) {
if (fare instanceof StandardFare) { ... }
else if (fare instanceof UrgentFare) { ... }
else if (fare instanceof InternationalFare) { ... }
else if (fare instanceof DomesticFare) { ... }
// ... 매번 추가
}
→ 다형성 (Unit 2.4) 으로 해결할 수 있는 문제를 instanceof로 풀고 있다는 신호.
진짜 해결:
public abstract class Fare {
public abstract void printDetail(); // 각 자식이 알아서
}
public void process(Fare fare) {
fare.printDetail(); // ✅ 다형성!
}
→ instanceof 가 코드에 많이 등장하면 설계 재검토.
instanceof 기본 문법객체 instanceof 타입
반환값: boolean
true: 객체가 그 타입 (또는 자손) 임false: 그렇지 않음Animal a = new Dog();
a instanceof Dog; // true — Dog 인스턴스
a instanceof Animal; // true — Animal의 자손이기도 함
a instanceof Object; // true — 모든 객체는 Object의 자손
a instanceof Cat; // false — Cat이 아님
a instanceof String; // 컴파일 에러 — 무관한 타입
핵심 규칙 ⭐ :
Animal a = null;
a instanceof Dog; // false (null은 어떤 타입도 아님)
a instanceof Animal; // false
→ instanceof 는 null 안전. NullPointerException 안 남.
활용:
// null 체크 + 타입 체크를 한 번에
if (obj instanceof String) {
// obj는 null이 아니고, String임
String s = (String) obj;
}
// vs 안 좋은 코드
if (obj != null && obj.getClass() == String.class) {
// 더 길고 명시적
}
두 종류:
Dog d = new Dog();
Animal a = d; // ✅ 암시적 캐스팅 (자동)
규칙:
다형성의 토대:
List<Animal> animals = new ArrayList<>();
animals.add(new Dog()); // Dog → Animal 자동 업캐스팅
animals.add(new Cat());
Animal a = new Dog();
Dog d = (Dog) a; // ✅ 명시적 캐스팅 필요
규칙:
(타입) 표시 필요Animal a = getAnimal();
if (a instanceof Dog) {
Dog d = (Dog) a; // 안전
d.bark();
}
Java 16부터 instanceof + 캐스팅을 한 줄로:
// 옛 방식
if (a instanceof Dog) {
Dog d = (Dog) a;
d.bark();
}
// Java 16+ 패턴 매칭
if (a instanceof Dog d) { // ← 한 번에!
d.bark();
}
더 간결한 사용:
// 부정 조건과 결합
if (!(a instanceof Dog d)) {
return; // Dog 아니면 종료
}
d.bark(); // 여기서는 d 사용 가능
// 조건 결합
if (a instanceof Dog d && d.getAge() > 3) {
d.bark();
}
public String describe(Animal a) {
return switch (a) {
case Dog d -> "개: " + d.getBreed();
case Cat c -> "고양이: " + c.getName();
case null -> "동물 없음";
default -> "알 수 없는 동물";
};
}
→ if-else 지옥을 깔끔하게 해결.
ILIC 활용 가능:
public String describeFare(Fare fare) {
return switch (fare) {
case UrgentFare u -> "긴급 (긴급비: " + u.getUrgentFee() + ")";
case InternationalFare i -> "국제 (" + i.getCurrency() + ")";
case StandardFare s -> "일반";
default -> "알 수 없는 운임";
};
}
instanceof 의 내부 동작JVM이 어떻게 instanceof를 검사하는지 보겠습니다.
Animal a = new Dog();
boolean result = a instanceof Dog;
JVM 내부 흐름:
1. a가 가리키는 객체의 클래스 정보 확인
↓
2. 그 클래스가 Dog인지 확인
↓
3. 아니면 부모 클래스로 거슬러 올라가며 확인
Dog → Animal → Object
↓
4. 비교 대상(Dog)을 발견하면 true
끝까지 못 찾으면 false
바이트코드:
INSTANCEOF Dog ← JVM 명령어
→ JVM이 직접 처리하는 단순하고 빠른 연산.
Animal a = new Dog();
Dog d = (Dog) a;
JVM 내부 흐름:
1. a가 가리키는 객체의 실제 클래스 확인
↓
2. 캐스팅 대상(Dog)과 호환되는지 검증
- a가 Dog 또는 Dog의 자식? → OK
- 아니면 → ClassCastException
↓
3. d 변수에 같은 객체 참조 할당
- 객체 자체는 변하지 않음 ⭐
- 단지 변수의 타입만 다름
바이트코드:
CHECKCAST Dog ← JVM이 검증 + 캐스팅
Animal a = new Dog();
Dog d = (Dog) a;
// a와 d는 같은 객체를 가리킴
System.out.println(a == d); // true ✅
의미:
비유:
Animal a = new Dog(); // Heap에 Dog 인스턴스 생성, a는 Animal 타입 참조
Dog d = (Dog) a; // d도 같은 Dog 인스턴스를 가리킴
메모리 구조:
[Stack]
a (Animal 타입 참조) ─┐
d (Dog 타입 참조) ─┤
↓
[Heap]
Dog 인스턴스 (1개만 존재)
- name
- breed
→ 참조 변수만 여러 개, 객체는 하나.
String s = "hello";
Integer i = (Integer) s; // ❌ 컴파일 에러
왜?:
Animal a = new Dog();
Dog d = (Dog) a; // ✅ 컴파일 OK (가능성 있음)
// 단, 런타임에 검증
getClass() vs instanceof ⭐두 가지 방법 비교:
Animal a = new Dog();
// 방법 1: getClass()
a.getClass() == Dog.class; // true (정확히 Dog)
a.getClass() == Animal.class; // false (Dog는 Animal이 아님)
// 방법 2: instanceof
a instanceof Dog; // true
a instanceof Animal; // true (자손 관계)
핵심 차이 ⭐ :
getClass() | instanceof | |
|---|---|---|
| 비교 방식 | 정확히 같은 클래스 | 자손 포함 |
| 다형성 | X | O |
| 사용 빈도 | 낮음 | 매우 높음 |
| 사용 케이스 | equals() 구현 등 | 일반적인 타입 검증 |
equals() 에서의 논쟁:
// 방식 1 — instanceof (Joshua Bloch 권장)
@Override
public boolean equals(Object o) {
if (!(o instanceof Customer)) return false;
// 자식도 equals 가능 (대칭성 깨질 수 있음)
}
// 방식 2 — getClass() (대칭성 보장)
@Override
public boolean equals(Object o) {
if (o == null || getClass() != o.getClass()) return false;
// 정확히 같은 클래스만 같다고 판단
}
→ 상황에 따라 다름. 6주차에서 자세히.
public class FareReportService {
public void printDetailedReport(List<Fare> fares) {
for (Fare fare : fares) {
// 공통 정보 (다형성)
System.out.println("=== 운임 #" + fare.getId() + " ===");
System.out.println("총액: " + fare.calculateTotal() + "원");
// 타입별 추가 정보 (instanceof + 캐스팅)
if (fare instanceof UrgentFare) {
UrgentFare urgent = (UrgentFare) fare;
System.out.println("긴급비: " + urgent.getUrgentFee() + "원");
System.out.println("긴급 처리 사유: " + urgent.getUrgentReason());
} else if (fare instanceof InternationalFare) {
InternationalFare intl = (InternationalFare) fare;
System.out.println("통화: " + intl.getCurrency());
System.out.println("환율: " + intl.getExchangeRate());
System.out.println("출발지: " + intl.getOrigin());
System.out.println("도착지: " + intl.getDestination());
}
}
}
}
public class FareReportService {
public void printDetailedReport(List<Fare> fares) {
for (Fare fare : fares) {
System.out.println("=== 운임 #" + fare.getId() + " ===");
System.out.println("총액: " + fare.calculateTotal() + "원");
// Java 16+ 패턴 매칭 — 한 줄로 검사 + 캐스팅
if (fare instanceof UrgentFare urgent) {
System.out.println("긴급비: " + urgent.getUrgentFee() + "원");
} else if (fare instanceof InternationalFare intl) {
System.out.println("통화: " + intl.getCurrency<());
System.out.println("환율: " + intl.getExchangeRate());
}
}
}
}
→ 변수 선언이 한 번에 끝남.
public class FareDescriptionService {
public String describe(Fare fare) {
return switch (fare) {
case UrgentFare u ->
String.format("긴급 운임 (긴급비: %d원, 사유: %s)",
u.getUrgentFee(), u.getUrgentReason());
case InternationalFare i ->
String.format("국제 운임 (%s, 환율 %.2f)",
i.getCurrency(), i.getExchangeRate());
case StandardFare s ->
"일반 운임";
case null ->
"운임 없음";
default ->
"알 수 없는 운임 타입";
};
}
}
→ if-else 지옥 → 깔끔한 switch.
public class CustomerService {
public void processCustomers(List<Customer> customers) {
int vipCount = 0;
int totalDiscount = 0;
for (Customer customer : customers) {
// 공통 처리
customer.register();
// VIP만의 추가 처리
if (customer instanceof VipCustomer vip) {
vipCount++;
totalDiscount += vip.getExtraVipDiscount();
vip.sendVipNotification();
}
// 파트너만의 추가 처리
if (customer instanceof PartnerCustomer partner) {
partner.notifyPartnerTeam();
}
}
System.out.println("VIP 수: " + vipCount);
System.out.println("VIP 추가 할인 총액: " + totalDiscount);
}
}
// ❌ 안 좋은 코드 — 다형성을 외면
public class FareCalculator {
public int calculate(Fare fare) {
if (fare instanceof StandardFare s) {
return s.getAmount();
} else if (fare instanceof UrgentFare u) {
return u.getAmount() + u.getUrgentFee();
} else if (fare instanceof InternationalFare i) {
return (int)(i.getAmount() * i.getExchangeRate());
}
return 0;
}
}
문제:
해결 — 다형성 활용:
public abstract class Fare {
public abstract int calculateTotal(); // 각 자식이 구현
}
public class FareCalculator {
public int calculate(Fare fare) {
return fare.calculateTotal(); // ✅ 다형성!
}
}
→ instanceof 가 자주 보이면 다형성으로 리팩토링.
언제 instanceof가 적합한가?
public void handleEvent(Object event) {
if (event instanceof OrderCreatedEvent ev) {
processOrder(ev);
} else if (event instanceof PaymentCompletedEvent ev) {
processPayment(ev);
}
}
→ 이벤트 시스템처럼 타입을 미리 알 수 없는 경우.
public void register(Object service) {
services.add(service);
// 추가 인터페이스 구현 여부 확인
if (service instanceof Closeable closeable) {
cleanupHandlers.add(closeable);
}
if (service instanceof Healthchecker checker) {
healthCheckers.add(checker);
}
}
→ 선택적 기능 을 위한 instanceof.
public void serialize(Object obj) {
if (obj instanceof Serializable) {
// 직렬화 가능
} else {
throw new IllegalArgumentException("직렬화 불가");
}
}
→ 마커 인터페이스 확인.
@Test
void 운임_생성_시_긴급_운임이라면_긴급비가_추가된다() {
Fare fare = fareService.create(new UrgentFareRequest(...));
assertThat(fare).isInstanceOf(UrgentFare.class); // 타입 검증
UrgentFare urgent = (UrgentFare) fare;
assertThat(urgent.getUrgentFee()).isPositive();
}
→ AssertJ는 isInstanceOf() 를 제공.
// ❌ instanceof 검사 없이 캐스팅
public void process(Animal a) {
Dog d = (Dog) a; // a가 Cat이면 즉시 폭발
d.bark();
}
해결:
public void process(Animal a) {
if (a instanceof Dog d) {
d.bark();
}
}
// ❌ 다형성을 안 씀
public int calculate(Shape shape) {
if (shape instanceof Circle c) {
return (int)(Math.PI * c.getRadius() * c.getRadius());
} else if (shape instanceof Rectangle r) {
return r.getWidth() * r.getHeight();
} else if (shape instanceof Triangle t) {
return t.getBase() * t.getHeight() / 2;
}
return 0;
}
해결 — 다형성:
public abstract class Shape {
public abstract int calculateArea(); // 각 자식이 구현
}
public int calculate(Shape shape) {
return shape.calculateArea(); // ✅
}
→ instanceof 분기가 3개 이상이면 다형성으로 리팩토링 검토.
String s = "hello";
if (s instanceof Integer) { // ❌ 컴파일 에러
// ...
}
→ String과 Integer는 상속 관계 없음. 컴파일러가 잡음.
예외 — Object 변수:
Object obj = "hello";
if (obj instanceof Integer) { // ✅ 컴파일 OK (런타임에 false)
// ...
}
→ Object 타입은 어떤 객체든 가리킬 수 있어서 컴파일은 통과.
Animal a = null;
Dog d = (Dog) a; // ✅ 컴파일 OK, 런타임 OK (d는 null)
d.bark(); // 💥 NullPointerException
해결:
if (a instanceof Dog d) { // null이면 false
d.bark();
}
→ instanceof 가 자동으로 null 처리해줌.
public class Cat extends Animal { ... }
public class Persian extends Cat { ... }
Animal a = new Persian();
// ❌ 잘못된 순서
if (a instanceof Cat c) {
System.out.println("고양이 처리");
} else if (a instanceof Persian p) { // 절대 도달 X (이미 Cat에서 잡힘)
System.out.println("페르시안 처리");
}
// ✅ 올바른 순서 — 가장 구체적인 것부터
if (a instanceof Persian p) {
System.out.println("페르시안 처리");
} else if (a instanceof Cat c) {
System.out.println("고양이 처리");
}
규칙: 자식 → 부모 순서.
// ❌ 위험한 equals
@Override
public boolean equals(Object o) {
if (o instanceof Customer) {
// 자식도 통과 → 대칭성 깨질 수 있음
}
}
VipCustomer v = new VipCustomer(1L);
Customer c = new Customer(1L);
v.equals(c); // true (Vip이 Customer로 비교)
c.equals(v); // 결과가 다를 수 있음 ⚠️ — 대칭성 깨짐
→ 6주차에서 깊이 다룰 주제.
// 옛 스타일 — 변수 두 번 선언
if (obj instanceof String) {
String s = (String) obj;
// ...
}
// 현대 스타일 (Java 16+)
if (obj instanceof String s) {
// 한 번에 끝
}
→ Java 16+ 환경이면 패턴 매칭 적극 활용.
// ❌ 안티패턴
try {
Dog d = (Dog) a;
d.bark();
} catch (ClassCastException e) {
// 무시
}
문제:
해결:
if (a instanceof Dog d) {
d.bark();
}
[Unit 2.4: 다형성]
↓
[Unit 2.5: instanceof와 형변환] ← 지금 여기
↓
[Unit 2.6: Nested/Inner/Anonymous]
1주차 내:
미래 주차:
<T extends Number> 등[다형성 (Unit 2.4)]
"부모 타입으로 자식 객체 처리"
↓
하지만 자식 고유 메서드는?
↓
[형변환 (Unit 2.5)]
"부모 타입을 자식 타입으로"
↓
잘못 캐스팅하면 위험
↓
[instanceof (Unit 2.5)]
"안전하게 타입 확인"
세 가지가 한 세트 — 하나만 알아도 부족.
| 질문 | 이 Unit에서의 답 |
|---|---|
| "instanceof가 뭔가요?" | 객체가 특정 타입인지 검사하는 연산자 |
| "다운캐스팅이 위험한 이유?" | ClassCastException 가능 — instanceof 사전 검사 필요 |
| "instanceof와 getClass()의 차이?" | instanceof는 자손 포함, getClass()는 정확한 타입 |
| "패턴 매칭이 뭔가요?" | Java 16+ 의 instanceof + 변수 선언 결합 문법 |
| "instanceof 남발의 문제?" | 다형성을 활용 못함 — 설계 재검토 신호 |
1️⃣ instanceof는 다형성의 안전장치다.
부모 타입으로 자식 객체를 다루다가 자식의 고유 메서드 를 쓰려면 다운캐스팅이 필요한데, 잘못 캐스팅하면 ClassCastException이 발생한다.
instanceof로 사전 타입 검증 후 안전하게 캐스팅하는 게 표준 패턴이다.2️⃣ Java 16+ 패턴 매칭으로 한 줄에 끝낸다.
옛 방식 (
if (a instanceof Dog) { Dog d = (Dog) a; ... }) 대신if (a instanceof Dog d) { d.bark(); }로 검사 + 캐스팅 + 변수 선언을 한 번에. Java 21+에서는 switch 패턴 매칭으로 if-else 지옥을 깔끔하게 해결.3️⃣ instanceof가 많으면 다형성을 외면한다는 신호다.
분기가 3개 이상이면 다형성으로 리팩토링 검토. 단, 외부 이벤트 처리, 옵션 인터페이스 확인, 마커 인터페이스 검사 같은 정당한 사용 케이스는 있다. 또한 다운캐스팅 시 가장 구체적인 자식 타입부터 검사해야 정확하다.
Q1: 부모 타입을 자식 타입으로 캐스팅하기 전에 왜 instanceof 검사가 필요한가?
한 문장: ClassCastException을 방지하기 위해.
상세 설명:
다운캐스팅 (부모 → 자식) 은 컴파일러가 검증할 수 없습니다:
Animal a = getAnimal(); // 실제 객체는 런타임에 결정
Dog d = (Dog) a; // 컴파일러: "캐스팅하겠다"만 확인
컴파일러는 "Animal 변수를 Dog로 캐스팅하겠다" 만 보고, 실제로 가능한지는 모름. 실제 가능 여부는 런타임에 JVM이 검증.
잘못 캐스팅 시:
Animal a = new Cat(); // 실제는 Cat
Dog d = (Dog) a; // 💥 ClassCastException
// — JVM이 런타임에 "Cat은 Dog 아님" 발견
instanceof 검사로 사전 방어:
Animal a = new Cat();
if (a instanceof Dog) { // false → 캐스팅 시도 안 함
Dog d = (Dog) a;
d.bark();
} else {
// 안전한 대체 처리
}
Java 16+ 패턴 매칭으로 더 깔끔하게:
if (a instanceof Dog d) { // 검사 + 캐스팅을 한 번에
d.bark();
}
핵심 통찰 ⭐ :
"컴파일러는 다운캐스팅의 가능성만 확인하고, 실제 가능 여부는 런타임에 결정된다.
그래서 instanceof로 사전 검증이 안전한 패턴이다."
예외 — 100% 확실한 경우:
public void process(Object obj) {
String s = obj.toString(); // toString() 결과는 항상 String
// 캐스팅 불필요
}
→ 메서드의 반환 타입이 명확하면 캐스팅도 불필요.
Q2: Object instanceof String 이 false일 수 있는 경우는?
가능한 모든 경우:
Object obj = 42; // Integer
obj instanceof String; // false
Object obj = null;
obj instanceof String; // false (null은 어떤 타입도 아님)
Object obj = new Object(); // 정확히 Object
obj instanceof String; // false (Object는 String의 부모이지 자손이 아님)
왜 false인가 — instanceof 의 동작 규칙:
"객체가 그 타입 또는 그 타입의 자손인 경우 에만 true"
핵심 정리:
| Object obj 의 실제 타입 | obj instanceof String |
|---|---|
null | false |
Object (정확히) | false |
Integer | false |
String | true |
String의 자식 (없음 — final 클래스) | true |
비유:
→ instanceof는 자기 또는 자손 만 true.
참고 — String의 특수성:
final 클래스 → 자식 클래스 없음instanceof String 이 true면 정확히 String