Java基础(二十五)Stream流

Stream流

JDK 1.8引入的新特性。用于解决已有集合类库既有的一些弊端。依赖于Lambda 表达式

传统从集合中获取需要的元素

代码如下:

 1 public class Demo01Stream {
 2     public static void main(String[] args) {
 3         // 构建一个集合
 4         List<String> list = new ArrayList<String>();
 5         list.add("abc123");
 6         list.add("aaa22");
 7         list.add("bcd125");
 8         list.add("abcd120");
 9         list.add("bbb230");
10         // 需要字符串中包含数字1的元素取出来
11         List<String> list2 = new ArrayList<String>();// abc123 bcd125 abcd120
12         for (String str: list) {
13             if (str.contains("1")) {
14                 list2.add(str);
15             }
16         }
17         // 需要集合当中字符串长度不能超过6个的元素取出来
18         List<String> list3 = new ArrayList<String>();
19         for (String str: list2) {
20               if( str.length() <= 6) {
21                   list3.add(str);
22               }
23         }
24         // 遍历查看最终想要的元素集合
25         for(String str: list3) {
26             System.out.println(str);
27         }
28     }
29 }

经过观察需要对源集合中的元素进行不断的循环,而且每次循环都需要从头到尾进行遍历

如果希望对集合中的元素进行筛选过滤:

  1. 将集合A中根据条件一过滤拿到子集合B;

  2. 再将子集合B根据条件二过滤筛选为子集合C。

Stream流的更优写法

借助于Stream流对象中的API方法:

 1 public class TestStream {
 2      public static void main(String[] args) {
 3         // 构建一个集合  of("abc123","aaa22")
 4         List<String> list = new ArrayList<String>();
 5         list.add("abc123");
 6         list.add("aaa22");
 7         list.add("bcd125");
 8         list.add("abcd120");
 9         list.add("bbb230");
10          
11        // 需要字符串中包含数字1的元素取出来
12       Stream<String> stream = list.stream();// 源集合A
13       // Stream<T> filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。 借助于它的方法 booolean test(T t)
14      Stream<String> stream02 = stream.filter(str -> str.contains("1"));  //子集合B
15       // 需要集合当中字符串长度不能超过6个的元素取出来
16      Stream<String> stream03 = stream02.filter(str -> str.length() <= 6); // 子集合C
17      // void forEach(Consumer<? super T> action)对此流的每个元素执行操作。
18      // 借助于Consumer中的accept(T t)  打印输出
19      stream03.forEach(str -> System.out.println(str));// 遍历子集合C 打印输出集合中的每个元素
20      // 再次优化
21      list.stream().filer(str -> str.contains("1")).filter(str -> str.length() <= 6).forEach(str -> System.out.println(str));     
22      } 
23 }

流式思想概述

整体来看,流式思想类似于工厂中的“生产流水线”。

当需要对多个元素进行操作的时候,尤其是多步操作,考虑到性能以及便利性。首先需要考虑一个"模型"步骤方案。然后按照你设计的步骤方案去执行。

比如你多某中类型的商品进行操作,你需要进行过滤、映射、跳过,计数等操作,这也是我们对集合中的元素操作的步骤,这一套步骤我们称之为一种处理方案,而方案就是一种"函数模型"。

方案中的操作的每一个步骤,我们称之为一个"流",调用指定的api方法,从一个流中转换为另一个流。

都有对应的api方法,filter、map、skip、count都是对函数模型进行操作。

当我们使用一个流的时候,通常需要包含三个基本步骤:①获取一个数据源--->②数据转换---->③执行操作获取想要的结果。每次转换原有的Stream对象,返回一个新的Stream对象。这样我们就可以像链条一样进行操作。

Stream流和以往的Collection集合有所不同。Stream操作有两个基础的特征:

  • 中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格,对中间操作进行优化,比如可以进行延迟执行和短路。

  • 内部迭代:以前咱们队集合遍历都是迭代器Iterator或者增强for循环,显式的在集合外部进行迭代。这叫做外部迭代。Stream流提供了内部迭代的方法,这个流可以直接调用遍历的方法。

 

Stream流 其实是一个集合元素的函数模型,他并不是集合,也不是数据结构。其本身并不存储任何元素(地址值)。

Stream流 是一个来自数据源的元素队列:

  • 元素是特定类型的对象,形成一个队列。Java当中的Stream并不会存储元素,而是按需计算。

  • 数据源 流的来源。可以是集合,也可以是数组等容器。

获取流对象

