Given the following example (using JUnit with Hamcrest matchers):
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
This does not compile with the JUnit assertThat method signature of:
public static <T> void assertThat(T actual, Matcher<T> matcher)
The compiler error message is:
Error:Error:line (102)cannot find symbol method
assertThat(java.util.Map<java.lang.String,java.lang.Class<java.util.Date>>,
org.hamcrest.Matcher<java.util.Map<java.lang.String,java.lang.Class
<? extends java.io.Serializable>>>)
However, if I change the assertThat method signature to:
public static <T> void assertThat(T result, Matcher<? extends T> matcher)
Then the compilation works.
So three questions:
Why exactly doesn't the current version compile? Although I vaguely understand the covariance issues here, I certainly couldn't explain it if I had to.
Is there any downside in changing the assertThat method to Matcher<? extends T>? Are there other cases that would break if you did that?
Is there any point to the genericizing of the assertThat method in JUnit? The Matcher class doesn't seem to require it, since JUnit calls the matches method, which is not typed with any generic, and just looks like an attempt to force a type safety which doesn't do anything, as the Matcher will just not in fact match, and the test will fail regardless. No unsafe operations involved (or so it seems).
For reference, here is the JUnit implementation of assertThat:
public static <T> void assertThat(T actual, Matcher<T> matcher) {
assertThat("", actual, matcher);
}
public static <T> void assertThat(String reason, T actual, Matcher<T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason);
description.appendText("\nExpected: ");
matcher.describeTo(description);
description
.appendText("\n got: ")
.appendValue(actual)
.appendText("\n");
throw new java.lang.AssertionError(description.toString());
}
}
First - I have to direct you to http://www.angelikalanger.com/GenericsFAQ/JavaGenericsFAQ.html -- she does an amazing job.
The basic idea is that you use
<T extends SomeClass>
when the actual parameter can be SomeClass or any subtype of it.
In your example,
Map<String, Class<? extends Serializable>> expected = null;
Map<String, Class<java.util.Date>> result = null;
assertThat(result, is(expected));
You're saying that expected can contain Class objects that represent any class that implements Serializable. Your result map says it can only hold Date class objects.
When you pass in result, you're setting T to exactly Map of String to Date class objects, which doesn't match Map of String to anything that's Serializable.
One thing to check -- are you sure you want Class<Date> and not Date? A map of String to Class<Date> doesn't sound terribly useful in general (all it can hold is Date.class as values rather than instances of Date)
As for genericizing assertThat, the idea is that the method can ensure that a Matcher that fits the result type is passed in.
Thanks to everyone who answered the question, it really helped clarify things for me. In the end Scott Stanchfield's answer got the closest to how I ended up understanding it, but since I didn't understand him when he first wrote it, I am trying to restate the problem so that hopefully someone else will benefit.
I'm going to restate the question in terms of List, since it has only one generic parameter and that will make it easier to understand.
The purpose of the parametrized class (such as List<Date> or Map<K, V> as in the example) is to force a downcast and to have the compiler guarantee that this is safe (no runtime exceptions).
Consider the case of List. The essence of my question is why a method that takes a type T and a List won't accept a List of something further down the chain of inheritance than T. Consider this contrived example:
List<java.util.Date> dateList = new ArrayList<java.util.Date>();
Serializable s = new String();
addGeneric(s, dateList);
....
private <T> void addGeneric(T element, List<T> list) {
list.add(element);
}
This will not compile, because the list parameter is a list of dates, not a list of strings. Generics would not be very useful if this did compile.
The same thing applies to a Map<String, Class<? extends Serializable>> It is not the same thing as a Map<String, Class<java.util.Date>>. They are not covariant, so if I wanted to take a value from the map containing date classes and put it into the map containing serializable elements, that is fine, but a method signature that says:
private <T> void genericAdd(T value, List<T> list)
Wants to be able to do both:
T x = list.get(0);
and
list.add(value);
In this case, even though the junit method doesn't actually care about these things, the method signature requires the covariance, which it is not getting, therefore it does not compile.
On the second question,
Matcher<? extends T>
Would have the downside of really accepting anything when T is an Object, which is not the APIs intent. The intent is to statically ensure that the matcher matches the actual object, and there is no way to exclude Object from that calculation.
The answer to the third question is that nothing would be lost, in terms of unchecked functionality (there would be no unsafe typecasting within the JUnit API if this method was not genericized), but they are trying to accomplish something else - statically ensure that the two parameters are likely to match.
EDIT (after further contemplation and experience):
One of the big issues with the assertThat method signature is attempts to equate a variable T with a generic parameter of T. That doesn't work, because they are not covariant. So for example you may have a T which is a List<String> but then pass a match that the compiler works out to Matcher<ArrayList<T>>. Now if it wasn't a type parameter, things would be fine, because List and ArrayList are covariant, but since Generics, as far as the compiler is concerned require ArrayList, it can't tolerate a List for reasons that I hope are clear from the above.
It boils down to:
Class<? extends Serializable> c1 = null;
Class<java.util.Date> d1 = null;
c1 = d1; // compiles
d1 = c1; // wont compile - would require cast to Date
You can see the Class reference c1 could contain a Long instance (since the underlying object at a given time could have been List<Long>), but obviously cannot be cast to a Date since there is no guarantee that the "unknown" class was Date. It is not typsesafe, so the compiler disallows it.
However, if we introduce some other object, say List (in your example this object is Matcher), then the following becomes true:
List<Class<? extends Serializable>> l1 = null;
List<Class<java.util.Date>> l2 = null;
l1 = l2; // wont compile
l2 = l1; // wont compile
...However, if the type of the List becomes ? extends T instead of T....
List<? extends Class<? extends Serializable>> l1 = null;
List<? extends Class<java.util.Date>> l2 = null;
l1 = l2; // compiles
l2 = l1; // won't compile
I think by changing Matcher<T> to Matcher<? extends T>, you are basically introducing the scenario similar to assigning l1 = l2;
It's still very confusing having nested wildcards, but hopefully that makes sense as to why it helps to understand generics by looking at how you can assign generic references to each other. It's also further confusing since the compiler is inferring the type of T when you make the function call (you are not explicitly telling it was T is).
The reason your original code doesn't compile is that <? extends Serializable> does not mean, "any class that extends Serializable," but "some unknown but specific class that extends Serializable."
For example, given the code as written, it is perfectly valid to assign new TreeMap<String, Long.class>() to expected. If the compiler allowed the code to compile, the assertThat() would presumably break because it would expect Date objects instead of the Long objects it finds in the map.
One way for me to understand wildcards is to think that the wildcard isn't specifying the type of the possible objects that given generic reference can "have", but the type of other generic references that it is is compatible with (this may sound confusing...) As such, the first answer is very misleading in it's wording.
In other words, List<? extends Serializable> means you can assign that reference to other Lists where the type is some unknown type which is or a subclass of Serializable. DO NOT think of it in terms of A SINGLE LIST being able to hold subclasses of Serializable (because that is incorrect semantics and leads to a misunderstanding of Generics).
I know this is an old question but I want to share an example that I think explains bounded wildcards pretty well. java.util.Collections offers this method:
public static <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
If we have a List of T, the List can, of course, contain instances of types that extend T. If the List contains Animals, the List can contain both Dogs and Cats (both Animals). Dogs have a property "woofVolume" and Cats have a property "meowVolume." While we might like to sort based upon these properties particular to subclasses of T, how can we expect this method to do that? A limitation of Comparator is that it can compare only two things of only one type (T). So, requiring simply a Comparator<T> would make this method usable. But, the creator of this method recognized that if something is a T, then it is also an instance of the superclasses of T. Therefore, he allows us to use a Comparator of T or any superclass of T, i.e. ? super T.
what if you use
Map<String, ? extends Class<? extends Serializable>> expected = null;
Related
I want to define a function which can convert a kind of Set to another, like convert HashSet to LinkedHashSet. Here is the function declaration. The sp is sharedpreferences.
public Set<String> decodeStringSet(String key, #Nullable Set<String> defaultValue, Class<? extends Set> cls){
Set<String> result = sp.getStringSet(key, defaultValue);
if(result == null){
return defaultValue;
}
else {
String[] array = result.toArray(new String[0]);
Set<String> a;
try {
a = cls.newInstance();
} catch (IllegalAccessException | InstantiationException var7) {
return defaultValue;
}
a.addAll(Arrays.asList(array));
return a;
}
}
However, the compiler remind me that "Unchecked assignment: '? extends java.util.Set' to 'java.util.Set<java.lang.String>'" on "a = cls.newInstance();". I don't know how to change cls to cls<java.lang.String>.
The warning is unavoidable. Isolate it in a helper method and toss the appropriate #SuppressWarnings at it. Or, refactor how this thing works. In general, the generics of Class<?> are weird and don't work well; if you try to write code that relies on the generics part to make it work, it's likely to result in many situations where you can't avoid these warnings, and the API is suboptimal.1
One tricky way to do what you're trying to do here in a one-size-fits-all way is so-called Super Type Tokens. You can search the web for this concept, because for what you're specifically doing here, STTs are overkill. What you are looking for, is a supplier.
You want the caller not to pass you the type of a set. No. You want the caller to pass you a piece of code that, if executed, creates the set.
While we're at it, let's get rid of the array, you're shifting the elements through that array for absolutely no sensible reason.
public <S extends Set<String>> S decodeStringSet(String key, #Nullable Set<String> defaultValue, Supplier<S> setMaker) {
Set<String> result = sp.getStringSet(key, defaultValue);
if(result == null) return defaultValue;
S a = setMaker.get();
a.addAll(result);
return a;
}
This code can be used as follows:
LinkedHashSet<String> mySet = decodeStringSet("myKey", null, LinkedHashSet::new);
Perhaps you're unfamiliar with this syntax. new LinkedHashSet() will, when you run that code, create a LinkedHashSet. In contrast, LinkedHashSet::new will, when you run that code, produce an object that can be asked to create a LinkedHashSet, by invoking its get() method. One does the act right this very moment. The other wraps 'do the act' into a little machine. You can hand the machine to other code, or press the button on the machine to make it do the act, and you can press the button as often as you feel like.
[1] Need some more explanations as to why relying on the generics of j.l.Class is awkward and not a good idea?
A class object simply cannot, itself, represent generics, whereas generics can represent generics. That is: List<List<String>> is perfectly fine. However, Class<List<String>> does not make sense. You can write it, (j.l.Class does not have hardcoded rules to keep sanity alive in the langspec), but it doesn't represent anything: There's just one class object that represents the type j.u.List. This one object cannot therefore represent the generics; you can't have one class object representing List<String> and another representing List<Integer>. Less important, but still annoying - there are things class objects can represent that generics cannot. int.class is types as Class<Integer> but this isn't quite right.
Hence, in your example, the compiler consider Class<? extends Set> as problematic; it's got a raw type inside the generics. However, it is technically correct, in that it is not possible to represent e.g. a Set<T>, merely 'a Set, whose generics are unknown, given that j.l.Class objects cannot represent them'.
Lastly, classes basically only produce (the P in PECS - which explains what the difference is between <Number>, <? extends Number>, and <? super Number>); it is mentally difficult to fathom the difference between Class<? extends String> and Class<String>, because it's an irrelevant difference, given that j.l.Class only produces. And yet, often you really do need to write Class<? extends String> because if you don't, the compiler refuses to compile your code for imaginary, irrelevant reasons. That's because, again, j.l.Class is not hardcoded in the lang spec: The compiler does not know that there is no effective distinction between Class<T> and Class<? extends T>, and java does not have a way to mark off a given generics param as forced Produces-only or some such.
I understand upper bound clearly, but don't fully understand lower bound. As for example I have this code:
public class Main<T> {
private T t;
public Main(T t) {
this.t = t;
}
private static class Base {}
public static void main(String[] args) {
Main<? super Base> main = new Main<>(new StringBuilder());
System.out.println(main.t.getClass());
}
}
Why there is no error during compile despite of StringBuilder isn't super class of Base. As I thought it would be illegal to provide type which is irrelevant (I know that it's impossible to assign non child class to t after type inference). Also it works with collections, does it means that collection could possibly store no child, no super class of Base? Please do not link me to PECS questions, I have read them a lot.
Edit: I saw it a little late that #markspace's answer is nearly the same and was posted before this. I am only retaining this here due different styles of explanation, that is all.
This should be due to the diamond operator <> due to which automatic inference happens based on the most possible route, in this case the constructor parameter.
I could not locate the official description of the diamond operator, but various sources describe it to the effect - the Java compiler does a type inference when given the diamond operator feature determines the most suitable constructor declaration that matches the invocation.
If you change your declaration to GenericsSuper<? super Base> main = new GenericsSuper<StringBuilder>(new StringBuilder()) you will get your expected error.
Without this explicit declaration, <> leads to <Object> due to the super restriction, and consequently anything is allowed, because:
The code in GenericsSuper has no problem with StringBuilder. It just wants T.
Base has a common super with StringBuilder in Object.
I'm going to add this as an actual answer.
I think <T> here is Object, which is a legal super type of both Base and StringBuilder. The way you are doing this / thinking about this is flawed, basically. Check out the answer to this question (ignore the duplicate text for PECS):
Difference between <? super T> and <? extends T> in Java
Notice the examples that the accepted answer gives, especially the one with <Object>:
List<? super Integer> foo3 = new ArrayList<Integer>(); // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>(); // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>(); // Object is a superclass of Integer
Since you're allowing the diamond operator to "figure out" the type of T, it "figures out" that Object works and uses that. Untested, but check if this also compiles:
Main<? super Base> main = new Main<Object>(new StringBuilder());
I have a method that is supposed to analyze a String and determine the correct Class<T>, where T extends BaseClass.
Suppose that BaseClass is extended by Child1, Child2 and Child3.
In my method, I want to do something like this:
public <T extends BaseClass> Class<T> from(String number) {
if (number.contains("Child1")) {
return Child1.class;
} else if (number.contains("Child2")) {
return Child2.class;
} else if (number.contains("Child3")) {
return Child3.class;
}
throw new UnsupportedOperationException("Cannot recognize class from " + number);
}
The problem is that the above method doesn't compile on all return statements, because Required type is Class<T>, provided is Class<Child1> (same for Child2 and Child3 of course).
Hence, I am forced to (unchecked) cast:
return (Class<T>) Child1.class;
... which of course works fine, since Child1.class is compatible with Class<T>.
How can I do what I'm trying to do in a cleaner way (without having the warning)?
... which of course works fine, since Child1.class is compatible with Class<T>.
That's incorrect. Understandable, as you're making a common mistake.
The fix
The fix is to have the return type be Class<? extends T> instead. But, scratch that...
But this code is bad style
Generics serve to link things. Any type variable should therefore be declared once and used in at least 2 places, or it is one of:
useless.
actively misleading.
a type-safety breaking hack.
I assume you intended none of that. You have declared T once, and are using it once, which therefore means it's incorrect. The correct version is:
public Class<? extends BaseClass> from(String number) { ... }
This does everything you want. Specifically, something like this:
BaseClass bc = from("Child3").getConstructor().newInstance();
will just compile, no need for casts (you will need to festoon this up with rather a lot of try/catch, but no need for a cast, at least), and the compiler will not emit type safety warnings either. Which, presumably, is what you're trying to accomplish.
Explanation
In java, typing relationships are covariant. That means any type can stand in for any of its supertypes.
In other words:
Number n = Integer.valueOf(5);
is valid java.
But the thing that generics are for simply doesn't adhere to this rule. Here is a trivial example. Imagine generics was just as covariant as basic type usage in java would be. Then I could write this, it would compile, and that would be bad, because this code would then be breaking the typing system:
List<Integer> listOfInts = List.of(1, 2, 3);
List<Number> listOfNums = listOfInts;
listOfNums.add(Double.valueOf(5.5));
int value = listOfInts.get(3);
Go through the above code and you realize there's a fundamental issue here.
The fix is that generics are invariant - a type is a valid standin only for itself; not for anything else.
In other words, the one and only thing you can assign to a List<Number> is an expression of type List<Number> or perhaps ArrayList<Number> ( because the non-generics part is covariant, we're talking only about the stuff in the <>), not List<Integer>.
That's the fix - that's why the above code isn't actually a problem for the typing system in java - it simply won't compile.
Now, when there is no such thing as 'adding', this becomes dubious, and Class is just such a type: Yes it's got a type param but you can't 'break stuff' if generics was covariant. Unfortunately, the generics feature of java does not ship with a gigantic list of 'the generics on THIS type can be covariant, but here they cannot be'.
Instead, java lets you choose your variance, and the APIs change to reflect what that means:
List<? extends Number> list = someListOfIntegers; // co-variance
List<? super Number> list = someListOfObject; // contra-variance
List list = someListOfAnything; // legacy-variance a.k.a. raw
List<Number> list = someListOfNumber; // invariance
Of course, you don't get this stuff for free: That covariant list (List<? extends Number>), you cannot call add on this list, at all. Well, the literal .add(null), because null is a standin for all types, that works, but nothing else will, add is always a compile time error. That's the cost. If you opt out of all add methods on a list, then and only then can you write a method that accepts as parameter a list of numbers, or integers, or doubles, etc.
With Class it gets dubious (as there's no 'writing'), but the co/contra/invariant system is baked into generics and doesn't care about the fact that Class doesn't have any add-style methods in it.
I have the following member in my class:
List<? extends SomeObject> list;
When I try to do:
list.add(list.get(0));
I get:
Test.java:7: error: no suitable method found for add(CAP#1)
list.add(list.get(0));
^
method Collection.add(CAP#2) is not applicable
(argument mismatch; Object cannot be converted to CAP#2)
method List.add(CAP#2) is not applicable
(argument mismatch; Object cannot be converted to CAP#2)
where CAP#1,CAP#2 are fresh type-variables:
CAP#1 extends Object from capture of ? extends Object
CAP#2 extends Object from capture of ? extends Object
My question is twofold:
Why doesn't it compile? Why can't I pass get()'s result to add()?
And how can I achieve this in another way without resorting to casting?
I understand that in a method with <T extends SomeObject> I can't just say:
T someObject = list.get(0);
list.add(someObject);
since my T could be another extension than the ? extension.
I also understand I can't say:
List<? extends SomeObject> list1;
List<? extends SomeObject> list2;
list1.add(list2.get(0));
But since the add and the get should work with the same generic type in list.add(list.get(0)) I don't understand why the compiler doesn't accept it.
What I really need is
[something of type T where T is whatever was used to instantiate list] someObject = list.get(0);
list.add(someObject);
so that I can later
list.add(someObject);
I don't think I should have to template my whole class to achieve this, should I?
class MyClass<T extends SomeObject> {
List<T> list;
and then later a method with
T someObject = list.get(0);
of course works, but screws other parts of my code.
So the first question is why doesn't this work, second question is what's the best workaround?
My question is twofold, why can't I do:
list.add(list.get(0));
Because the compiler isn't smart enough to know that you're adding something from list back into list. The compiler doesn't consider list.get(0) to have anything to do with list once it is evaluated: it's just "some expression" of type ? extends SomeObject.
To solve this, add a method with its own type variable:
private <T> void addFirst(List<T> list) {
list.add(list.get(0));
}
and replace the original list.add(list.get(0)); with an invocation of this:
addFirst(list);
This only defines a type variable on the method, and does not need to be visible outside the class, so you don't need a class-level type variable.
It's perhaps worth pointing out this is analogous to the Collections.swap method: that's using set rather than add, but, from a generics point of view, it's the same thing:
#SuppressWarnings({"rawtypes", "unchecked"})
public static void swap(List<?> list, int i, int j) {
// instead of using a raw type here, it's possible to capture
// the wildcard but it will require a call to a supplementary
// private method
final List l = list;
l.set(i, l.set(j, l.get(i)));
}
This takes an approach which is technically type-safe, and does avoid casts; but it's a bit gross, because it uses raw types.
I would imagine that it is only like this for backwards-compatibility reasons. Given a chance to write it again, you could just define a type variable as in the addFirst method above.
When use wildcards, we should follow The Get and Put Principle which introduced in Java Generics and Collections:
The Get and Put Principle: use an extends wildcard when you only get values out of a
structure, use a super wildcard when you only put values into a structure, and don’t use
a wildcard when you both get and put.
In your case, don't use a wildcard as you both get element from list and put element to list.
I have a method that returns List<Super>.
say
List<Super> myMethod();
When calling that method, I want to cast the returned list to List<Sub>, knowing that its runtime type will be a List of Sub.
I know that List<Sub> subs = (List<Sub>) myMethod() won't work because generics are invariant.
However, List<? super Sub> subs = myMethod() works, the compile time type becomes List<Object>, so subs.get(0).mySubMethod() won't work.
What I end up with is that I have to explicitly cast it as ((Sub) subs.get(0)).mySubMethod()
How do I do this correctly without explicit casting on each element?
The shortest version is:
List<Sub> subs = (List) myMethod();
List is a "raw type"; you can convert from List to any List<...> without a cast.
Note that this is circumventing the type system in a fundamentally incorrect way; the list was constructed as an ArrayList<Super> (or similar), so it's actually incorrect to trick the compiler into letting you refer to it with a reference of type List<Sub>, even if it so happens that all of the list elements are of type Sub. (The same is true of msandiford's longer version above; it may look more generics-friendly, but it's actually just another way of doing the same fundamentally incorrect thing.)
But, due to type erasure, you can generally get away with it.
One correct approach would be to make the method itself generic:
<T extends Super> List<T> myMethod(final Class<T> clazz) {
final List<T> ret = Collections.checkedList(new ArrayList<T>(), clazz);
// ... add elements to ret ...
return ret;
}