Java函数式编程--入门

2018-06-09  本文已影响0人  不如假如

引用来源

https://www.cnblogs.com/snowInPluto/p/5981400.html
https://my.oschina.net/joshuashaw/blog/487322
https://blog.csdn.net/yangjiachang1203/article/details/52619795
https://www.cnblogs.com/invoker-/p/7709052.html

Lambda表达式

java 8引入了Lambda表达式,用于简化代码编写。

  1. demo
button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent event) {
        System.out.println("button clicked");
    }
});

上面这段代码,使用lambda表达式 可以简化为:

button.addActionListener(event -> System.out.println("button clicked"));
  1. Lambda表达式的五种形式
    2.1 不包含参数,使用空括号 () 表示没有参数。例如,接口Runnable只有一个方法run。
Runnable noArguments = () -> System.out.println("Hello World");

该 Lambda 表达式 实现了 Runnable 接口,该接口也只有一个 run 方法,没有参数,且返回类型为 void。

2.2 包含且只包含一个参数,可省略参数的括号

ActionListener oneArgument = event -> System.out.println("button clicked");

2.3 Lambda 表达式的主体不仅可以是一个表达式,而且也可以是一段代码块,使用大括号 ({})将代码块括起来。该代码块和普通方法遵循的规则别无二致,可以用返 回或抛出异常来退出。只有一行代码的 Lambda 表达式也可使用大括号,用以明确 Lambda表达式从何处开始、到哪里结束。

Runnable multiStatement = () -> {
    System.out.print("Hello");
    System.out.println(" World");
};

2.4 Lambda 表达式也可以表示包含多个参数的方法,如➍所示。这时就有必要思考怎样去阅 读该 Lambda 表达式。这行代码并不是将两个数字相加,而是创建了一个函数,用来计算 两个数字相加的结果。变量 add 的类型是 BinaryOperator。

BinaryOperator<Long> add = (x, y) -> x + y;

2.5 Lambda 表达式都可以扩写为原始的“匿名类”形式。当你觉得这个 Lambda 表达式很复杂不容易理解的时候,不妨把它扩写为“匿名类”形式来看。

BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;

函数接口

  1. 定义
    所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个抽象方法。这种类型的接口也称为SAM接口,即Single Abstract Method interfaces。
    • 函数式接口里是可以包含默认方法,因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的;
    • 函数式接口里是可以包含静态方法,因为静态方法不能是抽象方法,是一个已经实现了的方法,所以是符合函数式接口的定义的;
    • 函数式接口里是可以包含Object里的public方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了Object类,包含了来自java.lang.Object里对这些抽象方法的实现;
    • 函数式接口里允许子接口继承多个父接口,但每个父接口中都只能存在一个抽象方法,且必须的相同的抽象方法。
  2. 常用的核心函数接口
    • Predicate<T>,参数:T,返回boolean。用于判别一个对象。比如求一个人是否为男性
    • Consumer<T> ,参数T,返回void。用于接收一个对象进行处理但没有返回,比如接收一个人并打印他的名字
    • Function<T, R> ,参数T,返回R。用于转换一个对象为不同类型的对象。
    • Supplier<T> ,无参数。返回T。用于提供一个对象。
    • UnaryOperator<T>,参数T,返回T。用于接收对象并返回同类型的对象。
    • BinaryOperator<T>,参数(T, T),返回T。用于接收两个同类型的对象,并返回一个原类型对象。
    • 其中 Cosumer 与 Supplier 对应,一个是消费者,一个是提供者。Predicate 用于判断对象是否符合某个条件,经常被用来过滤对象。Function 是将一个对象转换为另一个对象,比如说要装箱或者拆箱某个对象。UnaryOperator 接收和返回同类型对象,一般用于对对象修改属性。BinaryOperator 则可以理解为合并对象。

Stream操作

  1. Java 8 中,引入了流(Stream)的概念,这个流和以前我们使用的 IO 中的流并不太相同。所有继承自 Collection 的接口都可以转换为 Stream。demo:
class Person {
    public String name;
    public int age;
}

List<Person> personList;
统计personList中年龄大于20的数目:
long count = personList.stream()
             .filter(person -> person.getAge() > 20)
             .count();
  1. Stream 常用操作。Stream 的方法分为两类。一类叫惰性求值,一类叫及早求值。判断一个操作是惰性求值还是及早求值很简单:只需看它的返回值。如果返回值是 Stream,那么是惰性求值。其实可以这么理解,如果调用惰性求值方法,Stream 只是记录下了这个惰性求值方法的过程,并没有去计算,等到调用及早求值方法后,就连同前面的一系列惰性求值方法顺序进行计算,返回结果。通用形式为:
    Stream.惰性求值.惰性求值. ... .惰性求值.及早求值
  2. collect(toList())。由 Stream 里的值生成一个列表,是一个及早求值操作。
List<String> collected = Stream.of("a", "b", "c")
                         .collect(Collectors.toList());

  1. map. 如果有一个函数可以将一种类型的值转换成另外一种类型,map 操作就可以使用该函数,将一个流中的值转换成一个新的流。map 方法就是接受的一个 Function 的匿名函数类,进行的转换。