java.util.stream.Stream<T> 是JDK 1.8引入的新特性,较为常用的接口(本身并不是函数式接口)

获取一个流对象,有以下常见的操作:

  • 所有的Collection集合都可以通过stream()默认方法来获取。

  • Stream接口里面含有一个静态方法of也可以获取对应的流对象。

 

根据Collection集合或者of方法获取流对象

只要是Collection集合的实现类或者子接口都可以调用stream默认方法获取流对象

代码如下:

 1 public static void main(String[] args) {
 2         // 把集合转换为Stream流
 3         List<String> list = new ArrayList<>();
 4         Stream<String> stream1 = list.stream();
 5         
 6         HashSet<Integer> set = new HashSet<>();
 7         Stream<Integer> stream2 = set.stream();
 8         
 9         HashMap<String, String> map = new HashMap<>();
10         // map中的key存储到一个set中
11         Set<String> keySet = map.keySet();
12         Stream<String> stream3 = keySet.stream();
13         
14         // 把map中的value值存储到一个Collection集合中
15         Collection<String> values = map.values();
16         Stream<String> stream4 = values.stream();
17         
18         // 把map中的key和value值一起存储到entry(键与值的映射)中
19         Set<Entry<String,String>> entrySet = map.entrySet();
20         Stream<Entry<String, String>> stream5 = entrySet.stream();
21         
22         // 把数组转换为Stream流
23         Stream<Integer> stream6 = Stream.of(1,2,3,4,5,6);
24         
25         stream6.filter(num -> num > 3).filter(num -> num % 2 == 0).forEach(num ->System.out.println(num));
26         
27         // 可变参数是一个数组
28         String[] arr = {"a","b","c","d"};
29         Stream<String> stream7 = Stream.of(arr);
30     }

Stream流中的常用方法

流模型中的操作很多,大致上可以把其中的api方法分成两部分:

  • 延迟方法:返回值类型都是Stream接口自身,因此可以支持链式操作。

  • 终结方法:返回值就不是Stream接口自身,因此不能再进行链式操作。比如:count方法和forEach方法

forEach方法

1  void forEach(Consumer<T> consumer);// 借助于该函数式接口中的方法accept方法
2 // Consumer<T> 是一个消费型接口 用来消费一个指定泛型的数据。

代码如下:

 1  public class TestForEach {
 2     public static void main(String[] args) {
 3         // 1. 获取一个数据源
 4       Stream<String> stream =  Stream.of("abc","aaa","abd","bcd","ddd");
 5         // 2. 转换数据
 6         // 3. 执行操作获取想要的结果
 7         stream.forEach(str -> {
 8             if (str.contains("a")){
 9                 System.out.println(str);
10             }
11         });
12     } 
13 }
14 // 展示的结果
15 abc aaa abd

过滤:filter

可以通过filter方法将一个流转换成另外一个子集流。

Stream<T> filter(Predicate<? super T> predicate) 返回由与此给定谓词匹配的此流的元素组成的流。 
//借助于Predicate函数式接口当中的抽象方法 test(T t) 对数据进行过滤

该方法接收一个函数式接口Predicate,可以使用Lambda表达式进行条件的筛选。

Predicate接口

java.util.stream.Predicate函数式接口。其中唯一的抽象方法

boolean test(T t),该方法会返回布尔类型值,代表指定的条件是否满足,如果条件满足返回true,那么Stream流的方法filter将集合或者数组其中的元素保留下来,如果条件不满足返回false,那么filter方法会舍弃该元素。

代码如下:

public static void main(String[] args) {
        // 1、 准备一个数据源
        // 获取该数据源
        String[] arr = {"小孙","小王","小赵","老王","涂少","老刘"};
        // 2. 数据转换
        // 使用Stream流中的方法filter,对姓涂的人过滤掉
        Stream<String> stream = Stream.of(arr);
        Stream<String> stream2 = stream.filter(name -> !name.contains("涂"));
        Stream<String> stream3 = stream2.filter(name -> name.startsWith("小"));
        stream3.forEach(name ->System.out.println(name));
        stream2.filter(name -> !name.contains("少")).forEach(name ->System.out.println(name));
         
        /* Stream流属于管道流,每次只能被消费一次
         * 第一个Stream流调用完毕后,数据就会被转换到下一个Stream上
         * 而这时第一个Stream流已经使用完毕,就会关闭了。
         * 所以第一个Stream就不能再调用方法了。
         * 如果你强制调用方法,程序就会抛出非法状态异常
         * java.lang.IllegalStateException: stream has already been operated upon or closed
         * stream.filter(name -> !name.contains("涂"))
              .filter(name -> name.startsWith("小"))
              .forEach(name ->System.out.println(name));
        */
    }

