Java 8 新特性 - Stream流

本文介绍了Java 8中Stream流的使用,展示了如何通过filter、map、sum和并行处理简化集合操作。涉及Lambda表达式、方法引用,以及在任务计分、分组、权重计算和文件I/O中的应用实例。

目录:Java 8 新特性-终极指南


Java 8 新特性 - Stream流

新增的Stream API(java.util.stream)将生成环境的函数式编程引入了Java库中。这是目前为止最大的一次对Java库的完善,以便开发者能够写出更加有效、更加简洁和紧凑的代码。

Steam API极大得简化了集合操作(后面我们会看到不止是集合),首先看下这个叫Task的类:

public class Streams  {
    private enum Status {
        OPEN, CLOSED
    };
 
    private static final class Task {
        private final Status status;
        private final Integer points;
 
        Task( final Status status, final Integer points ) {
            this.status = status;
            this.points = points;
        }
 
        public Integer getPoints() {
            return points;
        }
 
        public Status getStatus() {
            return status;
        }
 
        @Override
        public String toString() {
            return String.format( "[%s, %d]", status, points );
        }
    }
}

Task类有一个分数(或伪复杂度)的概念,另外还有两种状态:OPEN或者CLOSED。现在假设有一个task集合:

final Collection< Task > tasks = Arrays.asList(
    new Task( Status.OPEN, 5 ),
    new Task( Status.OPEN, 13 ),
    new Task( Status.CLOSED, 8 ) 
);

首先看一个问题:在这个task集合中一共有多少个OPEN状态的点?

在Java 8之前,要解决这个问题,则需要使用 foreach 循环遍历task集合;

但是在Java 8中可以利用steams解决:包括一系列元素的列表,并且支持顺序和并行处理。

// Calculate total points of all active tasks using sum()
final long totalPointsOfOpenTasks = tasks
    .stream()
    .filter( task -> task.getStatus() == Status.OPEN )
    .mapToInt( Task::getPoints )
    .sum();
 
System.out.println( "Total points: " + totalPointsOfOpenTasks );

运行这个方法的控制台输出是:

Total points: 18

这里有很多知识点值得说。

  • 首先,tasks集合被转换成steam表示;
  • 其次,在steam上的 filter 操作会过滤掉所有CLOSED的task;
  • 第三,mapToInt 操作基于每个task实例的 Task::getPoints 方法将task流转换成Integer集合;
  • 最后,通过sum方法计算总和,得出最后的结果。

在学习下一个例子之前,还需要记住一些steams(点此更多细节)的知识点。Steam之上的操作可分为中间操作和终止操作。详情可参考:Stream API

中间操作会返回一个新的steam——执行一个中间操作(例如 filter )并不会执行实际的过滤操作,而是创建一个新的steam,并将原steam中符合条件的元素放入新创建的steam。

终止操作(例如 forEach 或者 sum ),会遍历steam并得出结果或者附带结果;在执行终止操作之后,steam处理线已经处理完毕,就不能使用了。在几乎所有情况下,终止操作都是立刻对steam进行遍历。

steam的另一个价值是创造性地支持并行处理(parallel processing)。对于上述的tasks集合,我们可以用下面的代码计算所有任务的点数之和:

// Calculate total points of all tasks
final double totalPoints = tasks
   .stream()
   .parallel()
   .map( task -> task.getPoints() ) // or map( Task::getPoints ) 
   .reduce( 0, Integer::sum );
 
System.out.println( "Total points (all tasks): " + totalPoints );

这里我们使用parallel方法并行处理所有的task,并使用reduce方法计算最终的结果。控制台输出如下:

Total points(all tasks): 26.0

对于一个集合,经常需要根据某些条件对其中的元素分组。利用steam提供的API可以很快完成这类任务,代码如下:

// Group tasks by their status
final Map< Status, List< Task > > map = tasks
    .stream()
    .collect( Collectors.groupingBy(Task::getStatus));
System.out.println( map );

控制台的输出如下:

