JavaSE : Stream 流操作

2 篇文章 0 订阅

1. Stream的概念

1.1.什么是Stream

Java 8引入了Lambda表达式和Stream API,Stream代表一个由数据元素组成的序列,支持一系列如过滤、映射、聚合等高级操作,但不支持元素的增加和删除。

1.2.Stream与集合、数组的关系

Stream与集合(如List、Set)、数组等数据结构紧密相关,但又有所不同。集合和数组用于存储数据,而Stream则提供了一种访问和处理这些数据的途径。完成操作后,Stream不会改变原始数据源,而是产生一个新的结果或者副作用(例如打印输出)。

2. 创建Stream

2.1.从集合创建Stream

Collection.stream()

在Java中,几乎所有实现了Collection接口的集合类(如ListSet)都有一个stream()方法,该方法可以返回一个代表该集合元素序列的Stream。

List<String> list = Arrays.asList("Java", "Kotlin", "Scala");
Stream<String> stream = list.stream();
Collection.parallelStream()

并行流可以在多核处理器上利用多线程并行处理集合中的数据,从而在适当的情况下提高性能,特别是对于大数据集的处理。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Stream<Integer> parallelStream = numbers.parallelStream();

2.2.从数组创建Stream

Stream API同样提供了从数组创建Stream的便捷方法。对于对象数组和基本数据类型数组,都有相应的方法来生成Stream。

数组对象

使用Arrays.stream(Object[] array)

String[] stringArray = new String[]{"Java", "Kotlin", "Scala"};
Stream<String> stringStream = Arrays.stream(stringArray);
基本数据类型数组

使用相应的方法,如IntStreamLongStreamDoubleStream

int[] intArray = new int[]{1, 2, 3, 4, 5};
IntStream intStream = Arrays.stream(intArray);

2.3.静态创建方法

Stream.of()

Stream.of()方法是一个静态方法,允许你直接从一个可变参数创建一个Stream。这个方法非常适合创建一个临时的Stream,特别是当需要快速生成一个包含几个元素的Stream时。

Stream<String> stream = Stream.of("Java", "Kotlin", "Scala");
IntStream.range( startInclusive, endExclusive )
  • startInclusive:范围的起始值,包含在流中。
  • endExclusive:范围的结束值,不包含在流中。
IntStream rangeStream = IntStream.range(1, 4); // 创建一个包含1, 2, 3的Stream

2.4.创建无限Stream

Java的Stream API还支持创建无限序列的Stream。这可以通过Stream.iterate()Stream.generate()方法实现。

创建无限Stream时需要特别小心,因为它们不会自然终止。通常,你需要使用limit()等操作来限制结果的数量,否则可能会导致程序长时间运行或内存溢出。

Stream.iterate()

从一个初始值开始,然后反复应用一个函数来生成后续的元素。

// 无限Stream,生成一个无限序列的随机数
Stream<Double> randomStream = Stream.generate(Math::random);
Stream.generate()

使用一个Supplier来生成每个元素。

// 无限Stream,从1开始,每次加1
Stream<Integer> infiniteStream = Stream.iterate(1, n -> n + 1);

2.5.其他特定类型

从文件创建
try (Stream<String> lines = Files.lines(Paths.get("path/to/file.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}
从其他数据源创建

Java的NIO(New I/O)包提供了多种方式从不同数据源读取数据到Stream,如BufferedReader.lines()。对于更复杂的数据源,可能需要结合InputStreamReaderBufferedReader等类来构造Stream。

3.操作类型

3.1.中间操作

中间操作 (Intermediate Operations)用于处理数据,但它们不会直接产生结果。相反,它们返回一个新的Stream,允许进一步的操作链式调用。

这些操作是惰性求值的,即直到遇到终止操作时才执行。

筛选与切片
filter 过滤

filter(Predicate<T> predicate):根据提供的谓词过滤出满足条件的元素。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
names.stream()
     .filter(name -> name.length() > 4)
     .forEach(System.out::println); // 输出: Charlie, David
distinct 去重复

distinct():去除重复元素。

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 4, 5);
numbers.stream()
       .distinct()
       .forEach(System.out::println); // 输出: 1, 2, 3, 4, 5
limit 取最多条

limit(long maxSize):限制Stream中元素的数量。

numbers.stream()
       .distinct()
       .limit(3)
       .forEach(System.out::println); // 输出: 1, 2, 3
skip 跳过

skip(long n):跳过Stream开始的n个元素。

numbers.stream()
       .distinct()
       .skip(2)
       .forEach(System.out::println); // 输出: 3, 4, 5
修改
peek 修改每个元素

peek():将每个元素修改值。

public class Person {

    String name;
    int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    public void setName(String name){
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }


    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class CustomSortExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
                new Person("Alice", 30),
                new Person("Bob", 25),
                new Person("Charlie", 30),
                new Person("David", 25)
        );


        List<Person> newPeoples = people.stream()
                .peek(person->{
                    person.setAge(person.getAge() + 10);
                })
                .collect(Collectors.toList());

        newPeoples.forEach(System.out::println);
    }
}
映射
map

