如何在安卓中使用Stream来替换for循环

  • 我们在开发过程中,有没有发现比如要在集合中寻找多个值的话,是不是要去通过for循环来查找,这样下来是不是代码量有时很大,有时大数据操作时还比较耗时。所以在java8中推出了stream(流)的概念来对容器功能的增强。它专注于对容器对象进行各种非常便利、高效的聚合操作(aggregate operation)或者大批量数据操作。

聚合操作 类似SQL语句一样的操作, 比如filter, map, reduce, find, match, sorted等

stream在java中用非常多,但是在安卓中很少见到,通过学习下,自我感觉原因有两点:
1 :stream一般会和Lambda表达式一起使用,但是Lambda表达式的优缺点非常明显,它让代码看起来更加简洁,但(个人认为)代码的可读性差。
2:stream实验发现只有在sdk 24(8.0)及以上才可以使用,所以兼容性有待优化。

1:什么是Stream?

  • Stream是Java8的一大亮点,是对容器对象功能的增强。Stream
    不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的
    Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的
    Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10
    的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

  • Stream
    就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。但是和迭代器不同的是,Stream
    可以并行化操作,迭代器只能命令式地、串行化操作。当使用串行化操作遍历时,每个 item 读完后再读下一个
    item。但并行化是把数据分成多段,其中每一个都在不同的线程中处理,然后将结果一起输出。所以效率比迭代器高。

    Stream 的另外一大特点是,数据源本身可以是无限的。

所有 Stream 的操作必须以 lambda 表达式为参数
如果不懂lambda的话可以点就上边的lanbda去特性和使用。

2:如何使用Stream?

一:构造与转换

常用的构造Stream的几种方法:
1:Individual values:

  if (android.os.Build.VERSION.SDK_INT >=        android.os.Build.VERSION_CODES.N) {
            Stream stream=Stream.of("a,"b","c","d");
        }

2:Arrays :

  String array[]=new String[]{"a,"b","c","d"};
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        Stream stream=Stream.of(array);
    }
    or:
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
       Stream  stream = Stream.of(array);
    }

3:Collections

 String array[]=new String[]{"1","2","3","4"};
        List<String> list = Arrays.asList(array);
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            Stream stream = list.stream();
        }

现在肯定会有人问, 你构造都是string类型的。对于基本数值型的要怎么构造呢?别急,先看下这个
在这里插入图片描述
可以发现Stream直接分装了IntStream、LongStream、DoubleStream。你也可以Stream、Stream、Stream,但是需要注意的是为什么要包装出这三个?而不直接指定类型就行。是因为 boxing(装箱)unboxing(拆箱) 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。

二:Stream的操作

流的操作常用的可以分为三种:

  • Intermediate:一个流可以后面跟随零个或多个intermediate操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
    例如:
    map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

  • Terminal:一个流只能有一个terminal操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以,这必定是流的最后一个操作。Terminal操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个side
    effect。
    例如:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

  • 还有一种Short-circuiting。大概的意思就是:对于一个intermediate操作,如果它接受的是一个无限大(infinite/unbounded)的Stream,但返回一个有限的新Stream;对于一个terminal操作,如果它接受的是一个无限大的Stream,但能在有限的时间计算出结果。

下边就来介绍下Intermediate和Terminal在实际中的运用:

map

map 方法用于映射每个元素到对应的结果,大概的意思就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。
例如:

  • 1:转换大写

        String wordList[] = new String[]{"a", "b", "c", "d"};
         List<String> list = Arrays.asList(wordList);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
             List<String> output = list.stream().map(String::toUpperCase).collect(Collectors.toList());
             txt.setText(output.toString());
         }
    

结果:[A, B, C, D]

  • 2:求平方(逻辑运算)

      List<Integer> nums = Arrays.asList(1, 2, 3, 4);
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
         List<Integer> squares = nums.stream().
             map(n -> n * n).
             collect(Collectors.toList());
         txt.setText(squares.toString());
     }
    

结果:[1, 4, 9, 16]

上边两个列子很好的说明了,map的操作是一一对应的关系,例如n对应了nn ,当n=1时,他output Stream 的值就是nn =1*1=1²。。。以此类推。但是如果需要一对多的时候怎么办,那就要用到flatMap。下边来看flatMap的使用。

