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 가 조금 앞서는 느낌이다.
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9