Java中功能接口的强大之处在哪里?

Gen*_*aut 1 java lambda functional-programming java-8

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

我没有掌握这种函数式编程的强大功能.它只是在编写lambda而不是普通代码吗?

如果我们有这个课程:

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

这个功能界面:

@FunctionalInterface
public interface DinosaurioTester {
    boolean test(Dinosaurio d);
}
Run Code Online (Sandbox Code Playgroud)

而这个主要课程:

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

    }

}
Run Code Online (Sandbox Code Playgroud)

这很好用,但我没有看到使用它的真正优势:

if(dt.test(d)) {
Run Code Online (Sandbox Code Playgroud)

而不是这个:

if(d.isEsMamifero())
Run Code Online (Sandbox Code Playgroud)

编辑:当然这个例子是功能编程实际使用的错误示例代码.但这就是我现在能想到的一切.

ern*_*t_k 6

这似乎是一个非常普遍的问题,尽管在我看来它有一个非常简单的答案.
对我来说,这是关于你如何看待你设计的合同以及你希望如何实现和使用它.

如您所知,您的示例显示了使用功能接口的不良方式.设计这些类型只是为了结束调用if(dt.test(d))而不是if(d.isEsMamifero())一个简单的形容词:坏.这可能与教科书有关.问题是大多数书籍教会我们使用功能接口,就像我们被教导一般使用接口/抽象一样,这省略了函数式编程的重点和更大的图景.当然,人们需要知道如何实现功能界面(毕竟它是一个界面),但许多书籍并没有告诉我们应用它们的位置.

以下是我自己解释的内容(用非常基本的术语):

1 - 将功能界面视为"命名逻辑"(将在合同的另一侧实施)

是的,功能接口是一种类型,但将功能接口视为命名逻辑更有意义.不同于普通的类型,如Serializable,Collection,AutoCloseable,功能intefaces像Tester(或Predicate)表示逻辑(或只是代码).我知道细微差别正在变得微妙,但我相信传统的OOP抽象"类型"与功能界面的意义之间存在差异.

2 - 将实现功能接口的代码与使用它的代码隔离开来

在代码中显而易见的是在同一组件中使用这两个问题.你不会编写一个函数接口,声明一个接受一个的方法,只是为了实现它并将它传递给你自己的方法.如果您正在执行此操作并且仅此操作,那么您出于错误的原因使用抽象,更不用说正确使用功能接口了.

有大量正确使用功能接口的例子.我来接Collection.forEachConsumer:

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

这与您的设计有何不同?

  1. Collection.forEach合同中的停止设计者采用a Consumer(他们不使用Consumerjust来命名/键入参数到他们自己的方法
  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在示例中的使用方式进行对比将是一个好主意.

有了这个说法,有很多理由支持/反对函数式编程,上面的重点是根据你的例子使用(或不使用)函数接口的原因(具体来说,从编写合同的开发人员的角度来看).


mmi*_*ldt 5

您混淆了两个术语:函数式接口和函数式编程。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);
}
Run Code Online (Sandbox Code Playgroud)

现在您已通过界面参数化了过滤条件。该表达式没有说明您如何过滤。它只说你想要一种恐龙并将它们打印到控制台。表达式是否使用循环或递归是完全隐藏的。您可以通过 致电 pintadorDinosaurios pintadorDinosaurios(lista, a->a.isEsMamifero());
如果没有函数式接口,则必须编写

pintadorDinosaurios(lista, new DinosaurioTester {
    boolean test(Dinosaurio d) {
        return d.isEsMamifero();
    }
});
Run Code Online (Sandbox Code Playgroud)

看起来很丑。因此,函数式接口与 Stream API 的结合使您能够以更舒适的方式编写更短的代码(如果您习惯的话)。