flatMap( 一对多)

 List<Integer> num1 = Arrays.asList(1, 2, 3, 4);
        List<Integer> num2 = Arrays.asList(5, 6);
        List<Integer> num3 = Arrays.asList(7, 8,9);
        List<Integer> num4 = Arrays.asList(10, 11,12);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            Stream<List<Integer>> inputStream=Stream.of(num1,num2,num3,num4);
            List<Integer> intStream=inputStream.flatMap(childList-> childList.stream()).collect(Collectors.toList());
            txt.setText(intStream.toString());
            Log.i("Stream", intStream.toString());

        }

结果:[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。

filter

filter对原始Stream进行某项测试,通过测试的元素被留下来生成一个新Stream。通俗点讲就是 根据条件筛选
下边的筛选是空格的字符有几个:

 List<String> strings= Arrays.asList("abs","","bc","efg","abcd","","jkl");
        int count= 0;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            count = (int) strings.stream().filter(string -> string.isEmpty()).count();
        }
                Log.i("Stream", count+"");

结果:2

筛选顾名思义就是通过某个特定的条件来进行过滤。所以这个条件都是自定义的,比如。过滤奇偶数。过滤特定的字符等等。

limit/skip

limit 方法用于获取指定数量的流。 以下代码片段使用 limit 方法打印出 10 条数据:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            List<String> strings=new ArrayList<>();
            for (int i=0;i<100;i++){
                strings.add("nums "+i);
            }
            List<String> intStream=strings.stream().limit(10).collect(Collectors.toList());
            Log.i("Stream", intStream.toString()+"");

        }

结果:[nums 0, nums 1, nums 2, nums 3, nums 4, nums 5, nums 6, nums 7,
nums 8, nums 9]

skip 方法用于丢弃前几个数量的流。 以下代码片段使用 skip 方法打印出丢弃后的数据:

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            List<String> strings=new ArrayList<>();
            for (int i=0;i<10;i++){
                strings.add("nums "+i);
            }
            List<String> intStream=strings.stream().skip(5).collect(Collectors.toList());
            Log.i("Stream", intStream.toString()+"");

        }

结果:[nums 5, nums 6, nums 7, nums 8, nums 9]

sorted

sorted 方法用于对流进行排序。以下代码片段使用 sorted 方法对乱序的数进行排序:

 private void sorted(){
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            List<Integer> num= Arrays.asList(1, 3, 5, 7,2,4,6,8,9,0);
            List<Integer> list=num.stream().sorted().collect(Collectors.toList());
            Log.i("Stream", list.toString()+"");
        }
    }

结果:[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

sorted不仅仅只能对数值进行排序,当我输入String类型时的数据时:

List<String> num= Arrays.asList("f", "a", "o", "p","c","b","w","d","m");

返回的结果就成了:[a, b, c, d, f, m, o, p, w]

还有一点。如果你要在sorted的同时,进行map,filter、limit、skip操作时。sorted会等你执行完后,在去进行排序,这远远比数组的排序更强。但是局限性还是有的:即不要求排序后再取值

forEach

forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            List<String> num= Arrays.asList("f", "ac", "oc", "p","c","bc","w","d","m");
           num.stream().filter(string -> string.contains("c")).forEach(string -> Log.i("Stream", string+""));
        }

结果:
I/Stream: ac
oc
c
bc

可以看出来,forEach是为Lambda而设计的,保持了最紧凑的风格。当需要为多核系统优化时,可以parallelStream().forEach(),只是此时原有元素的次序没法保证,并行的情况下将改变串行时操作的行为,此时forEach本身的实现不需要调整,而Java8以前的for循环代码可能需要加入额外的多线程逻辑。但一般认为,forEach和常规for循环的差异不涉及到性能,它们仅仅是函数式风格与传统 Java 风格的差别。

另外一点需要注意,forEach是terminal操作。因此,它执行后,Stream 的元素就被“消费”掉了,你无法对一个Stream进行两次terminal运算。下面的代码是错误的:

stream.forEach(string -> Log.i("Stream", string+""));
 stream.forEach(string -> Log.i("Stream", string+""));

