Java8+ API Changes
This guide covers the evolution of Java Collections API from Java 9 onwards, highlighting new methods, interfaces, and enhancements that improve developer productivity and code quality.
Java 9 (2017) - Factory Methods Revolution
Section titled “Java 9 (2017) - Factory Methods Revolution”Immutable Collection Factory Methods
Section titled “Immutable Collection Factory Methods”Java 9 introduced convenient factory methods for creating immutable collections.
List.of()
Section titled “List.of()”// Creating immutable listsList<String> fruits = List.of("apple", "banana", "orange");System.out.println(fruits);// Output: [apple, banana, orange]
// Empty listList<Integer> empty = List.of();System.out.println(empty.size());// Output: 0
// Attempting to modify throws UnsupportedOperationException// fruits.add("grape"); // Runtime exceptionSet.of()
Section titled “Set.of()”// Creating immutable setsSet<String> colors = Set.of("red", "green", "blue");System.out.println(colors.size());// Output: 3
// Duplicates are not allowed// Set.of("red", "red"); // IllegalArgumentExceptionMap.of() and Map.ofEntries()
Section titled “Map.of() and Map.ofEntries()”// Creating small immutable mapsMap<String, Integer> ages = Map.of( "Alice", 25, "Bob", 30, "Charlie", 35);System.out.println(ages.get("Alice"));// Output: 25
// For larger maps, use Map.ofEntries()Map<String, String> countries = Map.ofEntries( Map.entry("US", "United States"), Map.entry("UK", "United Kingdom"), Map.entry("IN", "India"));System.out.println(countries.get("US"));// Output: United StatesArrays Enhancements
Section titled “Arrays Enhancements”Arrays.mismatch()
Section titled “Arrays.mismatch()”int[] array1 = {1, 2, 3, 4, 5};int[] array2 = {1, 2, 9, 4, 5};
int mismatchIndex = Arrays.mismatch(array1, array2);System.out.println("First mismatch at index: " + mismatchIndex);// Output: First mismatch at index: 2
// Arrays are identicalint[] identical1 = {1, 2, 3};int[] identical2 = {1, 2, 3};System.out.println(Arrays.mismatch(identical1, identical2));// Output: -1Arrays.compare()
Section titled “Arrays.compare()”int[] arr1 = {1, 2, 3};int[] arr2 = {1, 2, 4};int[] arr3 = {1, 2, 3};
System.out.println(Arrays.compare(arr1, arr2));// Output: -1 (arr1 < arr2)
System.out.println(Arrays.compare(arr1, arr3));// Output: 0 (arrays are equal)
System.out.println(Arrays.compare(arr2, arr1));// Output: 1 (arr2 > arr1)Stream API Enhancements
Section titled “Stream API Enhancements”Stream.takeWhile() and dropWhile()
Section titled “Stream.takeWhile() and dropWhile()”List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Take elements while condition is trueList<Integer> taken = numbers.stream() .takeWhile(n -> n <= 5) .collect(Collectors.toList());System.out.println(taken);// Output: [1, 2, 3, 4, 5]
// Drop elements while condition is trueList<Integer> dropped = numbers.stream() .dropWhile(n -> n <= 5) .collect(Collectors.toList());System.out.println(dropped);// Output: [6, 7, 8, 9, 10]Stream.ofNullable()
Section titled “Stream.ofNullable()”String nullableValue = null;String validValue = "Hello";
// Handle nullable values gracefullylong count1 = Stream.ofNullable(nullableValue).count();System.out.println("Count for null: " + count1);// Output: Count for null: 0
long count2 = Stream.ofNullable(validValue).count();System.out.println("Count for valid: " + count2);// Output: Count for valid: 1Other Enhancements
Section titled “Other Enhancements”Enumeration.asIterator()
Section titled “Enumeration.asIterator()”Vector<String> vector = new Vector<>();vector.add("A");vector.add("B");vector.add("C");
Enumeration<String> enumeration = vector.elements();Iterator<String> iterator = enumeration.asIterator();
while (iterator.hasNext()) { System.out.println(iterator.next());}// Output: A// Output: B// Output: CJava 10 (2018) - Copy Factory Methods
Section titled “Java 10 (2018) - Copy Factory Methods”Immutable Copy Methods
Section titled “Immutable Copy Methods”// Original mutable collectionsList<String> originalList = new ArrayList<>();originalList.add("Java");originalList.add("Python");originalList.add("JavaScript");
// Create immutable copiesList<String> immutableList = List.copyOf(originalList);System.out.println(immutableList);// Output: [Java, Python, JavaScript]
// Original can be modified, copy remains unchangedoriginalList.add("C++");System.out.println("Original: " + originalList);// Output: Original: [Java, Python, JavaScript, C++]System.out.println("Copy: " + immutableList);// Output: Copy: [Java, Python, JavaScript]
// Same for Set and MapSet<String> originalSet = new HashSet<>(Set.of("A", "B", "C"));Set<String> immutableSet = Set.copyOf(originalSet);
Map<String, Integer> originalMap = new HashMap<>();originalMap.put("one", 1);originalMap.put("two", 2);Map<String, Integer> immutableMap = Map.copyOf(originalMap);Java 11 (2018) - Enhanced Array Conversion
Section titled “Java 11 (2018) - Enhanced Array Conversion”Collection.toArray(IntFunction)
Section titled “Collection.toArray(IntFunction)”List<String> languages = List.of("Java", "Python", "JavaScript", "Go");
// Type-safe array conversionString[] array = languages.toArray(String[]::new);System.out.println("Array length: " + array.length);// Output: Array length: 4System.out.println(Arrays.toString(array));// Output: [Java, Python, JavaScript, Go]
// Compare with old approachString[] oldWay = languages.toArray(new String[0]);System.out.println("Old way: " + Arrays.toString(oldWay));// Output: Old way: [Java, Python, JavaScript, Go]Optional.isEmpty()
Section titled “Optional.isEmpty()”Optional<String> emptyOptional = Optional.empty();Optional<String> valueOptional = Optional.of("Hello");
System.out.println("Empty optional isEmpty: " + emptyOptional.isEmpty());// Output: Empty optional isEmpty: trueSystem.out.println("Value optional isEmpty: " + valueOptional.isEmpty());// Output: Value optional isEmpty: false
// Useful in conditional logicif (emptyOptional.isEmpty()) { System.out.println("No value present");}// Output: No value presentJava 12 (2019) - Collectors Enhancement
Section titled “Java 12 (2019) - Collectors Enhancement”Collectors.teeing()
Section titled “Collectors.teeing()”Combines results from two collectors into a single result.
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Calculate sum and count simultaneouslyString result = numbers.stream() .collect(Collectors.teeing( Collectors.summingInt(Integer::intValue), Collectors.counting(), (sum, count) -> String.format("Sum: %d, Count: %d, Average: %.2f", sum, count, (double) sum / count) ));System.out.println(result);// Output: Sum: 55, Count: 10, Average: 5.50
// Separate even and odd numbersMap<String, List<Integer>> evenOdd = numbers.stream() .collect(Collectors.teeing( Collectors.filtering(n -> n % 2 == 0, Collectors.toList()), Collectors.filtering(n -> n % 2 != 0, Collectors.toList()), (evens, odds) -> Map.of("evens", evens, "odds", odds) ));System.out.println("Evens: " + evenOdd.get("evens"));// Output: Evens: [2, 4, 6, 8, 10]System.out.println("Odds: " + evenOdd.get("odds"));// Output: Odds: [1, 3, 5, 7, 9]Java 13-20 (2019-2023) - Stability Focus
Section titled “Java 13-20 (2019-2023) - Stability Focus”These versions primarily focused on:
- Language Features: Text blocks, pattern matching, records, sealed classes
- JVM Improvements: Performance optimizations, garbage collection enhancements
- Preview Features: Preparing future language enhancements
- No Major Collections API Changes: The Collections framework remained stable
Java 21 (2023) - Sequenced Collections
Section titled “Java 21 (2023) - Sequenced Collections”The Sequenced Collections Hierarchy
Section titled “The Sequenced Collections Hierarchy”Java 21 introduced three new interfaces to provide consistent access to first and last elements:
SequencedCollection├── SequencedSet└── SequencedMap (separate hierarchy)SequencedCollection Interface
Section titled “SequencedCollection Interface”import java.util.*;
public class SequencedCollectionExample { public static void main(String[] args) { // LinkedList implements SequencedCollection LinkedList<String> list = new LinkedList<>();
// Add elements at both ends list.addFirst("First"); list.addLast("Last"); list.addFirst("New First");
System.out.println("List: " + list); // Output: List: [New First, First, Last]
// Access first and last elements System.out.println("First element: " + list.getFirst()); // Output: First element: New First System.out.println("Last element: " + list.getLast()); // Output: Last element: Last
// Remove from both ends String removedFirst = list.removeFirst(); String removedLast = list.removeLast();
System.out.println("Removed first: " + removedFirst); // Output: Removed first: New First System.out.println("Removed last: " + removedLast); // Output: Removed last: Last System.out.println("Remaining: " + list); // Output: Remaining: [First]
// Get reversed view SequencedCollection<String> reversed = list.reversed(); System.out.println("Reversed view: " + reversed); // Output: Reversed view: [First] (same as original since only one element) }}SequencedSet Interface
Section titled “SequencedSet Interface”// LinkedHashSet implements SequencedSetLinkedHashSet<Integer> sequencedSet = new LinkedHashSet<>();sequencedSet.add(10);sequencedSet.add(20);sequencedSet.add(30);
System.out.println("Original set: " + sequencedSet);// Output: Original set: [10, 20, 30]
// Get reversed viewSequencedSet<Integer> reversedSet = sequencedSet.reversed();System.out.println("Reversed set: " + reversedSet);// Output: Reversed set: [30, 20, 10]
// Modifications to reversed view affect originalreversedSet.addFirst(5);System.out.println("After adding 5 to reversed: " + sequencedSet);// Output: After adding 5 to reversed: [10, 20, 30, 5]SequencedMap Interface
Section titled “SequencedMap Interface”// LinkedHashMap implements SequencedMapLinkedHashMap<String, Integer> sequencedMap = new LinkedHashMap<>();sequencedMap.put("first", 1);sequencedMap.put("second", 2);sequencedMap.put("third", 3);
System.out.println("Original map: " + sequencedMap);// Output: Original map: {first=1, second=2, third=3}
// Access first and last entriesMap.Entry<String, Integer> firstEntry = sequencedMap.firstEntry();Map.Entry<String, Integer> lastEntry = sequencedMap.lastEntry();
System.out.println("First entry: " + firstEntry);// Output: First entry: first=1System.out.println("Last entry: " + lastEntry);// Output: Last entry: third=3
// Add at specific positionssequencedMap.putFirst("zero", 0);sequencedMap.putLast("fourth", 4);
System.out.println("After putFirst and putLast: " + sequencedMap);// Output: After putFirst and putLast: {zero=0, first=1, second=2, third=3, fourth=4}
// Get reversed viewSequencedMap<String, Integer> reversedMap = sequencedMap.reversed();System.out.println("Reversed map: " + reversedMap);// Output: Reversed map: {fourth=4, third=3, second=2, first=1, zero=0}
// Sequenced views of keys, values, and entriesSequencedSet<String> sequencedKeys = sequencedMap.sequencedKeySet();SequencedCollection<Integer> sequencedValues = sequencedMap.sequencedValues();SequencedSet<Map.Entry<String, Integer>> sequencedEntries = sequencedMap.sequencedEntrySet();
System.out.println("Sequenced keys: " + sequencedKeys);// Output: Sequenced keys: [zero, first, second, third, fourth]Migration and Best Practices
Section titled “Migration and Best Practices”When to Use New Features
Section titled “When to Use New Features”-
Factory Methods (Java 9+):
// Good: For small, immutable collectionsList<String> constants = List.of("READ", "WRITE", "EXECUTE");// Avoid: For large collections or when mutability is needed// List<String> large = List.of(/* 100+ elements */); // Consider alternatives -
Copy Methods (Java 10+):
// Good: Creating defensive copiespublic List<String> getItems() {return List.copyOf(internalList); // Defensive copy} -
Sequenced Collections (Java 21+):
// Good: When you need consistent first/last accessSequencedCollection<Task> taskQueue = new LinkedList<>();taskQueue.addLast(newTask);Task nextTask = taskQueue.removeFirst();
Performance Considerations
Section titled “Performance Considerations”// Factory methods create specialized implementationsList<String> factoryList = List.of("a", "b", "c");System.out.println(factoryList.getClass().getSimpleName());// Output: ImmutableCollections$ListN (or similar)
// More memory efficient than traditional approachesList<String> traditionalList = Collections.unmodifiableList( Arrays.asList("a", "b", "c"));// Creates wrapper around ArrayList - less efficientSummary
Section titled “Summary”The Collections API evolution after Java 8 focuses on:
- Immutability: Factory and copy methods for creating immutable collections
- Convenience: Simplified creation and manipulation of collections
- Type Safety: Better generic type handling and array conversion
- Consistency: Sequenced collections provide uniform access patterns
- Performance: Optimized implementations for common use cases
These enhancements make Java collections more developer-friendly while maintaining backward compatibility and improving performance.