代码之家  ›  专栏  ›  技术社区  ›  Genaut

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

  •  1
  • Genaut  · 技术社区  · 6 年前

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

    我没有得到这个函数式编程的能力。它只写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 回复  |  直到 6 年前
        1
  •  6
  •   ernest_k Petronella    6 年前

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

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

    以下是我如何向自己解释(用非常基本的术语):

    1-将功能接口视为“命名逻辑” (合同另一方执行)

    是的,功能接口是 类型 ,但更合理的做法是看一个名为 逻辑 . 不像普通类型 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));
    

    这和你的设计有什么不同?

    1. 设计师 收藏.foreach 停在需要 消费者 (他们不使用 消费者 只需为自己的方法命名/键入一个参数
    2. 收藏.foreach 需要自定义逻辑的操作。正如
      s -> System.out.println(s) ,
      这个“自定义逻辑”可以是
      s -> myList.add(s)

      s -> myList.add(s.toUpperCase()) ,
      等,均依照 客户机 代码 (界面设计很久之后)。这个 收藏 接口的 forEach 方法协调迭代,并允许调用方提供在每个迭代中调用的逻辑。这分离了关注点。

    Stream.filter 具有 谓语 更接近于你的例子,对比一下你的使用方法是个好主意。 流滤波器 你如何使用 Tester.test 在你的例子中。

    有鉴于此,有许多原因支持/反对函数式编程,上面的重点是根据您的示例使用(或不使用)函数式接口的原因(特别是从编写合同的开发人员的角度)。

        2
  •  3
  •   mmirwaldt    6 年前

    您混合了两个术语:功能接口和功能编程。 功能接口在Java中作为一种类型化语言引入,以便实现功能性编程。没有好的方法来声明匿名方法(您必须使用匿名类)。而且每个匿名类都定义了一个新的类,当你使用其中的许多类时,它会导致类污染。)然而,匿名方法是函数编程的一个非常重要的元素。因此,如果你考虑函数接口的力量,你应该考虑Java的流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(lista, new DinosaurioTester {
        boolean test(Dinosaurio d) {
            return d.isEsMamifero();
        }
    });
    

    看起来很难看。 因此,与流API结合使用的功能接口使您能够以更舒适的方式(如果您习惯的话)将其写得更短。