前言:对大数据量的集合的循环处理,stream拥有极大的优点,彻底能够用stream去代替for循环。html
先说下Stream的优点:它是java对集合操做的优化,相较于迭代器,使用Stream的速度很是快,而且它支持并行方式处理集合中的数据,默认状况能充分利用cpu的资源。同时支持函数式编程,代码很是简洁。java
Stream是一种用来计算数据的流,它自己并无存储数据。你能够认为它是对数据源的一个映射或者视图。sql
它的工做流程是:获取数据源->进行一次或屡次逻辑转换操做->进行归约操做造成新的流(最后能够将流转换成集合)。编程
Stream的建立须要一个数据源(一般是一个容器或者数组):api
例1:Stream<String> stream = Stream.of("I", "got", "you", "too");数组
例2:String [] strArray = new String[] {"a", "b", "c"};app
stream = Arrays.stream(strArray);ide
例3:List<String> list = Arrays.asList(strArray);函数式编程
stream = list.stream();函数
流的操做类型分2种:中间操做与聚合操做。
中间操做就是对容器的处理过程,包括:排序(sorted...),筛选(filter,limit,distinct...),映射(map,flatMap...)等
2.1.1 排序操做(sorted):(参考:http://www.javashuo.com/article/p-xcyazmue-bg.html)
sorted提供了2个接口:
一、sorted()
默认使用天然序排序, 其中的元素必须实现
Comparable
接口 。
sorted(Comparator<? super T> comparator)
:咱们可使用lambada 来建立一个
Comparator
实例。能够按照升序或着降序来排序元素。
好比:将一些字符串在地址中按出现的顺序排列:
String address = "中山北路南京大学仙林校区";
List<String> aList = new ArrayList<>();
aList.add("南京");
aList.add("大学");
aList.add("仙林校区");
aList.add("仙林大学城");
aList.add("中山北路");
aList.stream().sorted(
Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);
也能够像下面这样不使用比较器:
aList.stream().sorted( (a,b)->address.IndexOf(a)-address.IndexOf(b) ).forEach(System.out :: println);//由大到小排序
输出结果:
注:1.这里仙林大学城这个字段没有出现,因此序号是-1,被排在最前面。
2.Comparator.comparing();这个是比较器提供的一个方法,它返回的也是一个比较器,源码以下:
public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
Function<? super T, ? extends U> keyExtractor)
{
Objects.requireNonNull(keyExtractor);
return (Comparator<T> & Serializable)
(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
}
2.1.2 筛选操做(filter):
上一步中,咱们把一些字符串,按照在地址中出现的顺序排序。
接下来咱们可能想要进行筛选,把不在地址中,可是indexof为“-1”,排在最前面的数据筛选掉:
filter能够对集合进行筛选,它的参数能够是一个lambda表达式,流中的数据将会经过该lambda表达式返回新的流。
这里Stream有一个特性很重要,它像一个管道,能够将多个操做链接起来,并只执行一次for循环,这样大大提升了效率,即便第二次的流操做须要第一次流操做的结果,时间复杂度也只有一个for循环:
因而我能够在前面加个filter(),这样把“-1”过滤掉:
String address = "中山北路南京大学仙林校区"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大学"); aList.add("仙林校区"); aList.add("仙林大学城"); aList.add("中山北路"); aList.stream().filter(a->address.indexOf(a)!=-1)
.sorted(
Comparator.comparing(a->address.indexOf(a))
).forEach(System.out :: println);
输出结果:
注:foreach是一个终端操做,参数也是一个函数,它会迭代中间操做完成后的每个数据,这里它将每一个不为空的元素打印出来。
其它的过滤操做还包括:
limit(long maxSize):得到指定数量的流。
distinct():经过hashCode和equals去除重复元素。
2.1.3 映射操做(map):
映射操做,就像一个管道,能够将流中的元素经过一个函数进行映射,返回一个新的元素。
这样遍历映射,最终返回一个新的容器,注意:这里返回的新容器数据类型能够不与原容器类型相同:
举个例子:咱们将address中每一个元素的位置找出,并返回一个int类型的存储位置信息的数组:
@Test public void test() { String address = "中山北路南京大学仙林校区"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大学"); aList.add("仙林校区"); aList.add("仙林大学城"); aList.add("中山北路"); List<Integer> aIntegers =aList.stream() .map(str->mapFunc(address, str)).collect(Collectors.toList()); System.out.println(aIntegers);//.forEach(System.out :: println);
} private int mapFunc(String address,String str) { return address.indexOf(str); }
结果以下:
以前的中间操做只是对流中数据的处理,最终咱们仍是要将它们整合输出为一个结果,好比,返回一个最大值,返回一个新的数组,或者将全部元素进行分组等,这就是规约(末端)操做的做用。
咱们经常使用的末端操做函数有Reduce()和collect();
reduce就是减小的意思,它会将集合中的全部值根据规则计算,最后只返回一个结果。
它有三个变种,输入参数分别是一个参数、二个参数以及三个参数;
1.一个参数的Reduce
它的参数就是一个函数接口:Optional<T> reduce(BinaryOperator<T> accumulator)
好比,咱们找出数组中长度最大的一个数:
public void test() { String address = "中山北路南京大学仙林校区"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大学"); aList.add("仙林校区"); aList.add("仙林大学城"); aList.add("中山北路"); Optional<String> a =aList.stream() .reduce((s1, s2) -> s1.length()>=s2.length() ? s1 : s2); System.out.println(a.get());//仙林大学城 }
这里的Optional<T>就是一个容器,它能够避免空指针,具体能够百度,这里也能够返回一个String的。
2.两个参数的Reduce
T reduce(T identity, BinaryOperator<T> accumulator)
2个参数其实除了一个函数接口之外,还包括一个固定的初始化的值,它会做为容器的第一个元素进入计算过程:
例:将每一个字符串拼接,并在以前加上“value:”:
public void test() { String address = "中山北路南京大学仙林校区"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大学"); aList.add("仙林校区"); aList.add("仙林大学城"); aList.add("中山北路"); String t="value:"; String a =aList.stream() .reduce(t, new BinaryOperator<String>() { @Override public String apply(String s, String s2) { return s.concat(s2); } }); System.out.println(a); }
结果以下:
3.三个参数的状况主要是在并行(parallelStream)状况下使用:能够参考(https://blog.csdn.net/icarusliu/article/details/79504602),有须要能够了解下。
collect是一个很是经常使用的末端操做,它自己的参数很复杂,有3个:
<R> R collect(Supplier<R> supplier, BiConsumer<R,? super T> accumulator, BiConsumer<R,R> combiner);
还好,考虑到咱们平常使用,java8提供了一个收集器(Collectors),它是专门为collect方法量身打造的接口:
咱们经常使用collect将流转换成List,Map或Set:
1.转换成list:
Stream<String> stream = Stream.of("I", "love", "you", "too");
List<String> list = stream.collect(Collectors.toList());
2.转换成Map:
咱们可使用Collector.toMap()接口:Collectors.toMap(keyMapper, valueMapper),这里就须要咱们指定key和value分别是什么。
例:咱们将数组中的字符串做为key,字符串长度做为value,生成一个map:
String address = "中山北路南京大学仙林校区"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大学"); aList.add("仙林校区"); aList.add("仙林大学城"); aList.add("中山北路"); String t="value:"; Map<String, Integer> maps = aList.stream().collect(Collectors.toMap(Function.identity(), String::length)); System.out.println(maps);
打印结果:
{中山北路=4, 大学=2, 仙林大学城=5, 仙林校区=4, 南京=2}
一般,咱们在进行分组操做的时候也会将容器转换为Map,这里也说明一下:Collectors.groupingBy(classifier)
groupingBy与sql的group by相似,就是一个分组函数,
例:咱们将数组中的字符串按长度分组:
String address = "中山北路南京大学仙林校区"; List<String> aList = new ArrayList<>(); aList.add("南京"); aList.add("大学"); aList.add("仙林校区"); aList.add("仙林大学城"); aList.add("中山北路"); String t="value:"; Map<Integer, List<String>> maps = aList.stream().collect(Collectors.groupingBy(String::length)); System.out.println(maps);
打印结果:
{2=[南京, 大学], 4=[仙林校区, 中山北路], 5=[仙林大学城]}
findFirst:返回第一个元素,常与orElse一块儿用: Stream.findFirst().orElse(null):返回第一个,若是没有则返回null
allMatch:检查是否匹配全部元素:Stream.allMatch(str->str.equals("a"))
anyMatch:检查是否至少匹配一个元素.
2.流的末端操做只能有一次: 当这个操做执行后,流就被使用“光”了,没法再被操做。因此这一定是流的最后一个操做。以后若是想要操做就必须新打开流。
关于流被关闭不能再操做的异常:
这里曾经遇到过一个错误:stream has already been operated upon or closed
意思是流已经被关闭了,这是由于当咱们使用末端操做以后,流就被关闭了,没法再次被调用,若是咱们想重复调用,只能从新打开一个新的流。