Can BiFunction references be passed to methods expecting a functional interface?
Asked Answered
P

3

7

I've always only used Java 6 and am right now catching up to learn what's new in Java 8. I was reading this article here: http://www.drdobbs.com/jvm/lambda-expressions-in-java-8/240166764?pgno=2

And it says:

The Java API defines several generic functional interfaces in the java.util.function package. One of the interfaces, BiFunction, describes functions with parameter types T and U and return type R. You can save our string comparison lambda in a variable of that type:

BiFunction<String, String, Integer> comp 
    = (first, second) -> Integer.compare(first.length(), second.length()); 

However, that does not help you with sorting. There is no Arrays.sort method that wants a BiFunction. If you have used a functional programming language before, you may find this curious. But for Java programmers, it's pretty natural. An interface such as Comparator has a specific purpose, not just a method with given parameter and return types. Java 8 retains this flavor. When you want to do something with lambda expressions, you still want to keep the purpose of the expression in mind, and have a specific functional interface for it.

However, when I see this thread: How do you assign a lambda to a variable in Java 8?

The answers to the question there suggest doing exactly what the quoted paragraph says you can't do.

So, is the information in the article incorrect, or am I mis-reading something here?

Thanks!

Pentosan answered 5/2, 2015 at 4:41 Comment(1)
You could just assign that same lambda to a Comparator and use it in a sort invocation...Detribalize
B
23

I don't see anything in the linked SO answer that contradicts the article.

Regular rules of the type system apply to functional interface.

If you declare a variable as BiFunction<String,String,Integer> bifunc, you will not be allowed to pass it to a method that requires Comparator<String> because BiFunction<String,String,Integer> is not a subtype of Comparator<String>.

The fact that functional types follow all the usual rules is what allowed this new functionality to be added with minimal perturbations.

And if you wish to make a Comparator out of a BiFunction all you have to do is add ::apply like so:

BiFunction<String,String,Integer> bifunc = (a,b) -> 
                               Integer.compare(a.length(), b.length());

Arrays.sort(array, bifunc::apply);  
Blanchette answered 5/2, 2015 at 7:25 Comment(0)
E
3

The article is correct in the sense, that you cant sort based on object of BiFunction type, but you always can using Comparator. But hey, they both can have the same body. For example:

private static void sort(Comparator<String> ls){
        Arrays.sort(someArray, ls);
}

Comparator<String> comp = (String f1, String f2) -> Integer.compare(f1.length(), f2.length());
sort(comp);

BiFunction<String, String, Integer> func = (String f1, String f2) -> Integer.compare(f1.length(), f2.length());
sort((String f1, String f2) -> Integer.compare(f1.length(), f2.length())); //line-4

sort(func) // compiler error

Above at line-4, you are able to pass a lambda which is exactly same as func. But you still cannot pass func to sort. Lambdas in java8 are an implementation of some FunctionalInterface. Functional Interfaces get their type based on its reference type. Thats how the same lambda on initialization can be either a BiFunction or Comparator.

But once a lambda is constructed and it gets it type, then you cant change it. Hence you cannot pass func of type BiFunction to sort which expects Comparator

Edi answered 5/2, 2015 at 5:55 Comment(0)
L
2

The article is correct. You can not assign e.g. a BiFunction to a Comparator.

With that being said, this great article written by Brian Goetz explains the problem in a nice way.

When the compiler encounters a lambda expression, it first lowers (desugars) the lambda body into a method whose argument list and return type match that of the lambda expression

So, a lambda can be desugared - but what does that mean? Well, basically it means that a new method (may) be created that somehow matches the lamdba.

class A {
    public void foo() {
        List<String> list = ...
        list.forEach( s -> { System.out.println(s); } );
    }
}

The code above will be desugared to something like this:

class A {
    public void foo() {
        List<String> list = ...
        list.forEach( [lambda for lambda$1 as Consumer] );
    }

    static void lambda$1(String s) {
        System.out.println(s);
    }
}

So, in the case of the BiFunction and Comparator. The provided lambda can be assigned to both:

// Assign the lambda to a BiFunction
BiFunction<String, String, Integer> b1 =
        (first, second) -> Integer.compare(first.length(), second.length());

// Assign the lambda to a Comparator
Comparator<String> c1 =
        (first, second) -> Integer.compare(first.length(), second.length());

// But, once the lambda has been assigned to a type you can not reassign it
BiFunction<String, String, Integer> b2 = c1; // <-- Error

Note that once a lambda has been assigned to a type (BiFunction or Comparator) then it can not be reassigned, even if the lambda expression matches.

Loch answered 5/2, 2015 at 8:2 Comment(1)
When you take over code snippets from this old article you should take the chance to update them to the actual API. What was called Block<String> back then has become Consumer<String>. Sticking with the old names can be confusing for readers not aware of the history.Leisaleiser

© 2022 - 2024 — McMap. All rights reserved.