8

In Java Precisely 3rd Ed., there's the following code snippet:

BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;

However, I noticed that even when I leave out <Double> after ::, the method reference is still valid (which makes sense due to type parameters for BiConsumer).

However, I'm pretty confused about whether there are cases in which ::<T> would be necessary in a method reference, and if so, an example would be very helpful.

Zz'Rot
  • 824
  • 1
  • 7
  • 24
  • The java compiler has gotten “smarter”. Assuming `FooBar` is derived from `Foo`: in Java7: `List foo = Arrays.asList(new FooBar());` won’t compile, but it will compile in Java8. Java7 needs the type parameter: `List foo = Arrays.asList(new FooBar());`. – AJNeufeld Mar 17 '18 at 05:57
  • We need an expert in the Java Language Specification (JLS), who can point us to the exact section of the JLS in which all the casuistry for type inference for methods' generic type parameters is fully described, as well as its relation to method references. Honestly, instead of learning all the cases when inference works or doesn't work, I think it's better to try and see if the compiler can infer the type params on its own; if not, then we can specify the type parameters with the `::` notation. – fps Mar 17 '18 at 19:51

3 Answers3

7

I figured Java 10's local variable type inference (var name = ...;) would be the answer to this conundrum. Instead of the destination variable type providing the type for the method reference, the right-hand-side would need to fully specify the type, necessitating the type parameter (::<T>) on the method reference.

First thought out the gate ...

var arraySorter = Arrays::<Double>sort;

... but method references, by themselves, do not define a type. They need to be translated to a functional object by the compiler, and the compiler won't search for known functional interfaces looking for the appropriate type, even if there was exactly one.


Next thought was to use a method reference as an argument to a method which returns a type based on the method's argument.

class Spy {
    static <T> Function<T,T> f2(Function<T,T> f) {
        return f.andThen(f);
    }

    static <T> T identity(T t) {
        return t;
    }
}

Using this, we can create our local variable passing a method reference to our method:

Function<Double,Double> double_identity = f2(Spy::<Double>identity);

As expected, we can remove the ::<Double>

Function<Double,Double> double_identity = f2(Spy::identity);

Unexpectedly, local variable type inference is fine with it.

var double_identity = f2(Spy::identity);             // Infers <Object>!
Object obj = null;
double_identity.apply(obj); 

But the real surprise comes when use the method reference type to override it.

var double_identity = f2(Spy::<Double>identity);     // Error: Double != Object

After a bit of fighting, I figured out why. We have to apply the type to the f2 method itself:

var double_identity = Spy.<Double>f2(Spy::identity); // Works.

In retrospect, this makes some sense. The type of the variable generally provides context for the outer function. Assigning the result to a Function<Double,Double> variable lets the compiler infer the type of f2(...), which then passes that type to the arguments. With var name = ..., without an explicit type, the only type is has available is Object, so the compiler infers Spy.<Object>f2(...), and then determines the argument type must be a Function<Object,Object>.

Unfortunately, it doesn't seem to parse from the inside out, so that Spy::<Double>identity doesn't cause the function to be inferred as Spy.<Double>f2(...) and the variable as Function<Double,Double>. Maybe Java 11? Maybe it would break too much, and can't work.

It does, however, put an end to my attempts to abuse var name = ...; to solve the OP's conundrum.