List<String> collected = Stream.of("a", "b", "hello")
                               .map(string -> string.toUpperCase())
                               .collect(toList());
  1. filter。遍历数据并检查其中的元素时,可尝试使用 Stream 中提供的新方法 filter。filter 方法就是接受的一个 Predicate 的匿名函数类,判断对象是否符合条件,符合条件的才保留下来。
List<String> beginningWithNumbers = 
        Stream.of("a", "1abc", "abc1")
              .filter(value -> isDigit(value.charAt(0)))
              .collect(toList());
  1. flatMap. 可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream。flatMap 最常用的操作就是合并多个 Collection。
List<Integer> together = Stream.of(asList(1, 2), asList(3, 4))
                               .flatMap(numbers -> numbers.stream())
                               .collect(toList());
  1. max和min. Stream 上常用的操作之一是求最大值和最小值。Stream API 中的 max 和 min 操作足以解决这一问题。max 和 min 方法返回的是一个 Optional 对象.
List<Integer> list = Lists.newArrayList(3, 5, 2, 9, 1);
int maxInt = list.stream()
                 .max(Integer::compareTo)
                 .get();
int minInt = list.stream()
                 .min(Integer::compareTo)
                 .get();

Integer::compareTo 也是属于 Java 8 引入的新特性,叫做 方法引用(Method References)。在这边,其实就是 (int1, int2) -> int1.compareTo(int2) 的简写

  1. reduce. reduce 操作可以实现从一组值中生成一个值。上述例子中用到的 count、min 和 max 方法,因为常用而被纳入标准库中。事实上,这些方法都是 reduce 操作。reduce 的第一个参数,是一个初始值。
int result = Stream.of(1, 2, 3, 4)
             .reduce(0, (acc, element) -> acc + element);
             
int result = Stream.of(1, 2, 3, 4)
             .reduce(1, (acc, element) -> acc * element);
  1. 数据并行化操作。并行化操作流只需改变一个方法调用。如果已经有一个 Stream 对象,调用它的 parallel() 方法就能让其拥有并行操作的能力。如果想从一个集合类创建一个流,调用 parallelStream() 就能立即获得一个拥有并行能力的流。
int sumSize = Stream.of("Apple", "Banana", "Orange", "Pear")
              .parallel()
              .map(s -> s.length())
              .reduce(Integer::sum)
              .get();

如果你去计算这段代码所花的时间,很可能比不加上 parallel() 方法花的时间更长。这是因为数据并行化会先对数据进行分块,然后对每块数据开辟线程进行运算,这些地方会花费额外的时间。并行化操作只有在 数据规模比较大 或者 数据的处理时间比较长 的时候才能体现出优势,所以并不是每个地方都需要让数据并行化,应该具体问题具体分析。

  1. 收集器collect。Stream 转换为 List 是很常用的操作,其他 Collectors 还有很多方法,可以将 Stream 转换为 Set, 或者将数据分组并转换为 Map,并对数据进行处理。也可以指定转换为具体类型,如 ArrayList, LinkedList 或者 HashMap。甚至可以自定义 Collectors,编写自己的收集器。
  2. 元素顺序。一些集合类型中的元素是按顺序排列的,比如 List;而另一些则是无序的,比如 HashSet。增加了流操作后,顺序问题变得更加复杂。如果集合本身就是无序的,由此生成的流也是无序的。一些中间操作会产生顺序,比如对值做映射时,映射后的值是有序的,这种顺序就会保留 下来。如果进来的流是无序的,出去的流也是无序的。如果我们需要对流中的数据进行排序,可以调用 sorted 方法。
List<Integer> list = Lists.newArrayList(3, 5, 1, 10, 8);
List<Integer> sortedList = list.stream()
                           .sorted(Integer::compareTo)
                           .collect(Collectors.toList());
  1. @FunctionalInterface. 每个用作函数接口的接口都应该添加这个注释。但 Java 中有一些接口,虽然只含一个方法,但并不是为了使用 Lambda 表达式来实现的。比如,有些对象内部可能保存着某种状态,使用带有一个方法的接口可能纯属巧合。该注释会强制 javac 检查一个接口是否符合函数接口的标准。如果该注释添加给一个枚举类型、类或另一个注释,或者接口包含不止一个抽象方法,javac 就会报错。重构代码时,使用它能很容易发现问题。

实际使用

  1. 场景描述:现在有很多用户,每个用户使用userId来标识,每个用户都有一定数量的money。Person类的定义如下:
@Data
class Person {
        private String userId;
        private Integer money;
}

现在有一个List<Person> personList,如果需要计算所有person的money总和,使用Lambda表达式,可以是:

Integer sum = personList.stream().mapToInt(p->p.getMoney()).sum();
  1. 场景描述:现在有一个List<String> stringList,要求将所有的String,按照长度分类。使用Lambda表达式,可以是:
Map<Integer,List<String>> result = stringList.stream().collect(Collectors.groupingBy(s -> s.length()));
上一篇下一篇

猜你喜欢

热点阅读