Reputation: 84844
There's a class A
that may be somehow mapped to another class B
or other one:
class A {}
class B {
final A a;
B(A a) {
this.a = a;
}
}
There's also a mapper factory that returns mappers from A
to another class based on the second class type passed as an argument:
class Mapper {
static Function<A, B> a2bmapper = B::new;
static <R> Function<A, R> findMapper(Class<R> cls) {
if(cls == B.class) {
return a2bmapper;
}
return null;
}
}
The problem is that on this line:
return a2bmapper;
java compiler issues incompatible types: Required R, Found B and IDE suggests casting to Function<A,R>
. Why is that? R
is just a generic type and should be substituted with B
.
Upvotes: 0
Views: 110
Reputation: 30696
I'm sorry about my bad english, so I take examples as more as possible. I wish you can understand what I meaning.
let take a simple example where the parameter type is an Object.
String string(Object value){
return value instanceof String ? value : null;
}
the example above still need to down-casting to a String
since the reference type of the value
is an Object
:
String string(Object value){
return value instanceof String ? (String)value : null;
}
AND then we expand the example with generic arguments, you can solved by forcing cast a String
to a type of T
is bounded where the method expression statement assigned. and casting will be failed on runtime if the bounded T
is not the type of String
class was derived from.
<T,R> T string(R value){
// the code cast R to T will generate a compile unchecked warnings.
return value instanceof String ? (T) value : null;
}
// the code is ok on compile stage, but will throw a ClassCastException on runtime.
Date date= string("bad");
// the code is ok both on compile & runtime.
// because a unbounded generic argument which will reference to Object .
string("ok");
base on the examples above then you can solve your code by casting the function to Function<T,R>
:
static <R> Function<A, R> findMapper(Class<R> cls) {
if (cls == B.class) {
return (Function<A, R>) a2bmapper;
}
return null;
}
we know the generic parameter R
is B
but still generate an unchecked compile warning due to the compiler don't know. and then we can do the following let the compiler to know it:
static <R> Function<A, R> findMapper(Class<R> cls) {
if (cls == B.class) {
return a2bmapper.getClass().cast(a2bmapper);
}
return null;
}
Upvotes: 0
Reputation: 206896
Generics are purely a compile time thing in Java. The compiler uses generics to check if your code is type-safe, at compile time of course.
But there are limits to the checks that the compiler does. The checks the compiler does do not go so far that the compiler is going to analyze the if
statement to conclude that at the point of the return
statement, R
is always equal to B
.
What if it were more complicated than one if
statement - would you still expect the compiler to analyze all possible paths through the code and conclude that it's safe? The logic could become arbitrarily complex.
Upvotes: 3
Reputation: 272705
findMapper
is supposed to return Function<A, R>
where R
can be anything, not necessarily B
.
Let's suppose we call this method with String.class
. Now R
is String
. The function is supposed to return a Function<A, String>
, but you are returning a Function<A, B>
instead. The compiler sees this possibility and says no to you.
"But I checked whether R
is B
before I return though!" you shouted. Well, that check is done at runtime, which the compiler don't care much about.
And because of type erasure, every generic parameter is just Object
at runtime. That's why you can cast it to Function<A, B>
to fix this problem.
Upvotes: 5
Reputation: 15704
While, semantically, there is no way for this method to return anything other than a Function<A,B>
when you pass in a B.class
, the compiler is not smart enough to realise this. A mistaken change to the if
condition, for example, would be enough to break your semantics. The JLS will usually err on the side of caution on these kinds of situations.
This is a situation where you will need an explicit cast to a Function<A,R>
in order to do as you wish.
Upvotes: 4