Java--Stream流详解

慈云数据 8个月前 (03-13) 技术支持 106 0

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
微信扫一扫加客服

微信扫一扫加客服

点击启动AI问答
Draggable Icon