JDK8特性:Stream流

1. Stream流是什么?

这个stream流和io流是完全不同的概念。
io流的工作是硬盘到内存以及内存到硬盘的操作,而stream流是针对集合的一种管道操作。
类似于mybatis-plus可以简化sql操作,stream流也可以简化对集合的操作。

2. Stream流常用方法

我们定义这样一个实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private Integer id;
    private String name;
    private String email;
    private Integer age;
}

我们想创建三个User对象放到一个List中,先创建这三个user对象

User user1 = new User(1, "张三", "111@qq.com", 30);
User user2 = new User(2, "李四", "112@qq.com", 43);
User user3 = new User(3, "王五", "113@qq.com", 33);

传统做法放到List中

List<User> userList = new ArrayList<>();
userList.add(user1);
userList.add(user2);
userList.add(user3);

使用stream流

List<User> userList = Stream.of(user1, user2, user3).collect(Collectors.toList());

2.1 of()

static <T> Stream<T> of(T... values) 

它的作用是为给定元素创建顺序流,它传入的是可变长度参数,它返回的是一个Stream对象,可以带泛型。在上面这个例子中,返回的是Stream<User>对象。

2.2 collect()

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

它的作用是,Stream流操作完数据后,如果需要将流的数据进行保存,我们使用这个方法,将流中的数据保存到集合中,其常用的实参有这么三种,我们可以见名思意。

1. Collectors.toList()
2. Collectors.toSet()
3. Collectors.toMap()

我们看一下toList()方法的源码

public static <T>
Collector<T, ?, List<T>> toList() {
    return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                               (left, right) -> { left.addAll(right); return left; },
                               CH_ID);
}

取集合对象某一字段放到另一集合
将上面userList的name字段提取出来放到一个新的List集合中

传统做法

List<String> userNameList = new ArrayList<>();
for (User user : userList) {
	userNameList.add(user.getName());
}

使用stream

List<String> userNameList = userList.stream().map(User::getName).collect(Collectors.toList());

2.3 stream()

default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
}

这个方法是接口Collection中的一个方法,目的就是把List或Set集合对象转化为流,便于进行后续的流操作。这也是开发中最常见的创建流的方式。

2.4 map()

其在Stream类中是这样定义的。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map()的作用就是将每个元素映射成新元素。观察方法参数,是一个Function函数式接口。
map()方法可以拿出对象中某一字段,也可以对基本类型集合每个数据进行转换操作或调用方法操作。
两个例子

List<String> list = Stream.of("Monkey", "Cat", "Tiger", "Lion")
.map(String::toUpperCase).collect(Collectors.toList());
System.out.println(list);

得到结果
[MONKEY, CAT, TIGER, LION]

相当于给list中每个String类型数据都调用了toUpperCase()方法,封装结果集为一个新集合。

List<Integer> list2 = Stream.of("Monkey", "Cat", "Tiger", "Lion")
.map(String::length).collect(Collectors.toList());
System.out.println(list2);

得到结果
[6, 3, 5, 4]

同理,相当于每个数据都调用了length()方法,封装结果为一个新集合。

2.4.1 peek()

Stream<T> peek(Consumer<? super T> action);

peek是一种特殊的map方法,其参数是一个消费型接口。
当方法没有返回值,或者参数就是返回值时,可以使用peek方法代替map。

2.4.2 flatMap()

如何处理二维数组与二维集合?我们使用flatMap方法进行处理。这里就先不细致讲解了,我们能弄明白map的用法即可。

2.4.3 其余

IntStream mapToInt(ToIntFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);

使用这三个方法给流转化为数字流后就可以进行一些数字操作,例如max()、min()、sum()、average()、count() 等。
例如我们想计算用户年龄的平均值,可以这样操作。

OptionalDouble average = userList.stream().mapToInt(User::getAge).average();

OptionalDouble 有一个getAsDouble()方法,可以返回Double对象,我们就可以拿到它的数值了。

double average = userList.stream().mapToInt(User::getAge).average().getAsDouble();

关于Optional类,我们之后再做讲解。

再以上面最初的那个userList为例,如果我们想在某一字段加上过滤条件,例如要过滤出名字带五的人。传统做法需要使用for进行逐个遍历判断,并把符合条件的放到新集合中。

List<User> newUserList = new ArrayList<>();
for (User user : userList) {
    if(!user.getName().contains("五")) {
        newUserList.add(user);
    }
}

如果我们用stream流

