TABLE OF CONTENTS (HIDE)

The Java Collection Framework

Part 2: Enhancements since JDK 8

Introduction

Prior to JDK 8, Java Interfaces can contain only abstract methods and constants. If you wish to add an additional abstract method to an existing interface, you need to re-write all the existing classes that implement this interface to support this additional method. This makes enhancement to existing interfaces (including those in the Collection Framework such as Collection and List) difficult.

JDK 8 introduces public default and public static methods into Interface. JDK 9 further introduces private and private static method. (See "Interface public default, public static, private and private static methods"). Adding these non-abstract methods to existing interfaces does not require re-writing the existing implementation classes. As a result, JDK 8 is able to enhance many interfaces in the Collection framework.

JDK 8 also introduces Lambda Expressions as a shorthand for creating an instance that implements a single-abstract-method interface (or functional interface) via anonymous inner class (See "Lambda Expressions"), new Function API and new Stream API (See "Stream API") to support Functional Programming (See "Functional Programming in Java"). The retrofitted Collection Framework in JDK 8 makes extensive use of these new features.

If you are not familiar with the new features in JDK 8, I suggest you read through the article "Lambda Expression, Streams and Functional Programming".

Collection API Enhancements since JDK 8

The Collection framework is defined via many interfaces, such as java.util.Collection<E>, java.util.List<T>, java.util.Set<T>, and java.util.Map<K,V>. With the introduction of default and static methods in interfaces, JDK 8 enhances these interfaces by adding many default and static methods, without breaking any existing implementation classes.

You can check out these new methods from the JDK API documentation. Select the desired interface ⇒ "SUMMARY METHOD" ⇒ "Default Methods" or "Static Methods".

Interface Iterable<T> New default Method .forEach() (JDK 8)

JDK 8 added a new default instance method .forEach() to the java.lang.Iterable<T> interface to consume each of the elements of the collection. Since java.util.Collection<T> is a subtype of Iterable<T>, .forEach() is available to all Collection objects.

The definition of .forEach() is:

// Interface java.lang.Iterable<T>
default void forEach(java.util.function.Consumer<? super T> action) {
   Objects.requireNonNull(action);  // Ensure that action is NOT null
   for (T t : this) {
      action.accept(t);
   }
}

The Consumer<T> is a functional interface (single-abstract-method interface) defined as follow:

// Functional Interface java.util.function.Consumer<T>
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);   // public abstract
   ......
}

In Consumer<T>, the single abstract method (called accept()) takes an object of type T, performs its programmed operations, and returns void. In JDK 8, you can create an instance of Functional Interface via the shorthand Lambda Expressions.

In .forEach(), this Consumer will be applied to each of the elements of the Collection.

Example

Person.java:

public class Person {
   private String name;  // private instance variables
   private int age;
   public Person(String name, int age) { this.name = name; this.age = age; } // constructor
   public String getName() { return name; }  // getter
   public int getAge() { return age; }
   public String toString() { return name + "(" + age + ")"; }  // toString() to describe itself
   // To be used in testing Consumer
   public void sayHello() { System.out.println(name + " says hello"); }
}

J8IterableForEachTest.java:

import java.util.List;
import java.util.Iterator;
public class J8IterableForEachTest {
   public static void main(String[] args) {
      List<Person> pList = List.of( // JDK 9 to construct an unmodifiable List
            new Person("Paul", 60),
            new Person("John", 15));
      System.out.println(pList); //[Paul(60), John(15)]

      // Pre-JDK 8: Using a for-each loop
      for (Person p: pList) {
         System.out.println(p);
      }
      //Paul(60)
      //John(15)

      // Pre-JDK 8: Using an Iterator
      Iterator<Person> iter = pList.iterator();
      while (iter.hasNext()) {
         Person p = iter.next();
         System.out.println(p.getName());
      }
      //Paul
      //John

      // JDK 8 introduces .forEach() in Iterable<T>
      pList.forEach(p -> System.out.println(p));  // Use Lambda Expression
      //Paul(60)
      //John(15)
      pList.forEach(System.out::println);  // Use method reference (same as above)

      pList.forEach(Person::sayHello);  // Use method reference to invoke an instance method
      //Paul says hello
      //John says hello

      pList.forEach(p -> System.out.print(p.getName() + " "));  // Another Lambda Expression
      //Paul John

      // JDK 8 also introduces Stream API, with a forEach() terminal operation,
      //  which is different from Iterable's forEach(). See "Stream API"
      pList.stream().filter(p -> p.getAge() >= 21).forEach(System.out::println);
      //Paul(60)
   }
}

