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?
In the signature
s1ands2are 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>andDouble implements Number, Comparable<Double>.Numberdoes not implement theComparableinterface. So constrainingEto extendComparable<E>won't help either, because there is no suchEwhich is valid for bothIntegerandDoubleinstances.Integers and doubles are not comparable with each other, thus cannot be managed within a single
TreeSetunless you provide a customComparatorimplementation at construction time which is able to compare Numbers of any concrete type.Here would be one such comparator implementation:
But of course this means that your method cannot handle arbitrary objects anymore, only objects of classes which implement the
Numberinterface. The widest interface of your function that you can provide is a method which takes two collections ofComparable<T>instances; but both collections must contain the same types: you cannot compare integers to strings, doubles to lists, nor maps to bigdecimals.