Можно ли передавать ссылки BiFunction методам, ожидающим функциональный интерфейс?

Я всегда использовал только Java 6 и сейчас догоняю, чтобы узнать, что нового в Java 8. Я читал эту статью здесь: http://www.drdobbs.com/jvm/lambda-expressions-in-java-8/240166764?pgno=2

И он говорит:

API Java определяет несколько общих функциональных интерфейсов в пакете java.util.function. Один из интерфейсов, BiFunction, описывает функции с типами параметров T и U и типом возвращаемого значения R. Вы можете сохранить нашу лямбду сравнения строк в переменной этого типа:

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

Однако это не поможет вам в сортировке. Нет метода Arrays.sort, которому требуется BiFunction. Если вы раньше использовали функциональный язык программирования, вам это может показаться любопытным. Но для Java-программистов это вполне естественно. Такой интерфейс, как Comparator, имеет конкретное назначение, а не просто метод с заданными параметрами и типами возвращаемых значений. Java 8 сохраняет эту особенность. Когда вы хотите что-то сделать с лямбда-выражениями, вы все равно хотите помнить о назначении выражения и иметь для него определенный функциональный интерфейс.

Однако, когда я вижу этот поток: Как присвоить лямбда переменной в Java 8?

Ответы на вопрос там предлагают делать именно то, что в цитируемом абзаце сказано, что вы не можете этого сделать.

Итак, информация в статье неверна, или я что-то не так читаю?

Спасибо!


person GrowinMan    schedule 05.02.2015    source источник
comment
Вы можете просто присвоить ту же лямбду Comparator и использовать ее в вызове сортировки...   -  person Louis Wasserman    schedule 05.02.2015


Ответы (3)


Я не вижу в связанном ответе SO ничего, что противоречило бы статье.

Обычные правила системы типов применяются к функциональному интерфейсу.

Если вы объявите переменную как BiFunction<String,String,Integer> bifunc, вы не сможете передать ее методу, для которого требуется Comparator<String>, поскольку BiFunction<String,String,Integer> не является подтипом Comparator<String>.

Тот факт, что функциональные типы следуют всем обычным правилам, позволил добавить эту новую функциональность с минимальными изменениями.

И если вы хотите сделать Comparator из BiFunction, все, что вам нужно сделать, это добавить ::apply следующим образом:

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

Arrays.sort(array, bifunc::apply);  
person Misha    schedule 05.02.2015

Статья правильная в том смысле, что нельзя сортировать по объекту типа BiFunction, но всегда можно, используя Comparator. Но эй, они оба могут иметь одно и то же тело. Например:

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

Выше, в строке 4, вы можете передать лямбду, точно такую ​​же, как func. Но вы по-прежнему не можете передать func в sort. Лямбды в java8 являются реализацией некоторого FunctionalInterface. Функциональные интерфейсы получают свой тип на основе ссылочного типа. Вот как одна и та же лямбда при инициализации может быть либо BiFunction, либо Comparator.

Но как только лямбда построена и получает тип, вы не можете ее изменить. Следовательно, вы не можете передать func типа BiFunction для сортировки, которая ожидает Comparator

person Jatin    schedule 05.02.2015

Статья правильная. Вы не можете назначить, например. от BiFunction до Comparator.

При этом эта замечательная статья написана Брайаном Гетцем. хорошо объясняет проблему.

Когда компилятор встречает лямбда-выражение, он сначала понижает (удаляет сахара) тело лямбда-выражения до метода, список аргументов и тип возвращаемого значения которого соответствуют таковым для лямбда-выражения.

Итак, лямбда может быть обессахарена, но что это значит? Ну, в основном это означает, что (может быть) создан новый метод, который каким-то образом соответствует лямбде.

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

Приведенный выше код будет обессахарен примерно так:

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);
    }
}

Итак, в случае BiFunction и Comparator. Предоставленная лямбда может быть назначена обоим:

// 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

Обратите внимание, что после того, как лямбда была назначена типу (BiFunction или Comparator), ее нельзя переназначить, даже если лямбда-выражение совпадает.

person wassgren    schedule 05.02.2015
comment
Когда вы берете фрагменты кода из этой старой статьи, вы должны воспользоваться возможностью обновить их до фактического API. То, что тогда называлось Block<String>, стало Consumer<String>. Использование старых названий может сбить с толку читателей, не знакомых с историей. - person Holger; 05.02.2015