[TODO] Explanation

Notes: The Iterable's .forEach() is different from Stream's .forEach() introduced in JDK 8 Stream API.

New Class java.util.Optional<T> (JDK 8)

JDK 8 introduces a new container class java.util.Optional<T> as a container which may contain a value or nothing. It is primarily intended for use as a method return-type, where the method may return a non-null value or nothing. Instead of returning null, which may trigger NullPointerException, you can return an Optional.empty().

The commonly-used methods in java.util.Optional<T> are:

public final class Optional<T> {
   static <T> Optional<T> empty()     // static factory method to return an empty Optional instance
   static <T> Optional<T> of(T value) // static factory method to return a non-null value instance

   boolean isEmpty()
   boolean isPresent()
   T get()
   T orElse(T other)

   // To support Functional Programming
   Optional<T> filter<Predicate<? super T> p)
   <U> Optional<U> map(Function<? super T, ? extends U> mapper)
   void ifPresent(Consumer<? super T> action)
   void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)
   Stream<T> stream()
   ......
}

For example,

import java.util.Optional;
public class JDK8OptionalTest {
   public static void main(String[] args) {
      Optional<String> o1 = Optional.of("apple");
      System.out.println(o1);                   //Optional[apple]
      System.out.println(o1.isPresent());       //true
      System.out.println(o1.get());             //apple
      System.out.println(o1.orElse("orange"));  //apple
      o1.ifPresent(System.out::println);        //apple
      System.out.println(o1.filter(v -> v.charAt(0) == 'a'));  //Optional[apple]
      System.out.println(o1.filter(v -> v.charAt(0) == 'o'));  //Optional.empty
      System.out.println(o1.map(v -> v.charAt(0)));  //Optional[a]

      // empty optional instance
      Optional<String> o2 = Optional.empty();
      System.out.println(o2);                   //Optional.empty
      System.out.println(o2.isEmpty());         //true
      //System.out.println(o2.get());
      //java.util.NoSuchElementException: No value present
      System.out.println(o2.orElse("orange"));  //orange
      o2.ifPresent(System.out::println);        //no output
   }
}

Primitive type specializations of OptionalInt, OptionalDouble, OptionalLong are also available.

Instantiation of an Unmodifiable Collection: New static Factory Method .of() and .copyOf()

JDK 9/10 introduces new static factory method .of() and .copyOf() in List, Set, Map, Optional, etc. to generate an instance of unmodifiable collection. In an unmodifiable collections, elements cannot be added, removed or replaced. Calling any mutator will trigger an UnsupportedOperationException.

List.of() and Set.of() (JDK 9)

JDK 9 introduces 11 new overloaded static factory methods List.of() and Set.of() to generate an unmodifiable List/Set, with 0 to 10 elements. The signatures are as follows:

// Interface java.util.List<E>
static <E> List<E> of()  // 11 overloaded methods to support 0 to 10 elements
static <E> List<E> of(e1)
static <E> List<E> of(e1, e2)
......
static <E> List<E> of(e1, e2, e3, e4, e5, e6, e7, e8, e9, e10)

// Interface java.util.Set<E>
static <E> Set<E> of()  // 11 overloaded methods to support 0 to 10 elements
static <E> Set<E> of(E e1)
static <E> Set<E> of(E e1, E e2)
......
static <E> Set<E> of(E e1, E e2, E e3, E e4, E e5, E e6, E e7, E e8, E e9, E e10)

