In this blog post, I look at the more minor changes in Java 9 to 21.
A smaller change is, for example, a new method added to an existing class or smaller language changes.
I don't cover in this article major changes like Local-variable type inference (var
) or the new HTTP client, or the new switch expression.
I also focus on useful features for my daily programming life as an application developer. I'm personally less interested in changes in low-level APIs like Reflection and low-level IO. I also omit in this blog post changes to the Java virtual machine (JVM), like new Garbage Collectors (GC).
I'm starting with Java 9, the release that started the new six-month release cycle.
I'm sure I missed a few of these smaller features. Send me a message if there is an interesting new feature that you use a lot. I will add it to this list.
Java 9 (September 2017) ¶
The big thing in this release is the introduction of the module system (Project Jigsaw, JSR 376). This
release also added JShell (JEP 222) and introduced the new java.util.concurrent.Flow
class
to support the Reactive Streams publish-subscribe pattern (JEP 266).
Private methods in interfaces ¶
It is now possible to add private methods to interfaces. Like any other private method, they can't be abstract and overridden and can only be called from the same source file. The use case for private methods in interfaces is to share code between default methods, a feature introduced in Java 8.
public interface Writable {
String content();
default void writeToFile(File file) {
write(file.toPath());
}
default void writeToPath(Path path) {
write(path);
}
private void write(Path path) {
try {
Files.writeString(path, content());
}
catch (IOException e) {
e.printStackTrace();
}
}
}
public class WritableImpl implements Writable {
@Override
public String content() {
return "Hello World";
}
@Override
public void writeToPath(Path path) {
// can't access write(Path)
System.out.println(content());
}
}
Diamond Operator refinement ¶
Java 7 introduced the diamond operator (<>
) to reduce verbosity.
// Java 6
List<Integer> result1 = new ArrayList<Integer>();
// Java 7
List<Integer> result2 = new ArrayList<>();
This feature did not work with anonymous inner classes.
With Java 9, this is now possible.
List<Integer> numbers = new ArrayList<>() {
public boolean add(Integer e) {
System.out.println("calling add");
return super.add(e);
}
};
numbers.add(1);
Compiling the code above with Java 7 or 8 results in this compiler error message.
error: cannot infer type arguments for ArrayList<E>
List<Integer> numbers = new ArrayList<>() {
^
reason: cannot use '<>' with anonymous inner classes
With Java 9+, this use case of the diamond operator is now supported.
try-with-resources statement refinement ¶
Until Java 9, the try-with-resources statement requires new variables to be declared for being managed by the statement.
In the following example, the method work()
receives references to two AutoCloseable
implementations, but the method has to
create new variables inside try(...)
so that the try-with-resources statement manages them.
class DbResource implements AutoCloseable {
@Override
public void close() {
System.out.println("closing resource");
}
}
private void doSomeWork() {
work(getConnection(), getConnection());
}
private void work(DbResource dbResource1, DbResource dbResource2) {
try (DbResource db1 = dbResource1;
DbResource db2 = dbResource2) {
System.out.println("do some work");
}
// do some work
// closing resource
// closing resource
}
private DbResource getConnection() {
return new DbResource();
}
Java 9 introduced a refinement to the try-with-resources statement. The statement can now also manage resources referenced by final or effectively final variables without declaring new variables.
With this change the following syntax is valid and the try-with-resources statement auto closes the managed resources.
private void work(DbResource dbResource1, DbResource dbResource2) {
try (dbResource1; dbResource2) {
System.out.println("do some work");
}
}
You don't need to add the modifier final
to a variable. The Java compiler checks if a
variable is changed after initialization. If not, the variable is effectively final and can be used in a
try-with-resources statement.
On the other hand, when a new value is assigned to a variable, it is no longer effectively final
and can't be used in a try-with-resources statement.
The following code throws a compiler error: "Local variable dbResource1 defined in an enclosing scope must be final or effectively final".
try (dbResource1; dbResource2) {
dbResource1 = getConnection();
}
Collection Factories ¶
Java 9 introduced static of
methods to the List
, Set
and Map
interface. These methods return unmodifiable collections with the given arguments as elements. An unmodifiable collection cannot store null values, and calling mutator methods on such a collection throws UnsupportedOperationException
.
List.of
and Set.of
are available as overloaded methods to take zero to nine arguments. Another overloaded method takes variable arguments (vararg), so these two methods work with any number of arguments.
Set<Integer> numbers = Set.of(1, 2, 3);
// [1, 2, 3]
List<String> cities = List.of("London", "Paris");
// [London, Paris]
numbers.add(4);
// java.lang.UnsupportedOperationException
List<Boolean> flags = List.of(false, true, null);
// java.lang.NullPointerException
It's important to note when given duplicate values to Set.of
it throws IllegalArgumentException
.
Set<Long> numbers = Set.of(1L, 2L, 3L, 3L);
// java.lang.IllegalArgumentException: duplicate element: 3
Maps store key/value pairs, and so the Map.of()
expects keys and values in alternating order.
Map<String, String> countryCapital = Map.of("FR", "Paris", "GB", "London", "ES", "Madrid");
String capitalOfSpain = countryCapital.get("ES");
// "Madrid"
Keys and values have to be non-null. Passing null
throws NullPointerException
.
Passing a duplicate key throws IllegalArgumentException
.
Map<Integer, String> users = Map.of(1, null);
// java.lang.NullPointerException
Map<String, String> countryCapital = Map.of("FR", "Paris", "FR", "Paris",
"GB", "London", "ES", "Madrid");
// java.lang.IllegalArgumentException: duplicate key: FR
The Map.of
method is available as an overloaded method to take zero to nine key/value pairs. If an application needs to create a
map with more than nine key/value pairs it has to use the Map.ofEntries(Map.Entry...)
method that expects a variable number of Map.Entry
instances as arguments.
Map<String, String> capitals =
Map.ofEntries(
Map.entry("FR", "Paris"), Map.entry("GB", "London"),
Map.entry("ES", "Madrid"), Map.entry("IT", "Rome"),
Map.entry("JP", "Tokyo"), Map.entry("IE", "Dublin"),
Map.entry("CH", "Bern"), Map.entry("DE", "Berlin"),
Map.entry("AT", "Vienna"), Map.entry("PT", "Lisbon"),
Map.entry("LI", "Vaduz"), Map.entry("AD", "Andorra la Vella"));
String capitalOfAustria = capitals.get("AT");
// "Vienna"
of
creates unmodifiable collections, which means that elements can't be added, removed, or updated.
But if elements are mutable and an element is modified, that change is reflected in the unmodifiable collections.
class User {
private String name;
User(String name) {
this.name = name;
}
@Override
public String toString() { return this.name; }
}
User userA = new User("Adam");
User userB = new User("Sarah");
List<User> users = List.of(userA, userB);
// [Adam, Sarah]
// changing name of userA
userA.name = "John";
String firstUserName = users.get(0).name;
// John
// users contains: [John, Sarah]
java.lang.Process ¶
Java 9 added new methods to the classes java.lang.ProcessBuilder
, java.lang.Process
and added a new interface java.lang.ProcessHandle
.
java.lang.ProcessHandle
provides three static factory methods to get handles for all processes (allProcesses()
),
to get the current process handle (current()
), and to get a handle for a specific process
(of(long)
).
ProcessHandle.allProcesses().forEach(ph -> {
System.out.printf("PID: %d Info: %s\n", ph.pid(), ph.info());
});
long myPid = ProcessHandle.current().pid();
ProcessHandle.of(myPid).ifPresent(ph -> System.out.println(ph.info()));
With the java.lang.ProcessHandle
an application can access the process id (pid()
), can check for liveness (isAlive()
), and can stop the process with destroy()
and destroyForcibly()
.
parent()
returns the handle of the parent process of this process, children()
and descendants()
return the process handles of all direct children resp. all descendants (children of children) of this process.
A CompletableFuture
available from onExit()
can be used to wait for process termination.
info()
returns an instance of ProcessHandle.Info
which contains the following information:
command()
: Executable pathname of the processcommandLine()
: Command line of the processarguments()
: Arguments of the processstartInstant()
: Start time of the process.totalCpuDuration()
: Total CPU time accumulated of the processuser()
: User of the process.
The java.lang.Process
class also got most of these methods.
An application can call toHandle()
to get the java.lang.ProcessHandle
of the process.
The difference between java.lang.ProcessHandle
and java.lang.Process
is that the latter represents processes started by the current process and
additionally provide access to the process input, output, and error stream.
The java.lang.ProcessBuilder
got a new method startPipeline
to create a pipeline of new processes that send the output of each process directly to the following process.
The following example creates a sample text file and then invokes the three commands.
cat sample | grep -v a | sort - r
These commands list the file, remove every word containing `a', and reverse sort the file.
Path sampleFile = Paths.get("./sample.txt");
Files.writeString(sampleFile, "Bat\nGoat\nApple\nDog\nFirst\nEat\nHide");
File workingDir = Paths.get(".").toFile();
ProcessBuilder cat = new ProcessBuilder()
.command("cat", "sample.txt")
.directory(workingDir);
ProcessBuilder grep = new ProcessBuilder()
.command("grep", "-v", "a")
.directory(workingDir);
ProcessBuilder sort = new ProcessBuilder()
.command("sort", "-r")
.directory(workingDir);
List<Process> commands = ProcessBuilder.startPipeline(List.of(cat, grep, sort));
Process last = commands.get(2);
last.onExit().thenAccept(process -> {
BufferedReader lineReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
lineReader.lines().forEach(System.out::println);
}).join();
/*
Hide
First
Dog
Apple
*/
java.util.Optional ¶
Java 9 added three new methods to the java.util.Optional
class.
ifPresentOrElse(Consumer,Runnable)
performs the Consumer passed as first argument if the Optional has a value, otherwise performs the Runnable passed as second argument.
Similar to the ifPresent(Consumer)
method but with one additional
parameter for the no value case.
Optional<Boolean> flag = Optional.of(true);
flag.ifPresentOrElse(b -> System.out.printf("value is %b", b),
() -> System.out.println("Optional has no value"));
// value is true
or(Supplier)
returns the Optional if a value is present, otherwise returns an Optional produced by the Supplier.
Optional<String> city1 = Optional.of("London");
Optional<String> city2 = Optional.of("Paris");
Optional<String> nextTrip = city1.or(() -> city2);
// Optional[London]
Optional<String> city3 = Optional.ofNullable(null);
nextTrip = city3.or(() -> city2);
// Optional[Paris]
stream()
returns a
stream with one value when the Optional has a value, otherwise returns an empty stream.
class User {
private final String name;
User(String name) {
this.name = name;
}
}
Optional<User> result = Optional.of(new User("Adam"));
String name = result.stream().map(u -> u.name).collect(Collectors.joining());
// "Adam"
Optional<User> resultN = Optional.ofNullable(null);
String nameN = resultN.stream().map(u -> u.name).collect(Collectors.joining());
// ""
java.util.stream.Stream ¶
The java.util.stream.Stream
interface got four new methods
ofNullable(T)
is a static method that
returns a sequential stream with one element if the argument is non-null, otherwise returns an empty stream.
String input = "John";
String output = Stream.ofNullable(input).map(String::toUpperCase).findFirst().orElse("");
// "John"
input = null;
output = Stream.ofNullable(input).map(String::toUpperCase).findFirst().orElse("");
// ""
The Stream
interface has a iterate(T, UnaryOperator)
method that creates an infinite streams. The first argument is the initial element, and the second argument is a function applied to the previous element to return a new element.
Java 9 added a new overloaded iterate(T,Predicate,UnaryOperator)
method that takes three arguments, the initial element (1st argument), the function that produces the new element (3rd argument) and a Predicate that determines when the stream must terminate (2nd argument).
//Infinite stream
Stream.iterate(1, i -> i += 1).limit(3).forEach(System.out::println);
// 1 2 3
//Java 9 iterate
Stream.iterate(1, i -> i < 4, i -> i += 1).forEach(System.out::println);
// 1 2 3
dropWhile(Predicate)
drops all elements from the stream until the given Predicate returns false
for the first time.
Including this element, dropWhile
lets all other elements pass.
takeWhile(Predicate)
returns elements from the beginning of the stream until the Predicate returns false
for the first time. At that point, takeWhile
terminates the stream.
Stream.of(1, 2, 20, 3, 4, 15).dropWhile(n -> n < 10).forEach(System.out::println);
// 20 3 4 15
Stream.of(1, 2, 20, 3, 4, 15).takeWhile(n -> n < 10).forEach(System.out::println);
// 1 2
In the first example, we see that dropWhile
removes 1
and 2
from the stream because they meet the criteria of the Predicate.
The number 20
is the first element of the stream where the Predicate returns false
, so dropWhile
returns all elements
starting with 20
. Also, the elements are no longer tested against the Predicate from this point.
The second example with takeWhile
only returns 1
and 2
because they meet the criteria of the Predicate. 20
is the first element where the Predicate returns false
. takeWhile
terminates the stream at this point and does not include
20
to the resulting stream.
Another example with dropWhile
and takeWhile
.
Our task is to write code that reads a file and
extracts the text between :body:
and :footer:
.
:header:
some header data
:body:
the body
more content
:footer:
footer information
Path input = Paths.get("./test.txt");
List<String> body = Files.lines(input)
.dropWhile(line -> !line.strip().equals(":body:"))
.skip(1)
.takeWhile(line -> !line.strip().equals(":footer:"))
.map(String::strip)
.collect(Collectors.toUnmodifiableList());
// ["the body", "more content"]
java.util.Arrays ¶
Java 9 introduced equals
, compare
and mismatch
to the java.util.Arrays
class.
These methods are available for each primitive data type long
, int
, short
, char
, byte
, boolean
, double
, float
, Object
. And there
is also a generic variant for custom classes. Each method is also overloaded to take from and to indexes.
equals ¶
equals
compares two arrays and returns true if both arrays contain the same elements in the same order.
int[] array1 = {1,2,3};
int[] array2 = {3,2,1};
int[] array3 = {1,2,3,4};
boolean isEqual = Arrays.equals(array1, array2);
// false
isEqual = Arrays.equals(array1, new int[]{1,2,3});
// true
isEqual = Arrays.equals(array1, array3);
// false
isEqual = Arrays.equals(array1, null);
// false
From and to indexes can be passed to the methods. Note that from index is inclusive and to index is exclusive.
int[] array1 = {1,2,3};
int[] array3 = {1,2,3,4};
boolean isEqual = Arrays.equals(array1, 0, 3, array3, 0, 3);
// true
When comparing custom objects, a Comparator needs to be passed as the third argument.
class User {
private final String name;
User(String name) {
this.name = name;
}
String getName() { return this.name; }
}
User[] users1 = new User[]{new User("John"), new User("Sarah")};
User[] users2 = new User[]{new User("John"), new User("Sarah")};
boolean isEqual = Arrays.equals(users1, users2, Comparator.comparing(User::getName));
// true
compare ¶
compare
returns 0 if the two arrays contain the same elements in the same order.
returns a value less than 0 if the first array is lexicographically less than the second array, and
a value greater than 0 if the first array is lexicographically greater than the second array.
List<byte[]> numbers = new ArrayList<>();
numbers.add(new byte[]{9});
numbers.add(new byte[]{101});
numbers.add(new byte[]{1,2,3,4});
numbers.add(null);
numbers.add(new byte[]{7,7,7});
numbers.add(new byte[]{8,9});
numbers.sort((a, b) -> Arrays.compare(a, b));
/*
null
[1, 2, 3, 4]
[7, 7, 7]
[8, 9]
[9]
[101]
*/
For byte
, short
, int
and long
there is a compareUnsigned
method available.
Compares the numerical elements as unsigned. See Integer.compareUnsigned(int,int)
for an example how this works internally.
Custom objects need to implement the
Comparable
interface.
Comparator<String> nullSafeStringComparator = Comparator
.nullsFirst(String::compareToIgnoreCase);
class User implements Comparable<User> {
private final String name;
User(String name) {
this.name = name;
}
String getName() {
return this.name;
}
@Override
public String toString() {
return this.name;
}
@Override
public int compareTo(User o) {
return nullSafeStringComparator.compare(this.name, o.name);
}
}
List<User[]> users = new ArrayList<>();
users.add(new User[] { new User("Walter"), new User("Sarah") });
users.add(new User[] { new User("Adam"), null, new User("Jane") });
users.add(new User[] { new User("Michael") });
users.add(null);
users.sort((a, b) -> Arrays.compare(a, b));
/*
null
[Adam, null, Jane]
[Michael]
[Walter, Sarah]
*/
mismatch ¶
mismatch
finds and returns the index of the first mismatch between two arrays. If there is no mismatch between the two arrays, the method returns -1.
long[] numbers1 = new long[] { 1, 2, 3, 4 };
long[] numbers2 = new long[] { 1, 2, 5 };
int mismatchIndex = Arrays.mismatch(numbers1, numbers2);
// 2
mismatchIndex = Arrays.mismatch(new short[] { 1, 2 }, new short[] { 1, 2 });
// -1
// from and to index
mismatchIndex = Arrays.mismatch(
new float[] { 1.1f, 2.2f, 3.3f }, 1, 3,
new float[] { 5.5f, 2.2f, 3.3f }, 1, 3);
// -1
A Comparator needs to be passed as the third argument when custom objects are used.
class User {
private final String name;
User(String name) {
this.name = name;
}
String getName() { return this.name; }
}
User[] users1 = new User[]{new User("John"), new User("Sarah")};
User[] users2 = new User[]{new User("John"), new User("Anna")};
int mismatchIndex = Arrays.mismatch(users1, users2, Comparator.comparing(User::getName));
// 1
java.util.Objects ¶
java.util.Objects
got a bunch of new methods
requireNonNullElse(T, T)
returns the first argument if it is non-null, otherwise returns the second argument.
Note when the first and second arguments are null, the method throws NullPointerException
.
String userName = "Adam";
String output = Objects.requireNonNullElse(userName, "");
// "Adam"
userName = null;
output = Objects.requireNonNullElse(userName, "");
// ""
userName = "Sara";
output = Objects.requireNonNullElse(userName, null);
// "Sara"
userName = null;
output = Objects.requireNonNullElse(userName, null);
// java.lang.NullPointerException: defaultObj
requireNonNullElseGet(T, Supplier)
returns the first argument if it is non-null, like the method before.
If the first argument is null, the method calls the Supplier function passed as the second argument and returns the value that this function produces. The method throws NullPointerException
when the first argument is null, and either the Supplier is null, or the produced value of the Supplier is null.
String userName = "Adam";
String output = Objects.requireNonNullElseGet(userName, () -> "<default>");
// "Adam"
userName = null;
output = Objects.requireNonNullElseGet(userName, () -> "<default>");
// <default>
output = Objects.requireNonNullElseGet(userName, () -> null);
// java.lang.NullPointerException: supplier.get()
output = Objects.requireNonNullElseGet(userName, null);
// java.lang.NullPointerException: supplier
checkIndex(int, int)
checks if the first argument is within 0 (inclusive) and the second argument (exclusive).
The method returns the first argument if it is within the bounds, otherwise throws IndexOutOfBoundsException
.
int index = 13;
int upperBound = 100;
index = Objects.checkIndex(index, upperBound);
// 13
index = Objects.checkIndex(-1, upperBound);
// java.lang.IndexOutOfBoundsException: Index -1 out of bounds for length 100
index = Objects.checkIndex(100, upperBound);
// java.lang.IndexOutOfBoundsException: Index 100 out of bounds for length 100
index = Objects.checkIndex(99, upperBound);
// 99
checkFromIndexSize(int, int, int)
checks if the range from first argument (inclusive) to first argument + second argument (exclusive)
is within 0 (inclusive) and the third argument (exclusive). The method returns the first argument if the range is within the bounds,
otherwise throws IndexOutOfBoundsException
.
int upperBound = 100;
int index = Objects.checkFromIndexSize(13, 20, upperBound);
// 13
index = Objects.checkFromIndexSize(-1, 20, upperBound);
// java.lang.IndexOutOfBoundsException: Range [-1, -1 + 20) out of bounds for length 100
index = Objects.checkFromIndexSize(100, 1, upperBound);
// java.lang.IndexOutOfBoundsException: Range [100, 100 + 1) out of bounds for length 100
index = Objects.checkFromIndexSize(90, 10, upperBound);
// 90
checkFromToIndex(int, int, int)
checks if the range from first argument (inclusive) to second argument (exclusive) is within 0 (inclusive) and the third argument (exclusive). The method returns the first argument if the range is within the bounds. Otherwise, it throws IndexOutOfBoundsException
.
int upperBound = 100;
int index = Objects.checkFromToIndex(13, 20, upperBound);
// 13
index = Objects.checkFromToIndex(-1, 20, upperBound);
// java.lang.IndexOutOfBoundsException: Range [-1, 20) out of bounds for length 100
index = Objects.checkFromToIndex(100, 101, upperBound);
// java.lang.IndexOutOfBoundsException: Range [100, 101) out of bounds for length 100
index = Objects.checkFromToIndex(90, 100, upperBound);
// 90
Java 10 (March 2018) ¶
The significant visible change for developers is the introduction of the reserved type name var
for local-variable type inference (JEP 286)
Unmodifiable copies of collections ¶
Java 10 added the static copyOf()
method to List
, Set
and Map
. These methods take a collection as input and return an unmodifiable snapshot of that collection. An unmodifiable collection cannot store null values and calling mutator methods on such a collection always throws UnsupportedOperationException
.
List<String> source = new ArrayList<>();
source.add("one");
source.add("two");
List<String> copy = List.copyOf(source);
// [one, two]
copy.add("three");
// java.lang.UnsupportedOperationException
Note that changing the source collection does not affect the copy.
Map<Integer, String> source = new HashMap<>();
source.put(1, "one");
source.put(2, "two");
Map<Integer, String> copy = Map.copyOf(source);
source.put(3, "three");
source.remove(1);
// source: {2=two, 3=three}
// copy: {2=two, 1=one}
If elements are mutable, and an element is modified, that change appears in the original collection and the copy.
class Counter {
private int count;
private Counter(int startCount) {
this.count = startCount;
}
private void inc() { this.count++; }
@Override
public String toString() {
return String.valueOf(this.count);
}
}
List<Counter> source = new ArrayList<>();
Counter counter = new Counter(0);
source.add(counter);
List<Counter> copy = List.copyOf(source);
// [0]
counter.inc();
// source.get(0).count == 1
// copy.get(0).count == 1
Null elements are not allowed. copyOf()
throws NullPointerException
when a program tries to copy a collection with null elements.
Set<String> source = new HashSet<>();
source.add("one");
source.add(null);
Set<String> copy = Set.copyOf(source);
// java.lang.NullPointerException
List.copyOf(Collection)
and Set.copyOf(Collection)
can create copies of any Collection
implementation. Only Map.copyOf(Map)
expects a Map instance as argument.
Set<Integer> numbers = new HashSet<>();
numbers.add(1);
numbers.add(2);
List<Integer> copy = List.copyOf(numbers);
// [1, 2]
Deque<Long> deque = new ArrayDeque();
deque.add(100L);
deque.add(200L);
Set<Long> copyOfDeque = Set.copyOf(deque);
// [100, 200]
Unmodifiable collection Collectors ¶
Java 10 added four new Collectors to java.util.stream.Collectors
.
toUnmodifiableList()
toUnmodifiableSet()
toUnmodifiableMap(Function,Function)
toUnmodifiableMap(Function,Function,BinaryOperator)
These four new Collectors have in common that they return an unmodifiable collection.
As mentioned before, these kinds of collections disallow null elements, and calling mutator methods on such a collection always throws UnsupportedOperationException
.
List<Integer> result = Stream.of(1, 2, 3, 4, 5).filter(n -> n % 2 == 0)
.collect(Collectors.toUnmodifiableList());
// [2, 4]
result.add(6); // throws java.lang.UnsupportedOperationException
Set<Integer> numbers = Stream.of(1,2,2,3,3,4,4,4).filter(n -> n % 2 == 0)
.collect(Collectors.toUnmodifiableSet());
// [4, 2]
Ensure that custom objects stored in a Set
implement equals(Object)
and hashCode()
properly.
toUnmodifiableMap(Function,Function)
expects two functions as arguments. The first argument is the key mapper, a function producing keys for the resulting map, and the second argument is the value mapper, a function producing the values. Both functions must return non-null values.
This example creates a map with the length of the string as key and the string itself as the value.
Map<Integer,String> result = Stream.of("Paris", "London", "Rome")
.collect(Collectors.toUnmodifiableMap(String::length, Function.identity()));
// {5=Paris, 6=London, 4=Rom}
The difference of this Collector to toUnmodifiableMap(Function,Function,BinaryOperator)
is how duplicate keys are handled.
If toUnmodifiableMap(Function,Function)
produces duplicate keys an IllegalStateException
is thrown.
Map<Integer,String> result = Stream.of("Paris", "London", "Rome", "Madrid")
.collect(Collectors.toUnmodifiableMap(String::length, Function.identity()));
// java.lang.IllegalStateException:
// Duplicate key 6 (attempted merging values London and Madrid)
If a duplicate key occurs with the toUnmodifiableMap(Function,Function,BinaryOperator)
variant, the function passed as the third argument is called. This function is the merge function and receives the existing and new values as parameters. The merge function has to resolve this collision by returning a new value.
The following example creates a multimap where the Map's values are a collection. The value mapper (2nd argument) wraps the stream items in a HashSet. The merge function (3rd argument) is called when a duplicate key occurs and adds the new value to the existing value. Both values passed to the merge function are an instance of HashSet.
Map<Integer, Set<String>> result = Stream.of("Paris", "London", "Rome", "Madrid")
.collect(Collectors.toUnmodifiableMap(
String::length,
item -> new HashSet<>(Set.of(item)),
(existing, newValue) -> {
existing.addAll(newValue);
return existing;
}));
System.out.println(result);
// {4=[Rom], 5=[Paris], 6=[Madrid, London]}
It's important to note that mutable objects stored in these collections can still be changed and the unmodifiable collection reflect the change.
class Counter {
private int count;
private Counter(int startCount) {
this.count = startCount;
}
private void inc() { this.count++; }
@Override
public String toString() {
return String.valueOf(this.count);
}
}
Counter zeroCounter = new Counter(0);
List<Counter> result = Stream.of(zeroCounter).collect(Collectors.toUnmodifiableList());
// [0]
zeroCounter.inc();
int c = result.get(0).count; // 1
java.io.Reader ¶
The class java.io.Reader
got a new transferTo(Writer)
method. The method reads all characters from this reader and writes the characters to the given writer. This is the Reader equivalent of the InputStream.transferTo(OutputStream)
method introduced in Java 9.
Here an example that copies all the characters from the file test1.txt
to the file test2.txt
FileReader fr = new FileReader("test1.txt");
FileWriter fw = new FileWriter("test2.txt");
try (fr; fw) {
fr.transferTo(fw);
}
It is worth noting that this particular use case is easier to implement with the Files.copy(Path, Path)
method.
java.nio.file.Files.copy(Paths.get("test1.txt"), Paths.get("test2.txt"));
java.util.Optional ¶
Java 10 added one new method to java.util.Optional
.
orElseThrow()
returns the wrapped value if it's not null,
otherwise throws NoSuchElementException
exception.
Optional<String> result = Optional.of("HelloWorld");
String value = result.orElseThrow(); // HelloWorld
result = Optional.ofNullable(null);
value = result.orElseThrow();
// java.util.NoSuchElementException: No value present
Java 11 (September 2018) ¶
The major new addition to the standard Java library is the HTTP client (JEP 321). I also wrote a blog post about this topic.
java.util.Collection ¶
The java.util.Collection
interface got the new default method toArray(IntFunction)
.
List<String> cities = List.of("Rome", "Paris", "London");
String[] citiesArray1 = cities.toArray(new String[cities.size()]);
Object[] citiesArray2 = cities.toArray();
//new in Java 11
String[] citiesArray3 = cities.toArray(String[]::new);
The new toArray(IntFunction)
method returns an array containing all of the elements in this collection, using the provided generator function to allocate the returned array. A IntFunction
is a function that accepts an int
value as an argument and returns a result.
The default implementation of toArray(IntFunction)
passes 0 to the IntFunction
.
According to the JavaDoc, this method acts as a bridge between array-based and collection-based APIs. It allows the creation of an array of a particular runtime type.
Use toArray()
to create an array whose runtime type is Object[]
, or use toArray(T[])
to reuse an existing array.
java.util.function.Predicate ¶
Java 11 introduced the new static method not(Predicate)
to the java.util.function.Predicate
interface.
not(Predicate)
takes a Predicate
and returns the negation of it.
java.util.function.Predicate<Integer> isEven = n -> n % 2 == 0;
java.util.function.Predicate<Integer> isOdd = Predicate.not(isEven);
List<Integer> evenNumbers = Stream.of(1,2,3,4,5,6).filter(isEven).collect(Collectors.toUnmodifiableList());
// [2, 4, 6]
List<Integer> oddNumbers = Stream.of(1,2,3,4,5,6).filter(isOdd).collect(Collectors.toUnmodifiableList());
// [1, 3, 5]
java.util.Optional ¶
The java.util.Optional
class got one new method.
isEmpty()
returns true when the value of the Optional is null, otherwise false
Optional<Integer> result = java.util.Optional.of(10);
boolean present = result.isEmpty(); // false
The new method is the opposite of isPresent()
which returns true when the value is NOT null.
java.lang.String ¶
Six new methods where added to the java.lang.String
class: isBlank()
, lines()
, repeat(int)
, stripLeading()
, stripTrailing()
, strip()
isBlank()
tests if the string is empty or contains only white space codepoints.
boolean b = "Hello World".isBlank(); // false
b = " \n \t ".isBlank(); // true
lines()
returns a stream of lines extracted from this string.
A line is either a sequence of zero or more characters followed by "\n"
, "\r"
or "\r\n"
, or it is a sequence of one or more characters followed by the end of the string.
The lines that lines()
extracts do not include "\n"
, "\r"
and "\r\n"
.
List<String> tokens = "Line 1\nLine 2\nLine 3".lines().collect(Collectors.toUnmodifiableList());
[Line 1, Line 2, Line 3]
repeat(int)
repeats this string n times and concatenates the repetitions together.
String www = "World".repeat(3); // WorldWorldWorld
stripLeading()
removes all leading white space code points, stripTrailing()
all trailing code points, and strip()
all leading and trailing white space code points.
String st = "\t\tHelloWorld\n\n".stripLeading(); // "HelloWorld\n\n"
st = "\t\tHelloWorld\n\n".stripTrailing(); // "\t\tHelloWorld"
st = "\t\tHelloWorld\n\n".strip(); // "HelloWorld"
Java already has a method that removes white space from both ends: trim()
.
The difference is that strip()
uses the method Character.isWhitespace(int)
to determine what white space is. trim()
on the other hand defines all codepoints less than or equal to U+0020
as a whitespace character. It is recommended to use strip()
because it uses the Unicode standard.
java.nio.file.Files ¶
Java 11 introduced four new methods to the java.nio.file.Files
class. writeString(Path,CharSequence,Charset,OpenOption...)
and readString(Path, Charset)
both methods with two overloaded variants writeString(Path,CharSequence,OpenOption...)
and readString(Path)
. These methods do what their names suggest, write a string to a file and read a string from a file.
Path file1 = Paths.get("./test1.txt");
Files.writeString(file1, "Hello World");
Path file2 = Paths.get("./test2.txt");
Files.writeString(file2, "Hello World", StandardCharsets.UTF_8);
String input1 = Files.readString(file1); // Hello World
String input2 = Files.readString(file2, StandardCharsets.UTF_8); // Hello World
The method that does not take a java.nio.charset.Charset
argument uses the UTF-8 charset. Hence the writeString()
and readString()
calls in the example above are equivalent.
Local-Variable Syntax for Lambda Parameters ¶
A small change to the language is that the reserved type name var
introduced in Java 10 can now be used when declaring
the formal parameters of implicitly typed lambda expressions.
The first two forms are valid syntax, and the third syntax was not allowed in Java 10, but now it is since Java 11.
Predicate<Integer> predicate1 = n -> n % 2 == 0;
Predicate<Integer> predicate2 = (Integer n) -> n % 2 == 0;
Predicate<Integer> predicate3 = (var n) -> n % 2 == 0;
Adding annotations to parameters is a reason for specifying the type or var
.
Predicate<Integer> predicate3 = (@Nonnull var n) -> n % 2 == 0;
Java 12 (March 2019) ¶
A significant change in this release is the addition of JMH (Java Microbenchmark Harness JEP 230). JMH is a library that existed before, but starting with Java 12, it is now part of the standard library.
CompactNumberFormat ¶
This is an extension to the existing number formatting API (java.text.NumberFormat
). This addition
provides the functionality to format numbers into more compact and, for humans, easier to grasp strings.
To obtain a compact number formatter call the method java.text.NumberFormat.getCompactNumberInstance(Locale, NumberFormatStyle)
and pass the locale and the style. Alternatively call NumberFormat.getCompactNumberInstance()
which returns a compact number format for the default locale and with the SHORT
format style.
The following code converts the number 1_234 into "1K" and "1 thousand".
java.text.NumberFormat shortFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
java.text.NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
String shortOutput = shortFormat.format(1_234); // 1K
String longOutput = longFormat.format(1_234); // 1 thousand
By default, the rounding mode half even is used but can be changed with
the setRoundingMode(RoundingMode)
method.
NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
longFormat.setRoundingMode(RoundingMode.UP);
String longOutput = longFormat.format(1_234); // 2 thousand
The default formatting behavior returns a formatted string with no fractional digits, however calling the
setMinimumFractionDigits(int)
method includes the fractional part. Note that the result is rounded according to the configured rounding mode.
NumberFormat shortFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
shortFormat.setMinimumFractionDigits(2);
longFormat.setMinimumFractionDigits(1);
String shortOutput = shortFormat.format(1_256); // 1.26K
String longOutput = longFormat.format(1_256); // 1.3 thousand
Parsing strings that contain numbers in a compact format is also supported.
NumberFormat longFormat = NumberFormat.getCompactNumberInstance(Locale.GERMAN, NumberFormat.Style.LONG);
String input1 = "1,11 Tausend";
String input2 = "1,22 Million";
String input3 = "1,33 Milliarde";
Number n1 = longFormat.parse(input1); // 1110
Number n2 = longFormat.parse(input2); // 1220000
Number n3 = longFormat.parse(input3); // 1330000000
teeing Stream Collector ¶
A stream can only be terminated with one collector. This is an issue if multiple streams' results need to be acquired. The new
Collectors.teeing(Collector, Collector, BiFunction)
collector can apply two collectors on the items of a stream.
The teeing
collector expects three arguments. Two Collectors and a BiFunction. The teeing
collector applies the two collectors
on the items. The result of each collector is then passed as arguments to the BiFunction, which produces the final result of the teeing
collector.
The following example loops over a list of strings, transforms them to upper case, and returns the number of elements and the list with the modified city names.
List<String> cities = List.of("Paris", "London", "Tokyo", "Madrid", "Rome");
List<Object> result = cities.stream().map(String::toUpperCase)
.collect(java.util.stream.Collectors.teeing(
Collectors.counting(),
Collectors.toUnmodifiableList(),
(count, list) -> List.of(count, list))
);
long count = (long) result.get(0); // 5
List<String> citiesUC = (List<String>) result.get(1); // [PARIS, LONDON, TOKYO, MADRID, ROME]
Multiple teeing
collectors can be combined to get more than just two results. The
following example returns the shortest name, longest name, the number of cities, and the list with the transformed cities themselves.
List<String> cities = List.of("Paris", "London", "Tokyo", "Madrid", "Rome");
var stringLengthComparator = Comparator.comparing(String::length);
var minMaxCollector = Collectors.teeing(Collectors.minBy(stringLengthComparator),
Collectors.maxBy(stringLengthComparator),
(min, max) -> List.of(min.get(), max.get()));
var countListCollector = Collectors.teeing(Collectors.counting(),
Collectors.toUnmodifiableList(), (count, list) -> List.of(count, list));
var result = cities.stream().map(String::toUpperCase).collect(
Collectors.teeing(minMaxCollector, countListCollector, (minMax, countList) -> List
.of(minMax.get(0), minMax.get(1), countList.get(0), countList.get(1))));
String min = (String) result.get(0); // ROME
String max = (String) result.get(1); // LONDON
long count = (long) result.get(2); // 5
List<String> list = (List<String>) result.get(3); // [PARIS, LONDON, TOKYO, MADRID, ROME]
java.nio.file.Files.mismatch(Path, Path) ¶
The new java.nio.file.Files.mismatch(Path, Path)
method finds and returns the position of the first mismatched byte in the content of two file, or -1L
if there is no difference.
// file1.txt:Hello World
// file2.txt:Hello world
java.nio.file.Path file1 = java.nio.file.Paths.get("file1.txt");
java.nio.file.Path file2 = java.nio.file.Paths.get("file2.txt");
long diffPos = java.nio.file.Files.mismatch(file1, file2); // 6
java.lang.String ¶
The new String.indent(int)
method adds spaces to the beginning of a string.
If there are multiple lines in the string, terminated with either "\n"
, "\r"
or "\r\n"
,
the methods adds spaces to each line.
String basic = "10 PRINT \"Hello, World!\"\n" +
"20 END";
String indented = basic.indent(3);
/*
10 PRINT "Hello, World!"
20 END
*/
It's important to note that the indent(int)
method also normalizes the line termination characters.
It always ends a line with "\n"
regardless of what line termination characters the input uses.
Even passing 0 to the method (indent(0)
), which does not indent the string, the method
still changes the line termination characters.
It is possible to pass a negative value to the method. In that case, indent(int)
removes the given number
of white space characters from the beginning of each line. If there are not sufficient white space characters
in one line, then all leading white space characters are removed.
String basic = " \t\t 10 PRINT \"Hello, World!\"\n" +
"\t20 END";
String indented = basic.indent(-3);
/*
10 PRINT "Hello, World!"
20 END
*/
With the new String.transform(Function)
method, a function can be applied to the string. The function should expect the string as an argument and return a result. The result can be another string or something different.
String hw = "Hello World";
int len2 = hw.transform(str -> str.length() * 2); // 22
String cities = "Tokyo;Madrid;London;Paris";
List<String> citiesList = cities.transform(str -> List.of(str.split(";")).stream()
.sorted().collect(Collectors.toUnmodifiableList()));
// [London, Madrid, Paris, Tokyo]
String text = "this is line 1\nthe second line";
String transformed = text.transform(str ->
str.lines()
.map(line -> line.substring(0, 1).toUpperCase() + line.substring(1))
.collect(Collectors.joining("\n")));
//This is line 1\nThe second line
Java 13 (September 2019) ¶
This release did not introduce major changes visible to the developer. A notable significant change under the surface is
the re-implementation of java.net.Socket
and java.net.ServerSocket
(JEP 353)
I didn't find a small change that qualifies for this article.
Java 14 (March 2020) ¶
With Java 14, we get a new language feature: Switch Expressions (JEP 361)
Exact Arithmetic ¶
When we do arithmetic in Java, we need to be aware that datatypes like int
and long
silently overflow.
int i = 2_147_483_647; // max value of Integer
i = i + 10; // i == -2_147_483_639
This can lead to subtle bug. Java 8 introduced methods like java.lang.StrictMath.addExact
,
which throw java.lang.ArithmeticException
if an overflow occurs.
int i = StrictMath.addExact(2_147_483_647, 10);
// java.lang.ArithmeticException: integer overflow
In Java 14, we get six more of these *Exact methods. Three for Integers and three for Longs.
int i = 2_147_483_647;
i = StrictMath.decrementExact(i); // i--
i = StrictMath.incrementExact(i); // i++
i = StrictMath.negateExact(i); // i = -i
See JavaDoc of StrictMath
for more information
Plural Support in CompactNumberFormat ¶
java.text.CompactNumberFormat
introduced in Java 12 is now capable of dealing with plural forms.
Here is an example in French that has plural forms for "number" words.
// Java 13
NumberFormat cnf = NumberFormat.getCompactNumberInstance(Locale.FRENCH, Style.LONG);
String output = cnf.format(1_000_000); // 1 million
output = cnf.format(2_000_000); // 2 million
// Java 14
NumberFormat cnf = NumberFormat.getCompactNumberInstance(Locale.FRENCH, Style.LONG);
String output = cnf.format(1_000_000); // 1 million
output = cnf.format(2_000_000); // 2 millions <=======
Java 15 (September 2020) ¶
Java 15 introduces Text Blocks (JEP 378).
Helpful NullPointerExceptions (JEP 358) ¶
Not a new language feature or API change, nonetheless a useful change for developers. When a NullPointException
occurs, the error message is more detailed and shows which part of the code caused the exception. Note this feature is for security, performance, and compatibility reasons disabled by default.
Let's assume we have this Java application.
class Address {
private final String city;
private Address(String city) {
this.city = city;
}
}
class Person {
private final Address address;
private Person(Address address) {
this.address = address;
}
}
Address a = new Address(null);
Person p = new Person(a);
String cityUpperCase = p.address.city.toUpperCase();
When we run the application with a JDK prior to Java 15, the JVM prints out this error message.
Exception in thread "main" java.lang.NullPointerException
at JavaTest.main(JavaTest.java:19)
Just from the error message alone, we don't know what caused the exception. In this application it could be because p
or p.address
or p.address.city
is null.
The same code running on Java 15 gives us a more detailed message, and we immediately see what caused the error.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "<local2>.address.city" is null
at JavaTest.main(JavaTest.java:19)
Java 16 (March 2021) ¶
Java 16 adds Record (JEP 395) classes and pattern matching for instanceof (JEP 394 to the language.
Stream.toList convenient method ¶
When you work with streams in Java, you often use a collector to collect all the elements from the stream into a container.
The most commonly used collector is .collect(Collectors.toList())
List<String> fruits = List.of("apple", "pear", "pineapple");
List<String> upperCaseFruits = fruits.stream().map(String::toUpperCase)
.collect(Collectors.toList());
Because this collector is so prevalent the JDK developers added a new convenient method to Stream interface: toList() With this addition, we can simplify the code above.
List<String> upperCaseFruitsTl = fruits.stream().map(String::toUpperCase)
.toList();
Be aware that .toList()
is not an exact equivalent of .collect(Collectors.toList()))
. The latter returns a modifiable
list whereas .toList()
returns an unmodifiable list. It behaves similar to .collect(Collectors.toUnmodifiableList())
List<String> fruits = List.of("apple", "pear", "pineapple");
List<String> upperCaseFruits = fruits.stream().map(String::toUpperCase)
.collect(Collectors.toList());
upperCaseFruits.add("ORANGE"); // ok
List<String> upperCaseFruitsTl = fruits.stream().map(String::toUpperCase)
.toList();
upperCaseFruitsTl.add("ORANGE"); // throws java.lang.UnsupportedOperationException
Stream.mapMulti ¶
mapMulti
is a new method of the Stream
interface. This method is an intermediate operation and replaces each stream element with zero or more elements.
The method is similar to flatMap
in that
it applies a one-to-many transformation and flattens the result. According to the JavaDoc, mapMulti
is preferable to flatMap
when an application replaces each element with a small (possibly zero)
number of elements. mapMulti
avoids the overhead of creating a new Stream instance as flatMap
requires. mapMulti
also uses an imperative approach which might be easier to implement for
certain use cases than returning the result as a Stream.
List<String> input = List.of("AA", "AB", "AC", "AD");
List<Character> output = input.stream()
.flatMap(elem -> Stream.of(elem.charAt(0), elem.charAt(1))).toList();
System.out.println(output);
// [A, A, A, B, A, C, A, D]
List<Character> outputMapMulti = input.stream()
.<Character>mapMulti((elem, consumer) -> {
consumer.accept(elem.charAt(0));
consumer.accept(elem.charAt(1));
}).toList();
System.out.println(outputMapMulti);
// [A, A, A, B, A, C, A, D]
DateTimeFormatter pattern B ¶
In the date-time formatter, we can use the new pattern B
to get the period of the day in human-readable form.
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("B", Locale.ENGLISH);
System.out.println(dtf.format(LocalTime.of(8, 0))); // in the morning
System.out.println(dtf.format(LocalTime.of(13, 0))); // in the afternoon
System.out.println(dtf.format(LocalTime.of(20, 0))); // in the evening
System.out.println(dtf.format(LocalTime.of(23, 0))); // at night
System.out.println(dtf.format(LocalTime.of(0, 0))); // midnight
dtf = DateTimeFormatter.ofPattern("B", Locale.FRENCH);
System.out.println(dtf.format(LocalTime.of(8, 0))); // du matin
System.out.println(dtf.format(LocalTime.of(13, 0))); // de l’après-midi
System.out.println(dtf.format(LocalTime.of(20, 0))); // du soir
System.out.println(dtf.format(LocalTime.of(23, 0))); // du soir
System.out.println(dtf.format(LocalTime.of(0, 0))); // minuit
The implementation of the B
pattern follows the Unicode standard.
Java 17 (September 2021) ¶
This release adds one new language feature to Java: Sealed Classes (JEP 409)
HexFormat ¶
The new java.util.HexFormat class handles conversions from byte array to hexadecimal strings and vice versa.
To use the new class, you need to create an instance with either of()
or ofDelimiter(String)
. The returned instance is thread-safe
HexFormat hexFormat = HexFormat.of();
byte[] input = new byte[] { 0, 1, 2, 3, 11, 12 };
String inputHex = hexFormat.formatHex(input); // 000102030b0c
byte[] output = hexFormat.parseHex(inputHex);
// [0, 1, 2, 3, 11, 12]
HexFormat also provides a method to convert an byte
, short
, int
, long
and char
into a hexadecimal string
String hex = HexFormat.of().toHexDigits(44)); // 0000002c
By default, HexFormat returns the hexadecimal string with lowercase characters. You can change this with withUpperCase()
. Additionally, Hexformat allows adding prefix, suffix, and delimiter to control the output of the hexadecimal string even further.
byte[] input = new byte[] { 0, 1, 2, 3, 11, 12 };
String hex1 = HexFormat.of().withUpperCase().formatHex(input));
// 000102030B0C
String hex2 = HexFormat.ofDelimiter(":").formatHex(input));
// 00:01:02:03:0b:0c
String hex3 = HexFormat.of().withPrefix("<").withSuffix(">").withDelimiter("-").formatHex(input);
// <00>-<01>-<02>-<03>-<0b>-<0c>
Enhanced Pseudo-Random Number Generators (JEP 356) ¶
JEP 356 provides new interfaces and implementations for pseudo-random number generators (PRNGs).
java.util.random.RandomGenerator is a new interface for objects that return random values.
The existing classes java.util.Random
, [java.util.SplittableRandom](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/SplittableRandom.html)
, and [java.security.SecureRandom](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/security/SecureRandom.html)
, now implement the RandomGenerator
interface, but apart from
that they behave the same as before.
java.util.random.RandomGeneratorFactory
is the entry point to the implementations of the RandomGenerator
interface.
all()
returns all implementations, of(String)
selects a specific implementation.
RandomGeneratorFactory.all().map(r -> r.name()).forEach(System.out::println);
RandomGenerator rnd = RandomGeneratorFactory.of("L64X128StarStarRandom").create();
int r = rnd.nextInt(1000);
System.out.println(r);
Check out this article for more information about the new pseudo-random number generators:
https://mbien.dev/blog/entry/enhanced-pseudo-random-number-generators
Java 18 (March 2022) ¶
This release changes the default charset encoding of the platform to UTF-8 (JEP 400). It also brings a simple webserver to the core library (JEP 408). It is also now easier to add code snippets to the JavaDoc (JEP 413).
In Java 18 java.lang.reflect.Method, Constructor, and Field were reimplemented on top of java.lang.invoke method handles (JEP 416) and lastly, hostname and address resolution got a service-provider interface (SPI) so that java.net.InetAddress can use resolvers other than the platform's built-in resolver.
New Math and StrictMath methods ¶
This release adds a bunch of new methods to the Math and StrichtMath class.
java.lang.Math.ceilDiv(int, int)
java.lang.Math.ceilDiv(long, int)
java.lang.Math.ceilDiv(long, long)
java.lang.Math.ceilDivExact(int, int)
java.lang.Math.ceilDivExact(long, long)
java.lang.Math.ceilMod(int, int)
java.lang.Math.ceilMod(long, int)
java.lang.Math.ceilMod(long, long)
java.lang.Math.divideExact(int, int)
java.lang.Math.divideExact(long, long)
java.lang.Math.floorDivExact(int, int)
java.lang.Math.floorDivExact(long, long)
java.lang.Math.unsignedMultiplyHigh(long, long)
Java 19 (September 2022) ¶
Apart from a lot of Incubator and Preview features, this release introduces a port for Linux/RISC-V (JEP 422).
Factory methods for creating Map and Set ¶
Java 19 introduces new factory methods for HashMap
, HashSet
, LinkedHashMap
, LinkedHashSet
, and WeakHashMap
.
java.util.HashMap.newHashMap(int numMappings)
java.util.HashSet.newHashSet(int numElements)
java.util.LinkedHashMap.newLinkedHashMap(int numMappings)
java.util.LinkedHashSet.newLinkedHashSet(int numElements)
java.util.WeakHashMap.newWeakHashMap(int numMappings)
These methods create a new empty Map or Set for the expected number of mappings or elements. The initial capacity is generally large enough so that the expected number of mappings or elements can be added without resizing the map or set.
parallelMultiply ¶
The BigInteger
gets a new parallelMultiply(BigInteger val)
to multiply numbers.
Compared to multiply(BigInteger val)
, parallelMultiply
uses a parallel multiplication algorithm that typically uses more CPU resources and memory to compute the result faster. Note that an application might only benefit of the parallel algorithm when both numbers are large, typically in the thousands of bits.
This method returns the same mathematical result as multiply(BigInteger val)
.
Java 20 (March 2023) ¶
Like Java 19, this release brings a lot of Incubator and Preview features. Java 20 now supports Unicode 15.
Regarding minor changes, there are new methods for named groups in regular expressions.
Java 21 (September 2023) ¶
The significant addition in this release is the support for Virtual Threads (JEP 444). Java 21 adds two new enhancements to the Java language. Deconstruction of record values with Record patterns (JEP 440) and Pattern Matching for switch (JEP 441) introduces pattern matching in switch expressions and statements.
The Z Garbage Collector (ZGC) got an update with (JEP 439).
The Windows 32-bit x86 port of Java is now deprecated and might be removed in a future release (JEP 449). Java 21 now warns when agents are loaded dynamically into a running JVM as a preparation for a future release that disallows dynamic loading of agents by default (JEP 451).
JEP 452 Introduces the Key Encapsulation Mechanism API, an encryption technique for securing symmetric keys using public key cryptography.
Sequenced Collections JEP 431 ¶
Java 21 introduces three new interfaces to the collections framework.
Collections that implement these interfaces store their elements in a defined encounter order. Each such collection has a well-defined first, second, and so forth element.
The Javadoc for each new interface shows which existing collections implement the new interfaces.
The most obvious example of a sequenced collection is ArrayList
, which stores the elements in an array. But also, LinkedHashMap
and LinkedHashSet
store their elements in a defined encounter order.
The SequencedCollection<E>
interface defines the following methods:
void addFirst(E e)
void addLast(E e)
E getFirst()
E getLast()
E removeFirst()
E removeLast()
SequencedCollection<E> reversed()
reversed()
returns a reverse-ordered view of the collection.
SequencedSet<E>
is a sub-interface of SequencedCollection<E>
and adds the definition of the following method: SequencedSet<E> reversed()
Note that TreeSet
implements the SequencedSet<E>
interface, but the addFirst
and addLast
methods throw UnsupportedOperationException
. The comparison method determines the encounter order of elements; therefore, explicit positioning is not supported.
Sequenced maps do not implement SequencedCollection<E>
instead they implement SequencedMap<K,V>
, which
defines the following methods:
Map.Entry<K,V> firstEntry()
Map.Entry<K,V> lastEntry()
Map.Entry<K,V> pollFirstEntry()
Map.Entry<K,V> pollLastEntry()
V putFirst(K k, V v)
V putLast(K k, V v)
SequencedMap<K,V> reversed()
SequencedSet<Map.Entry<K,V>> sequencedEntrySet()
SequencedSet<K> sequencedKeySet()
SequencedCollection<V> sequencedValues()
TreeMap
implements SequencedCollection<E>
, but the putFirst
and putLast
methods throw UnsupportedOperationException
. The comparison method determines the encounter order of mappings; therefore, explicit positioning is not supported.
List<String> abc = new ArrayList<>();
abc.add("b");
abc.add("c");
abc.addFirst("a");
abc.addLast("d");
System.out.println(abc.getFirst()); // "a"
System.out.println(abc.getLast()); // "d"
abc.removeFirst();
abc.removeLast();
System.out.println(abc.getFirst()); // "b"
System.out.println(abc.getLast()); // "c"
for (String s : abc.reversed()) {
System.out.println(s);
}
// c
// d
var map = new LinkedHashMap<>();
map.put(1, "one");
map.put(2, "two");
map.putFirst(0, "zero");
map.putLast(3, "three");
System.out.println(map.firstEntry()); // 0=zero
System.out.println(map.lastEntry()); // 3=three
for (var entry : map.reversed().entrySet()) {
System.out.println(entry);
}
// 3=three
// 2=two
// 1=one
// 0=zero
var first = map.pollFirstEntry();
System.out.println(first); // 0=zero
var last = map.pollLastEntry();
System.out.println(last); // 3=three
System.out.println(map); // {1=one, 2=two}
clamp ¶
The Math and StrictMath classes got new clamp()
methods to conveniently clamp the numeric value between the specified minimum and maximum values. Each class got four overloads of these methods to support int, long, float, and double.
int i = 12;
double d = -12.0;
float f = 23.5f;
long l = 1234567890L;
System.out.println(Math.clamp(i, 0, 10)); // 10
System.out.println(Math.clamp(d, 0, 10)); // 0.0
System.out.println(Math.clamp(f, 0, 10)); // 10.0
System.out.println(Math.clamp(l, 0, 10)); // 10
indexOf ¶
The String class gets two new methods: indexOf(int ch, int beginIndex, int endIndex)
and indexOf(String str, int beginIndex, int endIndex)
. The existing indexOf methods search in the whole string or from a given start index. The two new methods limit the search range further by specifying an end index.
Besides complete control over the search range, they are safer to use than indexOf(int ch, int fromIndex)
and indexOf(String str, int fromIndex)
, because they throw an exception on illegal search ranges.
String test = "Hello World";
int pos = test.indexOf("lo", 2, 5);
System.out.println(pos);
// 3
pos = test.indexOf('l', 6, 10);
System.out.println(pos);
// 9
// existing indexOf does not check range
pos = test.indexOf('l', 12);
System.out.println(pos);
// -1
// new indexOf throws exception
pos = test.indexOf('l', 6, 12);
// Exception in thread "main" java.lang.StringIndexOutOfBoundsException: Range [6, 12) out of bounds for length 11
repeat ¶
repeat(int codePoint, int count)
and repeat(CharSequence cs, int count)
have been added to java.lang.StringBuilder and java.lang.StringBuffer to simplify the appending of multiple copies of characters or strings.
StringBuilder sb1 = new StringBuilder();
sb1.repeat("Hello ", 5);
System.out.println(sb1);
// Hello Hello Hello Hello Hello
StringBuilder sb2 = new StringBuilder();
sb2.repeat('-', 5);
sb2.append("HELLO");
sb2.repeat('-', 5);
System.out.println(sb2);
// -----HELLO-----
splitWithDelimiters ¶
New splitWithDelimiters()
methods added to String and java.util.regex.Pattern.
Unlike the split()
method, splitWithDelimiters()
returns an alternation of strings and matching delimiters rather than just the strings.
String input = "1,us,10.0";
String[] parts = input.split(",");
System.out.println(parts.length); // 3
for (String part : parts) {
System.out.println(part);
}
// 1
// us
// 10.0
String[] partsWithDelimiter = input.splitWithDelimiters(",", 0);
System.out.println(partsWithDelimiter.length); // 5
for (String part : partsWithDelimiter) {
System.out.println(part);
}
// 1
// ,
// us
// ,
// 10.0
Pattern p = Pattern.compile(",");
String[] items = p.splitWithDelimiters(input, 0);
System.out.println(items.length); // 5
Emoji support ¶
The following six new methods are added to java.lang.Character for obtaining Emoji character properties, which are defined in the Unicode Emoji Technical Standard (UTS #51):
isEmoji(int codePoint)
isEmojiPresentation(int codePoint)
isEmojiModifier(int codePoint)
isEmojiModifierBase(int codePoint)
isEmojiComponent(int codePoint)
isExtendedPictographic(int codePoint)
The names of these methods can also be used in a regular expression pattern with the `p{IsXXX} construct.
Pattern isEmoji = Pattern.compile("\\p{IsEmoji}");
var matcher = isEmoji.matcher("\uD83D\uDE0D");
boolean matches = matcher.matches(); // true
HttpClient AutoCloseable ¶
The java.net.http.HttpClient is now auto-closable, and the the following methods have been added.
-
void close()
: closes the client gracefully, waiting for submitted requests to complete. -
void shutdown()
: initiates a graceful shutdown, then returns immediately without waiting for the client to terminate. -
void shutdownNow()
: initiates an immediate shutdown, trying to interrupt active operations, and returns immediately without waiting for the client to terminate. -
boolean awaitTermination(Duration duration)
: waits for the client to terminate, within the given duration; returns true if the client is terminated, false otherwise. -
boolean isTerminated()
: returns true if the client is terminated.
JDK Downloads ¶
You've reached the end of this article.
Here is a list of URLs to download JDK builds based on OpenJDK.