“我正在参加「掘金·启航计划」”
Stream
Java8中有两大最为重要的改变。第一个是 Lambda 表达式,在上篇文章已经讲述了一些;另外一个则是 StreamAPI(java.util.stream.*)。 Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
1. 什么是Stream?
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”流需要注意以下几个特点:
①Stream 自己不会存储元素。
②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
1. Stream操作的三个步骤
⚫ 创建Stream: 通过一个数据源(如:集合、数组),获取一个流。
⚫ 中间操作:一个中间操作链,对数据源的数据进行处理。
⚫ 终止操作(终端操作):一个终止操作,执行中间操作链,并产生结果。
3. Stream的创建
3.1 由集合创建流
Java8 中的 Collection 接口被扩展,提供了两个获取流的方法:
⚫ default Stream stream() : 返回一个顺序流
⚫ default Stream parallelStream() : 返回一个并行流
顺序流使用的较多,使用单线程按顺序对数据源进行操作,并行流的线程有多个,处理效率高但是顺序不确定。
3.2由数组创建流
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
⚫ static Stream stream(T[] array): 返回一个流
重载形式,能够处理对应基本类型的数组:
⚫ public static IntStream stream(int[] array)
⚫ public static LongStream stream(long[] array)
⚫ public static DoubleStream stream(double[] array)
3.2由值创建流
可以使用静态方法 Stream.of(), 通过显示值创建一个流。(它可以接收任意数量的参数。接收的参数会被保存到一个容器里面)
⚫ public static Stream of(T... values) : 返回一个流
4. Stream 的中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理! 而在终止操作时一次性全部处理,称为“惰性求值”,这些操作也是我们最关心的。
4.1 筛选与切片
| 流方法 | 含义 |
|---|---|
| filter(Predicate p) | 接收 Lambda,从流中排除某些元素。 |
| distinct() | 筛选,通过流所生成元素的 hashCode() 和 equals() 去除重复元素 |
| limit(long maxSize) | 截断流,使其元素不超过给定数量。 |
| skip(long n) | 跳过元素,返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。与 limit(n) 互补 |
@Test
public void test1(){
List<String> stringList = Arrays.asList("aa","bb","cc","cc","ddd","sdsdsds","aa");
//筛选字符串集合里面字符串长度不大于2的字符串并用新的集合收集起来
System.out.println(stringList.stream().filter(str -> str.length() <= 2).collect(Collectors.toList()));
//原集合不变
System.out.println(stringList);
//去掉重复的字符串,如果集合里是自定义的对象,需要重写hashCode() 和 equals()
System.out.println(stringList.stream().distinct().collect(Collectors.toList()));
//取前三个元素
System.out.println(stringList.stream().limit(3).collect(Collectors.toList()));
//跳过前三个,取后三个
System.out.println(stringList.stream().skip(3).collect(Collectors.toList()));
//也可以进行多次操作,但是进行终止操作后就不能再进行中间操作
System.out.println(
stringList.stream()
.filter(s -> s.length() <= 3)
.distinct()
.limit(4)
.skip(1)
.collect(Collectors.toList())
);
}
这样看就有些SQL的味道了,而且很容易理解。
4.2 映射
| 方法 | 描述 |
|---|---|
| map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
| mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 DoubleStream。 |
| mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 IntStream。 |
| mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的 LongStream。 |
| flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
@Test
public void test2(){
List<String> list = Arrays.asList("1","12","3","123","4");
System.out.println(list.stream().map(str -> Double.valueOf(str)).collect(Collectors.toList()));
List<User> users = new ArrayList<>();
users.add(new User(12,"Jack",null));
users.add(new User(13,"Tom",null));
users.add(new User(14,"Mack",null));
System.out.println(users);
//这里User类的set方法会返回User对象本身,否则没有返回值的话会报错
System.out.println(users.stream().map(user -> user.setAge(user.getAge()+1)).collect(Collectors.toList()));
}
map可以看成是对集合里每一个元素的处理,处理后可以映射为其它类型的对象,也可以映射为原来的对象。
flatMap和map的区别在于,map只能一对一的转换,而flatMap不仅可以一对一转换,它会返回一个流,也就是一对多进行转换,即可以把一个集合或者数组进行展开返回里面的多个值组成的流
@Test
public void test4(){
List<List<Integer>> list = new ArrayList<>();
List<Integer> sub1 = Arrays.asList(1,3);
List<Integer> sub2 = Arrays.asList(2,4);
List<Integer> sub3 = Arrays.asList(6,5);
list.add(sub1);
list.add(sub2);
list.add(sub3);
System.out.println(list);
System.out.println(list.stream().map(nums->nums.stream()).collect(Collectors.toList()));
System.out.println(list.stream().flatMap(nums->nums.stream()).sorted().collect(Collectors.toList()));
}
4.3 排序
| 方法 | 描述 |
|---|---|
| sorted() | 产生一个新流,其中按自然顺序排序 |
| sorted(Comparator comp) | 产生一个新流,其中按比较器顺序排序 |
@Test
public void test3(){
List<Integer> list = Arrays.asList(12, 123, 23, 3, 12, 33, 190, 9);
System.out.println(list.stream().sorted().collect(Collectors.toList()));
//compare方法是比较i和j,i < j返回-1,i = j返回0,i > j返回1
System.out.println(list.stream()
.sorted((i, j) -> -Integer.compare(i, j))
.collect(Collectors.toList()));
List<String> stringList = Arrays.asList("aa", "11", "cc", "dd", "ee", "bb");
System.out.println(stringList.stream().sorted().collect(Collectors.toList()));
List<User> users = new ArrayList<>();
users.add(new User(12, "Jack", null));
users.add(new User(13, "Tom", null));
users.add(new User(14, "Mack", null));
//从大到小排序
users.stream()
.sorted((u1, u2) -> -u1.getAge() < u2.getAge() ? -1 : 1 )
.forEach(System.out::println);
}
sorted()这个方法会对元素进行自然顺序排序,反应到数字就是从小到大,需要元素所对应的对象需要实现Comparable接口,重写compareTo接口,Integer和String都对此类进行了实现,否则直接使用sorted()会报错;sorted(Comparator comp)这个重载方法需要接收一个比较器,需要自定义排序规则。
5. 终止操作
终端操作会从流的流水线生成结果。其结果可以是任何不是流的 值,例如:List、Integer,甚至是 void 。
5.1 查找与匹配
| 方法 | 描述 |
|---|---|
| allMatch(Predicate p) | 检查是否匹配所有元素 |
| anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
| noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
| findFirst() | 返回第一个元素 |
| findAny() | 返回当前流中的任意元素 |
| count() | 返回流中元素总数 |
| max(Comparator c) | 返回流中最大值 |
| min(Comparator c) | 返回流中最小值 |
| forEach(Consumer c) | 内部迭代(使用 Collection 接口需要用户去做迭代,称为外部迭代。相反,Stream API 使用内部 迭代——它帮你把迭代做了) |
@Test
public void test5(){
City beijing = new City(1.68,"北京",1128);
City tianjin = new City(1.1,"天津",924);
City chongqing = new City(1.6,"重庆",1500);
City shanghai = new City(0.63,"上海",2489);
List<City> cities = Arrays.asList(beijing,tianjin,chongqing,shanghai);
//匹配所有:所有城市面积大于0.5万平方公里,返回true
System.out.println(cities.stream().allMatch(city->city.getArea()>0.5));
//至少匹配一个,至少有一个尝试的人口超过2000万,返回true
System.out.println(cities.stream().anyMatch(city->city.getPeoples()>2000));
//是否没有匹配,没有城市的名字以“湖”开头,返回true
System.out.println(cities.stream().noneMatch(city->city.getName().startsWith("湖")));
System.out.println(cities.stream().max((c1,c2)->c1.getArea()<c2.getArea()?-1:1));
System.out.println(cities.stream().min((c1,c2)->c1.getArea()<c2.getArea()?-1:1));
cities.stream().forEach(System.out::println);
}
min和max接受的仍然是比较器,返回的不是对象本身,而是用Optional包装的对象
5.2 规约
| 方法 | 描述 |
|---|---|
| reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回 T |
| reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回 Optional |
先看第二个方法reduce(BinaryOperator b),其中,BinaryOperator继承了BiFunction,源码如下,也就是BiFunction的apply方法的两个参数和返回值都是同一类型
public interface BinaryOperator<T> extends BiFunction<T,T,T>
再看第一个reduce(T iden, BinaryOperator b),区别在于返回值的不同,这个方法返回的类型是T,第二个返回的类型是Optiona。
@Test
public void test6(){
List<Integer> list = Arrays.asList(12,123,23,3,12,33,190,9);
System.out.println(list.stream().reduce((i, j) -> i + j));
System.out.println(list.stream().reduce(0, (i, j) -> i + j));
}
第一个结果是Optional[405],后面是405,0是初始值。
5.3 收集
| 方 法 | 描述 |
|---|---|
| collect(Collector c) | 将流转换为其他形式。接收一个 Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map),而且Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例。