Где сила функциональных интерфейсов в Java? - PullRequest
0 голосов
/ 19 января 2019

Я читаю об основах функционального программирования. Во всяком случае, я привожу несколько примеров, пытаясь понять концепцию и правильно ее использовать.

Я не понимаю возможности этого функционального программирования. Разве только в написании лямбд вместо нормального кода?

Если у нас есть этот класс:

public class Dinosaurio {
    private boolean esMamifero;
    private String nombre;
    public Dinosaurio(String n, boolean esMam) {...}
//getters and setters

Этот функциональный интерфейс:

@FunctionalInterface
public interface DinosaurioTester {
    boolean test(Dinosaurio d);
}

А это основной класс:

public class LambdaMain {

    public static void main(String[] args) {

        List<Dinosaurio> lista = new ArrayList<>(); 
        lista.add(new Dinosaurio("Manolo", true));
        lista.add(new Dinosaurio("Pepe", true));
        lista.add(new Dinosaurio("Paco", false));
        lista.add(new Dinosaurio("Curro", true));
        lista.add(new Dinosaurio("Nemo", false));

        pintadorDinosaurios(lista, a->a.isEsMamifero());
    }

    public static void pintadorDinosaurios(List<Dinosaurio> ld, DinosaurioTester dt) {

        for(Dinosaurio d : ld) {
            if(dt.test(d)) {
                System.out.println(d.getNombre());
            }
        }

    }

}

Это прекрасно работает, но я не вижу реального преимущества от использования этого:

if(dt.test(d)) {

вместо этого:

if(d.isEsMamifero())

Редактировать: Конечно, этот пример плохой пример кода для реального использования функционального программирования. Но это все, что я могу думать об этом сейчас.

Ответы [ 2 ]

0 голосов
/ 19 января 2019

Вы смешиваете два термина: функциональные интерфейсы и функциональное программирование. Функциональные интерфейсы были введены в Java как типизированный язык для обеспечения функционального программирования. Не было хорошего способа объявить анонимные методы (вам приходилось использовать анонимные классы. И каждый анонимный класс определяет новый класс, который может привести к загрязнению класса при использовании многих из них.) Однако анонимные методы являются очень важным элементом функциональных программирование. Поэтому, если вы думаете о силе функциональных интерфейсов, вам лучше рассмотреть Stream API Java. Это позволяет вам выражать код в декларативном стиле, в котором вы можете определить, что вы хотите, а не то, как вы его получите. Если вы думаете только о замене if(d.isEsMamifero()) на if(dt.test(d)), то вы не очень выигрываете. Но как насчет этого:

public static void pintadorDinosaurios(List<Dinosaurio> ld, DinosaurioTester dt) {
    ld.stream().filter(dt::test).foreach(System.out::println);
}

Теперь вы настроили критерии фильтра в соответствии с вашим интерфейсом. Выражение ничего не говорит о том, как вы фильтруете. Он только говорит, что вы хотите один вид динозавров и распечатать их на консоли. Использует ли выражение цикл или рекурсию, полностью скрыто. Вы можете позвонить pintadorDinosaurios по номеру pintadorDinosaurios(lista, a->a.isEsMamifero());.
Без функциональных интерфейсов вы должны написать

pintadorDinosaurios(lista, new DinosaurioTester {
    boolean test(Dinosaurio d) {
        return d.isEsMamifero();
    }
});

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

0 голосов
/ 19 января 2019

Это, кажется, очень распространенный вопрос, хотя, на мой взгляд, у него очень простой ответ.Для меня речь идет о том, как вы видите контракт, который вы разрабатываете, и о том, как вы ожидаете, что он будет реализован и использован.

Как вы заметили, в вашем примере показан неправильный способ использования функциональных интерфейсов.Разработка этих типов только для того, чтобы в итоге вызвать if(dt.test(d)) вместо if(d.isEsMamifero()), требует простого прилагательного: плохо.И это, вероятно, вменяется в учебники.Проблема в том, что большинство книг учат нас использовать функциональные интерфейсы так, как нас учат использовать интерфейсы / абстракцию в целом, и это не учитывает смысл и большую картину функционального программирования.Конечно, нужно знать, «как» реализовать функциональный интерфейс (в конце концов, это интерфейс), но многие книги не говорят нам, где их применять.

Вот как я объясняю это себе (вбазовые термины):

1 - функциональный интерфейс рассматривается как «именованная логика» (будет реализовано на другой стороне договора)

Да, функционалинтерфейс типа , но имеет больше смысла смотреть на функциональный интерфейс с именем logic .В отличие от обычных типов, таких как Serializable, Collection, AutoCloseable, функциональные интерфейсы, такие как Tester (или Predicate), представляют логику (или просто код).Я знаю, что нюансы становятся тонкими, но я полагаю, что есть разница между традиционным абстрактным «типом» ООП и тем, что подразумевается под функциональным интерфейсом.

2 - изолировать код, реализующий функциональныйинтерфейсы из кода, который его потребляет

Проблема использования двух в одном компоненте очевидна в вашем коде.Вы не написали бы функциональный интерфейс, не объявили бы метод, который берет один, все только для того, чтобы реализовать его и передать его своему собственному методу.Если вы делаете это и только это, вы используете абстракцию по неправильным причинам, не говоря уже о правильном использовании функциональных интерфейсов.

Существует множество примеров правильного использования функциональных интерфейсов.Я выберу Collection.forEach с Consumer:

Collection<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> System.out.println(s));

Чем это отличается от вашего дизайна?

  1. Дизайнер Collection.forEach останавливается на контракте, который принимаетConsumer (они не используют Consumer только для именования / ввода параметра в свой метод
  2. Collection.forEach - операция, для которой требуется настраиваемая логика. Как и в случае сs -> System.out.println(s),эта "пользовательская логика" может бытьs -> myList.add(s)или жеs -> myList.add(s.toUpperCase()),и т. д., все в соответствии с дизайном клиента кода (задолго до того, как был разработан интерфейс).Метод forEach интерфейса Collection организует итерацию и позволяет вызывающей стороне предоставлять логику, вызываемую в каждой итерации.Это разделяет проблемы.

Stream.filter с Predicate ближе к вашему примеру, будет хорошей идеей сравнить то, как вы используете Stream.filter с тем, как вы использовали Tester.test в своем примере.

С учетом вышесказанного, есть много причин для / против функционального программирования, вышеупомянутое сфокусировано на причине использования (или не использования) функциональных интерфейсов в соответствии с вашим примером (в частности, с точки зрения разработчика, которыйпишет договор).

...