JDK의 버전별 변경 사항은 여기를 참고하세요
Spec
Java SE 24 Platform JSR 399 에 정의된 바와 같이 JSR 399 구현이 목표
실제 Spec은 Final Release Specification 문서를 참고
Final Release Specification Feature Summary
전체 JEP Feature 목록은 OpenJDK의 JDK 24 문서 에서 확인할 수 있다.
JEP 485: Stream Gatherers
JDK 22의 JEP 461: Stream Gatherers (Preview), JDK 23의 JEP 473: Stream Gatherers (Second Preview) 을 거쳐 JDK 24에 정식 기능이 되었다.
그 사이 변경 점이 없기 때문에 이전 JDK 22의 해당 기능 소개를 참고하면 된다.
https://luvstudy.tistory.com/280#jep-461-stream-gatherers-preview
JEP 488: Primitive Types in Patterns, instanceof, and switch (Second Preview)
JDK 23의 JEP 455: Primitive Types in Patterns, instanceof, and switch (Preview) 를 거쳐 Second Preview로 제안되었다.
JEP 455와 변경점이 없기 때문에 이전 JDK 23의 해당 기능 소개를 참고하면 된다.
https://luvstudy.tistory.com/289#jep-455-primitive-types-in-patterns,-instanceof,-and-switch-preview
JEP 492: Flexible Constructor Bodies (Third Preview)
JDK 22의 JEP 447: Statements before super(...) (Preview) , JDK 23의 JEP 482: Flexible Constructor Bodies (Second Preview) 을 거쳐 Third Preview로 제안되었다.
JEP 482와 변경점이 없기 때문에 이전 JDK 23의 해당 기능 소개를 참고하면 된다.
https://luvstudy.tistory.com/289#jep-482-flexible-constructor-bodies-second-preview
JEP 494: Module Import Declarations (Second Preview)
Summary
module에서 export 한 모든 package를 간결하게 import 할 수 있는 기능으로 J ava programming 언어를 향상한다.
이를 통해 modular library의 재사용이 간소화되지만 iomport code가 module 자체에 있을 필요는 없다.
이것은 preview language feature 이다.
History
Module import 선언은 JEP 476 (JDK 23)에서 preview 기능으로 처음 제안되었다.
더 많은 경험과 피드백을 얻기 위해 두 가지 추가 사항을 포함하여 second preview를 제안한다:
- 어떤 module도
java.base
module에 대한 전이 의존성(transive dependence)을 선언할 수 없다는 제한을 해제하고java.se
module의 선언을java.base
module을 전이적으로 요구하도록 수정한다.
이러한 변경 사항을 적용하려면java.se
module을 import 하면 필요에 따라 전체 Java SE API를 가져올 수 있다. - type-import-on-demand 선언이 module import 선언을 가릴 수 있도록 허용한다.
- Goals
- 전체 module을 한 번에 import 할 수 있도록 하여 modular library의 재사용을 간소화한다.
- module에서 export 한 API의 다양한 부분을 사용할 때 여러 개의 type-import-on-demand 선언 (예 :
import com.foo.bar.*
)로 인한 잡음을 피한다. - 초보자도 package hierarchy에서 어디에 위치하는지 배우지 않고도 third-party library와 기본 java class를 보다 쉽게 사용할 수 있도록 한다.
- module import 선언이 기존 import 선언과 함께 원활하게 작동하는지 확인한다.
- module import 기능을 사용하는 개발자가 자신의 코드를 모듈화 할 필요는 없다.
Object
,String
, 및Comparable
같은java.lang
package의 class와 interface는 모든 java program에 필수적이다.
이러한 이유로 Java compiler는 요청 시java.lang
package의 모든 class와 interface를 다음과 같이 자동으로 import 한다. - Motivation
import java.lang.*;
이는 모든 source file의 시작 부분에 나타난다.
Java Platform이 발전함에 따라 List
, Map
, Stream
, 및 Path
와 같은 class와 interface는 거의 필수적인 요소가 되었다.
그러나 이중 어느 것도 java.lang
에 포함되어 있지 않으므로 자동으로 import 되지 않으며, 개발자는 모든 source file의 시작 부분에 수많은 import
선언을 작성하여 complier를 만족시켜야 한다.
예를 들어, 다음 코드는 문자열 배열을 대문자에서 소문자로 변환하지만, import에는 코드만큼이나 많은 줄이 필요하다:
import java.util.Map; // or import java.util.*;
import java.util.function.Function; // or import java.util.function.*;
import java.util.stream.Collectors; // or import java.util.stream.*;
import java.util.stream.Stream; // (can be removed)
String[] fruits = new String[] { "apple", "berry", "citrus" };
Map<String, String> m =
Stream.of(fruits)
.collect(Collectors.toMap(s -> s.toUpperCase().substring(0,1),
Function.identity()));
개발자들은 single-type-import 또는 type-import-on-demand 선언을 선언할지 여부에 대해 다양한 견해를 가지고 있다.
명확성이 가장 중요한 대규모의 성숙한 codebase에서는 single-type import를 선호 )하는 경우가 많다.
그러나 편의성이 명확성보다 우선시되는 초기 단계에서는 개발자가 on-demand import를 선호하는 경우가 많다.
예를 들면
- 코드를 prototyping 하고
java
launcher로 실행할 때; - Stream Gatherers 또는 Foreign Function & Memory API 같은 JShell 에서 새로운 API를 탐색할 때
- virtual threads 및 executors 와 같이 새로운 API와 함께 작동하는 새로운 기능으로 프로그래밍하는 방법을 배울 때
Java 9부터는 module을 통해 package set을 그룹화하여 단일 이름으로 재사용할 수 있다.
module의 export 한 package는 일관되고 일관된 API를 형성하기 위한 것이므로 개발자가 전체 module, 즉 module에서 내보낸 모든 package에서 필요에 따라 가져올 수 있다면 편리할 것이다.
마치 내보낸 모든 package를 한 번에 가져오는 것과 같다.
예를 들어, java.base
module을 on-demand로 import 하면 java.util
을 on-demand로, java.util.stream
을 on-demand로 수동으로 import 하지 않아도 List
, Map
, Stream
, 및 Path
에 즉시 접근할 수 있다.
module level에서 import 기능은 한 module의 API가 다른 module의 API와 밀접한 관계가 있는 경우 특히 유용하다.
이는 JDK와 같은 대규모 multi-module library에서 흔히 볼 수 있다.
예를 들어, java.sql
module은 java.sql
및 javax.sql
package를 통해 database access를 제공하지만, 그 interface 중 하나인 java.sql.SQLXML
은 java.xml
module의 javax.xml.transform
package의 insterface를 사용하는 signature를 가진 public
method를 선언한다.java.sql.SQLXML
에서 이러한 method를 호출하는 개발자는 일반적으로 java.sql.SQLXML
package와 javax.xml.transform
package를 모두 가져온다.
이 extra import를 용이하게 하기 위해 java.sql
module은 java.xml
module에 일시적( transitively )으로 종속되므로 java.sql
module에 종속된 program은 on-demand 방식으로 import 하면 java.xml
module도 on-demand방식으로 자동으로 import 하면 편리할 것이다.
이 시나리오에서는 java.sql
module을 on-demand로 import 하면 java.xml
module도 on-demand로 자동으로 가져오면 편리할 것이다.
transitive dependencies에서 필요에 따라 자동으로 import하면 prototype을 만들고 탐색할 때 더욱 편리할 것이다.
Description
module import 선언의 형태는 다음과 같다.
import module M;
필요에 따라 다음과 같은 모든 public
top-level class와 interface를 가져온다.
- module
M
에서 현재 module로 export 한 package와 - module
M
을 읽음으로써 현재 module에서 읽은 module이 export 한 package
두 번째 절은 프로그램이 다른 모듈의 class 및 interface를 참조할 수 있는 module의 API를 다른 모듈을 가져오지 않고도, 사용할 수 있게 해 준다.
예를 들면:
import module java.base
는java.base
module에서 export 한 각 packages 에 대해 하나씩 54개의 on-demand package import와 동일한 효과를 갖는다.
이는 마치 source file에import java.io.*
,import java.util.*
등이 포함된 것과 같다.import module java.sql
은import java.sql.*
및import javax.sql.*
와 동일한 효과에java.sql
module의 간접 export 를 위한 on-demand package import를 더한다.
This is a preview language feature, disabled by default
JDK 24에서 아래 예제를 사용해 보려면 preview 기능을 활성화해야 한다.
javac --release 24 --enable-preview Main.java
로 프로그램을 compile 하고java --enable-preview Main
로 실행하거나- source code launcher 를 사용하는 경우
java --enable-preview Main.java
또는 jshell
을 사용하는 경우jshell --enable-preview
로 시작한다.
Syntax and semantics
import module
절을 포함하도록 import 선언 문법(JLS §7.5) 을 확장한다:
ImportDeclaration:
SingleTypeImportDeclaration
TypeImportOnDemandDeclaration
SingleStaticImportDeclaration
StaticImportOnDemandDeclaration
ModuleImportDeclaration
ModuleImportDeclaration:
import module ModuleName;
import module
은 module name을 사용하므로, class path, 즉 name이 없는 module에서 package를 import 할 수 없다.
이는 module 선언의 requires
절과 일치한다.
즉, module-info.java
file에서 module name을 사용하고 명명되지 않은 module에 대한 종속성을 표현할 수 없다.
import module
은 해당 file이 명시적 module 정의의 일부인지 여부와 관계없이 모든 source file에서 사용할 수 있다.
예를 들어, java.base
및 java.sql
은 standard java runtime의 일부이며, class path에만 배포되는 class에서 가져올 수 있다. (기술적 배경은 JEP 261 참조)
명시적 module 정의에 포함된 source file 내에서 import module
을 사용하면 해당 module에서 조건 없이 export 하는 모든 package를 편리하게 가져올 수 있다.
이러한 source file에서 module에서 export 하지 않거나 조건을 붙여 export 하는 package는 기존 방식으로 계속 가져와야 한다.
(다시 말해, import module M
은 module M
내부의 코드에 대해 M
외부의 코드보다 더 강력하지 않다. )
source file은 동일한 module을 두 번 이상 import 할 수 있다.
Resolving ambiguous imports
module을 import 하는 것은 여러 pacakge를 import 하는 효과가 있으므로 서로 다른 package에서 동일한 간단한 name을 가진 class를 import 하는 것이 가능하다.
간단한 이름은 모호하므로 이를 사용하면 compile-time error가 발생한다.
예를 들어, 이 source file에서 간단한 이름 Element
는 모호하다:
import module java.desktop; // exports javax.swing.text,
// which has a public Element interface,
// and also exports javax.swing.text.html.parser,
// which has a public Element class
...
Element e = ... // Error - Ambiguous name!
...
또 다른 예로, 이 source file에서 간단한 이름 List
는 모호하다.
import module java.base; // exports java.util, which has a public List interface
import module java.desktop; // exports java.awt, which a public List class
...
List l = ... // Error - Ambiguous name!
...
마지막 예로, 이 source file에서 간단한 이름 Date
는 모호하다.
import module java.base; // exports java.util, which has a public Date class
import module java.sql; // exports java.sql, which has a public Date class
...
Date d = ... // Error - Ambiguous name!
...
모호성을 해결하는 방법은 간단하다: 다른 import 선언을 사용하면 된다.
예를 들어, single-type-import 선언을 추가하면 import module 선언에서 가져온 Date
class에 가려서(shadowing) 이전 예제의 모호한 Date
를 해결할 수 있다:
import module java.base; // exports java.util, which has a public Date class
import module java.sql; // exports java.sql, which has a public Date class
import java.sql.Date; // resolve the ambiguity of the simple name Date!
...
Date d = ... // Ok! Date is resolved to java.sql.Date
...
다른 경우에는 package의 모든 class를 shadowing 하여 모호성을 해결하기 위해 on-demand 선언을 추가하는 것이 더 편리하다:
import module java.base;
import module java.desktop;
import java.util.*;
import javax.swing.text.*;
...
Element e = ... // Element is resolved to javax.swing.text.Element
List l = ... // List is resolved to java.util.List
Document d = ... // Document is resolved to javax.swing.text.Document,
// regardless of any module imports
...
import 선언의 shadowing behavior는 그들의 specificity (구체성?)과 일치한다.
가장 구체적인, 즉 single-type-import 선언은 덜 구체적인 on-demand 및 module import 선언을 모두 shadow 할 수 있다.
on-demand 선언은 덜 구체적인 module import 선언을 shadow 할 수 있지만 더 구체적인 single-type-import 선언은 shadow 할 수 없다.
Coalescing import declarations
여러 개의 on-demand 선언을 single module import 선언으로 통합할 수 있다.
예를 들어 아래의 경우:
import javax.xml.*;
import javax.xml.parsers.*;
import javax.xml.stream.*;
아래처럼 대체될 수 있다:
import module java.xml;
Grouping import declarations
source file에 여러 종류의 import 선언이 섞여있는 경우 같은 종류 별로 group화 하면 가독성을 더욱 향상할 수 있다.
예를 들어:
// Module imports
import module M1;
import module M2;
...
// Package imports
import P1.*;
import P2.*;
...
// Single-type imports
import P1.C1;
import P2.C2;
...
class Foo { ... }
그룹의 순서는 shadowing behavior를 반영한다.
가장 구체적이지 않은 module inport 선언이 가장 먼저 나오고, 가장 구체적인 single-type import가 마지막에 나오고, on-demand import 선언이 그 사이에 나온다.
A worked example
다음은 import module
이 작동하는 방식의 예이다.
source file C.java
가 module M0
선언의 일부라고 가정한다:
// C.java
package q;
import module M1; // What does this import?
class C { ... }
여기서 module M0
에는 다음과 같은 선언이 있다:
module M0 { requires M1; }
import module M1
의 의미는 M1
의 export 및 M1
이 전이적으로(transitively) 필요로 하는 module에 따라 달라진다.
module M1 {
exports p1;
exports p2 to M0;
exports p3 to M3;
requires transitive M4;
requires M5;
}
module M3 { ... }
module M4 { exports p10; }
module M5 { exports p11; }
import module M1
의 효과는 다음과 같다.
M1
은p1
을 모든 사람에게 export 하므로, packagep1
에서public
top level class 및 interface를 가져온다.M1
은p2
를C.java
가 연결된 module인M0
으로 export 하므로 packagep2
에서public
top level class 및 interface를 가져온다.- Import the
public
top level classes and interfaces from packagep10
, sinceM1
requires transitivelyM4
, which exportsp10
. M1
은p10
을 export 하는M4
를 전이적으로 필요로 하므로 packagep10
에서public
top level class 및 interface를 가져온다.
p3
또는 p11
package에서 C.java
로 import 한 것은 없다.
Importing modules in simple source files
이 JEP는 JEP Simple Source Files and Instance main
Methods 와 함께 공동 개발 되었으며, 이는 java.base
module이 export 하는 모든 package의 모든 public 최상위 class와 interface가 simple source file에서 on-demand로 자동으로 import 하도록 지정한다.
즉, import module java.base
가 모든 해당 file의 시작 부분에 나타나는 것과 같다.
간단한 source file은 다른 모듈 (예: java.desktop
) 을 import 할 수 있으며, 중복되는 작업임에도 불구하고 java.base
module을 명시적으로 import 할 수 있다.
JShell tool은 필요에 따라 10개의 package를 on-demand로 import 한다.
package 목록은 임시로 작성된다.
따라서 JShell을 변경하여 import module java.base
를 자동으로 import 하도록 제안한다.
Importing aggregator modules
때로는 aggregator module 을 import 하는 것이 유용하다.
즉, 어떤 package도 자체적으로 export 하지 않지만 필요한 module에서 export 된 package를 export 하는 module이다.
예를 들어, java.se
module은 어떤 package도 export 하지 않지만 19개의 다른 module을 전이적으로 (transitively) 필요로 하므로, import java.se
의 효과는 해당 module에서 export 한 package를 import 하는 것이다. - 구체적으로 java.se
module의 간접 export (indirect exports ) 로 나열된 123개 package.
이 기능의 이전 preview에서 개발자들은 java.se
module을 import 해도 java.base
module을 import 하는 효과가 없다는 사실에 놀랐다.
따라서 java.base
module도 import 하거나 java.base
에서 특정 package (예: import java.util.*
)를 import 해야 했다.
java.se
module을 import해도 java.base
module을 import 하지 않는다.
Java 언어 사양에서는 모든 module이 java.base
module에 대한 전이적 종속성을 선언하는 것을 명시적으로 금했기(explicitly forbade ) 때문이다.
이러한 제한은 모든 module이 java.base
에 암묵적으로 종속되기 때문에 module 기능이 원래 설계에서는 합리적이었다.
그러나 module 선언을 사용하여 import 할 package set을 파생시키는 module import 기능을 사용하면 java.base
를 전이적으로 require 하는 기능이 유용하다.
따라서 우리는 이 언어 제한을 해제할 것을 제안한다.
또한 java.se
module의 선언을 수정하여 java.base
module을 전이적으로 요구하도록 할 것이다.
따라서 API를 export 하는데 얼마나 많은 module이 참여하든, standard Java API를 사용하는데 필요한 것은 single import module java.se
뿐이다.
Java Platform의 aggregator module 만 requires transitive java.base
를 사용해야 한다.
이러한 aggregator의 client는 java.base
를 포함하여 모든 java.*
module을 import 하기를 기대한다.
직접 export와 간접 export가 모두 있는 java Platform의 module은 엄밀히 말해서 aggregator가 아니다.
따라서 client의 namespace를 오염시킬 수 있으므로 require transitive java.base
를 사용해서는 안된다.
예를 들어, java.sql
module은 자체 package 뿐만 아니라 java.xml
및 다른 package도 export 하지만 import module java.sql
을 실행하는 client가 java.base
에서 모든 것을 import 하는데 관심이 있는 것은 아니다.
import module java.se
지시어는 이미 requires java.se
를 명시적 module의 정의에 포함된 source file에서만 동작한다.
module 정의를 벗어난 source file, 특히 암묵적으로 class를 선언한 간단한 source file에서는 unnamed module의 root module의 default set에 java.se
가 없기 때문에 import module java.se
을 사용하는 것이 실패한다.
자동 module 정의에 포함된 source file에서는 다른 해결된 명시적 module requres java.se
이 작동하면 import module java.se
가 작동한다.
Alternatives
import module ...
의 대안은java.lang
보다 더 많은 package를 자동으로 가져오는 것이다.
이렇게 하면 더 많은 class를 simple name으로 사용할 수 있게 되고 초보자가 import에 대해 배울 필요도 줄어든다.
그렇다면 어떤 추가 package를 자동으로 import 해야 할까?- 모든 독자는 어디에나 존재하는
java.base
module에서 어떤 package를 auto-import 할 것 인지에 대한 제안을 받게 될 것이다.java.io
와java.util
은 거의 보편적인 제안이 될 것이고,java.util.stream
과java.util.function
은 일반적이며,java.math
,java.net
, 및java.time
은 각각 supporter가 있을 것이다.
JShell tool의 경우 일회성 java code를 실험할 때 대체로 유용한 10개의java.*
package를 찾을 수 있었지만 모든 Java program에 영구적으로 자동으로 import 할 수 있는java.*
package의 subset을 파악하기는 어려웠다.
또한 이 목록은 Java platform이 발전함에 따라 변경될 수 있다. (예:java.util.stream
및java.util.function
은 Java 8에서만 도입됨)
개발자는 어떤 automatic import가 적용되고 있는지 상기시키기 위해 IDE에 의존하게 될 가능성이 높으며 이는 바람직하지 않은 결과이다. - 이 기능의 중요한 사용 사례는 암시적으로 선언된 class의
java.base
module에서 on-demand로 자동 import 하는 것이다.
또는java.base
에서 내보낸 54개의 package를 자동으로 import 할 수도 있다.
그러나 암시적 class가 예상 수명 주기인 일반 명시적 class로 migration 되는 경우 개발자는 54개의 on-demand package import를 작성하거나 필요한 import를 알아내야 한다. - Risks and Assumptions
하나 이상의 module import 선언을 사용하면 여러 package가 동일한 simple name을 가진 member를 선언하기 때문에 이름이 모호해질 위험이 있다.
이러한 모호성은 program에서 모호한 simple name이 사용될 때까지 감지되지 않으며 compile-time error가 발생한다.
simgle-type-import 선언을 추가하여 모호성을 해결할 수 있지만 이러한 이름 모호성을 관리하고 해결하는 것은 번거롭고 읽기 및 유지 관리가 어려운 코드로 이어질 수 있다.
JEP 495: Simple Source Files and Instance Main Methods (Fourth Preview)
JDK 21의 JEP 445: Unnamed Classes and Instance Main Methods (Preview) 에서 처음 preview로 제안되었고, 이후 JDK 22의 JEP 463: Implicitly Declared Classes and Instance Main Methods (Second Preview) 과 JDK 23의 JEP 477: Implicitly Declared Classes and Instance Main Methods (Third Preview)에서 개선 및 보완되었다.
추가적인 경험과 피드백을 얻기 위해 새로운 용어와 제목을 수정했지만 그 외에는 변경하지 않은 채 Fourth Preview를 제안한다.
'Study > Java' 카테고리의 다른 글
Playwright 사용해 보기 (0) | 2025.03.29 |
---|---|
Spring Boot ConfigurationMetaData 사용해 보기 (0) | 2025.03.28 |
Copilot4Eclipse 사용해 보기 (0) | 2025.01.28 |
Window 개발 환경에서 https로 Spring Boot Application 개발하기 (1) | 2025.01.04 |
Mybatis에서 custom MapTypeHandler 사용해 보기 (0) | 2024.12.29 |
ApplicationContextRunner에서 Condition Evaluation Report 확인하기 (0) | 2024.12.29 |
Spring Boot 3.4 Release Notes (0) | 2024.12.03 |
hibernate SqlType.JSON (json data) 사용해 보기 (0) | 2024.11.18 |
Spring Cloud Context의 @RefreshScope를 사용하여 properties 설정 갱신하기 (0) | 2024.11.17 |
springdoc swagger ui에 authorize 사용해 보기 (0) | 2024.11.07 |