【问题标题】:Where's the power of functional interfaces in Java?Java 中函数式接口的威力在哪里?
【发布时间】:2019-01-19 18:43:02
【问题描述】:

我正在阅读函数式编程基础知识。不管怎样,我做了一些例子来试图理解这个概念并正确使用它。

我没有得到这种函数式编程的力量。是不是只写 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())

编辑:当然,这个例子对于函数式编程的实际使用来说是一个糟糕的示例代码。但我现在能想到的就这些了。

【问题讨论】:

  • 如果你想测试恐龙的不同属性,例如:你想打印所有名为“Manolo”的恐龙吗?
  • 可能duplicate? VTC 还不够确定...如果有帮助,请参阅我对链接问题的回答。
  • 仅仅使用 Lambda 表达式不会让您了解函数式编程或更具体地说是声明式编程的需求。好吧,您可能可以先以命令式风格编写整个代码,然后将其与发布的代码进行比较!之后,您可能会对编程的函数式范式的力量有所了解。
  • @JaredSmith 谢谢。这对我的问题有很大帮助:)
  • @Genaut 对。但是如果你不使用函数参数,你必须有一个打印哺乳动物的方法,一个打印恐龙的方法叫 Manolo,一个打印名字长于 4 个字符的恐龙的方法......所以取而代之的是更改 1 行,您添加 7。

标签: java lambda java-8 functional-programming


【解决方案1】:

在我看来,这似乎是一个非常常见的问题,尽管它有一个非常简单的答案。
对我来说,这关乎您如何看待您设计的合同以及您期望它如何实施和使用。

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

我是这样对自己解释的(非常基本的):

1 - 将功能接口视为“命名逻辑”(将在合约的另一端实现)

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

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

在同一组件中使用两者的问题在您的代码中很明显。你不会编写一个函数式接口,声明一个接受一个的方法,所有这些只是为了实现它并将它传递给你自己的方法。如果您这样做并且仅这样做,那么您出于错误的原因使用抽象,更不用说正确使用功能接口了。

有大量正确使用函数式接口的示例。我会选择Collection.forEachConsumer

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

这与您的设计有何不同?

  1. Collection.forEach 的设计者停留在接受 Consumer 的合约上(他们不使用 Consumer 只是为自己的方法命名/键入参数
  2. Collection.forEach 是需要自定义逻辑的操作。就像
    s -&gt; System.out.println(s),
    这个“自定义逻辑”可以是
    s -&gt; myList.add(s)

    s -&gt; myList.add(s.toUpperCase()),
    等等,都按照设计者的要求客户端 代码(在界面设计很久之后)。 Collection 接口的forEach 方法协调迭代并允许调用者提供每次迭代中调用的逻辑。这将关注点分开。

Stream.filterPredicate 更接近您的示例,最好将您使用 Stream.filter 的方式与您在示例中使用 Tester.test 的方式进行对比。

话虽如此,支持/反对函数式编程的原因有很多,以上重点是根据您的示例使用(或不使用)函数式接口的原因(具体而言,从编写合同的开发人员的角度来看) .

【讨论】:

    【解决方案2】:

    您混淆了两个术语:函数式接口和函数式编程。 函数式接口作为一种类型语言在 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-&gt;a.isEsMamifero());联系pintadorDinosaurios。
    没有功能接口,你必须写

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-23
      • 2017-08-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-02-02
      相关资源
      最近更新 更多