1128 ~ 1211
# 1128 ~ 1211
# 1201 - Spring REST Docs + Rest Assured
[ build.gradle ]
plugins {
...
// Asciidoctor ํ๋ฌ๊ทธ์ธ์ ์ ์ฉ
id "org.asciidoctor.jvm.convert" version "3.3.2"
...
}
configurations {
...
// asciidoctorExtAsciidoctor๋ฅผ ํ์ฅํ๋ ์ข
์์ฑ์ ๋ํ ๊ตฌ์ฑ์ ์ ์ธ
asciidoctorExt
...
}
dependencies {
...
// asciidoctorExt : spring-restdocs-asciidoctor ๊ตฌ์ฑ ์์กด์ฑ์ ์ถ๊ฐ
// snippets์ .adoc๋ก ๊ฐ๋ฆฌํค๊ณ build/generated-snippets ์์ฑ๋๋๋ก ์๋์ผ๋ก ๊ตฌ์ฑ
asciidoctorExt 'org.springframework.restdocs:spring-restdocs-asciidoctor'
// spring-restdocs-restassured ์์กด์ฑ์ ์ถ๊ฐ
testImplementation 'org.springframework.restdocs:spring-restdocs-restassured'
...
}
ext {
...
// ์์ฑ๋ ์ค๋ํซ์ ์ถ๋ ฅ ์์น๋ฅผ ์ ์ํ๋๋ก ์์ฑ์ ๊ตฌ์ฑ
set('snippetsDir', file("build/generated-snippets"))
...
}
tasks.named('test') {
outputs.dir snippetsDir
useJUnitPlatform()
}
tasks.named('asciidoctor') {
inputs.dir snippetsDir
// asciidoctorExtํ์ฅ ์ ๋ํ ๊ตฌ์ฑ ์ฌ์ฉ์ ๊ตฌ์ฑ
configurations 'asciidoctorExt'
dependsOn test
}
tasks.named('bootJar') {
dependsOn asciidoctor
// ์์ฑ๋ ๋ฌธ์๋ฅผ jar static/docs๋๋ ํ ๋ฆฌ์ ๋ณต์ฌ
copy {
from "${asciidoctor.outputDir}"
into 'src/main/resources/static/docs'
}
}
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
[ ํ ์คํธ ํด๋์ค ]
- AcceptanceTest
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ExtendWith({RestDocumentationExtension.class, SpringExtension.class})
public class AcceptanceTest {
@LocalServerPort
int port;
protected RequestSpecification spec;
@BeforeEach
public void setUp(RestDocumentationContextProvider restDocumentation) {
RestAssured.port = port;
this.spec = new RequestSpecBuilder()
.addFilter(documentationConfiguration(restDocumentation))
.build();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- OrderRestControllerTest
class OrderRestControllerTest extends AcceptanceTest {
@Test
void postOrder() {
// given
String menuName = "๋ฉ๋ด";
// when
ExtractableResponse<Response> response = ์ฃผ๋ฌธ_์์ฑ_์์ฒญ(menuName, 2);
OrderModel order = response.as(OrderModel.class);
// then
assertThat(response.statusCode()).isEqualTo(HttpStatus.OK.value());
assertThat(order.getMenuName()).isEqualTo(menuName);
}
private ExtractableResponse<Response> ์ฃผ๋ฌธ_์์ฑ_์์ฒญ(String menuName, Integer quantity) {
OrderPostRequestDTO dto = new OrderPostRequestDTO(menuName, quantity);
return RestAssured
.given(this.spec).log().all()
.accept(APPLICATION_JSON_VALUE)
.filter(document("post-order",
requestFields(
fieldWithPath("order.menuName").type(STRING).description("order menu name"),
fieldWithPath("order.quantity").type(NUMBER).description("order quantity")),
responseFields(
fieldWithPath("order.menuName").type(STRING).description("order menu name"),
fieldWithPath("order.quantity").type(NUMBER).description("order quantity"))
))
.body(dto)
.contentType(APPLICATION_JSON_VALUE)
.when().post("/orders")
.then().log().all()
.extract();
}
}
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
32
33
34
35
36
37
38
[ adoc ๋ฌธ์ ์์ฑ ]
- common.adoc
[[index]]
=== link:./index-docs.html[HOME]
[[common]]
== Common API Docs
=== HTTP Status Code
[cols="2,3,5"]
|===
| Code | Description | Constraint
| 200
| Success
|
| 400
| Bad Request
|
|===
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- order.adoc
= Order API
:doctype: book
:icons: font
:source-highlighter: highlightjs
:toc: left
:toclevels: 3
:sectlinks:
include::{docfile}/../common.adoc[]
== Order API Docs
=== POST /orders
Curl Request
include::{snippets}/post-order/curl-request.adoc[]
Request Headers
Path Parameters
Request Parameters
Request Fields
include::{snippets}/post-order/request-fields.adoc[]
Response Fields
include::{snippets}/post-order/response-fields.adoc[]
HTTP Request
include::{snippets}/post-order/http-request.adoc[]
HTTP Response
include::{snippets}/post-order/http-response.adoc[]
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
32
33
# 1205 - Caffeine Cache
# Caffeine vs EhCache
- Ehcache๋ multi-level ์บ์, distributed ์บ์, ์บ์ ๋ฆฌ์ค๋ ๋ฑ๊ณผ ๊ฐ์ ๋ง์ ๊ธฐ๋ฅ์ ์ง์ํ๋ค.
- Caffeine์ Ehcache๋ณด๋ค ์บ์์ ์ฑ๋ฅ์ด ๋์ผ๋ฉฐ, ์ค์ ๋ก ๋ ์ฐ์ํ ์บ์ ์ ๊ฑฐ ์ ๋ต์ ์ฌ์ฉํ๋ค.(Window TinyLfu ํด์ถ ์ ์ฑ ์ ์ฌ์ฉ, ๊ฑฐ์ ์ต์ ์ ์ ์ค๋ฅ ์ ์ ๊ณต)
# Caffeine ๊ธฐ๋ฅ
# [ Population Strategy ]
์บ์ ์ถ๊ฐ ์ ๋ต
Caffeine Cache๋ ์๋์ ์ธ๊ฐ์ง ํ์ ์ ์บ์๋ก ์ ์ํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
- Manual
Cache<K, V> cache = Caffeine.newBuilder().build()
์ํธ๋ฆฌ๋ฅผ ์๋ ๋ก๋ํ๋ ์บ์๋ฅผ ์์ฑ.
- Loading (Synchronously)
LoadingCache<K, V> cache = Caffeine.newBuilder().build(CacheLoader<> loader)
๋๊ธฐ ๋ฐฉ์์ผ๋ก loader๋ฅผ ํตํด ์บ์ ์์ฑํ๋ค.
- Asynchronous Loading
AsyncLoadingCache<K, V> cache = Caffeine.newBuilder().buildAsync( ... );
๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก loader๋ฅผ ํตํด ์บ์ ์์ฑํ๋ค.
# [ Eviction ]
Caffeine Cache๋ ์๋์ ์ธ๊ฐ์ง ํ์ ์ผ๋ก ์บ์๋ฅผ Evictํ๋ ์ค์ ์ ํ ์ ์๋ค.
- Size-based
Caffeine.newBuilder().maximumSize(long)
ํฌ๊ธฐ ๊ธฐ์ค์ผ๋ก ์บ์๋ฅผ ์ ๊ฑฐํ๋ ๋ฐฉ์์ ๊ฐ๋ฐ์๊ฐ ์ค์ ํ ํน์ ๊ฐ์ ๊ธฐ์ค์ผ๋ก, entries์ ํฌ๊ธฐ๊ฐ ๊ทธ ๊ฐ์ ๋์ ๋ entries์ ์ผ๋ถ๋ถ์ ์ ๊ฑฐํ๋ค.(Window TinyLfu๋ฅผ ์ ์ฉํ์ฌ ๊ฐ์ฅ ์ต๊ทผ์ ์ฌ์ฉ๋์ง ์์๊ฑฐ๋, ์์ฃผ ์ฌ์ฉ๋์ด์ง์ง ์์ ๊ฒ์ ์ ๊ฑฐ)
- Time-based
Caffeine.newBuilder().expireAfterAccess(long)
Caffeine.newBuilder().expireAfterWrite(long[, TimeUnit])
Caffeine.newBuilder().expireAfter(Expiry)
- expireAfterAccess : (์บ์ ์์ฑ ์ดํ) ํด๋น ๊ฐ์ด ๊ฐ์ฅ ์ต๊ทผ์ ๋์ฒด๋๊ฑฐ๋ ๋ง์ง๋ง์ผ๋ก ์ฝ์ ํ ํน์ ๊ธฐ๊ฐ์ด ์ง๋๋ฉด ๊ฐ ํญ๋ชฉ์ด ์บ์์์ ์๋์ผ๋ก ์ ๊ฑฐ๋๋๋ก ์ง์ ํ๋ค.
- expireAfterWrite : ์บ์ ์์ฑ ํ ๋๋ ๊ฐ์ฅ ์ต๊ทผ์ ๋ฐ๋ ํ ํน์ ๊ธฐ๊ฐ์ด ์ง๋๋ฉด ๊ฐ ํญ๋ชฉ์ด ์บ์์์ ์๋์ผ๋ก ์ ๊ฑฐ๋๋๋ก ์ง์ ํ๋ค.
- expireAfter : ์บ์๊ฐ ์์ฑ๋๊ฑฐ๋ ๋ง์ง๋ง์ผ๋ก ์ ๋ฐ์ดํธ๋ ํ ์ง์ ๋ ์๊ฐ ๊ฐ๊ฒฉ์ผ๋ก ์บ์๋ฅผ ์๋ก ๊ณ ์นจํ๋ค.
- Reference-based
Caffeine.newBuilder().weakKeys().weakValues()
Caffeine.newBuilder().softValues()
Caffeine.weakKeys(), Caffeine.weakValues() : Week References๋ฅผ ์ฌ์ฉํ์ฌ ํค๋ฅผ ์ ์ฅํ๋ค. Week References๋ ํค์ ๋ํ ๋ค๋ฅธ ๊ฐ๋ ฅํ ์ฐธ์กฐ(Strong References)๊ฐ ์๋ ๊ฒฝ์ฐ ๊ฐ๋น์ง ์์งํ ์ ์๋ค. ๊ฐ๋น์ง ์์ง์ identity์๋ง ์์กดํ๋ฏ๋ก ์ ์ฒด ์บ์๊ฐ ๋๋ฑ์ฑ(.equals()) ๋์ identity ๋์ผ์ฑ(==)์ ์ฌ์ฉํ์ฌ ํค๋ฅผ ๋น๊ตํ๋ค.
Caffeine.softValues() : Soft References๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ ์ ์ฅํ๋ค. Soft References ์ค๋ธ์ ํธ๋ ๋ฉ๋ชจ๋ฆฌ ์์์ ๋ฐ๋ผ least-recently-used ๋ฐฉ์์ผ๋ก ๊ฐ๋น์ง๊ฐ ์์ง๋๋ค. ์ํํธ ๋ ํผ๋ฐ์ค๋ฅผ ์ฌ์ฉํ๋ฉด ํผํฌ๋จผ์ค๊ฐ ์ํฅ์ ๋ฐ๊ธฐ ๋๋ฌธ์ ์ผ๋ฐ์ ์ผ๋ก ์์ธก ๊ฐ๋ฅํ ์ต๋ ์บ์ ํฌ๊ธฐ๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข๋ค. softValues()๋ฅผ ์ฌ์ฉํ๋ฉด Week References์ ๋ง์ฐฌ๊ฐ์ง๋ก ๊ฐ์ด equals ๋์ identity(==) equality๋ฅผ ์ฌ์ฉํ์ฌ ๋น๊ต๋๋ค.
# [ Notification of Removal ]
์ ๊ฑฐ๋๋ ์บ์ ์ํธ๋ฆฌ์ ๋ํด ๋ฆฌ์ค๋๋ฅผ ๋ฌ์ ์ถ๊ฐ์ ์ธ ์์ ์ ํ ์ ์๋ค.
Caffeine.newBuilder()
.evictionListener((K, V, RemovalCause) -> { /* ... / })
.removalListener((K, V, RemovalCause) -> { / ... */ })
.build();
๋ ๋ฆฌ์ค๋์ ์ฐจ์ด๋ ์บ์ ๊ด๋ จ ์ฉ์ด๋ฅผ ์ดํดํ๋ฉด ์ ์ ํ ์ฌ์ฉํ ์ ์๋ค.
- Eviction : ์บ์ ์ ์ฑ Policy์ ์ํ ์ญ์
- Invalidation : Caller์ ์ํ ์๋ ์ญ์
- Removal : invalidation๊ณผ eviction, ๋ ๊ฐ์ง๋ฅผ ๋ชจ๋ ํฌํจ
ex)
Cache<Key, Graph> graphs = Caffeine.newBuilder()
.evictionListener((Key key, Graph graph, RemovalCause cause) ->
System.out.printf("Key %s was evicted (%s)%n", key, cause))
.removalListener((Key key, Graph graph, RemovalCause cause) ->
System.out.printf("Key %s was removed (%s)%n", key, cause))
.build();
2
3
4
5
6
# [ Compute ]
์ธ๋ถ ๋ฐ์ดํฐ(๋ฆฌ์์ค)์ ์ ๊ทผํ๋ฉฐ ์บ์๋ฅผ ์ธ ์ ์๋ค.
graphs.asMap().compute(key, (k, v) -> { /* ... */ });
๋ถ๋ฌ์จ ์บ์ ๋ฐ์ดํฐ๋ฅผ Map์ผ๋ก ๋ณ๊ฒฝํ ํ ํค๋ฅผ ์ฐจ๋ก๋ก ์กฐ์ํ ์ ์๋ค.
graphs.asMap().compute(key, (k, v) -> {
Graph graph = createExpensiveGraph(key);
... // update a secondary store
return graph;
});
map.compute(key, (k, v) -> (v == null) ? msg : v.concat(msg))
2
3
4
5
6
7
# [ Statistics ]
์บ์ ์ก์ธ์ค ํต๊ณ ์ ๋ณด๋ฅผ ์ ๊ณตํ๋ค. ํต๊ณ ์ ๋ณด๋ฅผ ์ฌ์ฉํ๋ฉด Caffeine.recordStats()๋ฅผ ์ค์ ํด์ฃผ๋ฉด ๋๋ค.
Cache.stats() ๋ฉ์๋๊ฐ CacheStats๋ฅผ ๋ฐํํด์ค๋ค. CacheStats๋ ์๋์ ๊ฐ์ ๋ด์ฉ์ ๊ฐ์ง๋ค.
public final class CacheStats {
// ...
private final long hitCount;
private final long missCount;
private final long loadSuccessCount;
private final long loadFailureCount;
private final long totalLoadTime;
private final long evictionCount;
private final long evictionWeight;
// ...
public double hitRate() {/* ... */}
public long evictionCount() {/* ... */}
public double averageLoadPenalty() {/* ... */}
// ...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Caffeine Configuration
- dependency
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>3.0.6</version>
</dependency>
2
3
4
5
- yml
spring:
cache:
caffeine:
spec: maximumSize=500,expireAfterWrite=5s
type: caffeine
cache-names:
- users
- books
2
3
4
5
6
7
8
- @configuration
@Configuration
@EnableCaching
public class CaffeineCacheConfig { /* ... */ }
2
3
ex)
@EnableCaching
@Configuration
public class CacheConfig {
@Bean
public Caffeine caffeineConfig() {
return Caffeine
.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(60, TimeUnit.MINUTES);
}
@Bean
public CacheManager cacheManager(Caffeine caffeine) {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.setCaffeine(caffeine);
return cacheManager;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 1209 - Java 17
- 2021๋ 9์ ์ถ์
- LTS(Long Term Support)
- 2018๋
์ ๋ฑ์ฅํ 11๋ฒ์ ์ดํ์ ์๋ก์ด LTS๋ฒ์
- (Java 8์ ์ข ๋ฃ ์ผ์ (EOL, End of Life)์ด Java 11๋ณด๋ค ๋ค๋ค)
- 2018๋
์ ๋ฑ์ฅํ 11๋ฒ์ ์ดํ์ ์๋ก์ด LTS๋ฒ์
# JDK 17 Release Notes
# [ JEP 356: Enhanced Pseudo-Random Number Generators ]
๊ธฐ๋ณธ ๋ ๊ฑฐ์ ๋๋ค(Java.util.Random) ํด๋์ค๋ฅผ ํ์ฅ, ๋ฆฌํฉํ ๋งํ RandomGenerator
๋์ ์์ฑ API๊ฐ ์ถ๊ฐ ๋์๋ค. ์๋์ ๊ฐ์ ์ฝ๋๋ก ์๋ฐ 17์์ ์ฌ์ฉ ๊ฐ๋ฅํ ์๊ณ ๋ฆฌ์ฆ์ ํ์ธํ ์ ์๋ค.
RandomGeneratorFactory.all()
.map(factory -> String.format("%s: %s", factory.group(), factory.name()))
.sorted()
.forEach(System.out::println);
2
3
4
LXM: L128X1024MixRandom
LXM: L128X128MixRandom
LXM: L128X256MixRandom
LXM: L32X64MixRandom
LXM: L64X1024MixRandom
LXM: L64X128MixRandom
LXM: L64X128StarStarRandom
LXM: L64X256MixRandom
Legacy: Random
Legacy: SecureRandom
Legacy: SplittableRandom
Xoroshiro: Xoroshiro128PlusPlus
Xoshiro: Xoshiro256PlusPlus
2
3
4
5
6
7
8
9
10
11
12
13
# [ JEP 382: New macOS Rendering Pipeline ]
์ ํ์ฌ์์ ๊ธฐ์กด์ OpenGL์ด ์๋ Metal ์ด๋ผ๋ ์๋ก์ด ๋ ๋๋ง API๋ก ๋์ฒดํ๋ ์์ง์์ ๋ง์ถ ๋์์ด๋ค. ํฅํ MacOS ๋ฒ์ ์์ OpenGL API๋ฅผ ์ ๊ฑฐํ๋ ๊ฒฝ์ฐ๋ฅผ ๋๋นํ๊ธฐ ์ํจ์ด๋ค. ์ฝ๋ ์ฌ์ฉ๊ณผ ์ง์ ์ ์ธ ๊ด๋ จ์ ์๊ฒ ์ผ๋, MacOS์์ ์ธํ ๋ฆฌ์ ์ด IDE์ ๋ ๋๋ง์ด ๊ฐ์ ๋์ง ์์๊น ํ๋ ๊ธฐ๋๊ฐ ์๋ค.
# [ JEP 398: Deprecate the Applet API for Removal ]
์ ํ๋ฆฟ(Applet) API๋ ์๋ฐ 9์์๋ถํฐ ์ด๋ฏธ @Deprecated ์ ์ธ๋์๋๋ฐ, ์ด๋ฒ ๋ฒ์ ์์๋ forRemoval ํ๊น ๋ ์ ์ธ๋์๋ค.
@Deprecated(since = "9", forRemoval = true)
@SuppressWarnings("removal")
public class Applet extends Panel {
// ...
}
2
3
4
5
# [ JEP 403: Strongly Encapsulate JDK Internals ]
์ค์ํ ๋ด๋ถ API๋ฅผ ์ ์ธํ๊ณ ๋ JDK์ ๋ชจ๋ ๋ด๋ถ ์์๋ฅผ ๊ฐ๋ ฅํ๊ฒ ์บก์ํํ๋ค. ์๋์ ๊ฐ์ ๋ฆฌํ๋ ์ ์ฝ๋๊ฐ ๋ ์ด์ ๋์ํ์ง ์๋๋ค.
var ks = java.security.KeyStore.getInstance("jceks");
var f = ks.getClass().getDeclaredField("keyStoreSpi");
f.setAccessible(true);
2
3
# [ JEP 406: Pattern Matching for switch (Preview) ]
์๋ฐ 14๋ถํฐ ๋ฑ์ฅํ๋ ๋ด์ฉ์ธ๋ฐ ์์ง๊น์ง ํ๋ฆฌ๋ทฐ(Preview) ๊ธฐ๋ฅ์ด๋ค. ์คํํ๋ ค๋ฉด ๋ช ๋ น์ด ์ต์ ์ถ๊ฐ๊ฐ ํ์ํ๋ค. (Intellij์์๋ Language Level ์ค์ ์ ๋ณ๊ฒฝํ๋ฉด ๋๋ค.)
์๋ ์์์ฒ๋ผ instanceof
๋ฅผ ์ฌ์ฉํ ๋ ์บ์คํ
ํ๋ ๋จ๊ณ๋ฅผ ๊ฐ์ํํ ์ ์๋ค.
// AS-IS: ๊ธฐ์กด์ instanceof ์ฐ์ฐ์ ์ฌ์ฉ. ์บ์คํ
์ด ๋ค์ด๊ฐ๋ค.
if (o instanceof String) {
String s = (String) str;
// ... ๋ณ์ s๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋
}
// TO-BE: ํ๋ณํ ๊ณผ์ ์ ์์ ๊ณ , ๊ทธ ๋ณ์('s')๋ฅผ ๋ด์ ์ ์๋ค.
if (o instanceof String s) {
// ... ๋ณ์ s๋ฅผ ์ฌ์ฉํ๋ ์ฝ๋
}
2
3
4
5
6
7
8
9
10
๊ทธ๋ฆฌ๊ณ null
์ ๋ํด์๋ ์กฐ๊ธ ๋ ๊ฐํธํ๊ฒ ํธ๋ค๋งํ ์ ์๋ค.
// ๊ธฐ์กด
static void someMethod(String s) {
if (s == null) {
System.out.println("null!");
return;
}
switch (s) {
case "kim", "taeng" -> System.out.println("Hello~");
default -> System.out.println("Wow!");
}
}
// ๋ณ๊ฒฝ
static void someMethod(String s) {
switch (s) {
case null -> System.out.println("null!");
case "kim", "taeng" -> System.out.println("Hello~");
default -> System.out.println("Wow!");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
๋ ๋์๊ฐ switch
๋ฌธ์ ์ํ ํจํด ๋งค์นญ์ ์ ์ฉํ ์ ์๋ค. ์๋์ ๊ฐ์ ํํ์ switch
์ฝ๋๋ฅผ ์์ฑํ ์ ์๋๋ฐ, ํ๋ผ๋ฏธํฐ o
๊ฐ์ Long l
์ ๋งค์นญ๋์ด ์ฝ๋๊ฐ ์คํ๋๋ค.
Object o = 123L;
String formatted = switch (o) {
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 -> o.toString();
};
2
3
4
5
6
7
8
# [ JEP 407: Remove RMI Activation ]
RMI ์ผ๋ถ ๊ธฐ๋ฅ(java.rmi.activation ํจํค์ง)์ด ์ ๊ฑฐ๋จ.
# [ JEP 409: Sealed Classes ]
์๋ฐ์์๋ ์์์ ํตํด์ ์ฝ๋๋ฅผ ์ฌ์ฌ์ฉํ ์ ์์ฐ๋ง ๋๋ฌด ๋ถ๋ถ๋ณํ ์์์ ์ฝ๋๋ฅผ ์ดํดํ๋๋ฐ ๋ ์ด๋ ค์์ ์ค ์ ์๋ค. ๋ฐ๋ผ์ ์์์ ์ผ๋ถ ์ ํํ๋ ๋ฐฉ๋ฒ์ด ์ ์๋์๋ค. JEP ์คํ ๋ฌธ์๋ฅผ ๋ณด๋ฉด ์ ์ ์๋ฏ์ด ์๋ก์ด ํจ๋ฌ๋ค์์ด๊ธฐ ๋๋ฌธ์ ์กฐ๊ธ ๋ณต์กํ๋ค. ๋ด์ฉ ๋ํ ์ฌ๋ฌ ๋ฒ์ ์ ๊ฑฐ์ณ์ Preview๋ก ์๊ฐ ๋์๋ค,
- JDK 15์์ Preview๋ก ์ ์(JEP 360) (opens new window)
- JDK 16์์ Second Preview๋ก ์์ ์ ์(JEP 397) (opens new window)
ํต์ฌ์ "ํ์ฅ(extends) ํ๊ฑฐ๋ ๊ตฌํ(implements) ํ ์ ์๋ ํด๋์ค ๋๋ ์ธํฐํ์ด์ค๋ฅผ ์ ํํ๋ค."๋ผ๊ณ ์๊ฐ ํ๋ฉด ๋๋ค. ๋ณดํต ์ฐ๋ฆฌ ๋ง๋ก๋ ๋ด์ธ ํด๋์ค ๋๋ ๋ด์ธ๋ ํด๋์ค๋ก ํ๊ธฐํ๋ค. ์ด์ ์์, ๊ตฌํ์ ์ด์ ๋ด์ธ
์ด๋ผ๋ ๋จ์ด๋ ์ฌ์ฉ๋ ๊ฑฐ๋ผ ์์ธก.
๊ธฐ์กด์๋ ์์์ ์ ํํ๋ ๋ฐฉ๋ฒ์ final ํค์๋๋ฅผ ํตํด์ ์ฌ์ฉ.
class Person {
}
// `Developer` ํด๋์ค๋ ํ์ฅ/์์(extends)ํ ์ ์๋ค.
final class Developer extends Person {
}
// `Designer` ํด๋์ค๋ ํ์ฅ/์์(extends)ํ ์ ์๋ค.
final class Designer extends Person {
}
2
3
4
5
6
7
8
9
10
์ด๋ฒ์๋ ๋ค๋ฅธ ์ปจ์ ์ผ๋ก, ํน์ ์๋ธ ํด๋์ค์๊ฒ๋ง ํ์ฅ์ ํ์ฉํ๊ณ ๋ค๋ฅธ ํด๋์ค์๋ ๋ด์ธ(sealed)ํ๋ ๋ฐฉ๋ฒ.
// `Person`๋ ํ์ฉ๋(permits) ์๋ธ ํด๋์ค๋ง ํ์ฅํ ์ ์๋ค.
sealed class Person
permits Developer, Designer {
}
// `Developer` ํด๋์ค๋ ๋ด์ธ์ด ํด์ ๋์๋ค.
non-sealed class Developer extends Person {
}
// ๋ด์ธ์ด ํด์ ๋ `Student` ํด๋์ค๋ ๋ค๋ฅธ ์๋ธ ํด๋์ค์์ ํ์ฅ ๊ฐ๋ฅํ๋ค.
// ๊ทธ๋ฆฌ๊ณ ์๊ธฐ ์์ ์ Developer ๋ด์ธ(sealed)ํ ์ ์๋ค.
sealed class Student extends Developer
permits HighSchoolStudent, MiddleSchoolStudent {
// ์ด ํด๋์ค๋ `HighSchoolStudent`, `MiddleSchoolStudent` ํด๋์ค๋ง ํ์ฅ ๊ฐ๋ฅํ๋ค.
}
// permitted ์๋ธ ํด๋์ค๋ ํ์ฅ์ ๋ชปํ๊ฒ ํ๊ฑฐ๋(final),
// ์๋ธ ํด๋์ค๋ฅผ ๊ฐ์ง์ฑ๋ก ์์ ์ ๋ด์ธํ๊ฑฐ๋(sealed), ๋ด์ธ์ ํด์ (non-sealed)ํด์ผ๋ง ํ๋ค.
final class HighSchoolStudent extends Student {
}
non-sealed class MiddleSchoolStudent extends Student {
}
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
๋ด์ธ ํด๋์ค๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ์๋์ ๊ฐ์ ๋ช๊ฐ์ง ๊ท์น์ด ์๋ค.
- sealed ํด๋์ค์ permitted๋ ์๋ธ ํด๋์ค์ ๋์ผํ ๋ชจ๋ ๋๋ ํจํค์ง์ ์ํด์ผํ๋ค.
- ๋ชจ๋ permitted ์๋ธ ํด๋์ค๋ sealed ํด๋์ค๋ฅผ ํ์ฅ(extends)ํด์ผ ํ๋ค. ๊ทธ๋ ์ง ์์ผ๋ฉด ์ปดํ์ผ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค.
- ๋ชจ๋ permitted ์๋ธ ํด๋์ค๋ ์ํผ ํด๋์ค์ ์ํด ์์๋ ๋ด์ธ์ ๊ณ์ํ ์ง ๋ง์ง ์ ์ธํด์ผํ๋ค.
- ๋์ด์ ํ์ฅ๋์ง ์๋๋ก final์ ์ ์ฉํ ์ ์๋ค.
- non-sealed๋ก ์ ์ธํ์ฌ ๋ค๋ฅธ ํด๋์ค๊ฐ ํ์ฅํ๋๋ก ํ ์ ์๋ค.
- ์๊ธฐ ์์ ๋ ๋ด์ธ(sealed) ํด๋์ค๋ก ์ ์ธ๋ ์ ์๋ค.
# [ JEP 410: Remove the Experimental AOT and JIT Compiler ]
AOT(Ahead-Of-Time), JIT(Just-In-Time) ์ปดํ์ผ๋ฌ๊ฐ ์ ๊ฑฐ๋์๋ค. ๋์์ jdk.aot
, jdk.internal.vm.compilerm
, jdk.internal.vm.compiler.management
์ด๋ค.
# [ JEP 411: Deprecate the Security Manager for Removal ]
java.lang.SecurityManager
์ ์ผ๋ถ ํด๋์ค์ @Deprecated(forRemoval=true)
๊ฐ ์ ์ธ๋จ.
์๋ฐ 14๋ฒ์ Preview ์ค ๋ช ๊ฐ์ง๊ฐ ์ ์ ๋ฐ์
# [ JEP 359: Records ]
record
๋ ์๋ฐ 16์์ ์คํ์ด ํ์ ๋์ด ์ ์ ์ถ๊ฐ๋จ.
record RecordPoint(int x, int y) {
// ์์ ๋ถ๊ฐ(final ํด๋์ค)
// ๊ฐ ํ๋๋ private final. ์์ ๋ถ๊ฐ
// x = 5;
// serialize ํ ๋๋? ํ๋์ `@JsonProperty`๋ฅผ ๋ถ์ฌ์ค๋ค.
// static ํ๋์ ๋ฉ์๋ ์์ ๊ฐ๋ฅ
static int MAX_LENGTH = 25;
public static int getMaxLength() {
return MAX_LENGTH;
}
}
// ์ฌ์ฉํ ๋๋? ํด๋์ค์ ๋์ผํ๊ฒ `new` ์ฐ์ฐ์๋ก ์ธ์คํด์คํํ๋ค.
RecordPoint recordPoint = new RecordPoint(2, 3);
// ๊ทธ๋ฐ๋ฐ getter๊ฐ ์๋ ์์ฑ!
recordPoint.x();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# [ JEP 378: Text Blocks ]
JDK 15์ ์ ์ ํฌํจ.
private void runJEP368() {
String html = """
{
"list": [
{
"title": "hello, taeng",
"author": "taeng"
},
{
"title": "hello, news",
"author": "taeng"
}
]
}
""".indent(2);
// indent ๋ ์๋ฐ 12์์ ์ถ๊ฐ๋ ๊ฒ์ธ๋ฐ, ๋ฌธ์์ด ๊ฐ ๋งจ ์ ํ์ n๋งํผ ๋์ด๋ค.
System.out.println(html);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- ๋ณ์ ๋งคํ
String textBlock = """
{
"title": %s,
"author": %s,
"id": %d
}
""".formatted("hi", "taeng", 2);
System.out.println(textBlock);
2
3
4
5
6
7
8
9