java8 常见函数式接口及stream

672 阅读4分钟

常见函数式接口

  1. Consumer 接收一个参数,没有返回值
void accept(T t);

用法示例:

List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.forEach(a -> System.out.println(a));

forEach接收的就是Consumer
  1. Function 接收一个参数,返回一个值
R apply(T t);

用法示例:
list.stream().map(a -> a * a).forEach(System.out::println);
map接收的就是 Function
  1. Comparator 接收两个参数,返回int
int compare(T o1, T o2);
用法示例: 
Collections.sort(list, Comparator.reverseOrder());

Collections 接收的第二个参数就是 Comparator
  1. Predicate 接收一个参数,返回boolean
boolean test(T t);
用法示例: 
list.stream().filter(a -> a > 5).forEach(System.out::println);

filter 接收的参数就是 Predicate

private void conditionFilter(List<Integer> list, Predicate<Integer> predicate) {
    for (Integer i : list) {
        if (predicate.test(i)) {
            System.out.println(i);
        }
    }
}

ins.conditionFilter(list, a -> a % 2 == 0);
ins.conditionFilter(list, a -> a > 4);
ins.conditionFilter(list, a -> true);

函数式接口,传递的是行为,而不是数据
  1. Supplier 不接受参数,返回一个值
T get();

Supplier fun = FunctionTest::new;
  1. Optional 基于值的类(value-base class),这个不是函数式接口
Employee employee= new Employee();
employee.setName("zhangsan");

Employee employee2=new Employee();
employee2.setName("lisi");

Company company = new Company();
company.setName("company1");

List<Employee> employees = Arrays.asList(employee, employee2);
company.setEmployees(employees);

Optional<Company> optional = Optional.ofNullable(company);

System.out.println(optional.map(theCompany-> theCompany.getEmployees()).orElse(Collections.emptyList()));

方法引用

方法引用共分为4类:

  1. 类名::静态方法名
  2. 引用名(对象名)::实例方法名
  3. 类名::实例方法名
  4. 构造方法引用:类名::new

stream

  • Collection提供了新的stream()方法
  • 流不存储值,通过管道的方式获取值
  • 本质是函数式的,对流的操作会生成一个结果,不过并不会修改底层的数据源,集合可以作为流的底层数据源
  • 延迟查找,很多流操作(过滤、映射、排序等)都可以延迟实现
  • 迭代器又不同的是, Stream可以并行化操作,迭代器只能命令式地、串行化操作
  • 当使用串行方式去遍历时,每个item读完后再读下一个item
  • 使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出
  • Stream的并行操作依赖于Java7中引入的Fork/Join框架

stream collect实现 集合的流操作到最后基本都会调用collect方法,用来返回处理后的流,例如:

Stream<String> stream = Stream.of("hello", "world", "juejin");
List<String> list = stream.collect(Collectors.toList());

collect的方法定义:

<R> R collect(Supplier<R> supplier,
              BiConsumer<R, ? super T> accumulator,
              BiConsumer<R, R> combiner);

按上面的定义,可以这样调用:

ArrayList<Object> list1 = stream.collect(ArrayList::new, ArrayList::add, ArrayList::addAll);

这样其实很明显的就能感觉到方法的调用传递的是行为,而不是数据。

小练习: 找出该流中大于2的元素,然后将每个元素乘以2,然后忽略掉流中的前两个元素,然后再取流中的前两个元素,最后求出流中元素的较大的值。

Stream<Integer> stream = Stream.iterate(1, item -> item + 2).limit(100);
stream.filter(item -> item > 2).mapToInt(item -> item * 2).skip(2).limit(2).max().ifPresent(System.out::println);

并发流及流的短路

  • 并发流
ArrayList<Object> list = new ArrayList<>(5000000);
for (int i = 0; i < 5000000; i++) {
    list.add(UUID.randomUUID().toString());
}
System.out.println("开始排序");

long start = System.currentTimeMillis();
list.stream().sorted().count();
System.out.println("耗时:" + (System.currentTimeMillis() - start));

stream 排序耗时
开始排序
耗时:4689

当改成list.parallelStream().sorted().count();并发流
开始排序
耗时:2473

  • 短路操作
List<String> list = Arrays.asList("hello", "world", "hello world");
list.stream().mapToInt(item -> {
    int length = item.length();
    System.out.println(item);
    return length;
}).filter(length -> length == 5).findFirst().ifPresent(System.out::println);

打印的结果:
hello
5 流的操作是先把所有的操作应用到第一个元素,在第二个,第三个...... 而不是把第一个动作应用到第一个元素,在第二个,第三个,再把第二个动作应用到第一个元素,在第二个,第三个

小练习:

  1. "hello welcome", "world hello", "hello world hello", "hello welcome" 不重复的字符串打印出来
List<String> list = Arrays.asList("hello welcome", "world hello", "hello world hello", "hello welcome");
list.stream().map(item -> item.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println);

  1. 把两个list的值交差合并
List<String> list = Arrays.asList("Hi", "Hello", "你好");
List<String> list2 = Arrays.asList("zhangsan", "lisi", "wangwu", "zhaoliu");
List<String> result = list.stream().flatMap(item -> list2.stream().map(item2 -> item + " " + item2)).collect(Collectors.toList());
result.forEach(System.out::println);

结果:

Hi zhangsan
Hi lisi
Hi wangwu
Hi zhaoliu
Hello zhangsan
Hello lisi
Hello wangwu
Hello zhaoliu
你好 zhangsan
你好 lisi
你好 wangwu
你好 zhaoliu

stream 分组

Student student1 = new Student("zhangsan", 100, 20);
student student2 = new student("lisi", 90, 20);
Student student3 = new student ("wangwu", 90, 30);
Student student4 = new student("zhangsan", 80, 40);

List<Object> students = Arrays.asList(student1, student2, student3, student4);
students.stream().collect(Collectors.groupingBy(Student::getNames));

collect

  1. collect:收集器
  2. Collector作为collect方法的参数
  3. Collector是一个接口,它是一个可变的汇聚操作,将输入元素累积到一个可变的结果容器中;它会在所有元素都处理完毕后,将累积的结果转换为一个最终的表示(这是一个可选操作);它支持串行与并行两种方式执行。
  4. Collectors本身提供了关于Collector的常见汇聚实现,Collectors本身实际上是一个工厂。
  5. 为了确保串行与并行操作结果的等价性,Collector函数需要满足两个条件:identity(同一性)与associativity(结合性)。
  6. a == combiner.apply(a, supplier.get())
  7. 函数式编程最大的特点:表示做什么,而不是如何做。 collect的官方示例:
// Accumulate names into a List
*     List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
*
*     // Accumulate names into a TreeSet
*     Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
*
*     // Convert elements to strings and concatenate them, separated by commas
*     String joined = things.stream()
*                           .map(Object::toString)
*                           .collect(Collectors.joining(", "));
*
*     // Compute sum of salaries of employee
*     int total = employees.stream()
*                          .collect(Collectors.summingInt(Employee::getSalary)));
*
*     // Group employees by department
*     Map<Department, List<Employee>> byDept
*         = employees.stream()
*                    .collect(Collectors.groupingBy(Employee::getDepartment));
*
*     // Compute sum of salaries by department
*     Map<Department, Integer> totalByDept
*         = employees.stream()
*                    .collect(Collectors.groupingBy(Employee::getDepartment,
*                                                   Collectors.summingInt(Employee::getSalary)));
*
*     // Partition students into passing and failing
*     Map<Boolean, List<Student>> passingFailing =
*         students.stream()
*                 .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));