java8 四大函数式接口 和 用于数据处理的 stream流 使用详解


在之前一篇博客里面介绍了 java8 中的 lambda 表达式,使用lambda表达式可以大大的简化我们的代码,使代码更加简洁、优雅,敲代码的效率也更高。

点击链接可以查看: java8 lambda表达式使用详解

然而,除了lambda表达式外,还有一个stream流,也可以极大的提高编码效率,并且能够提高代码的可读性。lambda表达式主要用在实现函数接口的地方,而stream流则主要用于处理数据。以下为相关功能介绍。

Stream 使用示例

首先给一个示例,(结合lambda使用,效率翻倍)

@Data
public class Student {
    private Integer id;
    private String major;
    private String name;
    private Integer level;
    private Grade grade; // 成绩
}
// 假装里面有很多students对象
List<Student> students = new ArrayList<>();

// 仅做示例, 不要纠结逻辑
Map<Integer, Grade> names = students.stream()		// 获取stream流
	.filter(Objects::nonNull)		// 过滤为null的, lambda基础版 o -> Objects.nonNull(o)
	.distinct()			//去重, 调用equals方法判断
	.sorted(Comparator.comparingInt(Student::getId))		// 排序, 根据id升序排列
    .map(Student::getGrade) 		// 映射, 由 student 转化成 Grade
    .collect(Collectors.toMap(Grade::getName, Function.identity(), 
    	BinaryOperator.maxBy(Comparator.comparing(Grade::getScore)));		// 最后转成map<gradeId, Grade>

四大函数式接口

由于 stream 流中经常用到 java8 中四大函数式接口, 并且这四个接口还衍生出了很多其他的接口. 可能有一些小伙伴对这几个基本的接口不太了解, 所以这里就简单介绍一下.

Function 功能型接口

Function 的 抽象方法如下,接收一个参数, 返回一个对象. 并且提供了一个返回自身对象的静态方法(常用)

R apply(T t);
 
static <T> Function<T, T> identity() {
    return t -> t;
}
Consumer 消费型接口

Consumer 的抽象方法如下, 接收一个参数, 没有返回值

void accept(T t);
Supplier 提供型接口

Supplier 接口的方法不接收参数, 但返回一个对象

 T get();
Predicate 断言型接口

Predicate 接口接收一个参数, 返回boolean基本类型, 用于进行判断, 同时提供一个 isEqual 的静态方法(啊,才发现)

boolean test(T t);

static <T> Predicate<T> isEqual(Object targetRef) {
	return (null == targetRef)
		? Objects::isNull
		: object -> targetRef.equals(object);
}

常用 api

看过上面的示例后,再来详细地介绍一些stream常用的api,

stream,parallelStream 获取流式对象
// Collection 接口中的stream方法
default Stream<E> stream() {
	return StreamSupport.stream(spliterator(), false);
}

在使用 stream 时,都需要通过 stream() 或 parallelStream() 获取流式对象 Stream,实现流Collection接口的类都可以使用 stream,其中 parallelStream 是使用多线程进行流式处理。

filter 过滤
Stream<T> filter(Predicate<? super T> predicate);

该方法是用于对数据流进行过滤,传入一个 Predicate (四大函数式接口之一,断言接口)实例。使用Predicate 只需要实现一个 boolean test(T t) 方法就可以。对于返回为true的通过,返回false的丢弃。

使用示例:

List<Student> stus = new ArrayList<>();
// 非null的通过
stus = stus.stream().filter(Objects::nonNUll)
			.collect(Collectors.toList());	
// 相当于  s -> Objects.nonNull(s)
sorted 排序
// 使用该无参方法,T必须实现Comparable接口,否则会抛出类型转换异常
Stream<T> sorted();

Stream<T> sorted(Comparator<? super T> comparator);

sorted 方法用于对数据流进行排序,且为升序,提供了两个重载方法,一个无参的方法,数据流必须实现了Comparable接口,通过对应的compare方法进行比较;而另一个方法需要传入Comparator实例,自己提供用于排序比较的方法。

使用示例

List<Student> students = new ArrayList<>();
students = students.stream()
	// 根据 int 类型的 student.id进行排序
	.sorted(Comparator.comparingInt(Student::getId)) 
	.collect(Collectors.toList());

// comparingInt 的源码
public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
   Objects.requireNonNull(keyExtractor);
   return (Comparator<T> & Serializable)
       (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
}
map 映射,转换
<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map方法用于将当前类型转换为另一种类型,只需要传入一个 Function 接口,实现R apply(T t) 方法,即传入 T 类型返回 R类型的对象。同时还提供的 mapToInt、mapToLong、mapToDouble 等特定转换的方法。

使用示例

List<Student> students = new ArrayList<>();

Set<String> majorSet = students.stream()
	// 将student对象转转成String, 得到major的set集合
	.map(Student::getMajor).collect(Collectors.toSet());
	// s -> s.getMajor()
peek 遍历执行某个操作
Stream<T> peek(Consumer<? super T> action);

该方法有点类似于 forEach 方法,但与forEach不同的是,forEach 会直接终止数据流,而peek还能继续执行下去。提供一个 Consumer 参数,对数据流进行某个操作。

List<Student> students = new ArrayList<>();
long count = students.stream().filter(Objects::nonNull)
	// 打印每个学生的名字
	.peek(s -> System.out.println(s.getName()))
	// 最后统计数量
	.count();
reduce 聚合,将数据流聚合成一个对象
// 提供一个标识对象, 拿identity与数据流进行聚合, 然后返回结果, 数据流空就返回identity
T reduce(T identity, BinaryOperator<T> accumulator);

// 没有标识对象, 直接在数据流内进行聚合, 可能不存在结果(数据流空的), 但避免返回null, 因此使用Optional对象
Optional<T> reduce(BinaryOperator<T> accumulator);

// 返回另一种类型的对象, 提供标识, accumulator 用上一个结果U与下一个数据T进行聚合返回U, combiner 将两个聚合结果进行合并
<U> U reduce(U identity,
                 BiFunction<U, ? super T, U> accumulator,
                 BinaryOperator<U> combiner);

reduce 的介绍注释基本都写了, 示例就不写了, 可以参照前面的自己尝试, 然后有两点提一下,

  • Optional 也是java8 新增的一个对象, 可以用于避免NPE(nullPointException) 问题, Optional 对象可以通过 empty(), of(T t), ofNullable(T t) 三个静态方法得到, 分别表示null, 非空对象, 可以为null对象. 然后可以直接get得到其持有的对象, 或直接使用Optional 提供的方法对其进行操作.
  • reduce 的第三种重载方法, 第三个参数, 是在并发场景下才会用到, 因为并发流使用的 fork/join 框架, 将数据流进行分割(类似二分法), 然后计算, 所以会得到多个结果, 最后需要对这些结果进行合并.
collect 可变聚合, 可以聚合成一个集合

这里主要介绍

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

// Collector 相当于对 上面三个参数的封装
<R, A> R collect(Collector<? super T, A, R> collector);

这个主要介绍第二个重载方法, 因为 Stream 提供了很多现成的 Collectors, 经常使用的如下

  • toList() 将数据流聚合成 ArrayList
  • toSet() 将数据流聚合成 HashSet
  • toCollection(Supplier<C> collectionFactory) 聚合成Collection, 自己提供cellection生成器 如LinkedList::new
  • groupingBy(Function<? super T, ? extends K> classifier) 根据classifier获取key, 生成 Map<?, List> 对象
  • groupingBy(Function<? super T, ? extends K> classifier, Collector<? super T, A, D> downstream) 同上, 可以自行选择下游聚合方式 (即生成List, 还是Set 或者其他)
  • toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper) 跟据两个参数(获取key 和 value)生成 Map<K,U>
  • toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) 同上, 当同一个key 存在多个value时, 使用 mergerFunction 进行合并(二选一)

当然除了上面介绍的这些还有很多好用的api, 大家用到的时候可以去自行查看.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值