为什么被视为一等公民的职能如此重要?

时间:2015-06-06 15:52:00

标签: java interface lambda functional-programming java-8

Java 8提供了许多我们可以使用lambda表达式实现的功能接口,它允许将函数视为 first-class citizen(作为参数传递,从方法返回等等。)。

示例:

Stream.of("Hello", "World").forEach(str->System.out.println(str));

为什么被视为一等公民的职能如此重要?有证明这种力量的任何例子吗?

4 个答案:

答案 0 :(得分:4)

我们的想法是能够将行为作为参数传递。例如,在实现Strategy pattern

时,这很有用

Streams API是将行为作为参数传递有用的完美示例:

people.stream()
  .map(person::name)
  .map(name->new GraveStone(name, Rock.GRANITE)
  .collect(Collectors.toSet())

它还允许程序员在功能编程方面进行思考,而不是面向对象的编程,这对于许多任务来说都很方便,但在答案中可以涵盖很多东西。

答案 1 :(得分:3)

我认为问题的第二部分已得到很好的解决。但我想尝试回答第一个问题。

根据定义,一流的公民功能可以做更多事情。一流的公民职能可以:

  1. 以变量命名
  2. 作为参数传递
  3. 作为另一个函数的结果返回
  4. 作为成员数据类型参与数据结构(例如,数组或列表)
  5. 这些是“一流”的特权。

答案 2 :(得分:2)

这是表达性的问题。您没有必要,但在许多实际情况下,它将使您的代码更具可读性和简洁性。例如,拿你的代码:

public class Foo {
    public static void main(String[] args) {
        Stream.of("Hello", "World").forEach(str->System.out.println(str));
    }
}

将它与我可以提出的最简洁的Java 7实现进行比较:

interface Procedure<T> {
    void call(T arg);
}

class Util {
    static <T> void forEach(Procedure<T> proc, T... elements) {
        for (T el: elements) {
            proc.call(el);
        }
    }
}

public class Foo {
    static public void main(String[] args) {
        Util.forEach(
            new Procedure<String>() {
                public void call(String str) { System.out.println(str); }
            },
            "Hello", "World"
        );
    }
}

结果是一样的,行数少一点:)还要注意,对于支持Procedure个不同参数的实例,你需要一个接口,或者(更实际)传递所有的参数作为单个Parameters对象。通过向Procedure实现添加一些字段,可以以类似的方式进行闭包。这是很多样板。

事实上,使用anonymous classes等一流的“仿函数”和(非变异)闭包这样的东西已经存在了很长时间,但它们需要大量的实现工作。 Lambdas只是让事情更易于阅读和写作(至少在大多数情况下)。

答案 3 :(得分:1)

这是一个简短的节目(可以说是)主要的差异化因素。

public static void main(String[] args) {
  List<Integer> input = Arrays.asList(10, 12, 13, 15, 17, 19);

  List<Integer> list = pickEvensViaLists(input);
  for (int i = 0; i < 2; ++i) 
    System.out.println(list.get(i));

  System.out.println("--------------------------------------------");
  pickEvensViaStreams(input).limit(2).forEach((x) -> System.out.println(x));
}      

private static List<Integer> pickEvensViaLists(List<Integer> input) {
  List<Integer> list = new ArrayList<Integer>(input);
  for (Iterator<Integer> iter = list.iterator(); iter.hasNext(); ) {
    int curr = iter.next();
    System.out.println("processing list element " + curr);
    if (curr % 2 != 0) 
      iter.remove();
  }
  return list;
}

private static Stream<Integer> pickEvensViaStreams(List<Integer> input) {
  Stream<Integer> inputStream = input.stream();
  Stream<Integer> filtered = inputStream.filter((curr) -> { 
    System.out.println("processing stream element " + curr);
    return curr % 2 == 0; 
  });
  return filtered;
}

该程序获取输入列表并从中打印前两个偶数。它这样做了两次:第一次使用带有手写循环的列表,第二次使用带有lambda表达式的流。

在任何一种方法中,必须要编写的代码量存在一些差异,但这不是(在我看来)主要观点。区别在于如何评估事物:

在基于列表的方法中,pickEvensViaLists()的代码遍历整个列表。它将从列表中删除所有奇数值,然后才会返回main()。因此,返回到main()的列表将包含四个值:10, 12, 20, 30main()将仅打印前两个值。

在基于流的方法中,pickEvensViaStreams()的代码实际上并不迭代任何东西。它返回一个流,其他人可以从输入流计算出来,但它还没有计算任何一个。只有当main()开始迭代(通过forEach())时,才会逐个计算返回流的元素。由于main()只关心前两个元素,因此实际只计算了返回流的两个元素。换句话说:使用流,您可以获得延迟评估:只根据需要迭代流。

要看到让我们检查一下这个程序的输出:

--------------------------------------------
list-based filtering:
processing list element 10
processing list element 12
processing list element 13
processing list element 15
processing list element 17
processing list element 19
processing list element 20
processing list element 30
10
12
--------------------------------------------
stream-based filtering:
processing stream element 10
10
processing stream element 12
12
带有列表的

迭代了整个输入(因此八个&#34;处理列表元素&#34;消息)。对于流,实际上只从输入中提取了两个元素,导致只有两个&#34;处理流元素&#34;消息。