Java 10 API Additions: copyOf, toUnmodifiableList & More
Introduction
Java 10 is best known for the
(Note - The collections returned by these methods are the same kind of unmodifiable collections that Java 9 introduced. To learn about them, read Java 9 immutable collections using List.of, Set.of and Map.ofClick to read tutorial: Java 9 Immutable Collections using List.of, Set.of, Map.of.) List.copyOf(), Set.copyOf() and Map.copyOf() Definitions of the copyOf() methods
Java 9 added
OUTPUT of the above code
Explanation of the code
Collectors.toUnmodifiableList(), toUnmodifiableSet() and toUnmodifiableMap()
OUTPUT of the above code
Explanation of the code
OUTPUT of the above code
Note - Use
Other additions in Java 10
OUTPUT of the above code
var keyword, but it also added a few useful methods to the core libraries. This tutorial explains these Java 10 API additions with examples. We will start with List.copyOf(), Set.copyOf() and Map.copyOf(), which create unmodifiable copies of collections, and see how a copy differs from the unmodifiable view returned by Collections.unmodifiableList(). We will then collect streams into unmodifiable collections with Collectors.toUnmodifiableList(), toUnmodifiableSet() and toUnmodifiableMap(). Lastly, we will look at the new no-argument Optional.orElseThrow(), and the other smaller additions.(Note - The collections returned by these methods are the same kind of unmodifiable collections that Java 9 introduced. To learn about them, read Java 9 immutable collections using List.of, Set.of and Map.ofClick to read tutorial: Java 9 Immutable Collections using List.of, Set.of, Map.of.) List.copyOf(), Set.copyOf() and Map.copyOf() Definitions of the copyOf() methods
static <E> List<E> copyOf(Collection<? extends E> coll)static <E> Set<E> copyOf(Collection<? extends E> coll)static <K,V> Map<K,V> copyOf(Map<? extends K, ? extends V> map)List.of(), Set.of() and Map.of() to create unmodifiable collections from individual elements. Java 10 adds the copyOf() methods, which create an unmodifiable collection from an existing collection or map. This is the usual way to make a defensive copy - for example, when a class stores a list passed to its constructor and must not be affected if the caller changes that list later.
Unmodifiable copies with copyOf()
//CopyOfDemo.java
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class CopyOfDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Tom", "Harry"));
List<String> copy = List.copyOf(names); // a snapshot
List<String> view = Collections.unmodifiableList(names); // a read-only view
names.add("Sam");
System.out.println("Original: " + names);
System.out.println("copyOf: " + copy);
System.out.println("View: " + view);
try {
copy.add("Ann");
} catch (UnsupportedOperationException e) {
System.out.println("copy.add() -> UnsupportedOperationException");
}
// Set.copyOf removes duplicates, Map.copyOf copies the entries
Set<String> unique = Set.copyOf(List.of("Tom", "Tom", "Harry"));
System.out.println("Set.copyOf size: " + unique.size());
Map<String, Integer> marks = new HashMap<>(Map.of("Tom", 78));
Map<String, Integer> marksCopy = Map.copyOf(marks);
marks.put("Harry", 91);
System.out.println("Map.copyOf: " + marksCopy);
// Copying an unmodifiable collection returns the same object
List<String> fixed = List.of("a", "b");
System.out.println("Same instance: " + (List.copyOf(fixed) == fixed));
}
}
Original: [Tom, Harry, Sam]
copyOf: [Tom, Harry]
View: [Tom, Harry, Sam]
copy.add() -> UnsupportedOperationException
Set.copyOf size: 2
Map.copyOf: {Tom=78}
Same instance: true
List.copyOf(names)copies the elements at the time it is called. Adding"Sam"tonamesafterwards does not change the copy.Collections.unmodifiableList(names), by contrast, is only a read-only view ofnames. It cannot be changed through the view itself, but it shows every change made to the underlying list, which is why it includes"Sam". UsecopyOf()when you need a collection which can never change.- Like all unmodifiable collections, the copy throws an
UnsupportedOperationExceptionwhen you try to modify it. Set.copyOf()silently drops duplicate elements, so the set has 2 elements. This differs fromSet.of("Tom", "Tom"), which throws anIllegalArgumentExceptionfor duplicates.- If the source is already an unmodifiable collection created by
List.of()orcopyOf(),copyOf()returns it as it is, without making a copy. - The collection passed to
copyOf()must not containnull. Anullelement, key or value throws aNullPointerException.
Collectors.toList() makes no promise about the type or mutability of the list it returns - in practice it is a modifiable ArrayList. When the result of a stream must not be changed, Java 10's new collectors return unmodifiable collections directly, instead of wrapping the result in Collections.unmodifiableList() using collectingAndThen()Click to read: collectingAndThen().
Collecting streams into unmodifiable collections
//ToUnmodifiableDemo.java
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
public class ToUnmodifiableDemo {
public static void main(String[] args) {
List<String> names = List.of("Tom", "Harry", "Sam", "Tom");
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableList());
Set<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toUnmodifiableSet());
Map<String, Integer> nameLengths = names.stream()
.distinct()
.collect(Collectors.toUnmodifiableMap(n -> n, String::length));
System.out.println(upper);
System.out.println(lengths.size() + " distinct lengths");
System.out.println("Harry -> " + nameLengths.get("Harry"));
try {
upper.set(0, "TOMMY");
} catch (UnsupportedOperationException e) {
System.out.println("upper.set() -> UnsupportedOperationException");
}
// Duplicate keys need a merge function
try {
names.stream().collect(Collectors.toUnmodifiableMap(n -> n, String::length));
} catch (IllegalStateException e) {
System.out.println("Duplicate key -> " + e.getMessage());
}
Map<String, Integer> counts = names.stream()
.collect(Collectors.toUnmodifiableMap(n -> n, n -> 1, Integer::sum));
System.out.println("Tom appears " + counts.get("Tom") + " times");
}
}
[TOM, HARRY, SAM, TOM] 2 distinct lengths Harry -> 5 upper.set() -> UnsupportedOperationException Duplicate key -> Duplicate key Tom (attempted merging values 3 and 3) Tom appears 2 times
toUnmodifiableList()keeps the order and the duplicates of the stream, whiletoUnmodifiableSet()removes duplicates -"Tom","Sam"and"Harry"have lengths 3, 3 and 5, so there are 2 distinct lengths.toUnmodifiableMap(keyMapper, valueMapper)throws anIllegalStateExceptionwhen two elements map to the same key, as"Tom"does here. The three-argument version takes a merge function,Integer::sum, which combines the values of a duplicate key - here, to count the occurrences of each name.- As with
copyOf(), these collectors throw aNullPointerExceptionif the stream contains anullelement, key or value.
public T orElseThrow()orElseThrow() returns the value if one is present, and otherwise throws a NoSuchElementException. That is exactly what Optional'sClick to read: Optional's get() method does. It was added because the name get() hides the fact that it can throw, so it was often called without checking whether the Optional had a value. orElseThrow() says it in its name, and is now the preferred alternative to get(). The same method was added to OptionalInt, OptionalLong and OptionalDouble.
Optional.orElseThrow() instead of get()
//OrElseThrowDemo.java
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
public class OrElseThrowDemo {
public static void main(String[] args) {
List<String> names = List.of("Tom", "Harry", "Sam");
String firstLong = names.stream()
.filter(n -> n.length() > 3)
.findFirst()
.orElseThrow(); // same as get(), but the name says it may throw
System.out.println("First long name: " + firstLong);
int maxLength = names.stream().mapToInt(String::length).max().orElseThrow();
System.out.println("Longest name length: " + maxLength);
try {
Optional.empty().orElseThrow();
} catch (NoSuchElementException e) {
System.out.println("Empty Optional -> NoSuchElementException: " + e.getMessage());
}
}
}
First long name: Harry Longest name length: 5 Empty Optional -> NoSuchElementException: No value present
orElseThrow() when an empty Optional means a bug - when there must be a value. When the value can be legitimately missing, use orElse(), orElseGet(), or orElseThrow(Supplier) with a more meaningful exception.
Reader.transferTo() and Charset overloads
//OtherJava10Additions.java
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
public class OtherJava10Additions {
public static void main(String[] args) throws IOException {
// Reader.transferTo(Writer) copies all characters from a reader to a writer
StringWriter writer = new StringWriter();
long count = new StringReader("Java 10").transferTo(writer);
System.out.println("Transferred " + count + " characters: " + writer);
// PrintStream(OutputStream, boolean, Charset) and ByteArrayOutputStream.toString(Charset)
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes, true, StandardCharsets.UTF_8);
out.print("café");
String decoded = bytes.toString(StandardCharsets.UTF_8);
System.out.println(bytes.size() + " bytes decoded to " + decoded.length() + " characters");
}
}
Transferred 7 characters: Java 10 5 bytes decoded to 4 characters
Reader.transferTo(Writer)copies all characters from a reader to a writer and returns the number of characters copied.InputStream.transferTo(OutputStream), its byte equivalent, was added in Java 9.- Classes which used to take a charset only as a
Stringname -PrintStream,PrintWriter,Formatter,ScannerandByteArrayOutputStream.toString()- got overloads which take aCharsetobject. ACharsetcannot be misspelled, so these overloads do not throw the checkedUnsupportedEncodingException. "café"(café) is 4 characters, but 5 bytes in UTF-8, sinceétakes 2 bytes. Decoding the bytes with the same charset gives back the 4 characters.
List.copyOf(), Set.copyOf() and Map.copyOf(), and saw how a copy differs from an unmodifiable view. We then collected streams into unmodifiable collections with toUnmodifiableList(), toUnmodifiableSet() and toUnmodifiableMap(), including handling duplicate keys. Next we used Optional.orElseThrow() as a clearer replacement for get(). Lastly, we looked at Reader.transferTo() and the new Charset overloads. In the next tutorial, we will see how Application Class-Data SharingClick to read tutorial: Java 10 Application Class-Data Sharing (AppCDS) Tutorial reduces the startup time of Java applications.Continue Reading Java 9 & Java 10 Tutorials
Java 10 Application Class-Data Sharing (AppCDS) TutorialClick to read tutorial: Java 10 Application Class-Data Sharing (AppCDS) Tutorial Java var Keyword: Local Variable Type InferenceClick to read tutorial: Java var Keyword: Local Variable Type Inference Overview of New Features in Java 10Click to read tutorial: Overview of New Features in Java 10 Java 10 Time-Based Release Versioning and Runtime.VersionClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version Java 10 Removed javah: Generate JNI Headers with javac -hClick to read tutorial: Java 10 Removed javah: Generate JNI Headers with javac -h Java 10 Unicode Locale Extensions: Currency, Week, RegionClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region Java 9 Modules Tutorial | Java Platform Module System (JPMS)Click to read tutorial: Java 9 Modules Tutorial | Java Platform Module System (JPMS)
Java 10 Application Class-Data Sharing (AppCDS) TutorialClick to read tutorial: Java 10 Application Class-Data Sharing (AppCDS) Tutorial Java var Keyword: Local Variable Type InferenceClick to read tutorial: Java var Keyword: Local Variable Type Inference Overview of New Features in Java 10Click to read tutorial: Overview of New Features in Java 10 Java 10 Time-Based Release Versioning and Runtime.VersionClick to read tutorial: Java 10 Time-Based Release Versioning and Runtime.Version Java 10 Removed javah: Generate JNI Headers with javac -hClick to read tutorial: Java 10 Removed javah: Generate JNI Headers with javac -h Java 10 Unicode Locale Extensions: Currency, Week, RegionClick to read tutorial: Java 10 Unicode Locale Extensions: Currency, Week, Region Java 9 Modules Tutorial | Java Platform Module System (JPMS)Click to read tutorial: Java 9 Modules Tutorial | Java Platform Module System (JPMS)