相反Peek是可以进行上述写法的。

 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            List<String> num= Arrays.asList("f", "ac", "oc", "p","c","bc","w","d","m");
            num.stream().filter(string -> string.contains("c")).peek(string -> Log.i("Stream", string+"")).map(String::toUpperCase).peek(string -> Log.i("Stream", string+""));
        }
        }

结果:ac
AC
oc
OC
c
C
bc
BC

reduce

这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce。

例如:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            //求和
            int add=Stream.of(1,2,3,4,5).reduce(0,Integer::sum);
            Log.i("Stream","求和:"+add);
            
            //求和 无指定值
            int add1=Stream.of(1,2,3,4,5).reduce(Integer::sum).get();
            Log.i("Stream","求和:"+add1);
            //取最小值
            int min=Stream.of(1,2,3,4,5).reduce(Integer.MIN_VALUE,Integer::min);
            Log.i("Stream","最小值:"+min);

            //取最大值
            int max=Stream.of(1,2,3,4,5).reduce(Integer.MAX_VALUE,Integer::max);
            Log.i("Stream","最大值:"+min);
            //字符串连接
            String str=Stream.of("a","b","c","d","e").reduce("",String::concat);
            Log.i("Stream","连接:"+str);

     }

结果:
求和:15
求和:15
最小值:-4.0
最大值:5
连接:abcde

上面代码例如第一个示例的 reduce(),第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator。这类有起始值的 reduce() 都返回具体的对象。而对于第二个示例没有起始值的 reduce(),由于可能没有足够的元素,返回的是 Optional,请留意这个区别。
还有要注意,第一个参数的使用。如果不需要起始值,那返回的时候要使用get()返回你需要的类型。

3:进阶之Collectors

上边的代码可以发现有许多Collectors操作。Collectors 类实现了很多归约操作,例如将流转换成集合和聚合元素。Collectors 可用于返回列表或字符串:

collect(Collectors.toList())
返回列表
collect(Collectors.joining(", "))
返回字符串。

groupingBy/partitioningBy

归组

  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

            Map<Integer, List<Person>> map = setData().stream().collect(Collectors.groupingBy(Person::getAge));
            Iterator it = map.entrySet().iterator();

            while (it.hasNext()) {
                Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();
                Log.i("Stream", "Age " + persons.getKey() + " = " + persons.getValue().size());
            }

            Map<String, List<Person>> map1 = setData().stream().collect(Collectors.groupingBy(Person::getSex));
            Iterator it1 = map1.entrySet().iterator();

            while (it1.hasNext()) {
                Map.Entry<String, List<Person>> persons = (Map.Entry) it1.next();
                Log.i("Stream", "Age " + persons.getKey() + " = " + persons.getValue().size());
            }
        }

结果: Age 20 = 5
Age 10 = 5
Age 女 = 5
Age 男 = 5

partitioningBy就不写例子了。partitioningBy 其实是一种特殊的 groupingBy,它依照条件测试的是否两种结果来构造返回的数据结构,get(true) 和 get(false) 能即为全部的元素对象。
比如:我判断了年龄是否满18。Collectors.partitioningBy(p -> p.getAge() < 18)
get(true)获取出来的就是满18的。get(false)就是不满18的。

结束语

总之,Stream 的特性可以归纳为:

  • 不是数据结构
  • 它没有内部存储,它只是用操作管道从 source(数据结构、数组、generator function、IO channel)抓取数据。
  • 它也绝不修改自己所封装的底层数据结构的数据。例如 Stream 的 filter 操作会产生一个不包含被过滤元素的新
    Stream,而不是从 source 删除那些元素。
  • 所有 Stream 的操作必须以 lambda 表达式为参数
  • 不支持索引访问
  • 你可以请求第一个元素,但无法请求第二个,第三个,或最后一个。不过请参阅下一项。
  • 很容易生成数组或者 List
  • 惰性化
  • 很多 Stream 操作是向后延迟的,一直到它弄清楚了最后需要多少数据才会开始。
  • 并行能力
  • 当一个 Stream 是并行化的,就不需要再写多线程代码,所有对它的操作会自动并行进行的。
  • 可以是无限的
    集合有固定大小,Stream 则不必。limit(n) 和 findFirst() 这类的 short-circuiting
    操作可以对无限的 Stream 进行运算并很快完成。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值