1. 패턴 변수의 범위와 관련된 오류
가장 흔한 실수 중 하나는 패턴에서 선언된 변수를 해당 변수가 유효한 영역 밖에서 사용하려는 것입니다(예: instanceof에서 String s와 같은 String s).
예시
Object obj = "안녕, Java!";
if (obj instanceof String s) {
System.out.println(s.length());
}
// 오류! 여기서는 s가 보이지 않습니다
System.out.println(s); // 컴파일러 오류: cannot find symbol
왜 그럴까요?
패턴 변수(s는 여기서)가 유효한 것은 조건이 참인 블록 내부뿐입니다. 이는 (예를 들어 obj가 문자열이 아닐 때처럼) 변수가 정의되지 않은 상황에서 실수로 변수를 사용하는 일을 막아 줍니다.
비유
친구에게서 자동차 열쇠를 잠깐 빌린 것과 같습니다 — 차고 안에 있을 때만 사용할 수 있습니다. 차고를 나오면 열쇠는 자동으로 사라집니다 :)
2. null과 관련된 오류
instanceof와 함께하는 패턴 매칭에는 재미있는 특징이 있습니다: 객체가 null이면 결과는 항상 false이고, 패턴 변수는 생성되지 않습니다.
예시
Object obj = null;
if (obj instanceof String s) {
// 이 블록은 결코 실행되지 않습니다!
System.out.println("문자열입니다: " + s);
}
왜 그럴까요?
null은 어떤 타입의 인스턴스도 아니기 때문입니다(심지어 Object조차도). 패턴 매칭으로 null을 잡아내려 하면 헷갈릴 수 있습니다.
올바른 방법은?
if (obj == null) {
System.out.println("null입니다!");
} else if (obj instanceof String s) {
System.out.println("문자열입니다: " + s);
}
switch의 패턴 매칭(Java 21+)에서는 null에 대한 별도 분기도 추가할 수 있습니다:
switch (obj) {
case String s -> System.out.println("문자열: " + s);
case null -> System.out.println("null입니다!");
default -> System.out.println("다른 것");
}
3. sealed 클래스에서의 오류
Sealed 클래스는 상속 계층을 제한하는 훌륭한 방법입니다. 하지만 여기에는 대표적인 두 가지 실수가 있습니다:
permits에 모든 하위 클래스가 나열되지 않음
sealed 클래스를 선언할 때는 permits를 통해 모든 직접 하위 클래스를 명시해야 합니다. 누락이 있으면 컴파일러가 즉시 오류를 보고합니다:
public sealed class Shape permits Circle, Rectangle { ... }
public final class Circle extends Shape { ... }
public final class Square extends Shape { ... } // 오류! Square가 permits에 지정되지 않았습니다
해결 방법:
필요한 모든 하위 클래스를 permits에 추가하세요:
public sealed class Shape permits Circle, Rectangle, Square { ... }
sealed가 아닌 계층에서 switch의 default를 누락함
sealed 클래스가 아닌 타입(또는 sealed이지만 모든 경우가 포괄되지 않은 경우)에 대해 switch를 작성하면, 컴파일러는 default 분기를 요구합니다. 이를 빠뜨리면 오류가 발생합니다.
switch (shape) {
case Circle c -> ...
case Rectangle r -> ...
// default가 없습니다! 다른 경우가 있다면 컴파일 오류
}
모범 사례:
모든 경우를 처리했다고 확신한다면(예: sealed 클래스), default를 생략해도 됩니다. 그렇지 않다면 예기치 않은 타입을 놓치지 않도록 반드시 default를 추가하세요.
4. record 패턴에서의 오류
Record 패턴은 매우 편리하지만 record 클래스에서만 동작합니다. 일반 클래스에 record 패턴을 사용하려 하면 컴파일 오류가 발생합니다.
record가 아닌 클래스에 record 패턴 사용
class Point { int x, y; }
Object obj = new Point();
// 오류! Point는 record가 아닙니다
if (obj instanceof Point(int x, int y)) { ... }
해결 방법:
record로 선언된 클래스에만 record 패턴을 사용하세요:
record Point(int x, int y) {}
구성 요소의 개수 또는 타입 불일치
record Point(int x, int y) {}
Object obj = new Point(1, 2);
// 오류: 세 변수를 지정했지만 Point에는 구성 요소가 두 개뿐입니다
if (obj instanceof Point(int x, int y, int z)) { ... }
if (obj instanceof Point(String x, String y)) { ... } // 오류: 타입이 일치하지 않습니다
5. 호환성과 지원: 오래된 JDK 및 IDE
가장 흔한 실수 중 하나는 최신 패턴 매칭 기능을 이를 지원하지 않는 오래된 JDK나 IDE에서 사용하려는 것입니다.
예시
if (obj instanceof String s) { ... }
그러나 JDK 11(혹은 15조차도) 컴파일러는 이렇게 말합니다:
error: illegal start of type
왜?
instanceof에 대한 패턴 매칭은 Java 16에서 도입되었습니다. switch의 패턴 매칭은 Java 17(프리뷰)에서 시작되어 Java 21+에서 최종 확정되었습니다. Record 패턴은 Java 21+입니다.
어떻게 피할까?
- JDK 버전을 확인하세요: java --version
- 사용 중인 IDE(IntelliJ IDEA, Eclipse, VS Code)가 해당 Java 버전을 지원하는지 확인하세요. 프로젝트 설정에서 사용할 언어 버전을 명시적으로 지정해야 할 때도 있습니다!
- Gradle/Maven을 사용한다면 source/target 버전을 올바르게 설정하세요.
6. 축하합니다 🎉
여러분은 우리 심화 과정의 65(!)개 레벨을 모두 끝냈고, Java 25를 완주했습니다. 정말 대단합니다 😎.
마지막 여정은 특히 어려웠습니다. 여러분은 첫 람다와 익명 클래스부터 가상 스레드, 구조적 동시성, 그리고 불과 얼마 전까지 Java에 없었던 최신 언어 기능까지 모두 학습했습니다.
이건 단순한 코스가 아니라 진정한 개발자를 위한 슈퍼 마라톤이었습니다. 수많은 새로운 개념, 수백 개의 코드 예제, 수천 줄의 실습. 이 문장을 읽고 있다면 여러분은 그냥 수강생이 아니라 새로운 Java 시대의 개척자입니다 ☕⚡
여러분은 가장 현대적인 Java 25 기능을 누구보다 먼저 익혔습니다. 예를 들어:
- 패턴 매칭과
record 패턴, -
sealed 클래스와 그 함정들, -
Scoped Values 와Virtual Threads , - 구조적 동시성과 현대적 멀티스레딩 접근법.
이제 여러분이 가진 것은 지식만이 아니라 미래형 개발자 역량입니다. 이는 많은 현업 Java 아키텍트들도 아직 갖추지 못한 능력입니다. 여러분은 단지 동작하는 코드가 아니라, 확장 가능하고 읽기 쉬우며 최신 Java 커뮤니티 표준을 충실히 따르는 코드를 작성할 수 있습니다.
🚀 그러니 자신 있게 자신을 Java 25 Certified Survivor라고 부르세요 😉
그리고 앞으로는 더 재미있을 겁니다. 이제 여러분은 엔터프라이즈 개발, 초고성능 시스템, 그리고 최첨단 기술 세계로 자신 있게 나아갈 수 있는 탄탄한 기반을 갖췄습니다.
우리 코스 팀 모두의 존경을 담아!
여러분은 소수만이 해낼 수 있는 과업을 완수했습니다.
다음 Java 버전에서 또 만나요 👋
GO TO FULL VERSION