티스토리 뷰

언어/Java

Java 버전 별 변화 변천사

snvlqkq 2025. 8. 3. 01:50

Stack Overflow 2024 설문조사에 따르면, Java는 여전히 세계에서 가장 많이 사용되는 프로그래밍 언어 중 하나다.

하지만 놀라운 건 Java 개발자들의 40% 이상이 아직도 Java 8을 사용하고 있다는 사실이다.

즉, 많은 개발자들이 Java의 최신 기능들을 제대로 활용하지 못하고 있다는 뜻이다.

만약 당신이 Java 8에 머물러 있다면, 이런 코드들을 놓치고 있는 것이다.

Java 8

1. 람다 표현식

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

// Before java 8
Collections.sort(names, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
        return a.compareTo(b);
    }
});

// Java 8
Collections.sort(names, (a, b) -> a.compareTo(b));
//또는
Collections.sort(names, String::compareTo);

2. Stream API

// Before java 8
List<String> result = new ArrayList<>();
for(String name : names) {
    if(name.length() > 3) {
        result.add(name.toUpperCase());
    }
}

// Java 8
List<String> result = names.stream()
    .filter(name -> name.length() > 3)
    .map(String::toUpperCase)
    .collect(Collectors.toList());

3. Optional

// Before Java 8 - null 체크 지옥
public String getUserEmail(Long userId) {
    User user = userRepository.findById(userId);
    if(user != null) {
        Profile profile = user.getProfile();
        if (profile != null) {
            return profile.getEmail();
        }
    }
    return "default@email.com";
}

// Java 8
public String getUserEmail(Long userId) {
    return userRepository.findById(userId)
        .map(User::getProfile)
        .map(Profile::getEmail)
        .orElse("default@email.com");
}

4. 새로운 날짜/시간 API

// Before Java 8 - Date는 mutable하고 thread-safe 하지 않음
Date date = new Date();
Calendar cal = Calendar.getInstance();
cal.setTime(date);
cal.add(Calendar.DAY_OF_MONTH, 30);
Date futureDate = cal.getTime();

// Java 8 - immutable하고 thread-safe
LocalDate today = LocalDate.now();
LocalDate futureDate = today.plusDays(30);
LocalDateTime meetingTime = LocalDateTime.of(2024, 12, 25, 14, 30);

Java 9(2017)

1. 모듈 시스템

module com.example.myapp {
    requires java.base;
    requires java.logging;
    exports com.example.myapp.api;
}

2. 컬렉션 팩토리 메서드

// Before Java 9
List<String> list = new ArrayList<>();
list.add("apple");
list.add("banana");
list.add("cherry");

Set<Integer> set = new HashSet<>();
set.add(1);
set.add(2);
set.add(3);

// Java 9
List<String> list = List.of("apple", "banana", "cherry");
Set<Integer> set = Set.of(1, 2, 3);
Map<String, Integer> map = Map.of("one", 1, "two", 2);

3. 인터페이스 private 메서드

// Java 9
public interface Calculator {
    default int addAndMultiply(int a, int b, int multiplier) {
        return multiply(add(a, b), multiplier);
    }

    default int subtractAndMultiply(int a, int b, int multiplier) {
        return multiply(subtract(a, b), multiplier);
    }

    // private 메서드로 중복 제거
    private int multiply(int a, int b) {
        return a * b;
    }

    private int add(int a, int b) {
        return a + b;
    }

    private int subtract(int a, int b) {
        return a - b;
    }
}

Java 10(2018)

var 키워드

// Before Java 10
Map<String, List<Integer>> complexMap = new HashMap<String, List<Integer>>();
List<String> names = Arrays.asList("Alice", "Bob");

// Java 10
var complexMap = new HashMap<String, List<Integer>>();
var names = List.of("Alice", "Bob");
var stream = names.stream().filter(name -> name.startsWith("A"));

// 주의 : 지역 변수에만 사용 가능
public class Example {
    // var field; // 컴파일 에러!
    public void method() {
        var localVar = "This is OK"; // 가능
    }
}

Java 11(2018) - LTS

1. String 메서드 추가

String text = " Hello World ";

// isBlank() 공백 문자만 있거나 비어 있으면 true
System.out.println("   ".isBlank()); //true
System.out.println("".isBlank()); //true
System.out.println("a".isBlank()); //false

// strip() 앞뒤 공백 제거(유니 코드 지원)
System.out.println(text.strip()); //"Hello World"
System.out.println(text.stripLeading()); //"Hello World "
System.out.println(text.stripTrailing()); //" Hello World"

// lines() 줄 단위로 나누어 Stream 반환
String multiline = "Line1\nLine2\nLine3";
multiline.lines().forEach(System.out::println);

2. Files 메서드

// Java 11
Path path = Paths.get("example.txt");

// 파일 읽기/쓰기가 간단해짐
String content = Files.readString(path);
Files.writeString(path, "Hello World");

// 이전에는 복잡했음
List<String> lines = Files.readAllLines(path);
String content = String.join("\n", lines);

3. HTTP Client API

HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://api.example.com/users"))
.header("Content-Type", "application/json")
.GET()
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());

