Java8中Stream的用法

        Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找、遍历、过滤以及常见计算等。

Stream的操作分类

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

        Intermediate主要是用来对Stream做出相应转换及限制流,其是一个中间操作,会使用惰性求值,不会说一个Intermediate操作就会扫描流中所有的数据,而是在有Terminal时,会将前面的Intermediate收集起来,然后一起进行数据的分析。

        使用Intermediate实际上是将源Stream转换为一个新的Stream,在新生成的Stream中仍然可以继续进行Intermediate的操作,但是对于Terminal来说只能有一个。

map

        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.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param <R> The element type of the new stream
     * @param mapper a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *               <a href="package-summary.html#Statelessness">stateless</a>
     *               function to apply to each element
     * @return the new stream
     */
    <R> Stream<R> 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

        filter方法对原Stream按照指定条件过滤,在新建的Stream中,只包含满足条件的元素,将不满足条件的元素过滤掉。

    /**
     * Returns a stream consisting of the elements of this stream that match
     * the given predicate.
     *
     * <p>This is an <a href="package-summary.html#StreamOps">intermediate
     * operation</a>.
     *
     * @param predicate a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                  <a href="package-summary.html#Statelessness">stateless</a>
     *                  predicate to apply to each element to determine if it
     *                  should be included
     * @return the new stream
     */
    Stream<T> 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

     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.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @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

       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

        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

         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

        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

      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 

验证不是每一个操作都是一个for循环,而是等到终端操作尽量去合并每一个操作。 

public class Test01 {
    public static void main(String[] args) {
        List<Emp> list = EmpUtils.getSource();   //会使用一次循环遍历数据
        list.stream()
                .peek(System.out::println)
                .filter((x) -> {
                    System.out.println("filter");
                    return x.getAge() > 20;
                })
                .mapToInt(x->{
                    System.out.println("mapToInt");
                    return x.getAge();
                })
                .distinct()
                .forEach(System.out::println);

        List<Emp> list1 = EmpUtils.getSource();
        Stream<Emp> filter = list1.stream()  //不会输出任何结果
                .peek(System.out::println)
                .filter((x) -> {
                    System.out.println("filter");
                    return x.getAge() > 20;
                });


    }

}

输出:
Emp{name='wj', age=24, money=5000.0}
filter
mapToInt
24
Emp{name='mat', age=19, money=4900.0}
filter
Emp{name='boj', age=23, money=8100.0}
filter
mapToInt
23
Emp{name='mary', age=24, money=6500.0}
filter
mapToInt
Emp{name='tom', age=18, money=4999.0}
filter
Emp{name='jack', age=24, money=5210.0}
filter
mapToInt

 

实战演练:

       在我做毕业设计时遇到了这样的需求,需要记录商品的访问记录,当我们点击一个商品时,就会将这个商品添加到浏览记录中,浏览记录最大只会记录最新访问的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<String> asList= Arrays.asList(split);
        LinkedList<String> list=new LinkedList<>(asList);
        //查看是否包括当前返回的商品
        if(list.contains(pids)){
            //如果存在,则删除
            list.remove(pids);
        }
        //再添加到第一个,说明是刚访问过
        list.addFirst(pids);
        //拼接pids的值
        StringBuffer sb=new StringBuffer();
        for(int i=0;i<list.size()&&i<=7;i++){
            sb.append(list.get(i));
            if(i+1!=list.size()){
                sb.append("-");
            }
        }
        return sb.toString();
    }

     使用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
发布了225 篇原创文章 · 获赞 30 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/qq_35634181/article/details/100565908