4

I'm attempting to write some code that receives various implementations of the classes in the java.util.function.* package, but I keep running against a certain compiler error for a specific syntax that I'd really like to get around (explicit type arguments like obj.<Type>foo(/*...*/) work, but they lack grace).

In short, I'd like to be able to always use method references where I can, and for some reason I don't understand why the compiler can't understand the references explained below.

Suppose the following two classes (implementation is irrelevant):

Some entity class with getter/setter methods:

class Thing {
  public List<String> getThings() {
    return null;
  }

  public void setThings(List<String> things) {
  // ...
  }
}

Some other class that performs actions on instances of that entity class:

class FooBar<A> {
  public <B> void foo(Function<A, List<B>> f) {
    // ...
  }

  public <B> void bar(BiConsumer<A, List<B>> f) {
    // ...
  }

  public <B> void wuz(Function<A, List<B>> x, BiConsumer<A, List<B>> y) {
    // ...
  }
}

When performing calls to these methods the compiler gives me various errors:

// create an instance of the handler class
FooBar<Thing> fb = new FooBar<>();

Ex. 1) Calling the method that expects a function works just fine, no compile errors:

fb.foo(Thing::getThings);

Ex. 2) Yet calling the method that expects a biconsumer gives me this:

fb.bar(Thing::setThings);
// The type Thing does not define setThings(Thing, List<B>) that is applicable here

Ex. 3) As expected, explicitly stating the type works just fine, no compile errors:

fb.<String>bar(Thing::setThings);

Ex. 4) Yet when I write out the lambda longhand, it gives me a different compile error (stating the types in the lambda argument works fine though):

fb.bar((thing, things) -> thing.setThings(things));
// The method setThings(List<String>) in the type Thing is not applicable for the arguments (List<Object>)

Ex. 5) And when calling the method that expects both, I get a different compile error for each argument:

fb.wuz(
  Thing::getThings,
  // The type of getThings() from the type Thing is List<String>, this is incompatible with the descriptor's return type: List<B>

  Thing::setThings
  // The type Thing does not define setThings(Thing, List<B>) that is applicable here
);

Ex. 6) Again, as expected, stating the type explicitly works fine again, no compile errors:

fb.<String>wuz(Thing::getThings, Thing::setThings);

Ex. 7) And this is what mystifies me most: for the method that expects both the function and the biconsumer, writing out just the biconsumer (no types) and using a method references for the function, works fine, no compile errors (?!):

fb.wuz(Thing::getThings, (thing, things) -> thing.setThings(things));

What I don't understand is that apparently, the compiler handles explicit lambdas and method references differently when determining runtime type/type erasure in different scenarios, namely:

  1. Functional interface that gets arguments and returns value
  2. Functional interface that gets arguments but returns no value
  3. Method with arguments for both 1. and 2. types

This only seems to occur when the type of the functional interface is a generic type itself (in this example, a List), which leads me to believe this has something to do with type erasure, but I'm at a loss as to the answer.

I would love to be able to write...

fb.wuz(Thing::getThings, Thing::setThings);

...without either the explicit type or a longhand lambda expression.

If there is a way to refactor the methods in FooBar to support this, I'd really like to know.

Of course, if someone is able to explain these various behaviors of the compiler, I'd really appreciate that too! :-)

EDIT

I'm especially interested in learning why examples #1 and #7 do work, yet why example #4 on its own doesn't work (and then, why example #2 doesn't work either).

Yuthura
  • 51
  • 6
  • I don't know if this could help but here it goes: `class FooBar`; remove from method signatures; `FooBar fb = new FooBar<>();` – Javi Mollá Mar 10 '15 at 15:55
  • That could indeed solve it, but that would mean that B is predefined for the entire instance, whereas I really need it to be different on each invocation to the method. I'm especially curious as to why the lambda longhand to one argument **doesn't** work, whilst the lambda longhand to two argument method **does** work (I'd really like to _understand_ why). – Yuthura Mar 10 '15 at 19:57
  • 2
    Which `jdk` version do you use? With mine, all but one invocations get compiled without errors. I’m downloading the most recent version right now to verify the results. – Holger Mar 23 '15 at 14:09
  • I am running jdk 1.8.0_25, but you got me thinking. I compiled the example manually, and that works just fine like you said! I looked a little further, and this appears to be a bug in Eclipse's editor. I updated Eclipse from 4.4.0 to 4.4.2 and then the editor worked just fine too. Thank you so much for helping me realize this, can't believe I didn't think of it sooner. This is not the first time the Eclipse editor made me spent hours figuring something out, just to find out it's a bug in Eclipse! – Yuthura Mar 24 '15 at 19:11

1 Answers1

1

The question posed doesn't actually seem to be a compiling error, but rather a bug in the Eclipse editor. The version of Eclipse in which the bug presented itself to me was Luna EE 4.4.0. After updating to Luna EE 4.4.2, the editor behaved the way I expected (and the way the Java compiler was already behaving).

Yuthura
  • 51
  • 6