3

I'm trying to make a method reference to a method which have a generic parameter specified in a class declaration. So I have:

public interface IExecutable<P extends IParameter> {

    void execute(P parameter);

}

public class Parameter implements IParameter {

    public void childSpecific() {
    ...
    }
}

public class TestClass {
    ...
    //somewhere in the code
    public void foo(Parameter parameter) {
        parameter.childSpecific();
    }

    public void test() {
        IExecutable<?> executable = this::foo; //compilation error
        // The type TestClass does not define inner(IParameter) that is applicable here
        executable.execute(new Parameter()); //compilation error as well
        // The method execute(capture#4-of ?) in the type IExecutable<capture#4-of ?> is not applicable for the arguments (Parameter)
    }
    ...
}

It's specific that I don't know the concrete generic type of the executable here. Using

IExecutable<Parameter> = ...

solves the problem immediately, but it's impossible for the case.

Clearly, I'm doing something wrong. But how to make it work?

Thx.

5
  • Does IExecutable<IParameter> work? Also, I think you are getting lots of warnings about missing generic types. Try to fix those. Commented May 27, 2015 at 0:16
  • 3
    If you have a reference of type IExecutable<?>, you wouldn't be able to invoke execute with anything other than the null literal as an argument. I therefore don't understand what you are trying to do. Commented May 27, 2015 at 0:18
  • There is an important difference between java7 and java8. In java7 this would work. In Java8 this evidently not. Commented May 27, 2015 at 0:28
  • 2
    @CommuSoft - I don't think java7 understands this::foo :) Commented May 27, 2015 at 1:29
  • @bayou.io: evidently not, but for equivalent cases it works different as well. Commented May 27, 2015 at 2:21

3 Answers 3

3

In this case, foo is not written to handle any IParameter other than Parameter. You could assign a reference to foo to a variable of type IExecutable<? extends IParameter>, however this means that it is an executable that handles some unknown type of IParameter (in this case, Parameter). Since the specific subtype is unknown, it would not be syntactically safe to pass any subtype of IParameter in to its execute method, since you don't know which it can handle within this scope!

What you need is another type variable instead of using a capture (the ?). This way you can specify that the IParameter you're passing in is the same type as the IParameter the executable accepts. You could introduce this with a new method, like I'm doing below:

public class TestClass {
  public static void foo(Parameter parameter) {
    parameter.childSpecific();
  }

  public static void main(String args) {
    execute(TestClass::foo, new Parameter());
  }

  public static <P extends IParameter> void execute(
        IExecutable<P> executable, P param) {
    executable.execute(param);
  }
}
Sign up to request clarification or add additional context in comments.

Comments

1

The type parameter P in your interface IExecutable is constrained to being a subtype of IParameter. Consider these two subtypes:

class Parameter implements IParameter { ... }
class AnotherParameter implements IParameter { ... }

Now, an IExecutable<?> is not more specific regarding the above mentioned constraint. In fact, the ? states that it is bound to an unknown subtype of IParameter, which could be Parameter or AnotherParameter (in my example).

With such a variable declaration, you face the two problems you mentioned.

  1. Your method foo(Parameter) does not match the more general constraint of an IExecutable<?>. As seen above, such an executable could be bound to AnotherParameter which clearly would violate the method signature of foo.

  2. Even if it matched, it cannot be used like you did. The compiler does not know to which type the ? actually was mapped. The only thing it knows: It must be a subtype of IParameter, but which one is not known. That means, the statement executable.execute(new Parameter()) is not allowed (as also executable.execute(new AnotherParameter())). The only parameter you are allowed to pass to execute is null.

Conclusion: Point 1 could be solved by declaring the variable executable with type IExecutable<? extends Parameter>. This matches the method signature of foo. But point 2 still does not allow the call to execute.

The only thing you can do is to declare the variable as

IExecutable<Parameter> executable = this::foo;

This will compile and allow the call to

executable.execute(new Parameter());

Comments

0

This line exposes failure in java type inference

IExecutable<?> executable = this::foo;

Let's look at it this way

IExecutable<?> executable = p->this.foo(p);

To compile it, java needs to know the meaning of foo(p). Before java8, the type of an expression is built on the types of sub-expressions; here, the type of p needs to be known 1st to resolve foo. But the type of p is not specified, it needs to be inferred from surrounding context. Here the context is IExecutable<? extends IParameter>, and p is inferred to IParameter - and method foo(Iparameter) does not exist.

In general, type inference faces a dilemma, does it infer from top down, or bottom up? Java8 defines an extremely complicated procedure for that, which is humanly impossible to understand:)

Workarounds: specify the type of p

IExecutable<?> executable = (Parameter p)->this.foo(p);

or specify a more specific target type

IExecutable<?> executable = (IExecutable<Parameter>)p->this.foo(p);

IExecutable<?> executable = (IExecutable<Parameter>)this::foo;

If you ask the language designers, they'd consider all of this is quite obvious ... but a programmer's best action is probably just try different things till it works, than to study the actual language spec.

1 Comment

Even with the various casts, it is not possible to do executable.execute(new Parameter()) with a IExecutable<?> executable.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.