-
-
Notifications
You must be signed in to change notification settings - Fork 57
/
FeaturesJava22.java
338 lines (298 loc) · 13.2 KB
/
FeaturesJava22.java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
package java22;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.StructuredTaskScope;
import java.util.stream.Gatherers;
import java.util.stream.Stream;
public class FeaturesJava22 {
/**
* Version 21 of Java add good new operators to interact with the data.
* Now we can get first and last elements of collections as Scala does with [head] and [tail].
* We can also reverse the order of elements.
*/
@Test
public void sequenceCollection() {
List<String> collection = List.of("hello", "new", "collection", "features");
System.out.println(collection.getFirst());
System.out.println(collection.getLast());
System.out.println(collection.stream().map(String::toUpperCase).toList());
System.out.println(collection.reversed());
}
record User(String name, String password) {
}
record Account(int amount) {
}
/**
* Since Java 21 [Pattern matching] it's getting better and getting close what scala can do.
* Now we can evaluate object type to any data type defined in the switch and reference to variable name.
* Or even unbox the content to the type in specific variable for each of them.
* And from Java 22 even use unnamed variables.
*/
@Test
public void patternMatching() {
runPatternMatchingUnboxing(new User("Politrons", "foo"));
runPatternMatchingUnboxing(new Account(1000));
runPatternMatching(new User("Politrons", "foo"));
runPatternMatching(new Account(1000));
}
private void runPatternMatching(Object o) {
switch (o) {
case User u -> System.out.println(STR."Welcome \{u.name}");
case Account a -> System.out.println(STR."You current amount is \{a.amount}");
default -> throw new IllegalStateException(STR."Unexpected value: \{o}");
}
}
private void runPatternMatchingUnboxing(Object o) {
switch (o) {
case User(String name, _) -> System.out.println(STR."Welcome \{name}");
case Account(int amount) -> System.out.println(STR."You current amount is \{amount}");
default -> throw new IllegalStateException(STR."Unexpected value: \{o}");
}
}
/**
* Since Java 21 Virtual Threads are quite mature enough to be used in our code base in case
* we're not using any external lib that internally already use them.
* API has no change since incubator state, and is as simple as invoke API
* [Thread.ofVirtual()] to inform JVM we dont want to use an OS thread, but virtual.
* Once Virtual Thread is created, you can decide to append the Runnable logic as eager [start]
* or lazy [unstarted]
*/
@Test
public void virtualThreads() throws InterruptedException {
var vt1 = createVirtualThread();
var vt2 = createVirtualThread();
var vt3 = createVirtualThread();
System.out.println(vt1.isVirtual());
vt1.join();
vt2.join();
vt3.join();
}
private static Thread createVirtualThread() {
return Thread.ofVirtual().name("MyVirtualThread").start(() -> {
System.out.println(Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
/**
* In case you want to postpone the execution of one Virtual thread as lazy, you can
* append the runnable using [unstarted], and once you want to be evaluated you can
* use [start]
*/
@Test
public void lazyVirtualThreads() throws InterruptedException {
var lazyVirtualThread = Thread.ofVirtual().name("MyVirtualThread").unstarted(() -> {
System.out.println(Thread.currentThread());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread.sleep(500);
System.out.println("Nothing happen now");
lazyVirtualThread.start();
lazyVirtualThread.join();
}
/**
* Make composition of multiple Virtual Threads like FlatMap it would be as simple
* as define and join each Virtual thread inside each other.
*/
@Test
public void composingVirtualThreads() throws InterruptedException {
composeVirtualThread();
}
private static void composeVirtualThread() throws InterruptedException {
Thread.ofVirtual().name("MyVirtualThread1").start(() -> {
System.out.println(STR."Running async logic in \{Thread.currentThread()}");
try {
Thread.sleep(1000);
Thread.ofVirtual().name("MyVirtualThread2").start(() -> {
System.out.println(STR."Running async logic in \{Thread.currentThread()}");
try {
Thread.sleep(1000);
Thread.ofVirtual().name("MyVirtualThread3").start(() -> {
System.out.println(STR."Running async logic in \{Thread.currentThread()}");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).join();
}
/**
* Using new Task Executor [newVirtualThreadPerTaskExecutor] in [CompletableFuture] We can use the DSL and run async
* computation using Virtual Threads.
*/
@Test
public void completableFutureWithVirtualThreads() {
var executor = Executors.newVirtualThreadPerTaskExecutor();
var vtFuture = CompletableFuture.runAsync(() -> {
System.out.println(STR."Doing some async task in \{Thread.currentThread()} through CompletableFuture");
}, executor);
vtFuture.join();
}
/**
* Finally in Java 22 we have similar feature we have in Scala for more than a decade,
* which is the possibility to _ a variable that for some reason it's not needed to be used at that time.
*/
@Test
public void unnamedVariables() {
record A() {
}
var _ = new A();
for (int _ : List.of(1, 2, 3)) {
System.out.println("Just an iteration, variable not used");
}
}
/**
* String templates has been improving during the last versions, now introduce a new feature to template
* variables inside a String, directly using the variable name inside the String.
* We have to use [Interpolate] class STR as util class to being able to specif the variable using
* [\{your_variable_name}]
*/
@Test
public void stringTemplate() {
var quote = "Life is too short to last long.";
System.out.println(STR."I love this Blink 182 quote. \{quote}");
}
/**
* Java 22 Introduce [Gather] operator to allow multiple Stream process. One of those options is to
* use [fold] to accumulate elements from a collection, and return that value.
*/
@Test
public void streamGathersFold() {
Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.fold(() -> 0, (item, accumulator) -> {
accumulator += item;
return accumulator;
})).forEach(System.out::println);
}
/**
* Another great feature of version 22 is [StructuredTaskScope] which allow async computation in parallel for multiple tasks
* we need to create a [StructuredTaskScope] with a Strategy in case of any task fail, and then use the [scope] variable
* created, to run new Virtual Thread executions for each of them using [fork] operator.
* Once all task are defined as [Subtask] we can use [join] operator to wait for all of them to finish.
* And then use [get] to get the output for each of them.
*/
@Test
public void structureConcurrency() throws InterruptedException, ExecutionException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
StructuredTaskScope.Subtask<String> hello = scope.fork(() -> {
Thread.sleep(new Random().nextInt(1000));
System.out.println(STR."Running on thread \{Thread.currentThread()}");
return "hello";
});
StructuredTaskScope.Subtask<String> world = scope.fork(() -> {
Thread.sleep(new Random().nextInt(1000));
System.out.println(STR."Running on thread \{Thread.currentThread()}");
return "world";
});
scope.join().throwIfFailed();
System.out.println(STR."\{hello.get()} \{world.get()}");
}
}
/**
* We can also handle the side-effect of [StructuredTaskScope] if we use operator [exception] once we join the tasks.
* It will return an [Optional] type of Throwable.
*/
@Test
public void structureConcurrencyCaptureSideEffect() throws InterruptedException {
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
StructuredTaskScope.Subtask<String> hello = scope.fork(() -> {
System.out.println(STR."Running on thread \{Thread.currentThread()}");
if (new Random().nextBoolean()) {
throw new IllegalStateException("This task smell fishy");
}
return "hello";
});
StructuredTaskScope.Subtask<String> world = scope.fork(() -> {
System.out.println(STR."Running on thread \{Thread.currentThread()}");
if (new Random().nextBoolean()) {
throw new IllegalStateException("This task smell fishy");
}
return "world";
});
var maybeSideEffect = scope.join().exception();
if (maybeSideEffect.isPresent()) {
System.out.println(STR."Task did not finish because side effect \{maybeSideEffect.get()}");
} else {
System.out.println(STR."\{hello.get()} \{world.get()}");
}
}
}
record UserInfo(String value) {
}
record AddressInfo(String value) {
}
record DBConnection(String session) {
}
private final static ScopedValue<UserInfo> USER_INFO_DEPENDENCY = ScopedValue.newInstance();
private final static ScopedValue<AddressInfo> ADDRESS_INFO_DEPENDENCY = ScopedValue.newInstance();
private final static ScopedValue<DBConnection> DATABASE_DEPENDENCY = ScopedValue.newInstance();
/**
* Java 22 introduce a new way of working simulating [Monad Reader], to be used to pass dependencies across your program.
* This immutable type, allows you to define any type, and inside the context of his execution, it can be passed implicitly.
* We have to define the dependency type [ScopedValue<YourDependencyType>] using builder [ScopedValue.newInstance()].
*/
@Test
public void scopedValueFeature() {
ScopedValue
.where(USER_INFO_DEPENDENCY, new UserInfo("Politrons"))
.where(ADDRESS_INFO_DEPENDENCY, new AddressInfo("Zenon street, N-1"))
.where(DATABASE_DEPENDENCY, new DBConnection("Connection open"))
.run(this::printUserInfo);
}
/**
* We can also extend the scope of the dependencies through child threads, as long as we run this computation using
* [StructuredTaskScope]
* Then once we're in that Virtual Thread we can keep using the scope dependencies.
*/
@Test
public void scopedValueChildThreadsFeature() {
ScopedValue
.where(USER_INFO_DEPENDENCY, new UserInfo("Politrons"))
.where(ADDRESS_INFO_DEPENDENCY, new AddressInfo("Zenon street, N-1"))
.where(DATABASE_DEPENDENCY, new DBConnection("Connection open"))
.run(()->{
printAddressInfo();
try (var scope = new StructuredTaskScope<String>()) {
scope.fork(this::printDatabaseInfo);
scope.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
}
public void printUserInfo() {
UserInfo userInfo = USER_INFO_DEPENDENCY.get();
System.out.println(userInfo.value);
printAddressInfo();
printDatabaseInfo();
}
public void printAddressInfo() {
AddressInfo addressInfo = ADDRESS_INFO_DEPENDENCY.get();
System.out.println(addressInfo.value.toUpperCase());
}
public String printDatabaseInfo() {
DBConnection dbConnection = DATABASE_DEPENDENCY.get();
String session = dbConnection.session;
System.out.println(STR."\{session} in \{Thread.currentThread()}");
return session;
}
}