For example,

import java.util.List;
import java.util.Set;
public class J9ListSetOfTest {  // JDK 9
   public static void main(String[] args) {
      List<String> lst = List.of("apple", "orange");  // 0 to 10 elements only
      System.out.println(lst);  //[apple, orange]
      System.out.println(lst.get(0));  //apple (List is ordered with numerical index)
      // Unmodifiable
      //lst.add("banana");  //java.lang.UnsupportedOperationException

      Set<Double> set = Set.of(1.1, 2.2, 3.3);  // 0 to 10 elements only
      System.out.println(set);  //[3.3, 1.1, 2.2] (Set is unordered)
      // Unmodifiable
      //set.clear();        //java.lang.UnsupportedOperationException
   }
}
Notes:
  • You will get an UnsupportedOperationException if you try to modify an unmodifiable Collection object.
  • The underlying implementation class is not known, but non-modifiable.
  • There are 11 overloaded static factory methods for handling 0 to 10 elements. Check out the API documentation.
Map.of(), Map.ofEntries() and Map.entry() (JDK 9)

JDK 9 introduces 11 new overloaded static factory methods Map.of() to generate an unmodifiable Map, with 0 to 10 key-value pairs. It also introduces new static methods Map.ofEntries() and Map.entry() to generate an unmodifiable Map, with arbitrary number of key-value pairs.

The signatures is as follows:

// Interface java.util.Map<K,V>
static <K,V> Map<K,V> of()  // 11 overloaded methods to support 0 to 10 key-value pairs
static <K,V> Map<K,V> of(K k1, V v1)
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2)
......
static <K,V> Map<K,V> of(K k1, V v1, K k2, V v2, ..., K k10, V v10)

static <K,V> Map<K,V> ofEntries(Map.Entry<? extends K, ? extends V>... entries)
static <K,V> Map.Entry<K,V> entry(K k, V v)

For example,

import java.util.Map;
public class J9MapOfEntryTest {
   public static void main(String[] args) {  // JDK 9
      // Using Map.of() factory method for 0 to 10 key-value pairs
      Map<String, Integer> nameAgePairs = Map.of("Peter", 60, "John", 16);
      System.out.println(nameAgePairs);  //{John=16, Peter=60}
      System.out.println(nameAgePairs.get("John"));  //16
      // Unmodifiable
      //nameAgePairs.put("Paul", 18);  //java.lang.UnsupportedOperationException

      // Using Map.ofEntries() and Map.entry() factory methods for an arbitrary number of key-value pairs
      Map<String, Double> coffeePrices = Map.ofEntries(
         Map.entry("espresso", 1.1),
         Map.entry("latte", 2.2),
         Map.entry("cappuccino", 3.3)
      );
      System.out.println(coffeePrices);  //{espresso=1.1, cappuccino=3.3, latte=2.2}
   }
}
List.copyOf(), Set.copyOf() and Map.copyOf() (JDK 10)

JDK 10 introduces static factory method List.copyOf(), Set.copyOf(), Map.copyOf() to create a unmodifiable instance from another collection. The signatures are:

// Construct an unmodifiable instance, copying from the given object
// Interface java.util.List<E>
static <E> List<E> copyOf(Collection<? extends E> c)

// Interface java.util.List<E>
static <E> Set<E> copyOf(Collection<? extends E> c)

