Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。
Stream的操作有Intermediate、Terminal和Short-circuiting:
Intermediate:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered、concat,limit
Terminal:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator
Short-circuiting:
anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit
Intermediate主要是用来对Stream做出相应转换及限制流,其是一个中间操作,会使用惰性求值,不会说一个Intermediate操作就会扫描流中所有的数据,而是在有Terminal时,会将前面的Intermediate收集起来,然后一起进行数据的分析。
使用Intermediate实际上是将源Stream转换为一个新的Stream,在新生成的Stream中仍然可以继续进行Intermediate的操作,但是对于Terminal来说只能有一个。
map方法将对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。为了提高处理效率,官方已封装好了,三种变形:mapToDouble,mapToInt,mapToLong。如果我们知道要将数据转化为int,long和double时尽量使用这三种方法,不要让虚拟机去自己推断,毕竟咱们的心思不告诉它,它会花费时间去思考的,这个就会有一定的效率问题。mapToDouble,mapToInt,mapToLong,这三种变形转换之后是基本类型,double,int和long,而不是包装类型。
点开Stream的map方法,如下:
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* This is an intermediate
* operation.
*
* @param The element type of the new stream
* @param mapper a non-interfering,
* stateless
* function to apply to each element
* @return the new stream
*/
Stream map(Function super T, ? extends R> mapper);
知道了其是使用Function函数式接口来进行的,Function接口有一个apply方法,传入一个数,经过操作之后,返回一个值,不懂得自行百度。
所以我们明白了map的用法,接收一个值(这个值就是流中值),经过一系列的操作,返回我们需要的值。
现在提一个简单的需求,将字符串转换为数字并打印出来,使用Stream的map的写法:
public class Convert{
public static void main(String[] args) {
Stream.of("12","13","1","34","34","67").map(Integer::parseInt)//这里可以使用mapToInt
.forEach(System.out::println); //forEach为将转换好的流进行打印
}
}
filter方法对原Stream按照指定条件过滤,在新建的Stream中,只包含满足条件的元素,将不满足条件的元素过滤掉。
/**
* Returns a stream consisting of the elements of this stream that match
* the given predicate.
*
* This is an intermediate
* operation.
*
* @param predicate a non-interfering,
* stateless
* predicate to apply to each element to determine if it
* should be included
* @return the new stream
*/
Stream filter(Predicate super T> predicate);
从上面的代码可以知道,filter使用需要传递Predicate函数式接口,这个接口也是JDK8提供的最用函数式接口,这个接口需要传入一个数,然后返回一个布尔值,filter函数也是根据这个返回值来进行过滤的,如果值为false则过滤掉。
现在提一个简单的需求,将字符串转换为数字,并且只保留偶数的,最后打印出来,使用Stream的filter的写法:
public class Test02 {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").mapToInt(x -> Integer.parseInt(x))//这个map的写法和那个一样,只不过上面那种更加简洁
.filter(x -> x % 2 == 0)//filter判断的值要是布尔类型的
.forEach(x -> System.out.println(x));
}
}
distinct方法以达到去除掉原Stream中重复的元素,生成的新Stream中没有没有重复的元素。distinct不用传值参数,去除的重复元素是除第一个以外的所有重复的数,也就是说一个数组中有很多个4,那么除了保留第一个4外,其他的均过滤掉,例如:有一串数字:10,3,5,6,8,10,5,8,1,10,去重之后的排列是10,3,5,6,8,1,10
/**
* Returns a stream consisting of the distinct elements of this stream.
*
* This is a stateful
* intermediate operation.
*
* @return the new stream
*/
IntStream distinct();
现在提一个简单的需求,将字符串转换为数字,去重之后保留偶数,最后打印出来,使用Stream的filter的写法:
public class DistinctTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").mapToInt(x -> Integer.parseInt(x))
.distinct()
.filter(x -> x % 2 == 0)
.forEach(x -> System.out.println(x));
}
}
sorted方法将对原Stream进行排序,返回一个有序列的新Stream。sorterd有两种变体sorted(),sorted(Comparator),前者将默认使用Object.equals(Object)进行排序,而后者接受一个自定义排序规则函数(Comparator),可按照意愿排序。
public class SortTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").map(x -> Integer.parseInt(x))
.distinct()
.sorted(((x, y) -> y - x))
.forEach(x -> System.out.println(x));
}
}
peek方法生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数,并且消费函数优先执行,这样我们就可以对需要消费的函数做出一些操作。
在Terminal操作执行时,Intermediate的操作才会执行,对于peek来说,如果peek的操作无法与peek之后关于流的操作无法进行优化一次执行,那么关于peek的操作就会先执行(不会说一个Intermediate操作就会扫描流中所有的数据,而是在有Terminal时,会将前面的Intermediate收集起来,然后一起进行数据的分析),例如:
public class PeekTest01 {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").map(x -> Integer.parseInt(x))
.peek(x->System.out.println("输出的结果为"+x))
.distinct()
.sorted(((x, y) -> y - x))
.forEach(System.out::println);
}
}
//输出结果为
//输出的结果为12
//输出的结果为13
//输出的结果为1
//输出的结果为34
//输出的结果为34
//输出的结果为67
//67
//34
//13
//12
//1
示例2:
public class PeekTest02 {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67").map(x -> Integer.parseInt(x))
.sorted(((x, y) -> y - x))
.peek(x->System.out.println("输出的结果为"+x))
.distinct()
.forEach(System.out::println);
}
}
//输出的结果为67
//67
//输出的结果为34
//34
//输出的结果为34
//输出的结果为13
//13
//输出的结果为12
//12
//输出的结果为1
//1
skip方法接受一个long类型的数值,将过滤掉原Stream中的前N个元素,返回剩下的元素所组成的新Stream。如果原Stream的元素个数大于N,将返回原Stream的后(原Stream长度-N)个元素所组成的新Stream;如果原Stream的元素个数小于或等于N,将返回一个空Stream。
public class SkipTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67","10","45").map(x -> Integer.parseInt(x))
.skip(3)
.forEach(System.out::println);
}
}
//输出结果
//34
//34
//67
//10
//45
concat方法将两个Stream连接在一起,合成一个Stream。若两个输入的Stream都时排序的,则新Stream也是排序的;若输入的Stream中任何一个是并行的,则新的Stream也是并行的;若关闭新的Stream时,原两个输入的Stream都将执行关闭处理。
public class ConcatTest03 {
public static void main(String[] args) {
Stream.concat(Stream.of(1, 2, 4), Stream.of(3, 5))
.forEach(integer -> System.out.print(integer + " "));
}
}
// 打印结果
// 1 2 4 3 5
limit接收一个long类型的数组,返回一个限定大小的流,如果limit(n),只会选取前n个值,之后的值则会被抛弃。
public class LimitTest {
public static void main(String[] args) {
Stream.of("12", "13", "1", "34", "34", "67","10","45").map(x -> Integer.parseInt(x))
.limit(4)
.forEach(x-> System.out.print(x+" "));
}
}
//输出结果
//12 13 1 34
在我做毕业设计时遇到了这样的需求,需要记录商品的访问记录,当我们点击一个商品时,就会将这个商品添加到浏览记录中,浏览记录最大只会记录最新访问的7条记录,当我们访问的时浏览记录中已经存在的记录,那么需要将这条记录提到最新访问的记录中。使用字符串来记录访问顺序,规则为“id-id-id"的形式,例如:当前的浏览记录为3-1-2-4,如果本次访问时2,此时的浏览记录应该为2-3-1-4,如果本次访问时4,此时的浏览记录应该为4-2-3-1,如果本次访问时5,此时的浏览记录应该为5-4-2-3(这是记录最新访问的4条记录的情况)
如果我们不是用Stream的写法:
// String pidVaule="2-3-1-4";
// String pids="3";
public String record(String pidVaule,String pids){
//进行分片
String[] split=pidVaule.split("-");
List asList= Arrays.asList(split);
LinkedList list=new LinkedList<>(asList);
//查看是否包括当前返回的商品
if(list.contains(pids)){
//如果存在,则删除
list.remove(pids);
}
//再添加到第一个,说明是刚访问过
list.addFirst(pids);
//拼接pids的值
StringBuffer sb=new StringBuffer();
for(int i=0;i
使用Stream:
// String pidVaule="2-3-1-4-10-8-23";
// String pid="8";
public String record(String pidValue,String pid){
String newValue=pid+"-"+pidValue;
String recodes = Stream.of(newValue.split("-"))
.distinct()
.limit(7)
.collect(Collectors.joining("-"));
return recodes;
}
//返回结果:8-2-3-1-4-10-23
或者是这样写:
// String pidVaule="2-3-1-4-10-8-23";
// String pid="8";
public String record(String pidValue,String pid){
String recodes = Stream.of(pidValue.split("-"))
.filter(x -> !pid.equals(x))
.limit(6)
.collect(Collectors.joining("-", pid + "-", ""));
return recodes;
}
//返回结果:8-2-3-1-4-10-23