Java 8 Stream API:第 1 部分
介绍
Java 是软件开发领域广泛使用的编程语言。截至 2013 年,有超过 30 亿台设备使用 Java,该语言主要用于 Web 应用程序和 Android 应用程序。
尽管如此,人们还是抱怨该语言比同类语言(如 Ruby 和 Python)更冗长且语法要求更高。有些人甚至说它是一种过时的语言。
幸运的是,Java 8 带来了许多令人耳目一新的变化,旨在将 Java 塑造成更简单、更现代的东西。更好的是,在撰写本文时,版本 9 即将推出,其中包含更多变化。
其中一个关键的变化是Stream 接口依赖于新的 Java 组件 lambda 表达式。
本指南介绍了 lambda 表达式和 Stream 接口,并重点介绍了集合上最常见的 Stream 操作。
在第二部分中,您将了解更高级的方法(如减少和收集)和并行流。
什么是 Lambda 表达式?
首先让我们回答这个问题:Java 上下文中的 lambda 表达式是什么?
Lambda 表达式使代码更具函数性,更少面向对象,从而缩短了代码长度。举个例子如何?
而不是像这样写:
List<Toy> usedToys = findToys(toys,
new Searchable() {
public boolean test(Toy toy) {
return toy.getType().equals(
ToyTypes.USED);
}
});
Lambda 表达式可以让你编写:
List<Toy> usedToys = findToys(toys,
Toy toy ->
toy.getType().equals(ToyTypes.USED);
术语“lambda 表达式”来自 lambda 演算,写作 λ-演算,其中 λ 是希腊字母 lambda。这种形式的演算涉及定义和应用函数。
因此,lambdas 以一种称为函数式编程的方式简化代码,这是一种不同于面向对象编程的范式。
Lambda 表达式由三部分组成:
参数列表
Lambda 表达式可以有零个(用空括号表示)、一个或多个参数:
() -> System.out.println("Hi");
(String s) -> System.out.println(s);
(String s1, String s2) -> System.out.println(s1 + s2);
参数的类型可以明确声明,也可以从上下文中推断:
(s) -> System.out.println(s);
如果只有一个参数,则会推断类型,并且不强制使用括号:
s -> System.out.println(s);
如果 lambda 表达式使用与封闭上下文的变量名相同的参数名,则会产生编译错误:
// This doesn't compile
String s = ""; s -> System.out.println(s);
一支箭
由字符-和>组成,用于分隔参数和主体。
一具尸体
Lambda 表达式的主体可以包含一个或多个语句。
如果主体有一个语句,则不需要花括号,并且返回表达式的值(如果有):
() -> 4; (int a) -> a*6;
如果主体有多条语句,则需要使用花括号,并且如果表达式返回值,则必须用 return 语句返回:
() -> {
System.out.println("Hi");
return 4;
}
(int a) -> {
System.out.println(a);
return a*6;
}
对于 lambda 表达式来说,返回不是必需的。例如,以下是等效的:
() -> System.out.println("Hi");
() -> {
System.out.println("Hi");
return;
}
函数式接口的抽象方法的签名提供了 lambda 表达式的签名(这个签名称为函数描述符)。
这意味着要使用 lambda 表达式,首先需要一个功能接口,它只是具有一个方法的接口的别称。例如:
interface Searchable {
boolean test(Car car);
}
实际上,lambda 表达式不包含它们正在实现哪个函数式接口的信息。表达式的类型是根据 lambda 使用的上下文推断出来的。这种类型称为目标类型。
所以 lambda 表达式是匿名类的替代品,但是它们并不相同。
它们有一些相似之处:
- 局部变量(方法中定义的变量或参数)只有被声明为 final 或实际上是 final 时才可使用。
- 您可以访问封闭类的实例或静态变量。
- 它们不能抛出比功能接口方法的 throws 子句中指定的更多的异常。只能是相同类型或超类型。
Lambda 和匿名类之间的一些显著差异:
- 对于匿名类,this关键字解析为匿名类本身。对于 lambda 表达式,this解析为编写 lambda 的封闭类。
- 函数式接口的默认方法无法从 lambda 表达式中访问。匿名类可以。
- 匿名类被编译为内部类,而 lambda 表达式则被转换为其封闭类中的私有、静态(在某些情况下)方法。使用invokedynamic指令(在Java 7中添加),它们被动态绑定。简而言之,由于不需要加载另一个类,lambda 表达式比匿名类更高效。
考虑到这一点,让我们介绍一下 Stream 接口。
什么是流?
首先,流不是集合。
一个简单的定义是,流是集合和数组的包装器。它们包装现有的集合(或其他数据源)以支持用 lambda 表达的操作,因此您可以指定要做什么,而不是如何做。
溪流的特征
- 流与 lambda 表达式完美地协同工作。
- 流不存储其元素。
- 流是不可变的。
- 流不可重复使用。
- 流不支持对其元素的索引访问。
- 流很容易并行化。
- 如果可能的话,流操作是惰性的。
允许这种惰性的一个原因是它们的操作设计方式。它们中的大多数都会返回一个新的流,从而允许将操作链接起来并形成一个管道,从而实现这种优化。
要设置此管道,您需要:
- 创建流。
- 应用零个或多个中间操作将初始流转换为新流。
- 应用终端操作来产生结果或副作用。
创建流
流由java.util.stream.Stream<T>接口表示。这仅适用于对象。
还有专门用于处理原始类型的方法,例如IntStream、LongStream和DoubleStream。此外,还有许多方法可以创建流。让我们看看最受欢迎的三种方法。
第一个是使用stream()方法从java.util.Collection实现创建流:
List<String> words = Arrays.asList(new String[]{"hello", "hola", "hallo", "ciao"});
Stream<String> stream = words.stream();
第二个是从单个值创建流:
Stream<String> stream = Stream.of("hello","hola", "hallo", "ciao");
第三个是从数组创建流:
String[] words = {"hello", "hola", "hallo", "ciao"};
Stream<String> stream = Stream.of(words);
中级操作
您可以轻松识别中间操作;它们总是返回一个新的流。这允许将操作连接起来。
Stream<String> s = Stream.of("m", "k", "c", "t")
.sorted()
.limit(3)
中间操作的一个重要特征是它们直到调用终端操作时才会处理元素;换句话说,它们是懒惰的。
中间操作又分为无状态操作和有状态操作。
无状态操作在处理新元素时不保留先前元素的状态,因此每个操作都可以独立于其他元素上的操作进行处理。
以下是一些示例:
- 流 <T> 过滤器(谓词 <? super T> 谓词)
- 返回与给定谓词匹配的元素流。
- <R> Stream<R> flatMap(Function<? super T,? extends Stream<? extends R>> mapper)
- 返回将提供的映射函数应用于每个元素所生成内容的流。还有int、long和double版本。
- <R> 流<R> 映射(函数<? super T,? extends R> 映射器)
- 返回一个流,该流由将给定函数应用于此流的元素的结果组成。还有int、long和double版本。</f
免责声明:本内容来源于第三方作者授权、网友推荐或互联网整理,旨在为广大用户提供学习与参考之用。所有文本和图片版权归原创网站或作者本人所有,其观点并不代表本站立场。如有任何版权侵犯或转载不当之情况,请与我们取得联系,我们将尽快进行相关处理与修改。感谢您的理解与支持!
请先 登录后发表评论 ~