Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。
Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。
- 中间操作,每次返回一个新的流,可以有多个。
- 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。
- stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
- stream不会改变数据源,通常情况下会产生一个新的集合或一个值
- stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。
获取一个流非常简单,有以下几种常用的方式:
//通过 java.util.Collection.stream() 方法用集合创建流
log.info("========通过 java.util.Collection.stream() 方法用集合创建流====start");
List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();
System.out.println(stream);
System.out.println(parallelStream);
log.info("========通过 java.util.Collection.stream() 方法用集合创建流====end");
log.info("========使用java.util.Arrays.stream(T[] array)方法用数组创建流====start");
//使用java.util.Arrays.stream(T[] array)方法用数组创建流
int[] array={1,3,5,6,8};
IntStream stream3 = Arrays.stream(array);
System.out.println(stream3);
log.info("========使用java.util.Arrays.stream(T[] array)方法用数组创建流====end");
log.info("========使用使用Stream的静态方法:of()、iterate()、generate()方法用数组创建流====start");
Stream<String> stream = Stream.of("1", "2", "3","4");
Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 2).limit(4);
stream2.forEach(System.out::println);
Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);
log.info("========使用使用Stream的静态方法:of()、iterate()、generate()方法用数组创建流====end");
stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求(可能会乱序)。例如筛选集合中的奇数,两者的处理不同之处
String info = "stream和parallelStream的简单区分";
log.info("========{}====start", info);
Stream<Integer> stream = Stream.of(1, 2, 3, 5, 9, 7, 4, 3, 8, 6, 2, 5);
stream.filter(x -> x % 2 == 1).forEach(x -> System.out.print(x));//输出:1359735
System.out.println();
Stream<Integer> parallelStream = Arrays.asList(1, 2, 3, 5, 9, 7, 4, 3, 8, 6, 2, 5).parallelStream();
parallelStream.filter(x -> x % 2 == 1).forEach(x -> System.out.print(x));//输出:3559713
System.out.println();
log.info("========{}====end", info);
延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用
public class User extends Mother{
private String name;
private Integer age;
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
}
User user = new User("小红",1);
User user2 = new User("小白",2);
String info = "终端操作:forEach(遍历/终结方法)";
log.info("========{}====start", info);
List<User> users = Arrays.asList(user, user2);
Stream<User> stream = users.stream();
本方法主要用于进行遍历,可简化for循环遍历。参数传入一个函数式接口:Consumer
stream.forEach(System.out::print);//User(name=小红, age=1)User(name=小白, age=2)
本方法主要用于进行过滤,可以通过 filter 方法将一个流转换成另一个子集流,从而进行所需的相关过滤。
stream2.filter(o-> o.getAge()==1).forEach(System.out::print);//User(name=小红, age=1)
如果需要进行映射转换,可以使用 map 方法将流中的元素映射到另一个流中。该接口需要一个 Function 函数式接口参数,java.util.stream.Function 函数式接口中唯一的抽象方法为
R apply(T t);
stream3.map(new Function<User, String>() {
@Override
public String apply(User user) {
String name = user.getName();
return name;
}
}).forEach(System.out::print);//小红小白
类似集合 Collection 当中的 size 方法一样,Stream流提供 count 方法来统计其中的元素个数,该方法返回一个long值代表元素个数(不再像集合是int值)。
System.out.println(stream4.filter(o -> o.getAge() == 1).count());//1
limit 方法主要用于对流进行截取,只取用前n个。参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用方法如下
stream5.limit(2).limit(1).forEach(System.out::print);
skip 方法主要用于跳过前几个元素,获取一个截取之后的新流。如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用方法如下
stream6.skip(1).forEach(System.out::print);
如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :
System.out.println(Stream.concat(stream7, stream8).count());
去除流中重复的元素(使用hashcode和equals方法来对比)
System.out.println(Stream.concat(stream9, stream10).distinct().count());
flatMap与map作用类似,内部传入一个Function函数式接口,跟map的区别就是这个会把流中的元素打开,再组合成一个新的流。
@Test
public void test(){
List<String> list = Arrays.asList("aa","bb","cc","dd");
// 1 map输出全为大写
list.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
// 2.map流里还有其他流,需要两个遍历才可以得到里面内容
Stream<Stream<Character>> streamStream = list.stream().map(new Function<String, Stream<Character>>() {
@Override
public Stream<Character> apply(String s) {
return null;
}
});
streamStream.forEach(s -> {
s.forEach(System.out::print);
});
System.out.println();
// 3.flatMap流里还有其他流,使用flatMap可以直接把里面的流打开,一次遍历即可
Stream<Character> characterStream = list.stream().flatMap(new Function<String, Stream<? extends Character>>() {
@Override
public Stream<? extends Character> apply(String str) {
ArrayList<Character> list = new ArrayList();
// 将字符串转成字符数组,并遍历加入list集合
for(Character c : str.toCharArray()){
list.add(c);
}
// 返回list集合的stream流
return list.stream();//aa
}
});
characterStream.forEach(System.out::print);
System.out.println();
}
/**
* 将字符串中的多个字符构成的集合转换为对应的stream
* @param str
* @return
*/
public static Stream<Character> fromStringToStream(String str){
ArrayList<Character> list = new ArrayList();
// 将字符串转成字符数组,并遍历加入list集合
for(Character c : str.toCharArray()){
list.add(c);
}
// 返回list集合的stream流
return list.stream();//aa
}
本方法主要用于排序,可以使用Comparator接口进行定制排序。具体使用方法如下:
public void test2(){
List<Integer> integers = List.of(124, 2, 15, 12, 51, -5, 5);
// 按照自然排序
integers.stream().sorted().forEach(System.out::println);
List<Integer> integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
// 定制排序(大到小),需要传入Comparator接口(如果流中的是引用类型,只能用定制排序)
// 简写:integers2.stream().sorted((e1,e2) -> e2-e1).forEach(System.out::println);
integers2.stream().sorted((e1,e2) -> {
return e2-e1;
}).forEach(System.out::println);
}
本类方法会返回一个Boolean值,具体分类如下:
是否全部匹配:allMatch
是否至少匹配一个:anyMatch
是否没有匹配的:noneMatch
@Test//检测匹配
public void test3(){
List<Integer> integers = Arrays.asList(124, 2, 15, 12, 51, -5, 5);
// 判断是否全部大于5
boolean b = integers.stream().allMatch(i -> i > 5);
// 结束输出false
System.out.println(b);
List<Integer> integers2 = Arrays.asList(124, 2, 15, 12, 51, -5, 5);
// 检测是否匹配至少一个元素
boolean b1 = integers2.stream().anyMatch(i -> i > 5);
// 输出true
System.out.println(b1);
List<Integer> integers3 = Arrays.asList(124, 2, 15, 12, 51, -5, 5);
// 检查是否没有匹配的元素
boolean b2 = integers3.stream().noneMatch(i -> i > 1000);
// 输出true,全部不匹配
System.out.println(b2);
}
- findAny方法会在流中查找任意一个元素并返回。在顺序流中,它会返回遇到的第一个元素;而在并行流中,它会返回最先找到的元素。请注意,返回的元素是不确定的,并且在不同的运行中可能会有所不同。
- 和 findAny() 方法一样,find() 方法也是在流中查找元素并返回。但是,它在顺序流中查找的是第一个匹配的元素(根据传递给它的 Predicate 参数),而在并行流中它不是保证返回的是最先找到的元素。此外,find() 方法在流中找不到匹配的元素时会返回一个空的 Optional 对象,而 findAny() 方法则可能返回 empty()。所以说,如果你只想要找到任意一个匹配的元素并不关心它是哪个,你可以使用 findAny() 方法。而如果你想找到一个固定的匹配元素,你可以使用 findFirst() 方法。
public void test4(){
List<Integer> integers = Arrays.asList(124, 2, 15, 12, 51, -5, 5);
// 输出第一个元素
Optional<Integer> first = integers.stream().findFirst();
// 输出结果是Optional[124]
System.out.println(first);
List<Integer> integers2 = Arrays.asList(2,124, 2, 15, 12, 51, -5, 5);
// 返回其中一个元素
Optional<Integer> any = integers2.stream().findAny();
System.out.println(any);
}
public void test5(){
List<User> list = new ArrayList<>();
list.add(new User("张三",25));
list.add(new User("李四",27));
list.add(new User("王五",35));
list.add(new User("赵六",55));
//查找年龄最大的人
Optional<User> max = list.stream().max((e1, e2) -> e1.getAge() - e2.getAge());
//返回赵六,55岁年龄最大
System.out.println(max.get());
}
@Test//【终端操作】 规约
public void test6() {
List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 求集合里数字的和(归约)reduce第一个参数是初始值。
Integer reduce = integers.stream().reduce(2, Integer::sum);
System.out.println(reduce);
List<User> list = new ArrayList<>();
list.add(new User("张三",25));
list.add(new User("李四",27));
list.add(new User("王五",35));
list.add(new User("赵六",55));
// 求所有人的年龄和(归约)
// 不用方法引用写法:Optional reduce = list.stream().map(person -> person.getSalary()).reduce((e1, e2) -> e1 + e2);
Optional<Integer> reduce2 = list.stream().map(User::getAge).reduce(Integer::sum);
System.out.println(reduce2.get());
}
collect(Collector c):将流转化为其他形式,接收一个Collector接口的实现
@Test//【终端操作】 收集
public void test7() {
List<User> list = new ArrayList<>();
list.add(new User("张三",25));
list.add(new User("李四",27));
list.add(new User("王五",35));
list.add(new User("赵六",55));
//30岁以上的user转为list
List<User> collect = list.stream().filter(o->o.getAge()>30).collect(Collectors.toList());
System.out.println(collect);
//30岁以上的user转为 set
Set<User> collect1 = list.stream().filter(o -> o.getAge() > 30).collect(Collectors.toSet());
System.out.println(collect1);
}
可以使用Stream.iterate创建流值,即所谓的无限流。
@Test//【中间操作】 iterate(迭代)(也是生成流的方式)
public void test8() {
List<User> list = new ArrayList<>();
list.add(new User("张三",25));
list.add(new User("李四",27));
list.add(new User("王五",35));
list.add(new User("赵六",55));
Stream.iterate(1,o->o*2).limit(3).forEach(System.out::println);
}
peek接收的是一个Consumer函数,peek 操作会按照 Consumer 函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性
@Test//【中间操作】 peek(查看)
// peek接收的是一个Consumer函数,peek 操作会按照 Consumer 函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性
public void test9() {
List<User> list = new ArrayList<>();
list.add(new User("张三",25));
list.add(new User("李四",27));
list.add(new User("王五",35));
list.add(new User("赵六",55));
list.stream().peek(e -> System.out.println("查看刚过滤出的值:" + e)).forEach(System.out::println);
}