我正在阅读有关函数式编程的基础知识。无论如何,我举一些例子来试图理解这个概念并正确地使用它。
我没有获得此功能编程的强大功能。难道只是在编写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())
编辑:确保该示例对于函数式编程的实际用法而言是错误的示例代码。但这就是我现在能想到的一切。
答案 0 :(得分:6)
尽管我认为这是一个非常简单的答案,但这似乎是一个非常常见的问题。
对我来说,这是关于如何看待设计合同以及如何实现和使用合同的问题。
您注意到,您的示例显示了使用功能接口的一种不好的方法。设计这些类型只是为了最终调用if(dt.test(d))
而不是if(d.isEsMamifero())
需要一个简单的形容词:bad。这可能归功于教科书。问题是大多数书籍教我们使用函数接口的方式与我们通常使用接口/抽象的方法相同,并且忽略了函数编程的重点和更广阔的前景。当然,需要知道“如何”实现功能接口(毕竟这是一个接口),但是很多书都没有告诉我们在哪里应用它们。
这是我向我自己解释的方式(非常基本的术语):
1-将功能接口视为“命名逻辑” (将在合同的另一侧实施)
是的,功能接口是类型,但是将功能接口命名为 logic 更为有意义。与诸如Serializable
,Collection
,AutoCloseable
之类的普通类型不同,诸如Tester
(或Predicate
)之类的功能接口表示逻辑(或仅代表代码)。我知道细微差别正在变得微妙,但是我相信传统的OOP抽象“类型”与功能接口的含义之间存在区别。
2-将使用功能接口的代码与使用它的代码隔离开来
在代码中很明显地发现了在同一组件中使用两者的问题。您不会编写功能接口,也不会声明一个采用一个接口的方法,而只是为了实现该接口并将其传递给您自己的方法。如果您只是这样做,那么您出于错误的原因在使用抽象,更不用说正确使用功能接口了。
有大量正确使用功能接口的示例。我将用Collection.forEach
选择Consumer
:
Collection<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> System.out.println(s));
这与您的设计有何不同?
Collection.forEach
的设计者止步于签订Consumer
的合同(他们不使用Consumer
只是为了为其自己的方法命名/键入参数Collection.forEach
是需要自定义逻辑的操作。与s -> System.out.println(s)
一样,s -> myList.add(s)
s -> myList.add(s.toUpperCase())
,Collection
接口的forEach
方法协调迭代,并允许调用者提供每次迭代中调用的逻辑。这可以消除关注点。 Stream.filter
与Predicate
更加接近您的示例,最好将您如何使用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结合使用,可以使您以更舒适的方式(如果您习惯使用它)来缩短编写时间。