// 비동기 처리
client.sendAsync(request, HttpResponse.BodyHandlers.ofString())
    .thenApply(HttpResponse::body)
    .thenAccept(System.out::println);

Java 14(2020)

1. Records(Preview)

// Before Java 14
public class Person {
    private final String name;
    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public boolean equals(Object o) {
        // ... 복잡한 equals 구현
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + "}";
    }
}

// Java 14 Record
public record Person(String name, int age) {
    // 생성자, getter, equals, hashCode, toString 자동 생성!

    // 추가 검증이 필요하면
    public Person {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }
}

// 사용
Person person = new Person("Alice", 30);
System.out.println(person.name()); // "Alice"
System.out.println(person.age());  // 30

2. Switch 표현식

// Before Java 14
String result;
switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        result = "6";
        break;
    case TUESDAY:
        result = "7";
        break;
    case THURSDAY:
    case SATURDAY:
        result = "8";
        break;
    case WEDNESDAY:
        result = "9";
        break;
    default:
        throw new IllegalStateException("Invalid day: " + day);
}

// Java 14
String result = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> "6";
    case TUESDAY -> "7";
    case THURSDAY, SATURDAY -> "8";
    case WEDNESDAY -> "9";
};

// 복잡한 로직도 가능
int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> {
        System.out.println("Processing: " + day);
        yield 6;
    }
    case TUESDAY -> 7;
    case THURSDAY, SATURDAY -> 8;
    case WEDNESDAY -> 9;
};

Java 15 (2020)

Text Blocks

// Before Java 15
String html = "<html>\n" +
              "  <body>\n" +
              "    <h1>Hello World</h1>\n" +
              "    <p>This is a paragraph with \"quotes\"</p>\n" +
              "  </body>\n" +
              "</html>";

String json = "{\n" +
              "  \"name\": \"John\",\n" +
              "  \"age\": 30,\n" +
              "  \"city\": \"New York\"\n" +
              "}";

// Java 15
String html = """
    <html>
      <body>
        <h1>Hello World</h1>
        <p>This is a paragraph with "quotes"</p>
      </body>
    </html>
    """;

String json = """
    {
      "name": "John",
      "age": 30,
      "city": "New York"
    }
    """;

// SQL 쿼리도 깔끔하게
String query = """
    SELECT u.name, u.email, p.title
    FROM users u
    JOIN posts p ON u.id = p.user_id
    WHERE u.active = true
    ORDER BY p.created_at DESC
    """;

Java 16 (2021)

Pattern Matching for instanceof

타입 체크 + 캐스팅 + 값 비교를 모두 한 번에

// Before Java 16
if (obj instanceof String) {
    String str = (String) obj;
    System.out.println(str.toUpperCase());
}

// Java 16
if (obj instanceof String str) {
    System.out.println(str.toUpperCase());
}

// 더 복잡한 예시
public String formatValue(Object obj) {
    if (obj instanceof Integer i && i > 10) {
        return "Large number: " + i;
    } else if (obj instanceof String s && !s.isEmpty()) {
        return "String: " + s.toUpperCase();
    } else if (obj instanceof Double d) {
        return String.format("%.2f", d);
    }
    return "Unknown: " + obj;
}

Java 17 (2021) - LTS

Sealed Classes

// Java 17
public abstract sealed class Shape 
    permits Circle, Rectangle, Triangle {
    // 오직 Circle, Rectangle, Triangle만 이 클래스를 상속할 수 있음!
    public abstract double area();
}

public final class Circle extends Shape {
    private final double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * radius * radius;
    }
}

public final class Rectangle extends Shape {
    private final double width, height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double area() {
        return width * height;
    }
}

public non-sealed class Triangle extends Shape {
    // non-sealed이므로 다른 클래스가 이를 상속할 수 있음
    private final double base, height;

    public Triangle(double base, double height) {
        this.base = base;
        this.height = height;
    }

    @Override
    public double area() {
        return 0.5 * base * height;
    }
}

// 사용 - 컴파일러가 모든 경우를 체크
public String describe(Shape shape) {
    return switch (shape) {
        case Circle c -> "Circle with area " + c.area();
        case Rectangle r -> "Rectangle with area " + r.area();
        case Triangle t -> "Triangle with area " + t.area();
        // default 케이스 불필요 - 모든 경우를 다룸
    };
}

Java 21 (2023) - LTS

1. Virtual Threads

// 기존 방식 - 플랫폼 스레드 (무겁고 제한적)
Thread thread = new Thread(() -> {
    // 작업 수행
    System.out.println("Platform thread: " + Thread.currentThread());
});
thread.start();

// Java 21 - Virtual Threads (가볍고 많이 생성 가능)
Thread virtualThread = Thread.ofVirtual().start(() -> {
    System.out.println("Virtual thread: " + Thread.currentThread());
});

// ExecutorService와 함께 사용
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
    // 수백만 개의 virtual thread도 가능!
    for (int i = 0; i < 1_000_000; i++) {
        executor.submit(() -> {
            // I/O 집약적인 작업
            try {
                Thread.sleep(1000);
                System.out.println("Task completed on: " + Thread.currentThread());
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        });
    }
}

