Java - Dynamically typesafe view of collections and maps

[Updated: May 13, 2017, Created: May 13, 2017]

Java 5 added various overloaded checkedXYZ() methods in java.util.Collections class which return a corresponding dynamically typesafe collection. The new returned collection wraps the user specified collection and add some additional runtime checks to avoid wrong type to be inserted/added to the collection.

From Java docs

The generics mechanism in the language provides compile-time (static) type checking, but it is possible to defeat this mechanism with unchecked casts. Usually this is not a problem, as the compiler issues warnings on all such unchecked operations. There are, however, times when static type checking alone is not sufficient. For example, suppose a collection is passed to a third-party library and it is imperative that the library code not corrupt the collection by inserting an element of the wrong type.

Following is the one of the methods from this group:

public static <E> List<E> checkedList(List<E> list, Class<E> type)

Let's understand the advantage of using these methods with an example.

Example

Let's say we have a third party library util method which supposed to square all integers elements of the provided list.

public class NotMyUtil {

  @SuppressWarnings("unchecked")
  public static void squared(List<Integer> list) {
      list.replaceAll(i -> Math.multiplyExact(i, i));
      List myList = list;
      myList.add("aString");
  }
}

The above method has a bug in it, it's adding a string element after unchecked assignment. Let's assume we don't know about the bug and use the method:

public class CollectionExample {

  public static void main(String[] args) {
      List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 4, 5));
      NotMyUtil.squared(list);
      IntStream intStream = list.stream()
                                .mapToInt(Integer::intValue);
      int sum = intStream.sum();
      System.out.println(sum);
  }
}

Output

Caused by: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
at java.util.stream.ReferencePipeline$4$1.accept(ReferencePipeline.java:210)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.IntPipeline.reduce(IntPipeline.java:456)
at java.util.stream.IntPipeline.sum(IntPipeline.java:414)
at com.logicbig.example.CollectionExample.main(CollectionExample.java:15)
... 6 more

The stack trace shows the exception is originating from our main class at the point where we are getting sum from inStream.

   at com.logicbig.example.CollectionExample.main(CollectionExample.java:15)

Let's use Collections.checkedList now:

public class TypeSafeCheckedCollectionExample {

  public static void main(String[] args) {
      List<Integer> list = new ArrayList<>(Arrays.asList(1, 3, 4, 5));
      list = Collections.checkedList(list, Integer.class);
      NotMyUtil.squared(list);
      IntStream intStream = list.stream()
                                .mapToInt(Integer::intValue);
      int sum = intStream.sum();
      System.out.println(sum);
  }
}

Output

Caused by: java.lang.ClassCastException: Attempt to insert class java.lang.String element into collection with element type class java.lang.Integer
at java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)
at java.util.Collections$CheckedCollection.add(Collections.java:3080)
at com.logicbig.example.NotMyUtil.squared(NotMyUtil.java:11)
at com.logicbig.example.TypeSafeCheckedCollectionExample.main(TypeSafeCheckedCollectionExample.java:14)
... 6 more

This time the exception is thrown at the right place, our checkList doesn't allow to add wrong type in it:

   at com.logicbig.example.NotMyUtil.squared(NotMyUtil.java:11)

In scenarios like above, by using Collections checkedXYZ methods, we can detect the problem way earlier.

Other checkedXYZ methods

Followings are the all Collections.checkedXYZ methods:

static <E> Collection<E> checkedCollection(Collection<E> c,
                                           Class<E> type)
static <E> Set<E> checkedSet(Set<E> s,
                             Class<E> type)
static <E> SortedSet<E> checkedSortedSet(SortedSet<E> s,
                                         Class<E> type)
static <E> List<E> checkedList(List<E> list,
                               Class<E> type)
static <K,V> Map<K,V> checkedMap(Map<K,V> m,
                                 Class<K> keyType,
                                 Class<V> valueType)
static <K,V> SortedMap<K,V> checkedSortedMap(SortedMap<K,V> m,
                                             Class<K> keyType,
                                             Class<V> valueType)

Example Project

Dependencies and Technologies Used :

  • JDK 1.8
  • Maven 3.3.9

Typesafe Checked Collections Select All Download
  • checked-collections
    • src
      • main
        • java
          • com
            • logicbig
              • example

See Also