userList = userList.stream().filter(user -> !user.getName().contains("五")).collect(Collectors.toList());

一行代码即可解决,而且不用再去创建新集合。

2.5 filter()

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

filter的方法定义是这样的,参数是一个断言型接口,实参可以使用lambda表达式或者方法引用。
这个过滤,是把符合断言为true的元素保留下来

2.6 count()

如果我们在前面不加任何过滤条件,这个count()毫无用处,因为它获得的是集合内所有元素个数总和,我们用集合的 size() 方法即可获得。
但我们加了条件后,就可以知道满足条件的元素有多少。

long count = userList.stream().filter(user -> !user.getName().contains("五")).count();

这样就知道名字里不带“五”的用户有几个了。

还是上面这个userList,如果我想把这个List转换成Map,key是user的id,value是user的name,使用传统方式的话,需要遍历整个List集合,拿到元素再放入Map。

我们使用上文提到的 Collectors.toMap() 处理这个。不过我们需要先注意两个问题

  1. Map的key重复问题
    我们通过key重复,value覆盖的方式解决。
  2. 空指针异常,userList可能有null的存在。
    我们通过filter过滤的方式解决。
Map<Integer, String> map = userList.stream()
.filter(Objects::nonNull)
.collect(Collectors.toMap(User::getId, User::getName, (key1, key2) -> key2));

2.7 判断方法

2.7.1 anyMatch()

有一个成功就返回true

//  true
boolean flag = userList.stream().anyMatch(user -> user.getAge() > 40);

2.7.2 allMatch()

所有都成功才返回true

// false
boolean flag = userList.stream().allMatch(user -> user.getAge() > 40);

2.7.3 noneMatch()

所有都失败才返回true

// false
boolean flag = userList.stream().noneMatch(user -> user.getAge() > 40);
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
# jdk7新特性 ## try-with-resources 是一种声明了`一种或多种资源的try语句`。资源是指在程序用完了之后必须要关闭的对象。try-with-resources语句保证了每个声明了的`资源在语句结束的时候都会被关闭`。任何实现了java.lang.`AutoCloseable`接口的对象,和实现了java .io .`Closeable`接口的对象,`都可以当做资源使用`。 ``` try ( InputStream is = new FileInputStream("xx"); OutputStream os = new FileOutputStream("xx") ) { //xxx //不用关闭了,JVM帮你关闭 ``` ## 多异常统一处理 在Java 7中,catch代码块得到了升级,用以在`单个catch块中处理多个异常`。如果你要捕获多个异常并且它们包含相似的代码,使用这一特性将会减少代码重复度。 ``` try { //xxx } catch (AException | BException e) { e.printStackTrace(); } ``` 缺点是异常处理细粒度降低 ## 泛型推导 ``` List<String> list = new ArrayList<>(); ``` `泛型实例`的创建可以通过`类型推断`来简化,`可以去掉`后面new部分的泛型类型,`只用<>`就可以了。 ## 使用ForkJoin Java 7开始引入了一种新的Fork/Join线程池,它可以执行一种特殊的任务:把一个大任务拆成多个小任务并行执行。 我们举个例子:如果要计算一个超大数组的和,最简单的做法是用一个循环在一个线程内完成: ```ascii ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ``` 还有一种方法,可以把数组拆成两部分,分别计算,最后加起来就是最终结果,这样可以用两个线程并行执行: ```ascii ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ ``` 如果拆成两部分还是很大,我们还可以继续拆,用4个线程并行执行: ```ascii ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ┌─┬─┬─┬─┬─┬─┐ └─┴─┴─┴─┴─┴─┘ ``` 这就是Fork/Join任务的原理:判断一个任务是否足够小,如果是,直接计算,否则,就分拆成几个小任务分别计算。这个过程可以反复“裂变”成一系列小任务。 我们来看如何使用Fork/Join对大数据进行并行求和: ``` public class Main { public static void main(String[] args) throws Exception { // 创建2000个随机数组成的数组: long[] array = new long[2000]; long expectedSum = 0; for (int i = 0; i < array.length; i++) { array[i] = random(); expectedSum += array[i]; } System.out.println("Expected sum: " + expectedSum); // fork/join: ForkJoinTask<Long> task = new SumTask(array, 0, array.length); long startTime = System.currentTimeMillis(); Long result = ForkJoinPool.commonPool().invoke(task); long endTime = System.currentTimeMillis(); System.out.println("Fork

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值