// Interface java.util.Map<K,V>
static <K,V> Map(K,V> copyOf(Map<? extends K, ? extends V> map)

For example,

import java.util.List;
import java.util.Set;
import java.util.Map;
public class J10ListSetMapCopyOfTest {  // JDK 10
   public static void main(String[] args) {
      List<String> sourceLst = List.of("apple", "orange");  // 0 to 10 elements only

      List<String> fruitLst = List.copyOf(sourceLst);
      System.out.println(fruitLst);  //[apple, orange]
      // Unmodifiable
      //fruitLst.add("banana");  //java.lang.UnsupportedOperationException

      Set<String> fruitSet = Set.copyOf(sourceLst);
      System.out.println(fruitSet);  //[orange, apple]

      Map<String, Double> mapSource = Map.of("tea", 1.1, "coffee", 2.2);
      Map<String, Double> mapCopy = Map.copyOf(mapSource);
      System.out.println(mapCopy);   //{coffee=2.2, tea=1.1}
   }
}

Interface Collection<E> Enhancements (JDK 8, 11)

JDK 8 introduces these new default methods:

  • To complement the existing abstract remove(Object) and removeAll(Collection), JDK 8 introduces default removeIf(Predicate) to remove all the elements that match the Predicate.
    The signatures are:
    // Interface java.util.Collection<E>
    // Before JDK 8
    abstract boolean remove(Object o)
    abstract boolean removeAll(Collection<?> c)
    // JDK 8
    default boolean removeIf(Predicate<? super E> filter)
    For example,
    import java.util.Collection;
    import java.util.List;
    import java.util.ArrayList;
    public class J8CollectionRemoveIfTest {  // JDK 8
       public static void main(String[] args) {
          Collection<String> fruits = new ArrayList<>();  // modifiable collection
          fruits.add("apple");
          fruits.add("orange");
          fruits.add("banana");
          System.out.println(fruits);  //[apple, orange, banana]
    
          // Pre-JDK 8 abstract remove(anObject) to remove anObject
          System.out.println(fruits.remove("apple"));  //true
          System.out.println(fruits);  //[orange, banana]
    
          // Pre-JDK 8 abstract removeAll(Collection) to remove those in Collection
          Collection<String> fruitsToRemove = List.of("orange", "pear");  // JDK 9 unmodifiable collection
          System.out.println(fruitsToRemove);  //[orange, pear]
          System.out.println(fruits.removeAll(fruitsToRemove));  //true
          System.out.println(fruits);  //[banana]
    
          // JDK 8 introduces default removeIf(Predicate) to remove those match Predicate
          System.out.println(fruits.removeIf(s -> s.charAt(0) == 'z'));  //false
          System.out.println(fruits);  //[banana]
          System.out.println(fruits.removeIf(s -> s.charAt(0) == 'b'));  //true
          System.out.println(fruits);  //[]
       }
    }
  • To support Stream API, JDK 8 introduces default stream() and parallelStream() to create a sequential and parallel Stream from a Collection.
  • To support parallel stream, JDK 8 introduces a default spliterator() to create a Spliterator for traversing and partitioning elements of a source. Similar to the sequential Iterator, Spliterator was designed to support efficient parallel traversal.

JDK 11 introduces these new default methods:

  • To complement toArray() which creates an Object[] and toArray(T[]) which creates a T[], JDK 11 introduces toArray(IntFunction<T[]> generator) to use the given generator to allocate the returned fix-sized array, based on the size of Collection. Recall that arrays have fixed size, while Collections are dynamically allocated.
    The signatures are:
    // Interface java.util.Collection<E>
    // Before JDK 11
    Object[] toArray()
    <T> T[] toArray(T[] a)
    // JDK 11
    default <T> T[] toArray(IntFunction<T[]> arrayGenerator)
    For example,
    import java.util.List;
    import java.util.Collection;
    import java.util.Arrays;
    public class J8CollectionToArrayTest {  // JDK 8
       public static void main(String[] args) {
          Collection<String> fruits = List.of("apple", "orange", "banana");  // JDK 9 unmodifiable list
          System.out.println(fruits);  //[apple, orange, banana]
    
          // Pre-JDK 11 toArray() to create a fix-sized "Object" array
          Object[] fruitObjArray = fruits.toArray();  // array length based on fruits.size()
          System.out.println(fruitObjArray.length);   //3
          System.out.println(Arrays.toString(fruitObjArray)); //[apple, orange, banana]
    
          // Pre-JDK 11 toArray(T[]) to create an T[]. Need to pre-allocated a fix-sized array.
          String[] fruitStrArray1 = new String[fruits.size()];
          System.out.println(fruitStrArray1.length);  //3
          fruits.toArray(fruitStrArray1);
          System.out.println(Arrays.toString(fruitStrArray1)); //[apple, orange, banana]
    
          // JDK 11 introduces toArray(arrayGenerator) to simplify the above
          String[] fruitStrArray2 = fruits.toArray(String[]::new);
          System.out.println(fruitStrArray2.length);  //3
          System.out.println(Arrays.toString(fruitStrArray2)); //[apple, orange, banana]
       }
    }

Interface Iterator<E> Enhancements (JDK 8)

JDK 8 added these default methods:

  • default forEachRemaining???(Consumer) to process the rest of elements. The signature is:
    // Interface java.util.Iterator<E>
    default void forEachRemaining(Consumer< super E> action)
    For example,
    import java.util.List;
    import java.util.Iterator;
    public class J8IteratorTest {
       public static void main(String[] args) {
          List<String> coffees = List.of("espresso", "latte", "cappuccino");  // JDK 9 unmodifiable List
          System.out.println(coffees);  //[espresso, latte, cappuccino]
    
          // JDK 8 introduces .forEachRemaining???(Consumer)
          Iterator<String> iter = coffees.iterator();
          iter.next();   // consume the first element
          iter.forEachRemaining(System.out::println);
          //latte
          //cappuccino
       }
    }

Interface Map(K,V) Enhancements (JDK 8, 9, 10)

JDK 8 introduces new default methods to java.util.Map(K,V):

  • To complement the existing abstract get(key), JDK 8 introduces default getOrDefault(key, defaultValue) to provide a default value if key is not present.
  • To complement the existing abstract put(key,value), JDK 8 introduces default putIfAbsent(key,value) method to prevent accidental update.
  • To complement the existing abstract remove(key), JDK 8 introduces default remove(key, value) to remove matching key-value pair.
  • JDK 8 introduces default forEach(BiConsumer) to consume each key-value pair of the map.
  • JDK 8 supports "Stream on Map" via entrySet().stream().
  • JDK 8 introduces default compute(key, BiFunction), computeIfPresent(key, BiFunction), computeIfAbsent(key, Function) to perform mapping of value based on key.
  • JDK 8 introduces default replace(key, newValue), replace(key, oldValue, newValue) to replace the value based on key.
  • JDK 8 introduces default merge(key, newValue, BiFunction) to merge the value based on key.

The signatures are:

// Interface java.util.Map(K,V)
default void forEach(BiConsumer<? super K, ? super V> action)  // Perform action on each key-value pair

default V getOrDefault(Object key, V defaultValue)
   // Existing: abstract V get(Object key)
default V putIfAbsent(K key, V value)
   // Existing: abstract V put(K key, V value)
   // Existing: abstract void putAll(Map<? extends K, ? extends V> m)
default boolean remove(Object key, Object value)
   // Existing: abstract V remove(Object key)
default V replace(K key, V value)
default boolean replace(K key, V oldValue, V newValue)
default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
default V merge(K key, V value, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

JDK 9 introduces these new static methods (see above):

  • static of(k1, v1, k2, v2,...) method to construct an unmodifiable map, for 1 to 10 key-value pairs.
  • static ofEntries() and entry() to construct an un unmodifiable map of arbitrary number of key-value pairs.

JDK 10 introduces these new static methods (see above):

  • static copyOf(Map) method to copy the given Map to a new unmodifiable map.

For example,

import java.util.Map;
import java.util.HashMap;
import java.util.OptionalDouble;
public class J8MapTest {
   public static void main(String[] args) {
      // Use JDK 9 static of() to construct a "unmodifiable" Map for 1 to 10 key-value pairs
      Map<String, Double> fruitPrices = Map.of("apple", 1.1, "orange", 2.2, "banana", 3.3);
      System.out.println(fruitPrices);  //{apple=1.1, orange=2.2, banana=3.3}
      //fruitPrices.put("apple", 4.4);    //java.lang.UnsupportedOperationException

      // Pre-JDK 8
      System.out.println(fruitPrices.isEmpty());             //false
      System.out.println(fruitPrices.containsKey("banana")); //true
      System.out.println(fruitPrices.containsValue(0.0));    //false

      // In additional to abstract get(key), JDK 8 introduces default getOrDefault(key)
      System.out.println(fruitPrices.get("banana"));  //3.3
      System.out.println(fruitPrices.get("pear"));    //null
      System.out.println(fruitPrices.getOrDefault("pear", 99.99));  //99.99

      // Use JDK 9 static entry() and ofEntries() to construct a "unmodifiable" Map
      //  for an arbitrary number of key-value pairs
      Map<String, Double> teaPrices = Map.ofEntries(
            Map.entry("green tea", 1.1),
            Map.entry("black tea", 2.2));
      System.out.println(teaPrices);  //{green tea=1.1, black tea=2.2}

      // Construct a modifiable map with a chosen implementation (Pre-JDK 8)
      Map<String, Double> coffeePrices = new HashMap<>();  // modifiable
      coffeePrices.put("cappuccino", 5.5);
      coffeePrices.put("espresso", 6.6);
      System.out.println(coffeePrices);     //{espresso=6.6, cappuccino=5.5}
      coffeePrices.put("cappuccino", 7.7);  // update
      System.out.println(coffeePrices);     //{espresso=6.6, cappuccino=7.7}

      // In additional to abstract put(), JDK 8 introduces default putIfAbsent()
      coffeePrices.putIfAbsent("cappuccino", 8.8);  // no update
      System.out.println(coffeePrices);  //{espresso=6.6, cappuccino=7.7}

      // JDK 8 introduces default forEach(BiConsumer)
      coffeePrices.forEach((k, v) -> System.out.println(k + ":" + v));
      //espresso:6.6
      //cappuccino:7.7

      // JDK 8 introduces Stream API
      // Stream API does not support Map directly. Need to get a Set view via .entrySet().
      OptionalDouble maxPrice = coffeePrices
            .entrySet()  // Returns a Set view Set<Map.Entry<K,V>> of this map
            .stream()
            .mapToDouble(Map.Entry::getValue)
            .max();
      System.out.println(maxPrice);  //OptionalDouble[7.7]

      // JDK 8 introduces default compute(key, BiFunction) to compute (update)
      //  a newValue (instead of get() and put())
      coffeePrices.compute("cappuccino", (k, v) -> v + 1.0);
      System.out.println(coffeePrices);  //{espresso=6.6, cappuccino=8.7}
      // Also default computeIfPresent(key, aBiFunction(key,oldValue))
      coffeePrices.computeIfPresent("cappuccino", (k, v) -> v + 1.0);
      System.out.println(coffeePrices);  //{espresso=6.6, cappuccino=9.7}
      // Also default computeIfAbsent(key, Function(key))
      coffeePrices.computeIfAbsent("latte", k -> 8.8);
      System.out.println(coffeePrices);  //{espresso=6.6, cappuccino=9.7, latte=8.8}
      System.out.println(coffeePrices.computeIfAbsent("latte", k -> 9.9)); // 8.8 (no update)

      // In additional to abstract remove(key), JDK 8 introduces default remove(key, value)
      //  for removing a matching key-value pair
      System.out.println(coffeePrices.remove("latte", 0.0));  //false
      System.out.println(coffeePrices.remove("latte", 8.8));  //true
      System.out.println(coffeePrices);  //{espresso=6.6, cappuccino=9.7}

      // JDK 8 introduces default replace(key, newValue) if key exists
      System.out.println(coffeePrices.replace("espresso", 9.9));  //6.6 (return old value)
      System.out.println(coffeePrices);  //{espresso=9.9, cappuccino=9.7}
      System.out.println(coffeePrices.replace("latte", 9.9));     //null (old value)
      // Also default replace(key, oldValue, newValue)
      System.out.println(coffeePrices.replace("espresso", 9.9, 11.1));  //true
      System.out.println(coffeePrices);  //{espresso=11.1, cappuccino=9.7}
      System.out.println(coffeePrices.replace("espresso", 0.0, 12.2));  //false (no change)

      // JDK 8 introduces default merge(key, newValue, BiFunction)
      // If key exists, replace by the output of BiFunction
      System.out.println(coffeePrices.merge("espresso", 1.1, (oldValue, v) -> oldValue + v));  //12.2 (replace)
      System.out.println(coffeePrices);  //{espresso=12.2, cappuccino=9.7}
      // If key does not exist, put an entry (key, newValue), ignore BiFunction
      System.out.println(coffeePrices.merge("latte", 1.1, (oldValue, v) -> oldValue + v));  //1.1
      System.out.println(coffeePrices);  //{espresso=12.2, cappuccino=9.7, latte=1.1}

      // JDK 10 introduces static copyOf(Map) to copy to a map to a new unmodifiable map
      Map<String, Double> newCoffeePrices = Map.copyOf(coffeePrices);
      System.out.println(newCoffeePrices);  //{latte=1.1, espresso=12.2, cappuccino=9.7} (unmodifiable)
   }
}

Interface java.util.Comparator<T> Enhancements

[TODO]

Others

  • more.

Enhancements to the Collection Framework by JDK Releases

JDK 5 Enhancements to the Collection Framework

New Language Features

JDK 5 introduces MAJOR upgrades to the Java Language. It introduces many new language features, which are applicable to collection framework:

  • Generics: support collection of generic type. See "Generics".
  • Auto-boxing/Auto-Unboxing of primitive types (such as int, double) to their wrapper types (such as Integer, Double). Since Collection does not hold primitives, the auto-boxing/auto-unboxing greatly simplifies the conversion. See "Auto-boxing/Auto-Unboxing".
  • Enhanced for-each loop: Simplifies looping through all elements of a Collection. See "Enhance for-each loop".
  • Annotations
  • Enumerations
  • Varargs (Variable number of arguments)
  • static import statement
New API Packages
  • New package java.util.concurrent to support concurrent (parallel) programming, with thread-safe collection classes that allow to be modified, such as CopyOnWriteArrayList, ConcurrentHashMap, CopyOnWriteArraySet.

JDK 6 Enhancements to the Collection Framework

[TODO]

JDK 7 Enhancements to the Collection Framework

New Language Features
  • JDK 7 improves type inference for generic instant creation with the diamond operator <>. For example,
    // Before JDK 7
    // Construct an instance of Generic
    List<String> lst = new ArrayList<String>();
    
    // JDK 7
    // The type can be omitted and inferred from the variable declaration
    List<String> lst = new ArrayList<>();

JDK 8 Enhancements to the Collection Framework

New Language Features

JDK 8 introduces MAJOR upgrades to the Java Language. It introduces many new language features (See "Lambda Expression, Streams and Functional Programming"):

  • public default and public static methods in interface.
  • Lambda Expressions: shorthand for creating an instance that implements a single-abstract-method interface via an anonymous inner class.
New API Packages

JDK 8 introduces these new API packages, which enhances collection framework (See "Lambda Expression, Streams and Functional Programming"):

  • Stream API: in package java.util.stream.
  • Function API: in package java.util.function.
New Classes/Interfaces

JDK 8 introduces these new classes/interfaces to the Collection framework:

  • Added class java.util.Optional<T>, which may contain a value or nothing, to avoid returning null that may trigger NullPointerException.
New Interface default/static Methods

JDK 8 enhances the Collection Library:

  • Interface java.lang.Iterable<T> (supertype of Interface java.util.Collection<E>):
    default void forEach(Consumer<? super T> action)
       // Perform action on each element of the Iterable (Collection)  
  • Interface java.util.Collection<E>:
    // Supporting Stream API
    default Stream<E> stream()           // Creates a sequential stream
    default Stream<E> parallelStream()   // Creates a parallel stream
    default Spliterator<E> spliterator() // Creates a Spliterator to support efficient parallel traversal
    
    // Others
    default boolean removeIf(Predicate<? super E> filter)  // Removes elements that satisfy the Predicate 
  • Interface java.util.Iterator<T>:
    default void forEachRemaining(Consumer<? super E> action)  // Performs action on the rest of elements
    default void remove()  // Remove the last element returned by this Iterator
  • Interface java.util.Map<K,V>:
    default void forEach(BiConsumer<? super K, ? super V> action)  // Perform action on each key-value pair
    
    default V getOrDefault(Object key, V defaultValue)
       // Existing: abstract V get(Object key)
    default V putIfAbsent(K key, V value)
       // Existing: abstract V put(K key, V value)
       // Existing: abstract void putAll(Map<? extends K, ? extends V> m)
    default boolean remove(Object key, Object value)
       // Existing: abstract V remove(Object key)
    default V replace(K key, V value)
    default boolean replace(K key, V oldValue, V newValue)
    default void replaceAll(BiFunction<? super K, ? super V, ? extends V> function)
    default V merge(K key, V value, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
    
    default V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)
    default V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction)
    default V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction)