map(Function<T, R> mapper):将每个元素转换为另一种形式或结构。

List<String> words = Arrays.asList("hello", "world");
words.stream()
     .map(String::toUpperCase)
     .forEach(System.out::println); // 输出: HELLO, WORLD

转换结构

  List<Map<String, Objcet>> peopleMap = people.stream()
                .map(person->{
                    Map<String, Objcet> map = new HashMap<>();
                    map.put("aaa", person.getAge() );
                    return map
                })
                .collect(Collectors.toList());

        peopleMap.forEach(System.out::println);
flatMap

flatMap(Function<T, Stream<R>> mapper):将每个元素转换为另一个Stream,然后将所有Stream连接成一个单一的Stream。

List<String> sentences = Arrays.asList("Hello world", "Java is fun");
sentences.stream()
         .flatMap(sentence -> Arrays.stream(sentence.split(" ")))
         .forEach(System.out::println); // 输出: Hello, world, Java, is, fun
排序
sorted自然顺序

sorted():自然排序,根据元素的自然顺序。

List<Integer> randomNumbers = Arrays.asList(5, 9, 1, 4, 7);
randomNumbers.stream()
            .sorted()
            .forEach(System.out::println); // 输出: 1, 4, 5, 7, 9
sorted 自定义排序

sorted(Comparator<T> comparator):自定义排序规则。

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() { return name; }
    public int getAge() { return age; }

    @Override
    public String toString() {
        return name + " (" + age + ")";
    }
}

public class CustomSortExample {
    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("Alice", 30),
            new Person("Bob", 25),
            new Person("Charlie", 30),
            new Person("David", 25)
        );

        // 使用自定义比较器进行排序
        Comparator<Person> byAgeThenName = Comparator
            .comparing(Person::getAge).reversed() // 首先按年龄降序
            .thenComparing(Person::getName);      // 年龄相同则按姓名升序

        List<Person> sortedPeople = people.stream()
                                       .sorted(byAgeThenName)
                                       .collect(Collectors.toList());

        sortedPeople.forEach(System.out::println);
    }
}
并行处理与合并
parallel 并行流

parallel():将流转换为并行流。

randomNumbers.parallelStream()
             .filter(num -> num % 2 == 0)
             .forEach(System.out::println); // 结果顺序可能不同,因为是并行处理
sequential 换回顺序流

sequential():将流转换回顺序流,意味着之后的操作将在单线程中顺序执行,按照数据源的自然顺序或之前指定的排序进行。当不需要并行处理或者并行处理带来额外开销(如线程同步成本)大于其带来的性能提升时,可以使用此方法。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
// 假设这是之前并行处理的代码
numbers.parallelStream()
       .map(n -> {
           // 模拟耗时操作
           Thread.sleep(100);
           return n * 2;
       })
       .sequential() // 转换为顺序流
       .forEach(System.out::println);
unordered 去除排序保证,允许优化

unordered():去除排序保证,允许优化。此方法去除了Stream的排序保证,允许某些操作(特别是并行操作)进行潜在的优化,因为不再需要维持元素的顺序。这对于那些不需要特定顺序就可以完成的操作来说,可能会提高效率。注意,只有在Stream来源本身没有固有排序(如HashSet转换的Stream)或通过无序操作(如unordered())创建的Stream上使用此方法才有意义。

List<String> words = Arrays.asList("Java", "Python", "C++", "Java", "Ruby");

long javaCount = words.stream()
                    .unordered() // 允许无序处理以优化
                    .filter(word -> word.equals("Java"))
                    .count();

System.out.println("Count of 'Java': " + javaCount); // 输出:Count of 'Java': 2
基本类型转换
boxed() 原始类型流

在原始类型流的接口中,如IntStream, LongStream, DoubleStreamboxed() 方法用于将原始类型流转换为包装类型的对象流(如Stream<Integer>, Stream<Long>, Stream<Double>)。

IntStream intStream = IntStream.of(1, 2, 3, 4, 5);
Stream<Integer> integerStream = intStream.boxed();
integerStream.forEach(System.out::println);

3.2.终止操作

终止操作 (Terminal Operations)会消费Stream,产生一个结果或副作用,并且执行后Stream不能再被使用。这些操作触发实际的计算。

查找与匹配
findFirst

findFirst():返回第一个元素(对于有序流有定义)。

Optional<String> first = words.stream()
                            .filter(word -> word.startsWith("h"))
                            .findFirst();
System.out.println(first.orElse("Not found")); // 输出: hello
anyMatch

anyMatch(Predicate<T> predicate):检查是否有至少一个元素满足条件。

boolean hasShortWord = words.stream()
                          .anyMatch(word -> word.length() < 5);
System.out.println(hasShortWord); // 输出: true
allMatch

allMatch(Predicate<T> predicate):检查是否所有元素都满足条件。

noneMatch

noneMatch(Predicate<T> predicate):检查是否没有元素满足条件。

归约
reduce

reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner):将流中的元素累积成一个单一的结果,常用于求和、乘积等。

