Skip to content

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”

Java 9 introduced convenient factory methods for creating immutable collections.

// Creating immutable lists
List<String> fruits = List.of("apple", "banana", "orange");
System.out.println(fruits);
// Output: [apple, banana, orange]
// Empty list
List<Integer> empty = List.of();
System.out.println(empty.size());
// Output: 0
// Attempting to modify throws UnsupportedOperationException
// fruits.add("grape"); // Runtime exception
// Creating immutable sets
Set<String> colors = Set.of("red", "green", "blue");
System.out.println(colors.size());
// Output: 3
// Duplicates are not allowed
// Set.of("red", "red"); // IllegalArgumentException
// Creating small immutable maps
Map<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 States
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 identical
int[] identical1 = {1, 2, 3};
int[] identical2 = {1, 2, 3};
System.out.println(Arrays.mismatch(identical1, identical2));
// Output: -1
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)
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// Take elements while condition is true
List<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 true
List<Integer> dropped = numbers.stream()
.dropWhile(n -> n <= 5)
.collect(Collectors.toList());
System.out.println(dropped);
// Output: [6, 7, 8, 9, 10]
String nullableValue = null;
String validValue = "Hello";
// Handle nullable values gracefully
long 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: 1
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: C
// Original mutable collections
List<String> originalList = new ArrayList<>();
originalList.add("Java");
originalList.add("Python");
originalList.add("JavaScript");
// Create immutable copies
List<String> immutableList = List.copyOf(originalList);
System.out.println(immutableList);
// Output: [Java, Python, JavaScript]
// Original can be modified, copy remains unchanged
originalList.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 Map
Set<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”
List<String> languages = List.of("Java", "Python", "JavaScript", "Go");
// Type-safe array conversion
String[] array = languages.toArray(String[]::new);
System.out.println("Array length: " + array.length);
// Output: Array length: 4
System.out.println(Arrays.toString(array));
// Output: [Java, Python, JavaScript, Go]
// Compare with old approach
String[] oldWay = languages.toArray(new String[0]);
System.out.println("Old way: " + Arrays.toString(oldWay));
// Output: Old way: [Java, Python, JavaScript, Go]
Optional<String> emptyOptional = Optional.empty();
Optional<String> valueOptional = Optional.of("Hello");
System.out.println("Empty optional isEmpty: " + emptyOptional.isEmpty());
// Output: Empty optional isEmpty: true
System.out.println("Value optional isEmpty: " + valueOptional.isEmpty());
// Output: Value optional isEmpty: false
// Useful in conditional logic
if (emptyOptional.isEmpty()) {
System.out.println("No value present");
}
// Output: No value present

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 simultaneously
String 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 numbers
Map<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]

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 introduced three new interfaces to provide consistent access to first and last elements:

SequencedCollection
├── SequencedSet
└── SequencedMap (separate hierarchy)
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)
}
}
// LinkedHashSet implements SequencedSet
LinkedHashSet<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 view
SequencedSet<Integer> reversedSet = sequencedSet.reversed();
System.out.println("Reversed set: " + reversedSet);
// Output: Reversed set: [30, 20, 10]
// Modifications to reversed view affect original
reversedSet.addFirst(5);
System.out.println("After adding 5 to reversed: " + sequencedSet);
// Output: After adding 5 to reversed: [10, 20, 30, 5]
// LinkedHashMap implements SequencedMap
LinkedHashMap<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 entries
Map.Entry<String, Integer> firstEntry = sequencedMap.firstEntry();
Map.Entry<String, Integer> lastEntry = sequencedMap.lastEntry();
System.out.println("First entry: " + firstEntry);
// Output: First entry: first=1
System.out.println("Last entry: " + lastEntry);
// Output: Last entry: third=3
// Add at specific positions
sequencedMap.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 view
SequencedMap<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 entries
SequencedSet<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]
  1. Factory Methods (Java 9+):

    // Good: For small, immutable collections
    List<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
  2. Copy Methods (Java 10+):

    // Good: Creating defensive copies
    public List<String> getItems() {
    return List.copyOf(internalList); // Defensive copy
    }
  3. Sequenced Collections (Java 21+):

    // Good: When you need consistent first/last access
    SequencedCollection<Task> taskQueue = new LinkedList<>();
    taskQueue.addLast(newTask);
    Task nextTask = taskQueue.removeFirst();
// Factory methods create specialized implementations
List<String> factoryList = List.of("a", "b", "c");
System.out.println(factoryList.getClass().getSimpleName());
// Output: ImmutableCollections$ListN (or similar)
// More memory efficient than traditional approaches
List<String> traditionalList = Collections.unmodifiableList(
Arrays.asList("a", "b", "c")
);
// Creates wrapper around ArrayList - less efficient

The Collections API evolution after Java 8 focuses on:

  1. Immutability: Factory and copy methods for creating immutable collections
  2. Convenience: Simplified creation and manipulation of collections
  3. Type Safety: Better generic type handling and array conversion
  4. Consistency: Sequenced collections provide uniform access patterns
  5. Performance: Optimized implementations for common use cases

These enhancements make Java collections more developer-friendly while maintaining backward compatibility and improving performance.