映射:map

如果你需要将流中的数据映射到另外一个流中,可以使用map方法。

<R> Stream<R> map(Function<? super T,? extends R> mapper)返回由给定函数应用于此流的元素的结果组成的流。

该方法接收一个函数式接口Function作为方法参数,可以将当前流中的T数据转换成另外一种R类型的数据。

Function接口

java.util.stream.Function函数式接口。其中唯一的抽象方法:

// 可以将一种T类型的数据转换成R类型的数据,那么这种转换的动作,我们称之为"映射"。    
R  apply(T t)

代码如下:

 1 public static void main(String[] args) {
 2         // 1. 准备一个数据源
 3         // 获取数据源
 4         // 把String字符串的整数-->int类型的整数
 5         Stream<String> stream = Stream.of("123","124","125","126","120");
 6         // 2. 数据转换 把字符串类型的数据转换成int类型的数据 由于Function是一个函数式接口,所以可以使用Lambda表达式
 7         // apply(T t) 
 8      //Stream<Integer> stream2 = stream.map(str -> Integer.valueOf(str));
 9         Stream<Integer> stream2 = stream.map((String str) -> {
10             return Integer.valueOf(str);
11         });
12         // 遍历
13         stream2.forEach(num -> System.out.println(num));
14     }

统计个数:count

可以像Collection集合当中的size()一样,统计流中的元素个数,通过count方法来实现。

// 返回此流中的元素数。
long count();

该方法返回一个long类型的值代表流中的元素个数(区别于size()返回值int值)

代码如下:

1 public class Demo01Count {
2     public static void main(String[] args) {
3       Stream<Integer> stream =  Stream.of(1,2,3,4,5,6);
4       // 统计个数
5       long count =   stream.count();
6       System.out.println(count);// 6  
7     } 
8 }

取用流中前几个:limit

limit()方法可以对流中的数据进行限制--->截取操作,需要一个参数max,设定取用流中前max个数。

Stream<T> limit(long maxSize)  //返回由此流的元素组成的流,截短长度不能超过 maxSize 。  

参数是一个long类型的,截取的长度不能超过流中最大元素个数;否则不进行操作。

代码如下:

 1 public class Demo02Limit {
 2     public static void main(String[] args){
 3         // 准备一个数据源
 4         // 获取数据源
 5        Stream<Integer> stream =  Stream.of(12,13,14,15,16,20,30);
 6        // 想要截取流中的前五个元素
 7        Stream<Integer> stream02  = stream.limit(5);
 8        // 查看流中的元素个数
 9         System.out.println(stream02.count());// 5
10     }   
11 }

跳过前几个:skip

如果你希望跳过前几个元素,取用后几个元素,可以使用skip方法来实现。

Stream<T> skip(long n) // 在丢弃流的第一个 n元素后,返回由该流的 n元素组成的流。

如果流中的当前个数小于n,你将会得到一个长度为0的空流;反之流中的个数大于n,则会跳过前n个元素。

代码如下:

 1 public static void main(String[] args) {
 2         // 1.
 3         String[] source = {"123","124","125","126","abc","abd","abe"};
 4         Stream<String> stream = Stream.of(source);
 5         // 2. 跳过前3个元素
 6         Stream<String> stream2 = stream.skip(source.length+1);// 空流
 7         // 3. 
 8         //stream2.forEach(str -> System.out.println(str));// abc abd abe
 9         long count = stream2.count();
10         System.out.println(count);// 0
11     }

组合:concat

如果有两个流,希望合并成一个流,那么可以使用concat静态方法

// 创建一个懒惰连接的流,其元素是第一个流的所有元素,后跟第二个流的所有元素。 
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) 

代码如下:

 1 public class Demo04Concat {
 2     public static void main(String[] args){
 3         // 准备二个数据源
 4         // 获取两次数据源
 5        Stream<Integer> stream   =   Stream.of(12,13,14,15,16,20,30);
 6        Stream<Integer> stream02 =   Stream.of(1,2,3,4,5,6,7);
 7        // 把两个流合并成一个流
 8        Stream<Integer> stream03 =   Stream.concat(stream,stream02); 
 9        stream03.forEach(num -> System.out.print(num + " "));
10        //展示结果: 12,13,14,15,16,20,30,1,2,3,4,5,6,7  
11     }   
12 }
原文地址:https://www.cnblogs.com/lk625/p/14170593.html