Many thanks to @Eugene for critiquing my previous attempts prior to Java 10's release.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
  • I almost liked this... but `var listMaker = Arrays::asList;` would not work, it would require an explicit target type – Eugene Mar 17 '18 at 21:04
  • @Eugene I have to admit, I haven’t actually tried it. The official release of Java 10 isn’t for 3 days, yet. But I’m surprised to hear it won’t work. There is only 1 `Arrays::asList` method ([` List Arrays::asList(T...)`](https://docs.oracle.com/javase/9/docs/api/java/util/Arrays.html#asList-T...-)). It should work; if it doesn’t, I have to ask why not, ‘cause it sounds like a bug if it can’t. – AJNeufeld Mar 17 '18 at 23:07
  • @Eugene Ok, I get it now. What is the type of `listMaker`? Is it `Function>`? That would make sense to me, but will the compiler search through every known `@FunctionalInterface` to find a class with single abstract method that matches the given signature? What if it finds more than one? `listMaker.getClass()` has to return a concrete result, and if there is ambiguity, it can't. So while `var xxx = ...` works perfectly fine with a method that returns a concrete type, a method reference doesn't have a concrete type and could be compatible with several. And I was so close... – AJNeufeld Mar 18 '18 at 01:30
  • @JNeufeld indeed yes you were close... the compiler will not search for anything (that will add quite an overhead if it did), it has to infer the type in place and btw `var` is just a compiler thing, the byte code is the same with it or with a pre-java-10 variables of type `List...` – Eugene Mar 18 '18 at 10:02
  • @Eugene I think I've got it. I've added a call to a function that takes a method reference, and returns a type based on the method reference's reference type, and assign the returned value to a local `var` variable. Can you confirm? Or debunk? Or improve? – AJNeufeld Mar 18 '18 at 20:34
  • nope, works just fine without the need to add ``, its just that it is inferred as `Function`.. – Eugene Mar 19 '18 at 08:19
  • I feel really bad not upvoting this, it was a very good trial btw, may be the best on this question. a well deserved, plus one! – Eugene Mar 31 '18 at 15:34
  • @Zz’Rot I appreciate the +1, but I think the green checkmark is going a little too far. I did not find an answer to the conundrum, nor did I prove it to be impossible. You should probably leave the question open, until someone chimes in with a definitive answer. – AJNeufeld May 21 '18 at 15:28
5

This is type inference in action, in the majority of the cases the compiler will infer the types for you and thus meaning you need not explicitly provide them.

However, there are certain circumstances in which you need to manually provide type hints.

Anyhow, Java 8 enhances the inference of generic arguments.

So, doing the following:

BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::sort;

in completly valid due to type inference.

A couple of examples which I can think of right now that would not have worked in Java-7 but works in Java-8 is something like:

void exampleMethod(List<Person> people) {
      // do logic
} 

exampleMethod(Collections.emptyList())

Another example:

someMethodName(new HashMap<>());
...
void someMethodName(Map<String, String> values);

You were required to explicitly provide the type arguments previously.

Also, because of the aforementioned type inference, it's the exact reason why we can now do something like:

...
...
.collect(Collectors.toList());

instead of this guy:

...
...
.collect(Collectors.<Person>toList());

Whether you should provide type arguments explicitly or not is a matter of preference in some cases and in others, you're forced to do so in order to help the compiler do its work.

Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • well to me, this is good, but not answering the question per-se. `::` is what the OP is looking for and reading your answer I really thought you found such a case... – Eugene Mar 17 '18 at 13:27
  • @Eugene _"However, I noticed that even when I leave out after ::, the method reference is still valid (which makes sense due to type parameters for BiConsumer)."_ , I have explained the reasoning to this. as for _"However, I'm pretty confused about whether there are cases in which :: would be necessary in a method reference, and if so, an example would be very helpful."_ , I cannot think of an example yet where if the target type is `BiConsumer` and you're forced to provide the type arguments explicitly to the thing on the right. – Ousmane D. Mar 17 '18 at 14:11
  • exactly, *yet*. I've tried to even grep for `::<` inside jvm sources of 8,9 and 10 to may be spot an example of such, no luck so far – Eugene Mar 17 '18 at 14:13
  • @Eugene as I cannot think of another example where if the target type is `BiConsumer` and you're forced to provide the type arguments explicitly to the thing on the right. I have instead shown a couple of examples where you would be forced to provide type arguments in order for them to work prior to Java-8. Though maybe it's not the exact example the OP is after, it's close enough to show where you're required to provide type arguments explicitly. Overall, I think this clears the OP ambiguity in regards to what "providing type arguments explicitly" means. – Ousmane D. Mar 17 '18 at 14:20
  • of course... I even think the OP does not know it has touched me that much, it's not just about `BiConsumer`, I am wondering of *any* example where additional type arguments would be required with a method reference (!= lambda, it is easy with lambda) – Eugene Mar 17 '18 at 14:22
  • @Eugene Right, ping me if you manage to come up with an example, I’d be interested to see. ;) – Ousmane D. Mar 17 '18 at 16:34
  • well it wasn't me (neither it's the desired result), but it's the closest to a real "close-enough" IMO on this question https://stackoverflow.com/a/49340725/1059372 – Eugene Mar 31 '18 at 15:36
2

Java 8 implements Generalized Target-Type Inference (JEP 101) that allows the compiler to infer the type parameter of a generic method. In your example, the Java 8 compiler infers the type parameter of the method sort from the right hand side of the assignment.

JEP 101 also proposed generalized target-type inference for chained method calls, but it was not implemented because of the complexity that it would have introduced to the inference algorithm (discussed here and here). So, chained generic-method calls is an example in which the type parameter of a generic method cannot be inferred.

Consider the following code snippet:

class Main {

    String s = MyList.nil().head(); // Incompatible types. Required: String. Found: Object.

    static class MyList<E> {
        private E head;
        static <Z> MyList<Z> nil() { return new MyList(); }
        E head() { return head; }
    }
}

The compiler fails to infer the type parameter for the generic method nil() in String s = MyList.nil().head(). So, we have to provide more information to the inference algorithm, either by adding a type parameter

String s = MyList.<String>nil().head();

or by splitting the chained calls

MyList<String> ls = MyList.nil();
String s = ls.head();

Note: The chained-calls example does not contain a method reference to a generic method (::<> syntax) as in the original question, but the inference technique invoked in both examples is the same. Therefore, the limitations of the inference are also the same.

SergiyKolesnikov
  • 7,369
  • 2
  • 26
  • 47