Commit bc5a4a
2026-01-13 10:07:52 강세보: Add java new features| /dev/null .. notes/programming/java new features.md | |
| @@ 0,0 1,1117 @@ | |
| + | # Java New Features |
| + | |
| + | Java 9 부터 코딩 스타일 위주로 변경된 내용을 기술한다. Preview 는 제외했고 정식 기능으로 들어간 것 위주라 실질적으로 몇몇 버전이 빠져있다. |
| + | |
| + | 코딩 스타일 외에 가장 큰 변화는 Java 21 의 Virtual Thread 를 들 수 있다. 기존의 Java thread 가 OS 의 thread 와 1:1 매핑이었다면, Virtual thread 는 go 의 coroutine 처럼 software thread 라고 할 수 있다. 많은 경우 그렇지만 virtual thread 도 만능은 아니다. 즉, 기존 thread 에서 세분화해야 하는 경우만 도입이 고려되지 그렇지 않은 경우는 software 로 OS thread 매핑이 한 번 더 되는 꼴이라 딱히 성능 이득을 볼 수 없을 수 있다. |
| + | |
| + | # JDK 10 |
| + | |
| + | ## 로컬 변수 타입 유추 |
| + | |
| + | 아래와 같이 var 로 선언하고 변수에 타입을 유추하는 형태 |
| + | |
| + | ```java |
| + | var list = new ArrayList<String>(); // infers ArrayList<String> |
| + | var stream = list.stream(); // infers Stream<String> |
| + | ``` |
| + | |
| + | # JDK 11 |
| + | |
| + | ## 로컬 변수 Lamba |
| + | |
| + | 암묵적인 타입 선언으로 람다를 구현하는 경우 |
| + | ```java |
| + | (var x, var y) -> x.process(y) |
| + | ``` |
| + | |
| + | |
| + | 동일 형태 |
| + | ```java |
| + | (x, y) -> x.process(y) |
| + | ``` |
| + | |
| + | 섞어서는 못 쓴다. |
| + | |
| + | ```java |
| + | (var x, y) -> x.process(y) // Cannot mix 'var' and 'no var' in implicitly typed lambda expression |
| + | (var x, int y) -> x.process(y) // Cannot mix 'var' and manifest types in explicitly typed lambda expression |
| + | ``` |
| + | |
| + | # JDK 14 |
| + | |
| + | ## Switch 구문 |
| + | |
| + | 축약해서 사용 가능 |
| + | |
| + | ```java |
| + | switch (day) { |
| + | case MONDAY, FRIDAY, SUNDAY -> System.out.println(6); |
| + | case TUESDAY -> System.out.println(7); |
| + | case THURSDAY, SATURDAY -> System.out.println(8); |
| + | case WEDNESDAY -> System.out.println(9); |
| + | } |
| + | ``` |
| + | |
| + | 변수 대입도 가능 |
| + | ```java |
| + | int numLetters = switch (day) { |
| + | case MONDAY, FRIDAY, SUNDAY -> 6; |
| + | case TUESDAY -> 7; |
| + | case THURSDAY, SATURDAY -> 8; |
| + | case WEDNESDAY -> 9; |
| + | }; |
| + | ``` |
| + | |
| + | # JDK 15 |
| + | |
| + | ## Text Block |
| + | |
| + | """ 로 시작해서 """ 로 끝나는 블록 구문이다. |
| + | |
| + | 기존 구문이 이렇다면 |
| + | ```java |
| + | String html = "<html>\n" + |
| + | " <body>\n" + |
| + | " <p>Hello, world</p>\n" + |
| + | " </body>\n" + |
| + | "</html>\n"; |
| + | ``` |
| + | |
| + | 이렇게 가능하다. |
| + | ```java |
| + | String html = """ |
| + | <html> |
| + | <body> |
| + | <p>Hello, world</p> |
| + | </body> |
| + | </html> |
| + | """; |
| + | ``` |
| + | |
| + | # JDK 16 |
| + | |
| + | ## Pattern Matching for instanceof |
| + | |
| + | 기존에 이렇게 쓰던 것을 |
| + | ```java |
| + | if (obj instanceof String) { |
| + | String s = (String) obj; // grr… |
| + | … |
| + | } |
| + | ``` |
| + | |
| + | 이렇게 사용할 수 있다. |
| + | ```java |
| + | if (obj instanceof String s) { |
| + | // Let pattern matching do the work! |
| + | … |
| + | } |
| + | ``` |
| + | |
| + | 근데 이게 Scope 가 묘한게 if 블럭 안에서만 유효하다. 예를 들어 |
| + | ```java |
| + | class Example1 { |
| + | String s; |
| + | |
| + | void test1(Object o) { |
| + | if (o instanceof String s) { |
| + | System.out.println(s); // Field s is shadowed |
| + | s = s + "\n"; // Assignment to pattern variable |
| + | … |
| + | } |
| + | System.out.println(s); // Refers to field s |
| + | … |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 심지어 else 구문에서도 이게 유효하지 않다. |
| + | ``` |
| + | class Example2 { |
| + | Point p; |
| + | |
| + | void test2(Object o) { |
| + | if (o instanceof Point p) { |
| + | // p refers to the pattern variable |
| + | … |
| + | } else { |
| + | // p refers to the field |
| + | … |
| + | } |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | |
| + | ## Records |
| + | |
| + | 데이터 구조체를 간단하게 구현한 것이다. |
| + | 예를 들어 전통적인 방식의 데이터 구조체를 class 로 구현한 것이 다음과 같다면 |
| + | ```java |
| + | class Point { |
| + | private final int x; |
| + | private final int y; |
| + | |
| + | Point(int x, int y) { |
| + | this.x = x; |
| + | this.y = y; |
| + | } |
| + | |
| + | int x() { return x; } |
| + | int y() { return y; } |
| + | |
| + | public boolean equals(Object o) { |
| + | if (!(o instanceof Point)) return false; |
| + | Point other = (Point) o; |
| + | return other.x == x && other.y == y; |
| + | } |
| + | |
| + | public int hashCode() { |
| + | return Objects.hash(x, y); |
| + | } |
| + | |
| + | public String toString() { |
| + | return String.format("Point[x=%d, y=%d]", x, y); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 이 기능을 다 하는 record 는 다음과 같이 정의한다. |
| + | ```java |
| + | record Point(int x, int y) { } |
| + | ``` |
| + | |
| + | 단, 이렇게 보면 좋아보이지만 이 x, y 는 초기에 값을 대입받으면 더 이상 수정할 수 없다. |
| + | ### Constructor |
| + | |
| + | Constructor 에서 추가 작업을 한다면 다음과 같이 사용할 수 있다. |
| + | ```java |
| + | record Rational(int num, int denom) { |
| + | Rational { |
| + | int gcd = gcd(num, denom); |
| + | num /= gcd; |
| + | denom /= gcd; |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 동일 코드를 명시적으로 작성하면 다음과 같다. |
| + | ```java |
| + | record Rational(int num, int denom) { |
| + | Rational(int num, int demon) { |
| + | // Normalization |
| + | int gcd = gcd(num, denom); |
| + | num /= gcd; |
| + | denom /= gcd; |
| + | // Initialization |
| + | this.num = num; |
| + | this.denom = denom; |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | Exception 처리 Case |
| + | ``` java |
| + | record Range(int lo, int hi) { |
| + | Range { |
| + | if (lo > hi) // referring here to the implicit constructor parameters |
| + | throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi)); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | Copy 는 다음과 같이 사용한다. 아래와 같은 record 가 있다고 하면 |
| + | ```java |
| + | record R(T1 c1, ..., Tn cn){ } |
| + | ``` |
| + | |
| + | r1 인스턴스가 먼저 있고 이게 null 이 아니면 |
| + | ```java |
| + | R r2 = new R(r1.c1(), r1.c2(), ..., r1.cn()); |
| + | ``` |
| + | |
| + | r2 가 r1의 복제이다. 이 경우 r1.equals(r2) 는 true 이다. |
| + | |
| + | ### Rules for record classes |
| + | |
| + | - 상속 불가. record class 의 부모는 모두 java.lang.Record 이다. sealed class 를 implements 는 가능하다. |
| + | ```java |
| + | package com.example.expression; |
| + | |
| + | public sealed interface Expr |
| + | permits ConstantExpr, PlusExpr, TimesExpr, NegExpr {...} |
| + | |
| + | public record ConstantExpr(int i) implements Expr {...} |
| + | public record PlusExpr(Expr a, Expr b) implements Expr {...} |
| + | public record TimesExpr(Expr a, Expr b) implements Expr {...} |
| + | public record NegExpr(Expr e) implements Expr {...} |
| + | ``` |
| + | - record class 는 암묵적으로 final 이고 abstract 가 되지 못한다. |
| + | - 필드들도 모두 final 이다. |
| + | - 인스턴스 필드를 명시적으로 정의할 수 없고 필드를 초기화할 수 없다. |
| + | ```java |
| + | record Invalid(int x, inty) { |
| + | int z; // 인스턴스 필드 명시화 안됨. |
| + | int a = 1; // 명시화도 안되고 초기화도 안됨. |
| + | } |
| + | ``` |
| + | |
| + | # JDK 17 |
| + | |
| + | ## Sealed Classes |
| + | |
| + | Sealed class 나 interface 는 상속 받을 대상을 명시한다. |
| + | 패키지 내부인 경우 |
| + | ```java |
| + | package com.example.geometry; |
| + | |
| + | public abstract sealed class Shape |
| + | permits Circle, Rectangle, Square { ... } |
| + | ``` |
| + | |
| + | 다른 패키지인 경우 |
| + | ```java |
| + | package com.example.geometry; |
| + | |
| + | public abstract sealed class Shape |
| + | permits com.example.polar.Circle, |
| + | com.example.quad.Rectangle, |
| + | com.example.quad.simple.Square { ... } |
| + | ``` |
| + | |
| + | 패턴 매칭에 사용 가능하다. |
| + | ```java |
| + | Shape rotate(Shape shape, double angle) { |
| + | return switch (shape) { // pattern matching switch |
| + | case Circle c -> c; |
| + | case Rectangle r -> shape.rotate(angle); |
| + | case Square s -> shape.rotate(angle); |
| + | // no default needed! |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | # JDK 18 |
| + | |
| + | ## Code Snippets in Java API Documentation |
| + | |
| + | @snippet 태그 사용 |
| + | ```java |
| + | /** |
| + | * The following code shows how to use {@code Optional.isPresent}: |
| + | * {@snippet : |
| + | * if (v.isPresent()) { |
| + | * System.out.println("v: " + v.get()); |
| + | * } |
| + | * } |
| + | */ |
| + | ``` |
| + | |
| + | 실제 코드를 당겨오는 것도 가능 |
| + | ```java |
| + | /** |
| + | * The following code shows how to use {@code Optional.isPresent}: |
| + | * {@snippet file="ShowOptional.java" region="example"} |
| + | */ |
| + | ``` |
| + | |
| + | ShowOptional.java 의 내용에 region 을 정의한다. |
| + | ```java |
| + | public class ShowOptional { |
| + | void show(Optional<String> v) { |
| + | // @start region="example" |
| + | if (v.isPresent()) { |
| + | System.out.println("v: " + v.get()); |
| + | } |
| + | // @end |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 그 밖에 강조를 하거나 문자열 대치, 링크 등을 사용할 수 있다. |
| + | |
| + | ## Deprecate Finalization for Removal |
| + | |
| + | 다음의 코드 대신에... |
| + | ```java |
| + | FileInputStream input = null; |
| + | FileOutputStream output = null; |
| + | |
| + | try { |
| + | input = new FileInputStream(file1); |
| + | output = new FileOutputStream(file2); |
| + | ... copy bytes from input to output … |
| + | output.close(); output = null; |
| + | input.close(); input = null; |
| + | } finally { |
| + | if (output != null) output.close(); |
| + | if (input != null) input.close(); |
| + | } |
| + | ``` |
| + | |
| + | try-resource-statement 와 cleaners 를 사용할 것. |
| + | |
| + | try-resource-statement 는 Java 7 에 도입됨. |
| + | ```java |
| + | try (FileInputStream input = new FileInputStream(file1); |
| + | FileOutputStream output = new FileOutputStream(file2)) { |
| + | ... copy bytes from input to output … |
| + | } |
| + | ``` |
| + | |
| + | cleaner 는 Java 9 에 도입됨. (interface 구하는 방식) |
| + | |
| + | # JDK 21 |
| + | |
| + | ## Sequenced Collections |
| + | |
| + | first 와 last 에 대한 명시적인 method 를 제공한다. |
| + | #### Sequenced collections |
| + | ```java |
| + | interface SequencedCollection<E> extends Collection<E> { |
| + | // new method |
| + | SequencedCollection<E> reversed(); |
| + | // methods promoted from Deque |
| + | void addFirst(E); |
| + | void addLast(E); |
| + | E getFirst(); |
| + | E getLast(); |
| + | E removeFirst(); |
| + | E removeLast(); |
| + | } |
| + | ``` |
| + | |
| + | ### Sequenced sets |
| + | ```java |
| + | interface SequencedSet<E> extends Set<E>, SequencedCollection<E> { |
| + | SequencedSet<E> reversed(); // covariant override |
| + | } |
| + | ``` |
| + | |
| + | ### Sequenced maps |
| + | ```java |
| + | interface SequencedMap<K,V> extends Map<K,V> { |
| + | // new methods |
| + | SequencedMap<K,V> reversed(); |
| + | SequencedSet<K> sequencedKeySet(); |
| + | SequencedCollection<V> sequencedValues(); |
| + | SequencedSet<Entry<K,V>> sequencedEntrySet(); |
| + | V putFirst(K, V); |
| + | V putLast(K, V); |
| + | // methods promoted from NavigableMap |
| + | Entry<K, V> firstEntry(); |
| + | Entry<K, V> lastEntry(); |
| + | Entry<K, V> pollFirstEntry(); |
| + | Entry<K, V> pollLastEntry(); |
| + | } |
| + | ``` |
| + | |
| + | ### Retrofitting |
| + | |
| + | 그래서 다음과 같이 재조정된다. |
| + | |
| + | - List now has SequencedCollection as its immediate superinterface, |
| + | - Deque now has SequencedCollection as its immediate superinterface, |
| + | - LinkedHashSet additionally implements SequencedSet, |
| + | - SortedSet now has SequencedSet as its immediate superinterface, |
| + | - LinkedHashMap additionally implements SequencedMap, and |
| + | - SortedMap now has SequencedMap as its immediate superinterface. |
| + | |
| + | ## Record Patterns |
| + | |
| + | JDK 16 의 Pattern Matching for instanceof 에 Record 확장판 |
| + | |
| + | JDK 16 당시는 다음과 같이 사용 |
| + | ```java |
| + | record Point(int x, int y) {} |
| + | |
| + | static void printSum(Object obj) { |
| + | if (obj instanceof Point p) { |
| + | int x = p.x(); |
| + | int y = p.y(); |
| + | System.out.println(x+y); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 이걸 JDK 21에서는 다음과 같이 사용 가능하다. |
| + | ```java |
| + | static void printSum(Object obj) { |
| + | if (obj instanceof Point(int x, int y)) { |
| + | System.out.println(x+y); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 이 때 Point(int x, inty) 를 record pattern 이라고 한다. |
| + | ### Nested record patterns |
| + | |
| + | 다단계인 경우 다음과 같이 사용 가능하다. |
| + | ```java |
| + | record Point(int x, int y) {} |
| + | enum Color { RED, GREEN, BLUE } |
| + | record ColoredPoint(Point p, Color c) {} |
| + | record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {} |
| + | ``` |
| + | |
| + | 1단계 |
| + | ```java |
| + | static void printUpperLeftColoredPoint(Rectangle r) { |
| + | if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) { |
| + | System.out.println(ul.c()); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 2단계 |
| + | ```java |
| + | static void printColorOfUpperLeftPoint(Rectangle r) { |
| + | if (r instanceof Rectangle(ColoredPoint(Point p, Color c), |
| + | ColoredPoint lr)) { |
| + | System.out.println(c); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | var 사용 예제 |
| + | ```java |
| + | Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1), |
| + | new ColoredPoint(new Point(x2, y2), c2)); |
| + | |
| + | static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) { |
| + | if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), |
| + | var lr)) { |
| + | System.out.println("Upper-left corner: " + x); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | ## Pattern Matching for switch |
| + | |
| + | ```java |
| + | static String formatter(Object obj) { |
| + | String formatted = "unknown"; |
| + | if (obj instanceof Integer i) { |
| + | formatted = String.format("int %d", i); |
| + | } else if (obj instanceof Long l) { |
| + | formatted = String.format("long %d", l); |
| + | } else if (obj instanceof Double d) { |
| + | formatted = String.format("double %f", d); |
| + | } else if (obj instanceof String s) { |
| + | formatted = String.format("String %s", s); |
| + | } |
| + | return formatted; |
| + | } |
| + | ``` |
| + | |
| + | 이랬던 것을 아래와 같이 사용 가능하다. |
| + | ```java |
| + | static String formatterPatternSwitch(Object obj) { |
| + | return switch (obj) { |
| + | case Integer i -> String.format("int %d", i); |
| + | case Long l -> String.format("long %d", l); |
| + | case Double d -> String.format("double %f", d); |
| + | case String s -> String.format("String %s", s); |
| + | default -> obj.toString(); |
| + | }; |
| + | } |
| + | ``` |
| + | |
| + | ### switch 에 null |
| + | |
| + | before |
| + | ```java |
| + | static void testFooBarOld(String s) { |
| + | if (s == null) { |
| + | System.out.println("Oops!"); |
| + | return; |
| + | } |
| + | switch (s) { |
| + | case "Foo", "Bar" -> System.out.println("Great"); |
| + | default -> System.out.println("Ok"); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | after |
| + | ```java |
| + | static void testFooBarNew(String s) { |
| + | switch (s) { |
| + | case null -> System.out.println("Oops"); |
| + | case "Foo", "Bar" -> System.out.println("Great"); |
| + | default -> System.out.println("Ok"); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | ### Case refinement |
| + | |
| + | 좀 더 나가서 (아래도 JDK 21 부터 가능) |
| + | ```java |
| + | static void testStringOld(String response) { |
| + | switch (response) { |
| + | case null -> { } |
| + | case String s -> { |
| + | if (s.equalsIgnoreCase("YES")) |
| + | System.out.println("You got it"); |
| + | else if (s.equalsIgnoreCase("NO")) |
| + | System.out.println("Shame"); |
| + | else |
| + | System.out.println("Sorry?"); |
| + | } |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 여러 가지로 가능하다. |
| + | ```java |
| + | static void testStringNew(String response) { |
| + | switch (response) { |
| + | case null -> { } |
| + | case String s |
| + | when s.equalsIgnoreCase("YES") -> { |
| + | System.out.println("You got it"); |
| + | } |
| + | case String s |
| + | when s.equalsIgnoreCase("NO") -> { |
| + | System.out.println("Shame"); |
| + | } |
| + | case String s -> { |
| + | System.out.println("Sorry?"); |
| + | } |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 좀 더 늘려서... |
| + | ```java |
| + | static void testStringEnhanced(String response) { |
| + | switch (response) { |
| + | case null -> { } |
| + | case "y", "Y" -> { |
| + | System.out.println("You got it"); |
| + | } |
| + | case "n", "N" -> { |
| + | System.out.println("Shame"); |
| + | } |
| + | case String s |
| + | when s.equalsIgnoreCase("YES") -> { |
| + | System.out.println("You got it"); |
| + | } |
| + | case String s |
| + | when s.equalsIgnoreCase("NO") -> { |
| + | System.out.println("Shame"); |
| + | } |
| + | case String s -> { |
| + | System.out.println("Sorry?"); |
| + | } |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | ### enum 쓰는 경우 |
| + | |
| + | before |
| + | ```java |
| + | public enum Suit { CLUBS, DIAMONDS, HEARTS, SPADES } |
| + | |
| + | static void testforHearts(Suit s) { |
| + | switch (s) { |
| + | case HEARTS -> System.out.println("It's a heart!"); |
| + | default -> System.out.println("Some other suit"); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | after (sealed 도 같이 사용) |
| + | ```java |
| + | sealed interface CardClassification permits Suit, Tarot {} |
| + | public enum Suit implements CardClassification { CLUBS, DIAMONDS, HEARTS, SPADES } |
| + | final class Tarot implements CardClassification {} |
| + | |
| + | static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) { |
| + | switch (c) { |
| + | case Suit s when s == Suit.CLUBS -> { |
| + | System.out.println("It's clubs"); |
| + | } |
| + | case Suit s when s == Suit.DIAMONDS -> { |
| + | System.out.println("It's diamonds"); |
| + | } |
| + | case Suit s when s == Suit.HEARTS -> { |
| + | System.out.println("It's hearts"); |
| + | } |
| + | case Suit s -> { |
| + | System.out.println("It's spades"); |
| + | } |
| + | case Tarot t -> { |
| + | System.out.println("It's a tarot"); |
| + | } |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 이렇게도 가능하다. (이게 좀 더 명시적인 듯...) |
| + | ```java |
| + | static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) { |
| + | switch (c) { |
| + | case Suit.CLUBS -> { |
| + | System.out.println("It's clubs"); |
| + | } |
| + | case Suit.DIAMONDS -> { |
| + | System.out.println("It's diamonds"); |
| + | } |
| + | case Suit.HEARTS -> { |
| + | System.out.println("It's hearts"); |
| + | } |
| + | case Suit.SPADES -> { |
| + | System.out.println("It's spades"); |
| + | } |
| + | case Tarot t -> { |
| + | System.out.println("It's a tarot"); |
| + | } |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | # JDK 22 |
| + | |
| + | ## Foreign Function & Memory API |
| + | |
| + | https://openjdk.org/jeps/454 |
| + | |
| + | 이건 Coding Style 보다는 기능의 대체이다. JNI 를 대체하기 위해 나왔고 조금 복잡해 보이긴 한데, JNI 도 복잡했으니 비슷하지 않을까 싶다. |
| + | |
| + | ### 목적 |
| + | Java runtime 바깥의 코드와 데이터에 대한 접근을 위한 것인데 JNI 의 단점을 보완하려고 한다. |
| + | * JNI의 native 메소드를 pure-java API 로 대체한다. |
| + | * JNI 보다는 오버헤드가 있을 듯 함. |
| + | * JVM 이 실행되는 모든 플랫폼의 native library 의 탐색 및 수행 |
| + | * 다양한 (native, persistent, managed heap) 메모리 접근에 대한 방법을 제공함 |
| + | * thread 간의 메모리 할당과 해지 시에도 쓰지 않는 메모리에 대한 해지 버그에 대해 보장 |
| + | * native code 와 data 에 대한 안전하지 않은 (unsafe) 동작에 대해 허용하되 이에 대해 사용자에게 경고하는 기능 |
| + | |
| + | ### Foreign memory (before) |
| + | 객체들은 heap memory 에 만들어지고 사용이 끝나면 GC 로 사라진다. heap memory 말고 다른 곳에 만들어지는 off-heap memory 가 필요한 경우를 지원. 몰랐는데 이전에도 off-heap 메모리를 사용했다고... |
| + | - ByteBuffer API 에서 시스템의 native I/O 에서 직접 access 할 수 있도록 하기 위해서 allocateDirect 로 할당 시 off-heap memory 로 관리. 주로 Non-block 이나 Async I/O 구현 하는 netty 같은 곳에서 사용. |
| + | - sun.misc.Unsafe API 를 사용하는 경우 -> 잘 안 썼던 것 같다. |
| + | |
| + | ### Foreign functions (before) |
| + | JNI 로 지원 |
| + | |
| + | ### 그래서 |
| + | |
| + | FFM API 는 다음의 클래스와 인터페이스를 추가함. |
| + | - foreign memory 에 대한 allocation 과 deallocation 제어 |
| + | - MemorySegment |
| + | - Arena |
| + | - SegmentAllocator |
| + | - 구조화된 foreign memory 접근 및 조작 |
| + | - MemoryLayout |
| + | - VarHandle |
| + | - foreign functions 호출 |
| + | - Linker |
| + | - SymbolLockup |
| + | - FunctionDescriptor |
| + | - MethodHandle |
| + | |
| + | ## Unnamed Variables & Patterns |
| + | 필요에 의해 정의는 하지만 실제로 쓰이지 않는 경우를 대비해서 만들어진 기능이다. |
| + | |
| + | for loop 를 위해 정의는 하지만 실제로 필요하지 않은 경우 |
| + | |
| + | ```java |
| + | static int count(Iterable<Order> orders) { |
| + | int total = 0; |
| + | for (Order order : orders) // order is unused |
| + | total++; |
| + | return total; |
| + | } |
| + | ``` |
| + | |
| + | 다음과 같이 사용할 수 있다. |
| + | |
| + | ```java |
| + | static int count(Iterable<Order> orders) { |
| + | int total = 0; |
| + | for (Order _ : orders) // Unnamed variable |
| + | total++; |
| + | return total; |
| + | } |
| + | ``` |
| + | |
| + | 아래의 경우에도 z 대신에 _ 를 사용할 수 있다. |
| + | ```java |
| + | Queue<Integer> q = ... // x1, y1, z1, x2, y2, z2 .. |
| + | while (q.size() >= 3) { |
| + | int x = q.remove(); |
| + | int y = q.remove(); |
| + | int z = q.remove(); // z is unused |
| + | ... new Point(x, y) ... |
| + | } |
| + | ``` |
| + | |
| + | try catch 구문에서 catch 한 Exception 을 만나서 실제로 쓰지 않는 경우에도 Exception 변수로 _ 사용 가능. |
| + | |
| + | 패턴의 경우에도 유사. |
| + | |
| + | ```java |
| + | sealed abstract class Ball permits RedBall, BlueBall, GreenBall { } |
| + | final class RedBall extends Ball { } |
| + | final class BlueBall extends Ball { } |
| + | final class GreenBall extends Ball { } |
| + | |
| + | Ball ball = ... |
| + | switch (ball) { |
| + | case RedBall red -> process(ball); |
| + | case BlueBall blue -> process(ball); |
| + | case GreenBall green -> stopProcessing(); |
| + | } |
| + | ``` |
| + | |
| + | 위와 같이 사용하는 경우 red, blue, greean 은 쓰이지 않으므로 다음과 같이 변경할 수 있다. |
| + | |
| + | ```java |
| + | switch (ball) { |
| + | case RedBall _ -> process(ball); // Unnamed pattern variable |
| + | case BlueBall _ -> process(ball); // Unnamed pattern variable |
| + | case GreenBall _ -> stopProcessing(); // Unnamed pattern variable |
| + | } |
| + | ``` |
| + | |
| + | |
| + | > [!Tip] Java 파일 수행 |
| + | > JDK 22 에 Launch Multi-File Source-Code Programs 라는 항목이 있다. 그리고 JDK 11 에서 java Prog.java 형태로 소스코드 파일을 바로 실행할 수 있었다고... 이제는 멀티가 가능하다고 한다. 자세한 내용은 https://openjdk.org/jeps/458 참고. |
| + | |
| + | # JDK 23 |
| + | |
| + | ## Markdown Documentation Comments |
| + | 기존에 html 태그를 넣어서 API 문서 작성하던 것을 Markdown 도 쓸 수 있게 함. |
| + | |
| + | ```java |
| + | /** |
| + | * Returns a hash code value for the object. This method is |
| + | * supported for the benefit of hash tables such as those provided by |
| + | * {@link java.util.HashMap}. |
| + | * <p> |
| + | * The general contract of {@code hashCode} is: |
| + | * <ul> |
| + | * <li>Whenever it is invoked on the same object more than once during |
| + | * an execution of a Java application, the {@code hashCode} method |
| + | * must consistently return the same integer, provided no information |
| + | * used in {@code equals} comparisons on the object is modified. |
| + | * This integer need not remain consistent from one execution of an |
| + | * application to another execution of the same application. |
| + | * <li>If two objects are equal according to the {@link |
| + | * #equals(Object) equals} method, then calling the {@code |
| + | * hashCode} method on each of the two objects must produce the |
| + | * same integer result. |
| + | * <li>It is <em>not</em> required that if two objects are unequal |
| + | * according to the {@link #equals(Object) equals} method, then |
| + | * calling the {@code hashCode} method on each of the two objects |
| + | * must produce distinct integer results. However, the programmer |
| + | * should be aware that producing distinct integer results for |
| + | * unequal objects may improve the performance of hash tables. |
| + | * </ul> |
| + | * |
| + | * @implSpec |
| + | * As far as is reasonably practical, the {@code hashCode} method defined |
| + | * by class {@code Object} returns distinct integers for distinct objects. |
| + | * |
| + | * @return a hash code value for this object. |
| + | * @see java.lang.Object#equals(java.lang.Object) |
| + | * @see java.lang.System#identityHashCode |
| + | */ |
| + | ``` |
| + | |
| + | Markdown 은 /// 로 구분한다. |
| + | |
| + | ```java |
| + | /// Returns a hash code value for the object. This method is |
| + | /// supported for the benefit of hash tables such as those provided by |
| + | /// [java.util.HashMap]. |
| + | /// |
| + | /// The general contract of `hashCode` is: |
| + | /// |
| + | /// - Whenever it is invoked on the same object more than once during |
| + | /// an execution of a Java application, the `hashCode` method |
| + | /// must consistently return the same integer, provided no information |
| + | /// used in `equals` comparisons on the object is modified. |
| + | /// This integer need not remain consistent from one execution of an |
| + | /// application to another execution of the same application. |
| + | /// - If two objects are equal according to the |
| + | /// [equals][#equals(Object)] method, then calling the |
| + | /// `hashCode` method on each of the two objects must produce the |
| + | /// same integer result. |
| + | /// - It is _not_ required that if two objects are unequal |
| + | /// according to the [equals][#equals(Object)] method, then |
| + | /// calling the `hashCode` method on each of the two objects |
| + | /// must produce distinct integer results. However, the programmer |
| + | /// should be aware that producing distinct integer results for |
| + | /// unequal objects may improve the performance of hash tables. |
| + | /// |
| + | /// @implSpec |
| + | /// As far as is reasonably practical, the `hashCode` method defined |
| + | /// by class `Object` returns distinct integers for distinct objects. |
| + | /// |
| + | /// @return a hash code value for this object. |
| + | /// @see java.lang.Object#equals(java.lang.Object) |
| + | /// @see java.lang.System#identityHashCode |
| + | ``` |
| + | |
| + | table 가능하고 JavaDoc 태그는 그냥 써야 하는 듯 하다. 코드 블럭도 가능하다. |
| + | |
| + | # JDK 24 |
| + | |
| + | 대부분 성능 관련 개선이나 API 개선에 대한 내용들이다. |
| + | |
| + | # JDK 25 |
| + | |
| + | ## Scoped Values |
| + | Virtual Thread 를 지원하기 위해 Thread 내에서 값을 공유할 수 있다. |
| + | ```java |
| + | public class Framework { |
| + | |
| + | private final Application application; |
| + | |
| + | public Framework(Application app) { this.application = app; } |
| + | |
| + | private static final ThreadLocal<FrameworkContext> CONTEXT |
| + | = new ThreadLocal<>(); // (1) |
| + | |
| + | void serve(Request request, Response response) { |
| + | var context = createContext(request); |
| + | CONTEXT.set(context); // (2) |
| + | Application.handle(request, response); |
| + | } |
| + | |
| + | public PersistedObject readKey(String key) { |
| + | var context = CONTEXT.get(); // (3) |
| + | var db = getDBConnection(context); |
| + | db.readKey(key); |
| + | } |
| + | |
| + | } |
| + | ``` |
| + | |
| + | Thread 간의 value 공유도 가능한 듯 한데, 이 부분은 structured concurrency(현재는 preview) 에서 가능한 듯 하다. |
| + | |
| + | ## Key Derivation Function API |
| + | |
| + | HKDF(RFC 5869)나 Argon2 (RFC 9106) 같은 키 도출 함수 지원. |
| + | |
| + | ```java |
| + | // Create a KDF object for the specified algorithm |
| + | KDF hkdf = KDF.getInstance("HKDF-SHA256"); |
| + | |
| + | // Create an ExtractExpand parameter specification |
| + | AlgorithmParameterSpec params = |
| + | HKDFParameterSpec.ofExtract() |
| + | .addIKM(initialKeyMaterial) |
| + | .addSalt(salt).thenExpand(info, 32); |
| + | |
| + | // Derive a 32-byte AES key |
| + | SecretKey key = hkdf.deriveKey("AES", params); |
| + | |
| + | // Additional deriveKey calls can be made with the same KDF object |
| + | ``` |
| + | |
| + | ## Module Import Declarations |
| + | import module 문으로 다양한 패키지를 모듈 단위로 import 한다. 예를 들어 아래와 같은 예는 |
| + | |
| + | ```java |
| + | import javax.xml.*; |
| + | import javax.xml.parsers.*; |
| + | import javax.xml.stream.*; |
| + | ``` |
| + | |
| + | 다음과 같이 바꿀 수 있다. 이는 계층 구조 전체를 가져올 수 있다는 뜻. |
| + | |
| + | ```java |
| + | import module java.xml; |
| + | ``` |
| + | ## Compact Source Files and Instance Main Methods |
| + | |
| + | 이 소스코드를... |
| + | ```java |
| + | public class HelloWorld { |
| + | public static void main(String[] args) { |
| + | System.out.println("Hello, World!"); |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 이렇게 줄일 수 있다고... |
| + | ```java |
| + | void main() { |
| + | IO.println("Hello, World!"); |
| + | } |
| + | ``` |
| + | |
| + | ```PowerShell |
| + | PS C:\Users\bruckner\Workspace\T2X> cat .\HelloWorld.java |
| + | void main() { |
| + | IO.println("Hello, World!"); |
| + | } |
| + | PS C:\Users\bruckner\Workspace\T2X> java .\HelloWorld.java |
| + | Hello, World! |
| + | PS C:\Users\bruckner\Workspace\T2X> java -version |
| + | openjdk version "25.0.1" 2025-10-21 LTS |
| + | OpenJDK Runtime Environment Temurin-25.0.1+8 (build 25.0.1+8-LTS) |
| + | OpenJDK 64-Bit Server VM Temurin-25.0.1+8 (build 25.0.1+8-LTS, mixed mode, sharing) |
| + | ``` |
| + | |
| + | 되네요. |
| + | |
| + | ## Flexible Constructor Bodies |
| + | |
| + | JDK 25 중 가장 중요한 변화. 상속 받은 Class 생성 시에 Super Class 먼저 생성하는 방식에 대한 변경이다. |
| + | |
| + | 이전에 이런 방식으로 밖에 짤 수 없었다. |
| + | |
| + | ```java |
| + | class Object { |
| + | Object() { |
| + | // Object constructor body |
| + | } |
| + | } |
| + | |
| + | class A extends Object { |
| + | A() { |
| + | super(); |
| + | // A constructor body |
| + | } |
| + | } |
| + | |
| + | class B extends A { |
| + | B() { |
| + | super(); |
| + | // B constructor body |
| + | } |
| + | } |
| + | |
| + | class C extends B { |
| + | C() { |
| + | super(); |
| + | // C constructor body |
| + | } |
| + | } |
| + | |
| + | class D extends C { |
| + | D() { |
| + | super(); |
| + | // D constructor body |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 호출하면 다음과 같이 된다. |
| + | |
| + | ``` |
| + | D |
| + | --> C |
| + | --> B |
| + | --> A |
| + | --> Object constructor body |
| + | --> A constructor body |
| + | --> B constructor body |
| + | --> C constructor body |
| + | D constructor body |
| + | ``` |
| + | |
| + | 변경된 방식은 다음과 같다. |
| + | |
| + | ```java |
| + | class Object { |
| + | Object() { |
| + | // Object constructor body |
| + | } |
| + | } |
| + | |
| + | class A extends Object { |
| + | A() { |
| + | // A prologue |
| + | super(); |
| + | // A epilogue |
| + | } |
| + | } |
| + | |
| + | class B extends A { |
| + | B() { |
| + | // B prologue |
| + | super(); |
| + | // B epilogue |
| + | } |
| + | } |
| + | |
| + | class C extends B { |
| + | C() { |
| + | // C prologue |
| + | super(); |
| + | // C epilogue |
| + | } |
| + | } |
| + | |
| + | class D extends C { |
| + | D() { |
| + | // D prologue |
| + | super(); |
| + | // D epilogue |
| + | } |
| + | } |
| + | ``` |
| + | |
| + | 호출 순서는 다음과 같다. |
| + | |
| + | ``` |
| + | D prologue |
| + | --> C prologue |
| + | --> B prologue |
| + | --> A prologue |
| + | --> Object constructor body |
| + | --> A epilogue |
| + | --> B epilogue |
| + | --> C epilogue |
| + | D epilogue |
| + | ``` |
| + | |
| + | 생각해보면 쉬운 일 같은데 지금까지 안해준 것도 신기하고... 물론 제약 사항도 있는데 super 를 호출하기 전 까지는 인스턴스화가 완전하게 된 것이 아니어서 다음과 같이 오류가 날 수 있다. |
| + | |
| + | ```java |
| + | class X { |
| + | |
| + | int i; |
| + | String s = "hello"; |
| + | |
| + | X() { |
| + | |
| + | System.out.print(this); // Error - explicitly refers to the current instance |
| + | |
| + | var x = this.i; // Error - explicitly refers to field of the current instance |
| + | this.hashCode(); // Error - explicitly refers to method of the current instance |
| + | |
| + | var y = i; // Error - implicitly refers to field of the current instance |
| + | hashCode(); // Error - implicitly refers to method of the current instance |
| + | |
| + | i = 42; // OK - assignment to an uninitialized declared field |
| + | |
| + | s = "goodbye"; // Error - assignment to an initialized declared field |
| + | |
| + | super(); |
| + | |
| + | } |
| + | |
| + | } |
| + | ``` |
| + | |
| + | # Garbage Collector 중간 정리 |
| + | |
| + | | 종류 | Option | 동작 방식 | 장점 | 단점 | |
| + | | ----------------- | -------------------------------------- | ---------------- | -------------------- | ---------------------------------- | |
| + | | Serial | -XX:+UseSerialGC | 싱글 스레드로 동작 | CPU 자원이 적을 때 효과적 | 멀티 스레드에서 쥐약. GC 동작 시 얼음 현상 | |
| + | | Parallel | -XX:+UseParallelGC | 멀티 스레드로 동작 | 가장 많은 Garbage 정리는 가능 | GC 동작 시 얼음 현상 | |
| + | | G1(Garbage First) | -XX:+UseG1GC | 파티션 구조 | 낮은 지연 시간 | 메모리 오버헤드 | |
| + | | Shenandoah | -XX:+UseShenandoahGC | 파티션 구조, 잦은 GC 수행 | Z 대비 처리 시간이 김 | Z 대비 Garbage 처리량이 높음 | |
| + | | Z | +XX:+UseZGC<br>+XX:+UseGenerationalZGC | 동적 파티션 구조 | 셰넌도 대비 처리 시간이 짧음 | 오리지널은 셰넌도 대비 처리량이 낮았으나 Z Gen 에서 극복 | |
| + | 일단 SerialGC 가 먼저 나왔고 ParallelGC 는 JDK 8 에서 default 가 되었다. 이외에 CMS GC 라는 것도 있었는데 JDK 9 이후 deprecated 되고 JDK 14 에서 제거되었다. 이후 G1GC 가 JDK7 에 도입되고 JDK9에 default 가 된다. ZGC 는 JDK 15 에, GenerationalZGC 는 JDK 21 부터 지원한다. Shenandoah GC 는 JDK 17 부터 지원한다. (지원의 의미는 안정화.) Generational Shenandoah 는 experimental 로 JDK25 부터 도입. (안정화는 기다려야 할 듯) |
| + | |
| + | G1이 JDK 9 이후 현재 Default 이며 (LTS 로 보면 11, 17, 21, 25) G1 보다 저지연 및 메모리 오버헤드를 줄인 GC 로 만들고 있는 것이 Z 하고 Shenandoah 라고 보면 되는데 전반적으로 Z 가 조금 앞서는 느낌이다. |
