Java中的功能接口的功能在哪里?

时间:2019-01-19 18:43:03

标签: java lambda java-8 functional-programming

我正在阅读有关函数式编程的基础知识。无论如何,我举一些例子来试图理解这个概念并正确地使用它。

我没有获得此功能编程的强大功能。难道只是在编写lambda而不是普通代码?

如果我们有此类:

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 :(得分:6)

尽管我认为这是一个非常简单的答案,但这似乎是一个非常常见的问题。
对我来说,这是关于如何看待设计合同以及如何实现和使用合同的问题。

您注意到,您的示例显示了使用功能接口的一种不好的方法。设计这些类型只是为了最终调用if(dt.test(d))而不是if(d.isEsMamifero())需要一个简单的形容词:bad。这可能归功于教科书。问题是大多数书籍教我们使用函数接口的方式与我们通常使用接口/抽象的方法相同,并且忽略了函数编程的重点和更广阔的前景。当然,需要知道“如何”实现功能接口(毕竟这是一个接口),但是很多书都没有告诉我们在哪里应用它们。

这是我向我自己解释的方式(非常基本的术语):

1-将功能接口视为“命名逻辑” (将在合同的另一侧实施)

是的,功能接口是类型,但是将功能接口命名为 logic 更为有意义。与诸如SerializableCollectionAutoCloseable之类的普通类型不同,诸如Tester(或Predicate)之类的功能接口表示逻辑(或仅代表代码)。我知道细微差别正在变得微妙,但是我相信传统的OOP抽象“类型”与功能接口的含义之间存在区别。

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())
    等,所有操作均按照客户端 代码的设计者进行(界面设计很久以后)。 Collection接口的forEach方法协调迭代,并允许调用者提供每次迭代中调用的逻辑。这可以消除关注点。

Stream.filterPredicate更加接近您的示例,最好将您如何使用Stream.filter与示例中的Tester.test进行对比。< / p>

话虽这么说,功能编程还是有很多反对的理由,上面的内容集中于根据您的示例使用(或不使用)功能接口的原因(特别是从编写合同的开发人员的角度来看)

答案 1 :(得分:3)

您混淆了两个术语:功能接口和功能编程。 在Java中将功能接口作为一种类型化语言引入,以支持功能编程。没有很好的方法来声明匿名方法(您必须使用匿名类。每个匿名类都定义一个新类,当您使用许多匿名类时,它们会导致类污染。)但是,匿名方法是功能的一个非常重要的元素编程。因此,如果您想到功能接口的强大功能,则应该考虑Java的Stream API。它使您能够以声明式的方式表达代码,在其中您可以定义所需的内容而不是获取方式。 如果您只考虑将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(lista, a->a.isEsMamifero());致电pintadorDinosaurios。
没有功能接口,您必须编写

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

看起来很难看。 因此,将功能接口与Stream API结合使用,可以使您以更舒适的方式(如果您习惯使用它)来缩短编写时间。