Effective Java 3: item 31: using <? extends T> for producers, is not applicable for different data structures

70 Views Asked by At

This code from the Item 31:

public class Union {
    public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2) {
        // Bloch used HashSet, but I want natural ordering and still make freedom of producer type
        Set<E> result = new TreeSet<>(s1);
        result.addAll(s2);
        return result;
    }

    public static void main(String[] args) {
        Set<Integer> integers = new HashSet<>();
        integers.add(1);
        integers.add(3);
        integers.add(5);

        Set<Double> doubles = new HashSet<>();
        doubles.add(2.0);
        doubles.add(4.0);
        doubles.add(6.0);

        Set<Number> numbers = union(integers, doubles);
        System.out.println(numbers);
    }
}

Compiles, but gives runtime error:

Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Double (java.lang.Integer and java.lang.Double are in module java.base of loader 'bootstrap')
    at java.base/java.lang.Double.compareTo(Double.java:49)
    at java.base/java.util.TreeMap.put(TreeMap.java:566)
    at java.base/java.util.TreeSet.add(TreeSet.java:255)
    at java.base/java.util.AbstractCollection.addAll(AbstractCollection.java:352)
    at java.base/java.util.TreeSet.addAll(TreeSet.java:312)
    at effectivejava.chapter5.item31.Union.union(Union.java:12)
    at effectivejava.chapter5.item31.Union.main(Union.java:27)

The problem seems to be in using TreeSet instead of HashSet, because there is apparently no natural sort order of types Number and Integer. But this would make me thing to never use of TreeSet/TreeMap in cases where generics are used, because the assumed freedom of types would turn to not work if an operation of the underlying data structure cannot be accomplished (in this case, the operation of sort, because of natural ordering in trees, which uses comparisons operators that could not be used for "any producer type", but only the same type). Is there way in Java to use generics as well as the features of specific data structures at the same time?

Maybe, if I used this union method declaration:

public static <E extends Comparable<? super E>> Set<E> union2(
            Set<? extends E> s1, Set<? extends E> s2
    ) 

That would allow me to properly used trees and catch types that does not implement Comparable at compile time?

1

There are 1 best solutions below

0
knittl On

In the signature

<E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2)

s1 and s2 are of different types. <? extends E> and <? extends E> do not describe the same type, each wildcard will be bound to a different type at compile time.

Furthermore, Integer implements Number, Comparable<Integer> and Double implements Number, Comparable<Double>. Number does not implement the Comparable interface. So constraining E to extend Comparable<E> won't help either, because there is no such E which is valid for both Integer and Double instances.

Integers and doubles are not comparable with each other, thus cannot be managed within a single TreeSet unless you provide a custom Comparator implementation at construction time which is able to compare Numbers of any concrete type.

Here would be one such comparator implementation:

Comparator<Number> comparator = Comparator.comparingDouble(Number::doubleValue);

But of course this means that your method cannot handle arbitrary objects anymore, only objects of classes which implement the Number interface. The widest interface of your function that you can provide is a method which takes two collections of Comparable<T> instances; but both collections must contain the same types: you cannot compare integers to strings, doubles to lists, nor maps to bigdecimals.