자바 함수형 마스터 — Modern Java (9~17) 핵심 신기능

2026-05-03확률과 통계 마스터 노트

자바 함수형 마스터 노트 시리즈 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 — 더 이상 상속 X
  • sealed — 또 제한
  • 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 10var 지역 변수 타입 추론
  • 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 16instanceof 패턴 매칭 (obj instanceof String s)
  • 캐스팅 자동, && 조합 OK
  • Java 16Record 정식
  • 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)

시리즈 다른 편

공식 문서: OpenJDK JEP Index 에서 각 버전 변화 더 깊이.

다음 글(6편, 마지막)에서는 Java 21 — 가상 스레드(Virtual Threads)·Record Patterns·Switch 패턴 매칭 정식까지 시리즈 마무리.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!