Reputation: 173
I am stuck trying to understand what exactly is going on over here. The two ways that the copy_v1 method is being used are shown below. The first way (1) produces a compile error. But the second way (2) does not produce any compile error (nor does it produce any runtime error when I try to execute the program once I comment out (1)). When I replace (2) with copy_v1(new Wrapper<String>("Hello"), new Wrapper<Object>(new Object()));
, I end up getting a compile error on (2) as well. Not sure what is happening. How is the code on (1) any different from (2)? Can someone shed some light on this please.
Thanks.
public class SOQuestion {
public static <T> void copy_v1(Wrapper<T> source, Wrapper<T> dest) {
T srcObj = source.getRef();
dest.setRef(srcObj);
}
public static void main(String[] args) {
Wrapper<Object> objectWrapper = new Wrapper<>(new Object());
Wrapper<String> stringWrapper = new Wrapper<>("Hello");
copy_v1(stringWrapper, objectWrapper); // Compile error on this line (1)
copy_v1(new Wrapper<>("Hello"), new Wrapper<>(new Object())); // But no error on this line (2)
}
}
class Wrapper<T> {
private T ref;
Wrapper(T ref) {this.ref = ref;}
public T getRef() {return this.ref;}
public void setRef(T ref) {this.ref = ref;}
}
Upvotes: 0
Views: 117
Reputation: 271020
In copy_v1(stringWrapper, objectWrapper);
, the generic parameter T
for copy_v1
cannot be inferred. There is actually no valid type for T
, such that the call is valid.
If T
is String
, then the second argument cannot be converted to Wrapper<String>
. Wrapper<Object>
is not a kind of Wrapper<String>
, because you can call setRef(new Object())
on the former, but not on the latter. I know that you are not doing that in copy_v1
, but the compiler doesn't look at what happens in the method to determine whether a call is valid.
Similarly, if T
is Object
, then the first argument cannot be converted to Wrapper<Object>
. Wrapper<String>
is not a kind of Wrapper<Object>
. You are guaranteed to get a String
when you call getRef
on the former, but not on the latter.
However, in copy_v1(new Wrapper<>("Hello"), new Wrapper<>(new Object()));
, you are asking the compiler to infer the type arguments for the Wrapper
s too. In that case, the compiler would try its best to make your method call valid, and infer it like this:
SOQuestion.<Object>copy_v1(new Wrapper<Object>("Hello"), new Wrapper<Object>(new Object()));
T
for copy_v1
is Object
, and the T
s for both Wrapper
s are Object
. Now everything is valid. new Wrapper<Object>
accepts an Object
parameter, and "Hello"
can be passed to that, because String
inherits from Object
.
Note that if you change your copy_v1
to this:
public static <T> void copy_v1(Wrapper<? extends T> source, Wrapper<? super T> dest) {
T srcObj = source.getRef();
dest.setRef(srcObj);
}
Then doing copy_v1(stringWrapper, objectWrapper);
is fine. This limits what you can do with dest
and source
inside the method, but allowing callers to pass e.g. Wrapper<String>
to dest
when it's supposed to take a Wrapper<Object>
.
Upvotes: 5
Reputation: 5980
When using new Wrapper<String>("Hello")
as an argument to copy_v1
, the inferred type is Wrapper<Object>
, whereas with your first example, the type of objectWrapper
is Wrapper<String>
.
Essentially:
copy_v1(new Wrapper<>("Hello"), new Wrapper<>(new Object()));
is equivalent to:
Wrapper<Object> objectWrapper = new Wrapper<>(new Object());
Wrapper<Object> stringWrapper = new Wrapper<>("Hello");
copy_v1(stringWrapper, objectWrapper);
which isn't quite what you have in your first case.
Upvotes: 2