JDK 9 Enhancements to the Collection Framework

New Language Features

JDK 9 introduces these new language features:

  • private and private static methods in interface.
New Interface default/static Methods

JDK 9 introduces these methods:

  • Interfaces java.util.List<E>, java.util.Set<E>, java.util.EnumSet<E>:
    // Construct an unmodifiable instance, with 0 to 10 elements
    static <E> List<E> of(e1, e2, ...)
    static <E> Set<E> of(e1, e2, ...)
  • Interface java.util.Map<K,V>:
    // Constructs an unmodifiable instance of Map, with 0 to 10 key-value pairs
    static <K,V> Map(K,V) of(k1, v1, k2, v2, ...)
    
    // Constructs an unmodifiable instance of Map, with arbitrary number of key-value pairs
    static <K,V> Map(K,V) ofEntries(Map.Entry<? extends K, ? extends V>... entries)
    static <K,V> Map.Entry<K,V> entry(K k, V v)

JDK 10 Enhancements to the Collection Framework

New Interface default/static Methods

JDK 10 introduces these methods:

  • Interfaces java.util.List<E>, java.util.Set<E>, java.util.EnumSet<E>:
    // Construct an unmodifiable instance, copying from the given Collection
    static <E> List<E> copyOf(Collection<? extends E> c)
    static <E> Set<E> copyOf(Collection<? extends E> c)
    
  • Interface java.util.Map<K,V>:
    // Construct an unmodifiable instance of Map, copy from the given Map
    static <K,V> Map(K,V> copyOf(Map<? extends K, ? extends V> map)
    

JDK 11 Enhancements to the Collection Framework

New Interface default/static Methods

JDK 11 enhances the Collection Libraries:

  • java.util.Collection<E>:
    default <T> T[] toArray(IntFunction<T[]> generator)
       // Returns an array containing all elements in this Collection,
       //   using the provided generator function to allocate the returned fixed-size array
       // Existing: Object[] toArray()
       // Existing: <T> T[] toArray(T[] a)
       // Similar function in java.util.stream.Stream

JDK 12 Enhancements to the Collection Framework

[TODO]

JDK 13 Enhancements to the Collection Framework

[TODO]

JDK 14 Enhancements to the Collection Framework

[TODO]

LINK TO JAVA REFERENCES & RESOURCES

More References

  1. Java Online Tutorial on "Generics" @ http://docs.oracle.com/javase/tutorial/extra/generics/index.html.
  2. Java Online Tutorial on "Collections" @ http://docs.oracle.com/javase/tutorial/collections/index.html.