// HTTP 클라이언트에서 활용
HttpClient client = HttpClient.newBuilder()
    .executor(Executors.newVirtualThreadPerTaskExecutor())
    .build();

2. Pattern Matching for switch(정식)

// Java 21
public String processValue(Object obj) {
    return switch (obj) {
        case null -> "null value";
        case Integer i when i > 0 -> "Positive integer: " + i;
        case Integer i when i < 0 -> "Negative integer: " + i;
        case Integer i -> "Zero";
        case String s when s.length() > 10 -> "Long string: " + s.substring(0, 10) + "...";
        case String s -> "Short string: " + s;
        case Double d -> String.format("Double: %.2f", d);
        case List<?> list when list.isEmpty() -> "Empty list";
        case List<?> list -> "List with " + list.size() + " elements";
        default -> "Unknown type: " + obj.getClass().getSimpleName();
    };
}

3. Record Patterns

// Record 정의
public record Point(int x, int y) {}
public record ColoredPoint(Point point, String color) {}

// Java 21 - Record Patterns
public String describe(Object obj) {
    return switch (obj) {
        case Point(int x, int y) -> "Point at (" + x + ", " + y + ")";
        case ColoredPoint(Point(int x, int y), String color) -> 
            "Colored point at (" + x + ", " + y + ") in " + color;
        default -> "Unknown object";
    };
}

// 네스티드 패턴도 가능
public boolean isOriginPoint(Object obj) {
    return switch (obj) {
        case Point(0, 0) -> true;
        case ColoredPoint(Point(0, 0), String color) -> true;
        default -> false;
    };
}

Java 22-23 (2024-2025)

String Templates(Preview)

// Java 22+ (Preview)
String name = "World";
int value = 42;

// 기존 방식
String message1 = String.format("Hello %s! Value is %d", name, value);
String message2 = "Hello " + name + "! Value is " + value;

// String Templates
String message = STR."Hello \{name}! Value is \{value}";

// 포맷팅도 가능
double pi = Math.PI;
String formatted = STR."Pi is approximately \{pi %.2f}";

// HTML 생성
String html = STR."""
    <html>
        <body>
            <h1>Hello \{name}</h1>
            <p>Your score is \{value}/100</p>
        </body>
    </html>
    """;

// JSON 생성
String json = STR."""
    {
        "name": "\{name}",
        "score": \{value},
        "passed": \{value >= 60}
    }
    """;

Unnamed Variables and Patterns

// Java 22+
// 사용하지 않는 변수를 _ 로 표시
try {
    processData();
} catch (IOException _) {
    // 예외 객체를 사용하지 않음
    log.error("IO error occurred");
}

// 튜플에서 일부만 사용
var (name, _, age) = getPersonData(); // 중간 값은 무시

// for-each에서
for (var (key, _) : map.entrySet()) {
    // value는 사용하지 않고 key만 사용
    processKey(key);
}

정리 및 전망

Java의 진화 과정을 살펴보면 몇 가지 뚜렷한 트렌드를 발견할 수 있다.

코드 간소화

  • Records: 50줄 데이터 클래스 → 1줄
  • Text Blocks: 복잡한 문자열 연결 → 깔끔한 멀티라인
  • var 키워드: 장황한 타입 선언 → 간결한 타입 추론
  • Switch 표현식: 복잡한 분기 처리 → 표현력 있는 패턴 매칭

타입 안전성 강화

  • Optional: null 체크 지옥 → 안전한 null 처리
  • Sealed Classes: 예측 불가능한 상속 → 제한된 상속 계층
  • Pattern Matching: 불안전한 캐스팅 → 컴파일 타임 안전성

성능 혁신

  • Stream API: 명령형 → 선언적 데이터 처리
  • Virtual Threads: 플랫폼 스레드의 한계 → 수백만 경량 스레드
  • 새로운 GC: 더 나은 메모리 관리와 응답성

함수형 프로그래밍 도입

  • Lambda: 익명 클래스 → 간결한 함수 표현
  • Stream API: 반복문 → 함수형 데이터 파이프라인
  • Pattern Matching: 조건문 → 선언적 패턴 기반 처리

새로운 기능들이 추가될수록 기존 Java 8에 머물러 있는 것은 더 큰 기술 부채가 될 수 있다.
하지만 변화가 두렵다면 무리할 필요는 없다.
새 프로젝트에서 Records 하나만 써보는 것부터 시작해도 된다.
Optional로 null 체크를 개선해보는 것도 좋은 출발점이다.
두려워 하지 말고 어서 빨리 시작하자!!

'언어 > Java' 카테고리의 다른 글

Optional 바르게 사용하기  (0) 2025.08.26
JVM 메모리 구조 정리  (0) 2025.08.16
String, StringBuilder, StringBuffer 성능 차이  (0) 2025.05.13
Jackson의 @JsonProperty로 JSON 키와 Java 필드 매핑하기  (0) 2025.04.09
JDBC란?  (0) 2025.03.22
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2026/05   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함