Optional<Integer> sum = numbers.stream()
                              .reduce(Integer::sum);
System.out.println(sum.orElse(0)); // 输出: 15
收集
collect

collect(Collector<T, A, R> collector):将流转换为另一种数据结构或计算单一结果,如列表、集合、映射等。

  • Collectors.toList()Collectors.toSet()

    List<String> upperCaseWords = words.stream()
                                     .map(String::toUpperCase)
                                     .collect(Collectors.toList());
    System.out.println(upperCaseWords); // 输出: [HELLO, WORLD]
    
  • Collectors.toMap()

    基本应用:键值一对一映射

    import java.util.*;
    import java.util.stream.*;
    
    class Person {
        String name;
        int age;
    
        Person(String name, int age) {
            this.name = name;
            this.age = age;
        }
    
        public String getName() { return name; }
        public int getAge() { return age; }
    }
    
    public class ToMapExample {
        public static void main(String[] args) {
            List<Person> people = Arrays.asList(
                new Person("Alice", 30),
                new Person("Bob", 25),
                new Person("Charlie", 30)
            );
    
            Map<String, Person> mapByName = people.stream()
                                              .collect(Collectors.toMap(Person::getName, p -> p));
    
            mapByName.forEach((name, person) -> System.out.println(name + ": " + person.getAge()));
        }
    }
    

    处理键冲突:合并函数

    Map<String, Integer> mapWithMerge = people.stream()
                                          .collect(Collectors.toMap(
                                              Person::getName, 
                                              Person::getAge, 
                                              (oldValue, newValue) -> oldValue + newValue // 合并函数,这里简单相加处理冲突
                                          ));
    
    mapWithMerge.forEach((name, ageSum) -> System.out.println(name + ": " + ageSum));
    

    如果有同名的人,他们的年龄会被相加

groupingBy

groupingBy() 是 Java Stream API 中的一个收集器(Collector),它用于将流中的元素按照某个属性或者函数的结果进行分组。下面我将通过一个例子来说明如何使用 groupingBy() 方法。

假设我们有一个列表,里面存储的是员工(Employee)对象,每个员工有姓名(name)和部门(department)两个属性。现在我们想按部门对员工进行分组,可以这样做:

首先,定义一个简单的 Employee 类:

public class Employee {
    private String name;
    private String department;

    // 构造方法、getter和setter省略...
}

然后,创建一个员工列表并使用 groupingBy() 进行分组:

import java.util.*;
import java.util.stream.Collectors;

public class GroupingExample {
    public static void main(String[] args) {
        List<Employee> employees = Arrays.asList(
                new Employee("Alice", "HR"),
                new Employee("Bob", "IT"),
                new Employee("Charlie", "HR"),
                new Employee("David", "Finance"),
                new Employee("Eva", "IT")
        );

        // 使用groupingBy按部门分组
        Map<String, List<Employee>> employeesByDepartment = employees.stream()
                                                                  .collect(Collectors.groupingBy(Employee::getDepartment));

        // 打印结果
        for (Map.Entry<String, List<Employee>> entry : employeesByDepartment.entrySet()) {
            System.out.println("Department: " + entry.getKey());
            for (Employee employee : entry.getValue()) {
                System.out.println("  Employee: " + employee.getName());
            }
        }
    }
}

在这个例子中,Collectors.groupingBy(Employee::getDepartment) 表示根据员工的部门属性进行分组,最终得到的结果是一个 Map<String, List<Employee>>,其中键是部门名称,值是该部门的所有员工列表。

输出结果将会是按部门分组的员工列表,类似于这样:

Department: HR
  Employee: Alice
  Employee: Charlie
Department: IT
  Employee: Bob
  Employee: Eva
Department: Finance
  Employee: David

这个例子展示了如何使用 groupingBy() 来对集合中的对象进行分组处理,是一个非常实用且强大的Stream API特性。

partitioningBy

partitioningBy()

聚合统计
count

count():返回流中元素的数量。

long evenCount = numbers.stream()
                       .filter(num -> num % 2 == 0)
                       .count();
System.out.println(evenCount); // 输出: 3
min

min(Comparator<? super T> comparator)max(Comparator<? super T> comparator):找出最小或最大元素。

average

average():计算平均值(仅适用于数值类型流)。

连接字符串
joining

Collectors.joining(CharSequence delimiter):在收集器中使用,连接流中的元素为一个字符串。

String.join(CharSequence delimiter, CharSequence... elements):静态方法,非Stream操作,但常与Stream结合使用来连接字符串。

String joinedWords = words.stream()
                        .collect(Collectors.joining(", "));
System.out.println(joinedWords); // 输出: hello, world
输出
forEach

forEach(Consumer<? super T> action):对流中的每个元素执行给定操作。

numbers.stream()
        .forEach(num -> System.out.println("Number: " + num));
// 输出: Number: 1, Number: 2, ... (按顺序)
forEachOrdered

forEachOrdered(Consumer<? super T> action):与forEach相似,但在顺序流中保证元素的遍历顺序。

  • 29
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值