{CLOSED=[[CLOSED, 8]], OPEN=[[OPEN, 5], [OPEN, 13]]}

最后一个关于tasks集合的例子问题是:如何计算集合中每个任务的点数在集合中所占的比重,具体处理的代码如下:

// Calculate the weight of each tasks (as percent of total points) 
final Collection< String > result = tasks
    .stream()                                        // Stream< String >
    .mapToInt( Task::getPoints )                     // IntStream
    .asLongStream()                                  // LongStream
    .mapToDouble( points -> points / totalPoints )   // DoubleStream
    .boxed()                                         // Stream< Double >
    .mapToLong( weigth -> ( long )( weigth * 100 ) ) // LongStream
    .mapToObj( percentage -> percentage + "%" )      // Stream< String> 
    .collect( Collectors.toList() );                 // List< String > 
 
System.out.println( result );

控制台输出结果如下:

[19%, 50%, 30%]

最后,正如之前所说,Steam API不仅可以作用于Java集合,传统的IO操作(从文件或者网络一行一行得读取数据)可以受益于steam处理,这里有一个小例子:

final Path path = new File( filename ).toPath();
try( Stream<String> lines = Files.lines(path, StandardCharsets.UTF_8)) {
    lines.onClose( () -> System.out.println("Done!") ).forEach( System.out::println );
}

Stream的方法 onClose 返回一个等价的有额外句柄的Stream,当Stream的 close() 方法被调用的时候这个句柄会被执行。

Stream API、Lambda表达式还有接口默认方法和静态方法支持的方法引用,是Java 8对软件开发的现代范式的响应。

Stream流与Lambda表达式、方法引用等结合使用,效果还是比较不错的,可以多加练习。

### Java 8Stream API 的特点和优势 #### 支持函数式编程风格 Stream API 设计之初就考虑到了支持函数式编程的概念。通过使用 lambda 表达式以及方法引用,开发者能够写出更加简洁明了的代码[^1]。 ```java // 使用lambda表达式的例子 List<String> result = list.stream() .filter(s -> s.startsWith("a")) .map(String::toUpperCase) .sorted() .collect(Collectors.toList()); ``` #### 高效处理集合数据 对于大规模的数据集操作而言,Stream 提供了一种声明性的处理方式而不是命令式的循环迭代。这不仅提高了可读性和维护性,还使得并行化变得简单易行[^2]。 ```java // 创建一个整数并过滤掉偶数值 Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5); stream1.filter(n -> n % 2 != 0).forEach(System.out::println); ``` #### 构建灵活多样的对象 除了可以直接由集合创建外,还可以利用 `Stream.Builder` 或者静态工厂方法如 `of()` 来方便地构建不同类型的实例。 ```java // 利用Builder模式逐步添加元素至Stream.Builder<String> builder = Stream.builder(); builder.add("a").add("b").add("c"); Stream<String> stringStream = builder.build(); // 使用 of() 方法快速初始化带有固定参数列表的 Stream<Integer> integerStream = Stream.of(1, 2, 3, 4, 5); ``` #### 自动资源管理能力 当涉及到外部资源(比如文件输入/输出)时,Stream 可以很好地配合 try-with-resources 结构,在不再需要的时候自动释放这些资源,从而减少了潜在的风险[^3]。 ```java try (Stream<String> lines = Files.lines(Paths.get("data.txt"))) { lines.forEach(System.out::println); } catch (IOException e) { e.printStackTrace(); } ``` #### 内置多种终端操作 提供了诸如 `forEach`, `reduce`, `collect` 等丰富的终止操作来完成最终的结果收集工作;同时也包含了像 `limit` 和 `skip` 这样用于控制中间程的行为的方法[^4]。 ```java // 截取前三个元素形成新的列表 List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5); List<Integer> limited = numbers.stream().limit(3).collect(Collectors.toList()); System.out.println(limited); // 输出 [1, 2, 3] // 跳过前面两个元素后再进行后续处理 numbers.stream().skip(2).forEach(System.out::println); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值