简介
在java8中,基于Lambda所带来的函数式编程, 引入了一个全新的Stream流概念,称为流式操作,用于简化集合和数组操作的API。
这种风格将要处理的元素集合看作一种流, 流在管道中传输, 并且可以在管道的节点上进行处理, 比如筛选, 排序,聚合等。
元素流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果。
Stream的目的: 用于简化集合和数组操作的API。
Stream流的思想核心: 流水线, 不是IO操作
Stream使用步骤:
-
得到集合/数组Stream流对象
-
把元素放上去(创建Stream对象)
-
调用Stream类相关API进行数据加工处理
API方法分为两大类:
-
中间操作的API 得到新的Stream
-
终止操作的API 得到结果
Stream流的三类方法:
-
获取Stream流
创建一条流水线,并把数据放到流水线上准备进行操作
-
中间方法
流水线上的操作。一次操作完毕之后,还可以继续进行其他操作。
-
终结方法
一个Stream流只能有一个终结方法,是流水线上的最后一个操作
Stream流使用注意事项:
-
在Stream流中无法直接修改集合、数组中的数据
-
Stream流只能操作一次
-
Stream方法返回的是新的流
-
Stream不调用终止方法,中间的操作不会执行
Stream操作推荐链式调用,因为Stream流只能操作一次
获取Stream流
1.Collection集合得到Stream流
Collection接口提供stream()
以下是三种集合获取Stream流的方法
//List集合
List<String> list1 = new ArrayList<>();
list1.add("abc");
list1.add("bbb");
list1.add("ccc");
list1.add("abc");
//得到Stream对象
Stream<String> stream = list1.stream();
//System.out.println(stream);
//Set集合
Set<String> set = new HashSet<>();
set.add("abc");
set.add("bbb");
set.add("ccc");
set.add("abc");
Stream<String> stream1 = set.stream();
//Map集合
Map<String,Integer> map = new HashMap<>();
map.put("abc",123);
map.put("bcd",345);
//得到key集合的stream
Stream<String> stream2 = map.keySet().stream();
//得到Entry的stream
Stream<Map.Entry<String, Integer>> stream3 = map.entrySet().stream();
2.数组得到Stream对象
Stream.of() 得到
注意: Stream.of() 要求的数组必须是对象数组, 基本数据类型的数组不支持
//数组的stream对象
Stream<Integer> stream4 = Stream.of(1, 2, 3, 4, 5, 6);
Integer[] arr = {2,4,6,8,10};
Stream<Integer> stream5 = Stream.of(arr);
/*
int[] arr1 = {1,2,3,4,5};
Stream<int[]> arr11 = Stream.of(arr1); //把int[]数组作为一个元素, 错误的
*/
Stream流常用方法
-
终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。终结方法包括 count() 和 forEach() 方法;
-
中间方法:又叫函数拼接方法。值返回值类型仍然是 Stream 类型的方法,支持链式调用(除了终结方法外,其与方法均为中间方法)
常用方法:
1.forEach
void forEach(Consumer<? super T> action);
forEach() 方法用来遍历流中的数据,是一个终结方法。该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。示例如下
List<String> arr = new ArrayList<>();
Collections.addAll(arr,"小A","小B","小C","小D","小F");
//forEach遍历
// 未简写
// arr.forEach((String s)->{
// System.out.println(s);
// } );
//简写1
//arr.forEach(s -> System.out.println(s));
//简写2
arr.forEach(System.out::println);
2.filter
Stream<T> filter(Predicate<? super T> predicate);
filter() 方法,用于过滤数据,返回符合过滤条件的数据,是一个非终结方法。我们可以通过 filter() 方法将一个流转换成另一个子集流。该接口接收一个 Predicate 函数式接口参数(可以是一个 Lambda 或 方法引用) 作为筛选条件。示例如下:查找名字长度大于2的元素
List<String> list = new ArrayList<>();
Collections.addAll(list,"张三","王乐乐","田亮","李四","王五");
//filter()过滤,返回名字长度大于2的元素
list.stream().filter(str->str.length()>2).forEach(System.out::println);
3.count
long count()
count() 方法,用来统计集合中的元素个数,是一个终结方法。该方法返回一个 long 值代表元素个数,示例如下:
List<String> list = new ArrayList<>();
Collections.addAll(list,"张三","王乐乐","田亮","李四","王五");
long count = list.stream().count();
System.out.println("集合个数:"+count);
4.limit
Stream<T> limit(long maxSize);
limit() 方法,用来对 Stream 流中的数据进行截取,只取用前 n 个,是一个非终结方法。参数是一个 long 型,如果集合当前长度大于参数则进行截取,否则不进行操作。因为 limit() 是一个非终结方法,所以必须调用终止方法。示例如下:
List<String> list = new ArrayList<>();
Collections.addAll(list,"张三","王乐乐","田亮","李四","王五");
//截取前面三个元素
list.stream().limit(3).forEach(System.out::println);
5.skip
Stream<T> skip(long n);
如果希望跳过前几个元素,取后面的元素,则可以使用 skip()方法,获取一个截取之后的新流,它是一个非终结方法。参数是一个 long 型,如果 Stream 流的当前长度大于 n,则跳过前 n 个,否则将会得到一个长度为 0 的空流。因为 skip() 是一个非终结方法,所以必须调用终止方法。示例如下:
List<String> list = new ArrayList<>();
Collections.addAll(list,"张三","王乐乐","田亮","李四","王五");
list.stream().skip(3).forEach(System.out::println);
6.sorted
//根据元素的自然规律排序
Stream<T> sorted();
//根据比较器指定的规则排序
Stream<T> sorted(Comparator<? super T> comparator);
因为 sorted() 方法是一个非终结方法,所以必须调用终止方法。
场景:
-
sorted() 方法:按照自然规律,默认为升序排序。
-
sorted(Comparator comparator) 方法,按照指定的比较器规则排序(以降序为例)。 示例如下
//sorted():根据元素的自然规律排序
Stream<Integer> stream01 = Stream.of(66,33,11,55);
stream01.sorted().forEach(System.out::println);
//sorted(Comparator<? super T> comparator):根据比较器规则降序排序
Stream<Integer> stream02 = Stream.of(66,33,11,55);
stream02 .sorted((i1,i2)-> i2-i1).forEach(System.out::println);
7.distint
Stream<T> distinct();
distinct() 方法,可以用来去除重复数据。因为 distinct() 方法是一个非终结方法,所以必须调用终止方法。
去除重复数据,此处有几种情况:①基本类型去重 ②String类型去重 ③引用类型去重(对象去重)
①②使用 distinct() 方法可以直接去重 ③对象类型需要重写 equals() 和 hasCode() 方法,使用 distinct() 方法才能去重成功。
//基本类型去重
Stream<Integer> stream01 = Stream.of(66,33,11,55,33,22,55,66);
stream01 .distinct().forEach(System.out::println);
//字符串去重
Stream<String> stream02 = Stream.of("AA","BB","AA");
stream02.distinct().forEach(System.out::println);
8.concat
public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
--> 合并两个流
concat() 方法,可以将两个Stream流合并成一个流进行返回。如果是三个流,则需要两两合并,不能一次性合并三个流。concat() 方法是 Stream 接口的静态方法,我们可以直接使用【类名.方法名】调用。
注意:concat() 方法此处接收的是 Stream 类型,不能接收 IntStream 等类型。concat() 是一个静态方法,与 java.lang.String 中的 concat() 方法是不同的。
//concat()方法
Stream<Integer> aStream = Stream.of(1, 2, 3);
Stream<Integer> bStream = Stream.of(4, 5, 6);
Stream<Integer> concatStream = Stream.concat(aStream, bStream);
concatStream.forEach(System.out::println);
9.reduce
//1.
Optional<T> reduce(BinaryOperator<T> accumulator); //无默认值(可能为空,所以返回 Optional<T> 类型)
//2.
T reduce(T identity, BinaryOperator<T> accumulator); //有默认值(所以返回类型为 T)
//T identity:默认值
//BinaryOperator<T> accumulator:对数据进行处理的方式
//提醒:BinaryOperator<T> 接口继承自 BiFunction,实际上使用的还是 BiFunction<T, U, R>中的apply()方法,因为apply(T t,U u)有两个参数,
//所以 在下面你会看到 BinaryOperator<T> accumulator 这个参数传递的是(x,y) 两个参数。
//3.
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
需要将 Sream 流中的所有数据,归纳得到一个数据的情况,可以使用 reduce() 方法。如果需要对 Stream 流中的数据进行求和操作、求最大/最小值等(都是归纳为一个数据的情况),此处就可以用到 reduce() 方法。示例如下
//reduce():求和操作
Stream<Integer> stream01 = Stream.of(4,3,5,9);
Integer sum = stream01.reduce(0,(a,b)-> a + b);
System.out.println("求和:"+sum);
//reduce():求最大值操作
Stream<Integer> stream01 = Stream.of(4,3,5,9);
Integer max= stream01.reduce(0,(x,y)-> x > y ? x : y);
System.out.println("最大值为:"+max);
//reduce():求最小值操作
Stream<Integer> stream01 = Stream.of(4,3,5,9);
Optional<Integer> max= stream01.reduce((x, y)-> x < y ? x : y);
System.out.println("最小值为:"+max.get());
10.map
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
map() 方法,可以将流中的元素映射到另一个流中。即:可以将一种类型的流转换为另一种类型的流,map() 方法是一个非终结方法。该接口需要一个 Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。
这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong 和 mapToDouble。这三个方法也比较好理解,比如 mapToInt 就是把原始 Stream 转换成一个新的 Stream,这个新生成的 Stream 中的元素都是 int 类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗。
IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper);
因为 map() 方法是一个非终结方法,所以必须调用终止方法。通过如下示例,使用 map() 方法,通过方法引用,便将字符串类型转换成了 Integer 类型。示例如下:
List<String> list = new ArrayList<>();
Collections.addAll(list,"11","22","33","44","55");
//通过map()方法,可以将String类型的流转换为int类型的流
/*list.stream().map((String str)->{
return Integer.parseInt(str);
}).forEach((Integer num) -> {
System.out.println(num);
});*/
//简化:
//list.stream().map(str->Integer.parseInt(str)).forEach(str->System.out.println(str));
//简化后:
list.stream().map(Integer::parseInt).forEach(System.out::println);
11.flatMap
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap 的使用,同 map 类似。map只是一维 1对1 的映射,返回的是指定的类型;而flatMap返回的则还是一个Stream流,可以对其进行进一步操作。(区别:map返回的是指定类型(比如int),而flatMap返回的还是一个Stream流)
假如你的集合流中包含子集合(或者需要更深进一步操作),那么使用 flatMap 可以返回该子集合的集合流。示例代码如下所示:
public class Province {
private String name;
private List<String> city;
//get/set 方法
}
public class FlatMapDemo{
public static void main(String[] args) {
List<Province> provinceList = new ArrayList<>();
List<String> bjCityList = new ArrayList<>();
bjCityList.add("海淀");
bjCityList.add("朝阳");
List<String> hnCityList = new ArrayList<>();
hnCityList.add("长沙");
hnCityList.add("株洲");
hnCityList.add("张家界");
Province bjProvince = new Province();
bjProvince.setName("北京");
bjProvince.setCity(bjCityList);
provinceList.add(bjProvince);
Province hnProvince = new Province();
hnProvince.setName("湖南");
hnProvince.setCity(hnCityList);
provinceList.add(hnProvince);
//使用map,需要多次forEach
provinceList.stream().map(str->str.getCity()).forEach(cityList -> cityList.forEach(System.out::println));
System.out.println("----------");
//使用 flatMap
provinceList.stream().flatMap(str->str.getCity().stream().filter(city->city.length()>2)).forEach(System.out::println);
}
}