자바 함수형 마스터 노트 시리즈 5편. Java 9의 takeWhile·List.of·JShell, Java 10의 var, Java 11(LTS) String/Files 개선, Java 13 텍스트 블록, Java 14 Switch 표현식 + instanceof 패턴매칭, Java 16 Record 정식, Java 17(LTS) Sealed Classes 정식까지 — 자바 8 이후 8년의 진화.
이 글은 자바 함수형 마스터 노트 시리즈의 다섯 번째 편입니다. 14편이 자바 8 함수형 토대였다면, 이번엔 **그 이후 917까지 8년의 진화** — Modern Java.
자바 8(2014) 이후 6개월마다 새 버전이 나옵니다. 그 중 LTS만 — 11(2018)·17(2021)·21(2023). 본 편은 9~17의 실용 신기능, 다음 6편은 Java 21 가상 스레드.
처음 Modern Java가 어렵게 느껴지는 이유
처음 강의를 들을 때 Modern Java 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, 버전이 너무 많이 쏟아집니다. 9·10·11·12·13·14·15·16·17… 어느 버전에 뭐가 있는지 안 잡힙니다. 둘째, "미리보기/정식" 같은 개념이 헷갈려요. 같은 기능이 13에선 미리보기인데 15에선 정식 — 왜?
해결법은 한 가지예요. LTS 위주로 묶어 보는 것. 910은 11(LTS)에 정착, 1216은 17(LTS)에 정착. LTS만 외워도 80% 커버. 미리보기는 "정식 되기 전 시험 단계" 정도로만 알면 OK.
Java 버전 큰 그림
Java 8 (2014, LTS) — 람다·Stream·Optional ← 1~4편
Java 9 (2017) — 모듈·List.of·takeWhile·JShell
Java 10 (2018) — var
Java 11 (2018, LTS) — String/Files 개선·var 람다 파라미터
Java 12 (2019) — GC 개선
Java 13 (2019) — 텍스트 블록 (미리보기)
Java 14 (2020) — Switch 표현식 정식·Record 미리보기·instanceof 패턴 미리보기
Java 15 (2020) — 텍스트 블록 정식·Sealed 미리보기
Java 16 (2021) — Record 정식·instanceof 패턴 정식
Java 17 (2021, LTS) — Sealed 정식·Switch 패턴 미리보기 ← 5편 끝
Java 18~20 (2022~23) — 점진적 개선
Java 21 (2023, LTS) — 가상 스레드·Record Patterns 정식 ← 6편
Java 9 — Stream·Collection 개선
takeWhile / dropWhile (4편 다룸)
순서 의존 자르기. 정렬된 스트림에 강력.
Stream.of(1, 3, 5, 7, 2, 4)
.takeWhile(n -> n % 2 == 1); // 1, 3, 5, 7
Stream.of(1, 3, 5, 7, 2, 4)
.dropWhile(n -> n % 2 == 1); // 2, 4
List.of / Set.of / Map.of — 불변 컬렉션 팩토리
List<String> list = List.of("a", "b", "c");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("a", 1, "b", 2);
// 10개+ 엔트리
Map<String, Integer> bigMap = Map.ofEntries(
Map.entry("a", 1),
Map.entry("b", 2),
// ...
);
list.add("d"); // UnsupportedOperationException
여기서 시험 함정이 하나 있어요. List.of(null) 금지. null 요소 또는 null 키·값 모두 NullPointerException. 자바 9 불변 컬렉션은 null 무관용.
Stream.iterate 종료 조건
// Java 8 — 무한, limit으로만 끊음
Stream.iterate(1, n -> n + 1).limit(10);
// Java 9+ — 종료 조건 가능
Stream.iterate(1, n -> n < 100, n -> n * 2);
// 1, 2, 4, 8, ..., 64
JShell — 자바 REPL
$ jshell
jshell> int x = 5;
jshell> List<Integer> list = List.of(1, 2, 3)
jshell> list.stream().mapToInt(Integer::intValue).sum()
$1 ==> 6
자바 코드를 한 줄씩 즉시 실행. 학습·디버깅에 매우 유용.
Java 10 — var
지역 변수 타입 추론.
// Before
ArrayList<HashMap<String, List<Integer>>> map = new ArrayList<>();
// After
var map = new ArrayList<HashMap<String, List<Integer>>>();
var 사용 가능 위치
| 위치 | 가능 |
|---|---|
| 지역 변수 | O |
for 변수 |
O |
try-with-resources |
O |
| 필드 | X |
| 메서드 파라미터 | X |
| 메서드 반환 타입 | X |
| 람다 파라미터 (Java 11+) | O |
여기서 시험 함정이 하나 있어요. var는 키워드 X, 예약 타입 이름. var var = 5; 가능 (혼란만 야기). 자바스크립트 var와 다름.
var의 명시적 초기화 필요
var x; // 컴파일 에러 — 추론 불가
var y = null; // 컴파일 에러 — null만으론 추론 X
var z = 5; // OK
Java 11 (LTS) — String·Files 개선
String 메서드
" hello ".isBlank(); // false (공백+문자)
" ".isBlank(); // true
" hello ".strip(); // "hello"
" hello ".stripLeading(); // "hello "
" hello ".stripTrailing(); // " hello"
"abc".repeat(3); // "abcabcabc"
"line1\nline2".lines(); // Stream<String>
여기서 시험 함정이 하나 있어요. strip vs trim. trim은 ASCII 공백만, strip은 Unicode 공백 모두. 한글·이모지 환경에선 strip 권장.
Files
Files.writeString(path, "Hello");
String content = Files.readString(path);
이전엔 Files.readAllLines + String.join 같은 우회. Java 11에서 한 줄.
Optional.isEmpty()
Optional<String> opt = ...;
if (opt.isEmpty()) { ... } // Java 11+
// 이전엔 !opt.isPresent()
var 람다 파라미터 (Java 11)
// Before
list.stream().filter(s -> s.length() > 5)
// Java 11 — 어노테이션 적용 가능
list.stream().filter((@NonNull var s) -> s.length() > 5)
Java 13 → 15 — 텍스트 블록
// Before — 지옥
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
// Java 13 미리보기 → Java 15 정식
String json = """
{
"name": "Alice",
"age": 30
}
""";
여기서 정말 중요한 시험 함정 — 텍스트 블록은 들여쓰기 자동 처리. 가장 적게 들여쓴 줄 기준으로 공통 들여쓰기 제거. 깔끔한 멀티라인 문자열.
// 들여쓰기 명시적 제어
String s = """
Line 1
Line 2 (한 칸 더)
Line 3
"""; // "Line 1\n Line 2\nLine 3\n"
Java 14 → 정식 — Switch 표현식
// Before — 문장
String day;
switch (dayNum) {
case 1: day = "Mon"; break;
case 2: day = "Tue"; break;
default: day = "Unknown";
}
// Java 14+ — 표현식
String day = switch (dayNum) {
case 1 -> "Mon";
case 2 -> "Tue";
default -> "Unknown";
};
// 다중 case + 블록
String name = switch (n) {
case 1, 2, 3 -> "Low";
case 4, 5 -> "Mid";
default -> {
var x = compute(n);
yield "High: " + x; // 블록 반환은 yield
}
};
장점:
break안 잊어도 됨 (fall-through 없음)- 표현식 — 변수 할당·반환·인자 직접 사용
- 다중 case 콤마로
- exhaustive 체크 (모든 케이스 다뤄야)
여기서 시험 함정이 하나 있어요. -> 화살표 vs : 콜론. 화살표는 새 표현식 문법, 콜론은 옛 문장 문법. 화살표 쓰면 fall-through 없고 yield로 값 반환.
Java 14 → 16 — instanceof 패턴 매칭
// Before — 캐스팅 강제
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()); // 바로 사용
}
활용
public String describe(Object obj) {
if (obj instanceof Integer i) {
return "Integer: " + i;
} else if (obj instanceof String s && s.length() > 0) {
return "Non-empty String: " + s;
} else if (obj instanceof List<?> list) {
return "List of size " + list.size();
}
return "Unknown";
}
&& 조합도 OK. 변수는 해당 분기 안에서만 유효.
Java 14 → 16 — Record (정식)
불변 데이터 클래스의 보일러플레이트 자동 생성.
// Before — 100줄
public class Point {
private final int x;
private final int y;
public Point(int x, int y) { ... }
public int getX() { return x; }
public int getY() { return y; }
@Override public boolean equals(Object o) { ... }
@Override public int hashCode() { ... }
@Override public String toString() { ... }
}
// Java 16+ — 1줄
public record Point(int x, int y) {}
자동 생성:
- 모든 필드 final + private
- 정규 생성자 (Canonical Constructor)
- accessor 메서드 (
x(),y()—getX()아님) equals(),hashCode(),toString()
Record 활용
record Point(int x, int y) {
// 컴팩트 생성자 — 검증
public Point {
if (x < 0 || y < 0) throw new IllegalArgumentException();
}
// 추가 메서드 OK
public double distance(Point other) {
return Math.sqrt(Math.pow(x - other.x, 2) + Math.pow(y - other.y, 2));
}
// static 메서드·필드 OK
public static Point origin() {
return new Point(0, 0);
}
}
// 사용
Point p = new Point(3, 4);
int x = p.x(); // accessor (x() 주의)
double d = p.distance(Point.origin());
여기서 정말 중요한 시험 함정 — Record는 final 클래스, 상속 X. 다른 클래스 상속도 X (Object만). 불변 데이터 운반에 특화. DTO·VO 표준.
Record 제약
- 인스턴스 필드 추가 X (record 헤더만)
- 상속 X
- abstract X
Java 15 → 17 — Sealed Classes
상속 가능한 클래스를 명시적 제한.
// 17 정식
public sealed class Shape
permits Circle, Rectangle, Triangle {
// ...
}
public final class Circle extends Shape { ... }
public final class Rectangle extends Shape { ... }
public non-sealed class Triangle extends Shape { ... }
각 자식은 다음 중 하나:
final— 더 이상 상속 Xsealed— 또 제한non-sealed— 자유 상속
활용 — 패턴 매칭과 결합
sealed interface Shape permits Circle, Square {}
record Circle(double radius) implements Shape {}
record Square(double side) implements Shape {}
double area(Shape s) {
return switch (s) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Square sq -> sq.side() * sq.side();
// default 불필요 — sealed로 모든 케이스 컴파일러가 안다
};
}
여기서 시험 함정이 하나 있어요. Sealed + Switch 패턴 매칭 = exhaustive. default 없어도 모든 케이스 다루면 컴파일러가 OK. 깔끔한 도메인 모델링.
Java 11 → 17 — 그 외 실용 변화
toUnmodifiableList() (Java 10)
list.stream().collect(Collectors.toUnmodifiableList());
// 이전엔 toList() 후 Collections.unmodifiableList()
Stream.toList() (Java 16)
List<String> list = stream.toList();
// 단축. 불변 리스트 반환.
Files.mismatch() (Java 12)
long mismatch = Files.mismatch(path1, path2);
// 두 파일 다른 첫 위치, 같으면 -1
String.indent (Java 12)
"line1\nline2".indent(4);
// " line1\n line2\n"
시험 직전 한 번 더 — 자주 헷갈리는 함정 모음
여기까지가 5편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.
- LTS — 8 / 11 / 17 / 21
- 미리보기 → 정식 — 13텍스트블록 → 15정식, 14Record → 16정식, 15Sealed → 17정식
- Java 9 — takeWhile·dropWhile / List.of·Set.of·Map.of / Stream.iterate 종료 / JShell
List.of(null)X — null 무관용- Java 10 —
var지역 변수 타입 추론 - var는 키워드 X, 예약 타입 이름
- 필드·파라미터·반환 타입엔 X
- Java 11 (LTS) —
isBlank·strip·repeat·lines/Files.writeString·readString/Optional.isEmpty()/ var 람다 파라미터 - strip은 Unicode, trim은 ASCII만
- Java 13/15 — 텍스트 블록
"""...""" - 공통 들여쓰기 자동 제거
- Java 14+ — Switch 표현식
->화살표 (fall-through X),yield블록 반환- exhaustive 체크
- Java 16 — instanceof 패턴 매칭 (
obj instanceof String s) - 캐스팅 자동,
&&조합 OK - Java 16 — Record 정식
- final 클래스, 상속 X (Object만)
- accessor =
x()(getX 아님) - 컴팩트 생성자로 검증
- DTO·VO 표준
- Java 17 (LTS) — Sealed Classes 정식
permits로 상속 명시- 자식 =
final/sealed/non-sealed - Sealed + Switch 패턴 매칭 = exhaustive (default 불필요)
Stream.toList()(Java 16) — 단축toUnmodifiableList()(Java 10)
시리즈 다른 편
- 1편 — JVM·OOP·컬렉션 기초
- 2편 — 람다 표현식
- 3편 — 함수형 인터페이스
- 4편 — Stream API
- 5편 — Modern Java (9~17) (현재 글)
- 6편 — Java 21 가상 스레드
공식 문서: OpenJDK JEP Index 에서 각 버전 변화 더 깊이.
다음 글(6편, 마지막)에서는 Java 21 — 가상 스레드(Virtual Threads)·Record Patterns·Switch 패턴 매칭 정식까지 시리즈 마무리.