Opal
Opal

Reputation: 84844

Why returned function needs additional casting (incompatible types)?

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

Answers (4)

holi-java
holi-java

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

Jesper
Jesper

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

Sweeper
Sweeper

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

Joe C
Joe C

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

Related Questions