12.8 모던 자바 문법 (Java 15~21)
Java 15부터 21까지 추가된 현대적인 문법 기능들을 알아봅니다. 코드를 더 간결하고 표현력 있게 만들어주는 기능들입니다.
1. Text Blocks (Java 15+) - 여러 줄 문자열
""" 세 따옴표로 여러 줄의 문자열을 원본 그대로 작성할 수 있습니다. JSON, HTML, SQL, XML 등 다중 행 텍스트를 다룰 때 혁명적으로 편리합니다.
// ❌ 기존 방식 - 이스케이프와 + 연산으로 복잡
String json1 = "{\n" +
" \"name\": \"홍길동\",\n" +
" \"age\": 30,\n" +
" \"city\": \"서울\"\n" +
"}";
// ✅ Text Block (Java 15+)
String json2 = """
{
"name": "홍길동",
"age": 30,
"city": "서울"
}
""";
System.out.println(json2);
// {
// "name": "홍길동",
// "age": 30,
// "city": "서울"
// }
Text Block 들여쓰기 규칙
Text Block의 공통 들여쓰기는 자동으로 제거됩니다. 닫는 """의 위치가 들여쓰기 기준을 결정합니다.
// 닫는 """ 위치에 따른 들여쓰기 차이
String aligned = """
Hello
World
"""; // 닫는 """가 4칸 들여쓰기 → "Hello\nWorld\n"
String indented = """
Hello
World
"""; // 닫는 """가 0칸 → " Hello\n World\n" (4칸 남음)
Text Block + formatted
String name = "홍길동";
int age = 30;
String html = """
<div class="profile">
<h1>%s</h1>
<p>나이: %d세</p>
</div>
""".formatted(name, age);
String sql = """
SELECT *
FROM users
WHERE name = '%s'
AND age >= %d
ORDER BY created_at DESC
LIMIT 10
""".formatted(name, age);
주요 Text Block 메서드
String block = """
Hello
World
""";
// 줄 끝 공백 제거
String stripped = block.stripIndent(); // Text Block에서 자동 처리됨
// 이스케이프 해석
String interpreted = "\\n".translateEscapes(); // "\n" 실제 줄바꿈으로
2. Record (Java 16+) 심화
[ch12/record.md]에서 기본을 배웠다면, 여기서는 고급 활용을 다룹니다.
// 커스텀 생성자 (Compact Constructor)
record Range(int min, int max) {
// Compact Constructor: 유효성 검사
Range {
if (min > max)
throw new IllegalArgumentException("min은 max보다 작아야 합니다: " + min + " > " + max);
}
// 추가 메서드 정의 가능
int size() { return max - min; }
boolean contains(int value) { return value >= min && value <= max; }
}
Range r = new Range(1, 10);
System.out.println(r.size()); // 9
System.out.println(r.contains(5)); // true
// new Range(10, 1); // ❌ IllegalArgumentException
// Record + 인터페이스
interface Printable { void print(); }
record Point(int x, int y) implements Printable {
@Override
public void print() {
System.out.printf("Point(%d, %d)%n", x, y);
}
}
new Point(3, 4).print(); // Point(3, 4)
3. 패턴 매칭 심화 (Java 16~21)
instanceof 패턴 매칭 (Java 16+)
// 타입 + 변수 선언을 동시에
Object obj = "Hello, Java!";
// 기존
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// Java 16+
if (obj instanceof String s) {
System.out.println(s.length()); // s 바로 사용
}
// 조건을 한 줄에
if (obj instanceof String s && s.length() > 5) {
System.out.println("긴 문자열: " + s);
}
// else 블록에서도 패턴 변수 사용 불가 (범위 밖)
if (!(obj instanceof String s)) {
System.out.println("String이 아님");
} else {
System.out.println(s.toUpperCase()); // else 안에서는 사용 가능
}
Switch 패턴 매칭 (Java 21)
sealed interface Shape permits Circle, Rectangle, Triangle {}
record Circle(double radius) implements Shape {}
record Rectangle(double width, double height) implements Shape {}
record Triangle(double base, double height) implements Shape {}
// Switch 표현식 + 패턴 매칭 + 가드 조건
static String describeShape(Shape shape) {
return switch (shape) {
case Circle c when c.radius() > 100 -> "거대한 원 (반지름: " + c.radius() + ")";
case Circle c -> "원 (반지름: " + c.radius() + ")";
case Rectangle r when r.width() == r.height() -> "정사각형 (변: " + r.width() + ")";
case Rectangle r -> "직사각형 (" + r.width() + "×" + r.height() + ")";
case Triangle t -> "삼각형";
};
}
System.out.println(describeShape(new Circle(5))); // 원 (반지름: 5.0)
System.out.println(describeShape(new Circle(150))); // 거대한 원 (반지름: 150.0)
System.out.println(describeShape(new Rectangle(5, 5))); // 정사각형 (변: 5.0)
System.out.println(describeShape(new Rectangle(4, 6))); // 직사각형 (4.0×6.0)
레코드 패턴 (Record Patterns, Java 21)
레코드의 컴포넌트를 switch에서 구조 분해(destructuring) 할 수 있습니다.
record Point(int x, int y) {}
record Line(Point start, Point end) {}
Object obj = new Line(new Point(1, 2), new Point(5, 6));
// 중첩 레코드 패턴으로 구조 분해
if (obj instanceof Line(Point(var x1, var y1), Point(var x2, var y2))) {
System.out.printf("선: (%d,%d) → (%d,%d)%n", x1, y1, x2, y2);
// 선: (1,2) → (5,6)
}
// switch에서의 레코드 패턴
String result = switch (obj) {
case Line(Point(int x1, int y1), Point(int x2, int y2))
when x1 == x2 -> "수직선 (x=" + x1 + ")";
case Line(Point(int x1, int y1), Point(int x2, int y2))
when y1 == y2 -> "수평선 (y=" + y1 + ")";
case Line l -> "일반 선";
default -> "선이 아님";
};
System.out.println(result); // 일반 선
4. Sequenced Collections (Java 21)
Java 21에서 추가된 인터페이스로, 첫 번째와 마지막 요소에 일관된 방법으로 접근할 수 있습니다.
// SequencedCollection 인터페이스 메서드
List<String> list = new ArrayList<>(List.of("A", "B", "C", "D"));
System.out.println(list.getFirst()); // "A" (Java 21+)
System.out.println(list.getLast()); // "D"
list.addFirst("Z"); // ["Z", "A", "B", "C", "D"]
list.addLast("X"); // ["Z", "A", "B", "C", "D", "X"]
list.removeFirst(); // "Z" 제거
list.removeLast(); // "X" 제거
// reversed() - 역순 뷰 (새 리스트 생성 아님)
List<String> reversed = list.reversed();
System.out.println(reversed); // [D, C, B, A]
// SequencedMap도 동일하게 firstEntry(), lastEntry() 제공
LinkedHashMap<String, Integer> map = new LinkedHashMap<>();
map.put("A", 1); map.put("B", 2); map.put("C", 3);
System.out.println(map.firstEntry()); // A=1
System.out.println(map.lastEntry()); // C=3
고수 팁
Text Block 활용 팁:
- JSON 응답 목 데이터, SQL 쿼리, HTML 이메일 템플릿에서 특히 유용합니다.
stripIndent(),translateEscapes()메서드와 함께 사용하면 더욱 유연합니다.\줄 이음 연산자로 줄바꿈 없이 긴 줄 표현 가능:String longLine = """
이 줄은 \
실제로 한 줄입니다.
""";
// "이 줄은 실제로 한 줄입니다.\n"
패턴 매칭 실무 활용:
복잡한 instanceof + 캐스팅 체인을 switch 패턴으로 교체하면 코드가 훨씬 읽기 좋아집니다. 특히 sealed 인터페이스 + switch 패턴은 컴파일러가 모든 경우를 검사해주어 실수를 방지합니다.