Exploring the Exciting New Features in Java 21: Examples and Insights
In this article provides an overview of the new features introduced in JDK 21, as specified by JSR 396 in the Java Community Process. From language improvements to performance optimizations, these features aim to enhance productivity, flexibility, and efficiency in Java development. Let’s dive into the details and explore the exciting advancements in JDK 21.
All Features Listed in this Update: Features
Let’s explore a different example to showcase the power of string templates in performing advanced formatting. Consider a scenario where you want to display a product’s information, including its name, price, and availability status. Traditionally, you might concatenate multiple strings using the + operator:
// Prior to Java 21
String productName = "Widget";
double productPrice = 29.99;
boolean productAvailable = true;
String productInfo = "Product: " + productName + "\nPrice: $" + productPrice + "\nAvailability: " + (productAvailable ? "In Stock" : "Out of Stock");
System.out.println(productInfo);
With string templates, you can simplify the formatting process and make the code more readable:
// As of Java 21
String productName = "Widget";
double productPrice = 29.99;
boolean productAvailable = true;
String productInfo = `Product: ${productName}
Price: $${productPrice}
Availability: ${productAvailable ? "In Stock" : "Out of Stock"}`;
System.out.println(productInfo);
In this example, we use string templates to embed the variables productName, productPrice, and productAvailable directly within the string literal. The expressions are enclosed within ${} and can include additional formatting, such as adding a dollar sign before the productPrice. The resulting string is more concise, easier to read, and eliminates the need for explicit concatenation and formatting operations.
2. Sequenced Collections In JDK 21, the introduction of Sequenced Collections brings new interfaces and methods to simplify and streamline collection processing. This enhancement aims to address common scenarios where accessing the first and last elements of various collection types in Java required non-uniform and sometimes cumbersome approaches. This article explores the Sequenced Collections functionality and its benefits through examples of different collection processing scenarios.
Sequenced Collections Interfaces Sequenced Collections introduces three new interfaces: SequencedSet, SequencedCollection, and SequencedMap. These interfaces come with additional methods that provide improved access and manipulation capabilities for collections.
Accessing the First and Last Element
Prior to JDK 21, retrieving the first and last elements of collections in Java involved different methods and approaches depending on the collection type. Let’s examine some examples of accessing the first and last elements using the pre-JDK 21 JDK API calls:
For List — First Element — list.get(0) Last Element — list.get(list.size()-1)
For Deque — First Element — deque.get first() Last Element — deque.getLast()
For Set — First Element — set.iterator().next() or set.stream ().findFirst().get() Last Element — requires iterating through the set
For SortedSet — First Element — set.first() Last Element — set.last()
With the introduction of JDK 21 and the Sequenced Collections feature, accessing the first and last elements becomes more consistent and straightforward. The new methods simplify the process across different collection types:
For List, Deque, Set First Element — collection.getFirst() Last Element — collection.getLast()
3. Record Patterns Enhance the Java programming language with record patterns to deconstruct record values. Record patterns and type patterns can be nested to enable a powerful, declarative, and composable form of data navigation and processing.
Pattern matching and records
// As of Java 16
record Point(int x, int y) {}
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
The pattern variable p is used here solely to invoke the accessor methods x() and y(), which return the values of the components x and y. (In every record class there is a one-to-one correspondence between its accessor methods and its components.) It would be better if the pattern could not only test whether a value is an instance of Point but also extract the x and y components from the value directly, invoking the accessor methods on our behalf. In other words:
// As of Java 21
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
4. Pattern Matching for switch Enhance the Java programming language with pattern matching for switch expressions and statements. Extending pattern matching to switch allows an expression to be tested against a number of patterns, each with a specific action, so that complex data-oriented queries can be expressed concisely and safely.
// Prior to Java 21
static String formatter(Object obj) {
String formatted = "unknown";
if (obj instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (obj instanceof Long l) {
formatted = String.format("long %d", l);
} else if (obj instanceof Double d) {
formatted = String.format("double %f", d);
} else if (obj instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
But switch is a perfect match for pattern matching! If we extend switch statements and expressions to work on any type and allow case labels with patterns rather than just constants, then we can rewrite the above code more clearly and reliably:
// As of Java 21
static String formatterPatternSwitch(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> obj.toString();
};
}
2. Sequenced Collections
Introduce new interfaces to represent collections with a defined encounter order. Each such collection has a well-defined first element, second element, and so forth, up to the last element. It also provides uniform APIs for accessing its first and last elements, and for processing its elements in reverse order.
Description
We define new interfaces for sequenced collections, sequenced sets, and sequenced maps, and then retrofit them into the existing collections type hierarchy. All of the new methods declared in these interfaces have default implementations.
Sequenced collections
A sequenced collection is a Collection whose elements have a defined encounter order. (The word "sequenced" as used here is the past participle of the verb to sequence, meaning "to arrange elements in a particular order.") A sequenced collection has first and last elements, and the elements between them have successors and predecessors. A sequenced collection supports common operations at either end, and it supports processing the elements from first to last and from last to first (i.e., forward and reverse).
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
The new reversed() method provides a reverse-ordered view of the original collection. Any modifications to the original collection are visible in the view. If permitted, modifications to the view write through to the original collection.
The reverse-ordered view enables all the different sequenced types to process elements in both directions, using all the usual iteration mechanisms: Enhanced for loops, explicit iterator() loops, forEach(), stream(), parallelStream(), and toArray().
领英推荐
For example, obtaining a reverse-ordered stream from a LinkedHashSet was previously quite difficult; now it is simply
linkedHashSet.reversed().stream()
(The reversed() method is essentially a renamed NavigableSet::descendingSet, promoted to SequencedCollection.)
The following methods of SequencedCollection are promoted from Deque. They support adding, getting, and removing elements at both ends:
The add*(E) and remove*() methods are optional, primarily to support the case of unmodifiable collections. The get*() and remove*() methods throw NoSuchElementException if the collection is empty.
There are no definitions of equals() and hashCode() in SequencedCollection because its sub-interfaces have conflicting definitions.
Sequenced sets
A sequenced set is a Set that is a SequencedCollection that contains no duplicate elements.
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed(); // covariant override
}
Collections such as SortedSet, which position elements by relative comparison, cannot support explicit-positioning operations such as the addFirst(E) and addLast(E) methods declared in the SequencedCollection superinterface. Thus, these methods can throw UnsupportedOperationException.
The addFirst(E) and addLast(E) methods of SequencedSet have special-case semantics for collections such as LinkedHashSet: If the element is already present in the set then it is moved to the appropriate position. This remedies a long-standing deficiency in LinkedHashSet, namely the inability to reposition elements.
Sequenced maps
A sequenced map is a Map whose entries have a defined encounter order.
interface SequencedMap<K,V> extends Map<K,V> {
// new methods
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// methods promoted from NavigableMap
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
The new put*(K, V) methods have special-case semantics, similar to the corresponding add*(E) methods of SequencedSet: For maps such as LinkedHashMap, they have the additional effect of repositioning the entry if it is already present in the map. For maps such as SortedMap, these methods throw UnsupportedOperationException.
The following methods of SequencedMap are promoted from NavigableMap. They support getting and removing entries at both ends:
Retrofitting
The three new interfaces defined above fit neatly into the existing collections type hierarchy (click to enlarge):
In detail, we make the following adjustments to retrofit existing classes and interfaces:
We define covariant overrides for the reversed() method in the appropriate places. For example, List::reversed is overridden to return a value of type List rather than a value of type SequencedCollection.
We also add new methods to the Collections utility class to create unmodifiable wrappers for the three new types:
Alternatives
Types
An alternative to adding new types would be to repurpose the List interface as a general sequenced collection type. Indeed List is sequenced, but it also supports element access by integer index. Many sequenced data structures do not naturally support indexing and would thus be required to support it iteratively. This would result in indexed access having O(n) performance instead of the expected O(1), perpetuating the mistake of LinkedList.
Deque seems promising as a general sequence type, since it already supports the right set of operations. However, it is cluttered with other operations, including a family of null-returning operations (offer, peek, and poll), stack operations (push and pop), and operations inherited from Queue. These operations are sensible for a queue but less so for other collections. If Deque were repurposed as a general sequence type then List would also be a Queue and would support stack operations, resulting in a cluttered and confusing API.
Naming
The term sequence, which we have chosen here, implies elements that are arranged in order. It is commonly used across various platforms to represent collections with semantics similar to those described above.
The term ordered is not quite specific enough. We require iteration in both directions, and operations at both ends. An ordered collection such as a Queue is a notable outlier: It is ordered, but it is also decidedly asymmetric.
The term reversible, used in an earlier version of this proposal, does not immediately evoke the concept of having two ends. Perhaps a bigger issue is that the Map variant would be named ReversibleMap, which misleadingly implies that it supports lookup by key and by value (sometimes called a BiMap or BidiMap).
Add, put, and UnsupportedOperationException
As described above, explicit-positioning APIs such as SortedSet::addFirst and SortedMap::putLast throw UnsupportedOperationException because the sequence of their elements is determined by relative comparison. The asymmetry of having some collections not implement all of the SequencedCollection operations may seem unpleasant. It is nonetheless valuable because it brings SortedSet and SortedMap into the sequenced collection family, allowing them to be used more broadly than otherwise. This asymmetry is, also, consistent with prior design decisions in the collections framework. For example, the Map::keySet method returns a Set, even though the implementation returned does not support addition.
Alternatively, the addition operations could be kept separate by rearranging the interfaces along structural lines. That would result in new interface types with very thin semantics (e.g., AddableCollection) that are not useful in practice and that clutter up the type hierarchy.)
Full Stack Developer | Java | Spring boot | Hibernate | Spring Security | React.JS | JavaScript | MySQL | Rest Api's | Git | GitHub | Maven | Tomcat | MongoDB
1 年Thanks for sharing