Search code examples
javajava-8functional-interface

Java 8 streams, why does this compile part 2... Or what is a method reference, really?


OK, the first question in this "series" was this one.

Now, here is another case:

Arrays.asList("hello", "world").stream().forEach(System.out::println);

This compiles, and works...

OK, in the last question, static methods from a class were used.

But now this is different: System.out is a static field of System, yes; it is also a PrintStream, and a PrintStream has a println() method which happens to match the signature of a Consumer in this case, and a Consumer is what forEach() expects.

So I tried this...

public final class Main
{
    public static void main(final String... args)
    {
        Arrays.asList(23, 2389, 19).stream().forEach(new Main()::meh);
    }

    // Matches the signature of a Consumer<? super Integer>...
    public void meh(final Integer ignored)
    {
        System.out.println("meh");
    }
}

And it works!

This is quite a different scope here, since I initiate a new instance and can use a method reference right after this instance is constructed!

So, is a method reference really any method which obeys the signature? What are the limits? Are there any cases where one can build a "@FunctionalInterface compatible" method which cannot be used in a @FunctionalInterface?


Solution

  • The syntax of method references is defined in JLS #15.13. In particular it can be of the form:

    Primary :: [TypeArguments] Identifier

    Where Primary can be, among other things, a:

    ClassInstanceCreationExpression

    so yes, your syntax is correct. A few other interesting examples:

    this::someInstanceMethod    // (...) -> this.someInstanceMethod(...)
    "123"::equals               // (s) -> "123".equals(s)
    (b ? "123" : "456")::equals // where b is a boolean
    array[1]::length            // (String[] array) -> array[1].length()
    String[]::new               // i -> new String[i]
    a.b()::c                    // (...) -> a.b().c(...)
    

    By the way, since you mention static methods, it is interesting to note that you can't create a static method reference from an instance:

    class Static { static void m() {} }
    Static s = new Static();
    
    s.m(); //compiles
    someStream.forEach(s::m); //does not compile
    someStream.forEach(Static::m); //that's ok