Stream是Java 8 API添加的一个新的抽象,称为流Stream,以一种声明性方式处理数据集合(侧重对于源数据计算能力的封装,并且支持序列与并行两种操作方式)
Stream流是从支持数据处理操作的源生成的元素序列,源可以是数组、文件、集合、函数。流不是集合元素,它不是数据结构并不保存数据,它的主要目的在于计算
Stream流是对集合(Collection)对象功能的增强,与Lambda表达式结合,可以提高编程效率、间接性和程序可读性。
特点
1、代码简洁:函数式编程写出的代码简洁且意图明确,使用stream接口让你从此告别for循环
2、多核友好:Java函数式编程使得编写并行程序如此简单,就是调用一下方法
流程
1、将集合转换为Stream流(或者创建流)
2、操作Stream流(中间操作,终端操作)
stream流在管道中经过中间操作(intermediate operation)的处理,最后由最终操作(terminal operation)得到前面处理的结果
接口继承关系
BaseStream:基础接口,声明了流管理的核心方法;
Stream:核心接口,声明了流操作的核心方法,其他接口为指定类型的适配
一、流创建操作
生成流的方式主要有五种
1、Stream创建
List list = new ArrayList(); Stream stream = list.stream(); //串行流 Stream parallelStream = list.parallelStream(); //并行流 Stream stream1 = Stream.of(1,2,3,4,5);
2、Collection集合创建(应用中最常用的一种)
List integerList = new ArrayList(); integerList.add(1); integerList.add(2); integerList.add(3); integerList.add(4); integerList.add(5); Stream listStream = integerList.stream();
3、Array数组创建
int[] intArr = {1, 2, 3, 4, 5}; IntStream arrayStream = Arrays.stream(intArr);
通过Arrays.stream方法生成流,并且该方法生成的流是数值流【即IntStream】而不是 Stream
注:
使用数值流可以避免计算过程中拆箱装箱,提高性能。
Stream API提供了mapToInt、mapToDouble、mapToLong三种方式将对象流【即Stream 】转换成对应的数值流,同时提供了boxed方法将数值流转换为对象流
4、文件创建
try { Stream fileStream = Files.lines(Paths.get("data.txt"), Charset.defaultCharset()); } catch (IOException e) { e.printStackTrace(); }
通过Files.line方法得到一个流,并且得到的每个流是给定文件中的一行
5、函数创建
iterator
Stream iterateStream = Stream.iterate(0, n -> n + 2).limit(5);
iterate方法接受两个参数,第一个为初始化值,第二个为进行的函数操作,因为iterator生成的流为无限流,通过limit方法对流进行了截断,只生成5个偶数
generator
Stream generateStream = Stream.generate(Math::random).limit(5);
generate方法接受一个参数,方法参数类型为Supplier ,由它为流提供值。generate生成的流也是无限流,因此通过limit对流进行了截断
Stream中的静态方法:of()、iterate()、generate()
Stream stream = Stream.of(1,2,3,4,5,6); stream.forEach(System.out::println); // 输出:1 2 3 4 5 6 Stream stream2 = Stream.iterate(0, (x) -> x + 2).limit(6); stream2.forEach(System.out::println); // 输出:0 2 4 6 8 10 Stream stream3 = Stream.generate(Math::random).limit(2); stream3.forEach(System.out::println); // 输出:两个随机数
BufferedReader.lines() 方法,将每行内容转成流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt")); Stream lineStream = reader.lines(); lineStream.forEach(System.out::println);
Pattern.splitAsStream() 方法,将字符串分隔成流
Pattern pattern = Pattern.compile(","); Stream stringStream = pattern.splitAsStream("a,b,c,d"); stringStream.forEach(System.out::println); //输出:a b c d
二、操作符
流的操作类型主要分为两种:中间操作符、终端操作符
(一)中间操作符
通常对于Stream的中间操作,可以视为是源的查询,并且是懒惰式的设计,对于源数据进行的计算只有在需要时才会被执行,与数据库中视图的原理相似;
Stream流的强大之处便是在于提供了丰富的中间操作,相比集合或数组这类容器,极大的简化源数据的计算复杂度
一个流可以跟随零个或多个中间操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用
这类操作都是惰性化的,仅仅调用到这类方法,并没有真正开始流的遍历,真正的遍历需等到终端操作时,常见的中间操作有下面即将介绍的 filter、map 等
流方法 | 含义 | 示例 |
---|---|---|
filter | 用于通过设置的条件过滤出元素 | List strings = Arrays.asList(“abc”, “”, “bc”, “efg”, “abcd”,"", “jkl”); List filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); |
map | 接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”) | List strings = Arrays.asList(“abc”, “abc”, “bc”, “efg”, “abcd”,“jkl”, “jkl”); List mapped = strings.stream().map(str->str+"-IT").collect(Collectors.toList()); |
distinct | 返回一个元素各异(根据流所生成元素的hashCode和equals方法实现)的流 | List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);numbers.stream().filter(i -> i % 2 == 0).distinct().forEach(System.out::println); |
sorted | 返回排序后的流 | List strings1 = Arrays.asList(“abc”, “abd”, “aba”, “efg”, “abcd”,“jkl”, “jkl”); List sorted1 = strings1.stream().sorted().collect(Collectors.toList()); |
limit | 会返回一个不超过给定长度的流 | List strings = Arrays.asList(“abc”, “abc”, “bc”, “efg”, “abcd”,“jkl”, “jkl”); List limited = strings.stream().limit(3).collect(Collectors.toList()); |
skip | 返回一个扔掉了前n个元素的流 | List strings = Arrays.asList(“abc”, “abc”, “bc”, “efg”, “abcd”,“jkl”, “jkl”); List skiped = strings.stream().skip(3).collect(Collectors.toList()); |
flatMap | 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流 | List strings = Arrays.asList(“abc”, “abc”, “bc”, “efg”, “abcd”,“jkl”, “jkl”); Stream flatMap = strings.stream().flatMap(Java8StreamTest::getCharacterByString); |
peek | 对元素进行遍历处理 | List strings = Arrays.asList(“abc”, “abc”, “bc”, “efg”, “abcd”,“jkl”, “jkl”); strings .stream().peek(str-> str + "a").forEach(System.out::println); |
public static void main(String[] args) { List userList = getUserList(); } private static List getUserList() { List userList = new ArrayList(); userList.add(new User(1,"张三",18,"上海")); userList.add(new User(2,"王五",16,"上海")); userList.add(new User(3,"李四",20,"上海")); userList.add(new User(4,"张雷",22,"北京")); userList.add(new User(5,"张超",15,"深圳")); userList.add(new User(6,"李雷",24,"北京")); userList.add(new User(7,"王爷",21,"上海")); userList.add(new User(8,"张三丰",18,"广州")); userList.add(new User(9,"赵六",16,"广州")); userList.add(new User(10,"赵无极",26,"深圳")); return userList; }
1、filter;过滤
用于通过设置的条件过滤出元素
//1、filter:输出ID大于6的user对象 List filetrUserList = userList.stream().filter(user -> user.getId() > 6).collect(Collectors.toList()); filetrUserList.forEach(System.out::println);
根据对象属性去重
List list = new ArrayList() {{ add(new User("Tony", 20, "12")); add(new User("Pepper", 20, "123")); add(new User("Tony", 22, "1234")); add(new User("Tony", 22, "12345")); }}; //只通过名字去重 List streamByNameList = list.stream().collect(Collectors.collectingAndThen( Collectors.toCollection(() -> new TreeSet(Comparator.comparing(User::getName))), ArrayList::new )); System.out.println(streamByNameList); //[User{name='Pepper', age=20, Phone='123'}, // User{name='Tony', age=20, Phone='12'}] //通过名字和年龄去重 List streamByNameAndAgeList = list.stream().collect(Collectors.collectingAndThen( Collectors.toCollection( () -> new TreeSet(Comparator.comparing(o -> o.getName() + o.getAge()))), ArrayList::new )); System.out.println(streamByNameAndAgeList); //[User{name='Pepper', age=20, Phone='123'}, // User{name='Tony', age=20, Phone='12'}, // User{name='Tony', age=22, Phone='1234'}]
collectingAndThen 这个方法的意思是: 将收集的结果转换为另一种类型。
因此上面的方法可以理解为:把 new TreeSet(Comparator.comparingLong(BookInfoVo::getRecordId))这个set转换为 ArrayList
2、map
接受一个函数作为参数。这个函数会被应用到每个元素上,并将其映射成一个新的元素(使用映射一词,是因为它和转换类似,但其中的细微差别在于它是“创建一个新版本”而不是去“修改”)
//2、map List mapUserList = userList.stream().map(user -> user.getName() + "用户").collect(Collectors.toList()); mapUserList.forEach(System.out::println);
新值类型和原来的元素的类型相同示例
List list = Arrays.asList("a,b,c", "1,2,3"); //将每个元素转成一个新的且不带逗号的元素 Stream s1 = list.stream().map(s -> s.replaceAll(",", "")); s1.forEach(System.out::println); // abc 123 Stream s2 = list.stream().flatMap(s -> { //将每个元素转换成一个stream String[] split = s.split(","); Stream s3 = Arrays.stream(split); return s3; }); s2.forEach(System.out::println); // a b c 1 2 3
新值类型和原来的元素的类型不同示例
User u1 = new User("aa", 10); User u2 = new User("bb", 20); User u3 = new User("cc", 10); List list = Arrays.asList(u1, u2, u3); Set ageSet = list.stream().map(User::getAge).collect(Collectors.toSet()); ageSet.forEach(System.out::println); //20 10 int[] ageInt = list.stream().map(User::getAge).mapToInt(Integer::intValue).toArray(); //下边这样也可以 //Integer[] ages = list.stream.map(User::getAge).toArray(Integer[]::new); for (int i : ageInt) { System.out.println(i); } //10 20 10
map的原型为: Stream map(Function