Home > Software design >  Using wildcard with Generic method
Using wildcard with Generic method

Time:06-30

Would like to get some advise on it

My class structure is like this

public abstract class Base {}
public class Derived1 extends Base {}
public class Derived2 extends Base {}

public class ServiceClass {
    public String method1(Derived1 derived1) {
        return "derived1";
    }
    public String method2(Derived2 derived2) {
        return "derived2";
    }
}

In my main code (MainProg),I am trying to use the same function to refer to the 2 methods in service class and seeing the compilation error as put in the comment

public class MainProg {
    public static void main(String[] args) {
        ServiceClass serviceClass = new ServiceClass();

        //Incompatible types: Base is not convertible to Derived1
        Function<? extends Base,String> function1 = serviceClass::method1; 

        //Incompatible types: Object is not convertible to Derived1            
        Function<?,String> function2 = serviceClass::method1;
            
        //Incompatible types: Base is not convertible to Derived2
        Function<? super Base,String> function3 = serviceClass::method2;
    }
}

Is there a way to declare my function object so that same function object can be used to refer to methods taking different type of arguments?

CodePudding user response:

Yes, by using an intermediary variable:

    Function<Derived1, String> function1 = serviceClass::method1;
    Function<Derived2, String> function2 = serviceClass::method2;        
    Function<? extends Base, String> function = condition ? function1 : function2;

That said, you won't be able to invoke function, because you don't know whether to pass a Derived1 or a Derived2, or as the compiler puts it:

    function.apply(new Derived2()); 
    // error: The method apply(capture#1-of ? extends Base) in the type Function<capture#1-of ? extends Base,String> is not applicable for the arguments (Derived2)

You could, however, use generics to abstract over the argument type:

<T extends Base> invokeSafely(Function<T, String> function, T argument) {
    function.apply(argument);
}

which would allow you to write

invokeSafely(serviceClass::method1, new Derived1());
invokeSafely(serviceClass::method2, new Derived2());

but not

invokeSafely(function, new Derived1()); // same error as before

CodePudding user response:

//Incompatible types: Base is not convertible to Derived1
Function<? extends Base,String> function1 = serviceClass::method1; 

It's some weird quirk of type inference that I don't fully understand. It's inferring Function<Base,String> from the left-hand side and trying to apply it to the right-hand-side.

You can fix it like this. The cast is safe:

Function<? extends Base,String> function1 = (Function<Derived1, String>) serviceClass::method1;

or this:

Function<Derived1, String> tmp = serviceClass::method1;
Function<? extends Base,String> function1 = tmp;

It's worth noting that you can never invoke this function (except with null), since you have thrown away the knowledge of what type of argument it expects. ? extends Base is something that extends Base (i.e. Derived1 or Derived2) but we don't know which.

//Incompatible types: Object is not convertible to Derived1            
Function<?,String> function2 = serviceClass::method1;

Same issue as the first one, except it's inferring Object instead of Base.

//Incompatible types: Base is not convertible to Derived2
Function<? super Base,String> function3 = serviceClass::method2;

Derived1 or Derived2 are not superclasses of Base. They are subclasses. Only Object is a superclass of Base. method2 does not expect Object.

  • Related