Lambda与流计算
lambda
前言
java lambda表达式的重要用法是简化某些匿名内部类的写法,实际上lambda表达式底层和匿名内部类不太一样,匿名内部类在编译时会产生一个class,而Lambda表达式被封装成了主类的一个私有方法,并通过invokedynamic指令进行调用。
例子1:无参函数的简写
如果要创建一个线程,非常常见的写法如下:
new Thread(new Runnable(){
@Override
public void run(){
System.out.println("Thread run()");
}
}).start();
上述代码给Tread类传递了一个匿名的Runnable对象,重载Runnable接口的run()方法来实现相应逻辑。
// lambda 表达式的写法
new Thread(
() -> System.out.println("Thread run()");
).start();
上述代码跟匿名内部类的作用是一样的,但比匿名内部类更进一步。这里连接口名和函数名都一同省掉了,如果函数体有多行,可以用大括号括起来
new Thread(
() -> {
System.out.print("Hello");
System.out.println(" Hoolee");
}
).start();
Lambda多种用法
interface Description {
String brief();
}
interface Body {
String detailed(String head);
}
interface Multi {
String twoArg(String head, Double d);
}
public class LambdaExpressions {
static Body bod = h -> h + "No Parens!"; //当只用一个参数,可以不需要括号 ()。 然而,这是一个特例。
static Body bod2 = (h) -> h + "More details"; //正常情况使用括号 () 包裹参数。 为了保持一致性,也可以使用括号 () 包裹单个参数,虽然这种情况并不常见。
static Description desc = () -> "short info"; //如果没有参数,则必须使用括号 () 表示空参数列表。
static Multi mult = (h,d) -> h + d; //对于多个参数,将参数列表放在括号 () 中。
static Description moreLines = () -> {
System.out.println("多个参数用大括号");
return "666";
};
public static void main(String[] args) {
System.out.println(bod.detailed("Oh!"));
System.out.println(bod2.detailed("Hi!"));
System.out.println(desc.brief());
System.out.println(mult.twoArg("Pi! ", 3.14159));
System.out.println(moreLines.brief());
}
}
上述代码跟匿名内部类的作用是一样的。除了省略了接口名和方法名,代码中把参数表的类型也省略了。这得益于javac的类型推断机制,编译器能够根据上下文信息推断出参数的类型,当然也有推断失败的时候,这时就需要手动指明参数类型了。注意,Java是强类型语言,每个变量和对象都必需有明确的类型。
为了解决lambda表达式被赋值时,编译器必须确定确切的类型以生成正确的代码,所以java8引入了java.util.function包,它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型。 每个接口只包含一个抽象方法,称为 函数式方法 。
在编写接口时,可以使用 @FunctionalInterface 注解强制执行此“函数式方法”模式:
@FunctionalInterface
interface Functional {
String goodbye(String arg);
}
interface FunctionalNoAnn {
String goodbye(String arg);
}
/*
@FunctionalInterface
interface NotFunctional {
String goodbye(String arg);
String hello(String arg);
}
产生错误信息:
NotFunctional is not a functional interface
multiple non-overriding abstract methods
found in interface NotFunctional
*/
public class FunctionalAnnotation {
public String goodbye(String arg) {
return "Goodbye, " + arg;
}
public static void main(String[] args) {
FunctionalAnnotation fa =
new FunctionalAnnotation();
Functional f = fa::goodbye;
FunctionalNoAnn fna = fa::goodbye;
// Functional fac = fa; // Incompatible
Functional fl = a -> "Goodbye, " + a;
FunctionalNoAnn fnal = a -> "Goodbye, " + a;
}
}
上面的NotFunctional接口不是一个函数式接口,因为他包含了两个抽象方法, 在编译时会报错。
java.util.function 包旨在创建一组完整的目标接口,使得我们一般情况下不需再定义自己的接口。主要因为基本类型的存在,导致预定义的接口数量有少许增加。 如果你了解命名模式,顾名思义就能知道特定接口的作用。
以下是基本命名准则:
- 如果只处理对象而非基本类型,名称则为
Function,Consumer,Predicate等。参数类型通过泛型添加。 - 如果接收的参数是基本类型,则由名称的第一部分表示,如
LongConsumer,DoubleFunction,IntPredicate等,但返回基本类型的Supplier接口例外。 - 如果返回值为基本类型,则用
To表示,如ToLongFunction <T>和IntToLongFunction。 - 如果返回值类型与参数类型相同,则是一个
Operator:单个参数使用UnaryOperator,两个参数使用BinaryOperator。 - 如果接收参数并返回一个布尔值,则是一个 谓词 (
Predicate)。 - 如果接收的两个参数类型不同,则名称中有一个
Bi。
下表描述了 java.util.function 中的目标类型(包括例外情况):
| 特征 | 函数式方法名 | 示例 |
|---|---|---|
| 无参数; 无返回值 | Runnable (java.lang) run() |
Runnable |
| 无参数; 返回类型任意 | Supplier get() getAs类型() |
Supplier<T> BooleanSupplier IntSupplier LongSupplier DoubleSupplier |
| 无参数; 返回类型任意 | Callable (java.util.concurrent) call() |
Callable<V> |
| 1 参数; 无返回值 | Consumer accept() |
Consumer<T> IntConsumer LongConsumer DoubleConsumer |
| 2 参数 Consumer | BiConsumer accept() |
BiConsumer<T,U> |
| 2 参数 Consumer; 第一个参数是 引用; 第二个参数是 基本类型 | Obj类型Consumer accept() |
ObjIntConsumer<T> ObjLongConsumer<T> ObjDoubleConsumer<T> |
| 1 参数; 返回类型不同 | Function apply() To类型 和 类型To类型 applyAs类型() |
Function<T,R> IntFunction<R> LongFunction<R> DoubleFunction<R> ToIntFunction<T> ToLongFunction<T> ToDoubleFunction<T> IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
| 1 参数; 返回类型相同 | UnaryOperator apply() |
UnaryOperator<T> IntUnaryOperator LongUnaryOperator DoubleUnaryOperator |
| 2 参数,类型相同; 返回类型相同 | BinaryOperator apply() |
BinaryOperator<T> IntBinaryOperator LongBinaryOperator DoubleBinaryOperator |
| 2 参数,类型相同; 返回整型 | Comparator (java.util) compare() |
Comparator<T> |
| 2 参数; 返回布尔型 | Predicate test() |
Predicate<T> BiPredicate<T,U> IntPredicate LongPredicate DoublePredicate |
| 参数基本类型; 返回基本类型 | 类型To类型Function applyAs类型() |
IntToLongFunction IntToDoubleFunction LongToIntFunction LongToDoubleFunction DoubleToIntFunction DoubleToLongFunction |
| 2 参数; 类型不同 | Bi操作 (不同方法名) | BiFunction<T,U,R> BiConsumer<T,U> BiPredicate<T,U> ToIntBiFunction<T,U> ToLongBiFunction<T,U> ToDoubleBiFunction<T> |
此表仅提供些常规方案。通过上表,你应该或多或少能自行推导出你所需要的函数式接口。
下面枚举了基于 Lambda 表达式的所有不同 Function 变体的示例:
import java.util.function.*;
class Foo {}
class Bar {
Foo f;
Bar(Foo f) { this.f = f; }
}
class IBaz {
int i;
IBaz(int i) {
this.i = i;
}
}
class LBaz {
long l;
LBaz(long l) {
this.l = l;
}
}
class DBaz {
double d;
DBaz(double d) {
this.d = d;
}
}
public class FunctionVariants {
static Function<Foo,Bar> f1 = f -> new Bar(f);
static IntFunction<IBaz> f2 = i -> new IBaz(i);
static LongFunction<LBaz> f3 = l -> new LBaz(l);
static DoubleFunction<DBaz> f4 = d -> new DBaz(d);
static ToIntFunction<IBaz> f5 = ib -> ib.i;
static ToLongFunction<LBaz> f6 = lb -> lb.l;
static ToDoubleFunction<DBaz> f7 = db -> db.d;
static IntToLongFunction f8 = i -> i;
static IntToDoubleFunction f9 = i -> i;
static LongToIntFunction f10 = l -> (int)l;
static LongToDoubleFunction f11 = l -> l;
static DoubleToIntFunction f12 = d -> (int)d;
static DoubleToLongFunction f13 = d -> (long)d;
public static void main(String[] args) {
Bar b = f1.apply(new Foo());
IBaz ib = f2.apply(11);
LBaz lb = f3.apply(11);
DBaz db = f4.apply(11);
int i = f5.applyAsInt(ib);
long l = f6.applyAsLong(lb);
double d = f7.applyAsDouble(db);
l = f8.applyAsLong(12);
d = f9.applyAsDouble(12);
i = f10.applyAsInt(12);
d = f11.applyAsDouble(12);
i = f12.applyAsInt(13.0);
l = f13.applyAsLong(13.0);
}
}
递归
递归函数是一个自我调用的函数。可以编写递归的 Lambda 表达式,但需要注意:递归方法必须是实例变量或静态变量,否则会出现编译时错误。 我们将为每个案例创建一个示例。
这两个示例都需要一个接受 int 型参数并生成 int 的接口:
// functional/IntCall.java
interface IntCall {
int call(int arg);
}
整数 n 的阶乘将所有小于或等于 n 的正整数相乘。 阶乘函数是一个常见的递归示例:
// functional/RecursiveFactorial.java
public class RecursiveFactorial {
static IntCall fact;
public static void main(String[] args) {
fact = n -> n == 0 ? 1 : n * fact.call(n - 1);
for(int i = 0; i <= 10; i++)
System.out.println(fact.call(i));
}
}
输出结果:
1
1
2
6
24
120
720
5040
40320
362880
3628800
这里,fact 是一个静态变量。 注意使用三元 if-else。 递归函数将一直调用自己,直到 i == 0。所有递归函数都有“停止条件”,否则将无限递归并产生异常。
我们可以将 Fibonacci 序列用递归的 Lambda 表达式来实现,这次使用实例变量:
// functional/RecursiveFibonacci.java
public class RecursiveFibonacci {
IntCall fib;
RecursiveFibonacci() {
fib = n -> n == 0 ? 0 :
n == 1 ? 1 :
fib.call(n - 1) + fib.call(n - 2);
}
int fibonacci(int n) { return fib.call(n); }
public static void main(String[] args) {
RecursiveFibonacci rf = new RecursiveFibonacci();
for(int i = 0; i <= 10; i++)
System.out.println(rf.fibonacci(i));
}
}
输出结果:
0
1
1
2
3
5
8
13
21
34
55
将 Fibonacci 序列中的最后两个元素求和来产生下一个元素。
一般lambda配合流计算是非常强大的利器,同时在使用lambda的时候,一行代码是最理想的,三行是合理的最大极限,如果违背了这个原则,可能对程序的可读性造成严重的危害。
流式编程
集合优化了对象的存储,而流(Streams)则是关于一组组对象的处理。
在大多数情况下,将对象存储在集合中就是为了处理它们,因此你会发现你把编程的主要焦点从集合转移到了流上。
流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。
举个例子,假如你要随机展示 5 至 20 之间不重复的整数并进行排序。你要对它们进行排序的事实,会使你首先关注选用哪个有序集合,然后围绕这个集合进行后续的操作。但是使用流式编程,你就可以简单陈述你要做什么
import java.util.Random;
public class Randoms {
public static void main(String[] args) {
new Random(100)
.ints(5,20)// 产生一个随机数在5-20的流
.distinct()//去重
.limit(7) //取前7个
.sorted() // 排序
.forEach(System.out::println);//遍历
}
}
与之对应的迭代写法如下:
import java.util.*;
public class ImperativeRandoms {
public static void main(String[] args) {
Random random = new Random(100);
SortedSet<Integer> set = new TreeSet<>();
while (set.size() < 7) {
int r = random.nextInt(20);
if(r < 5) {
continue;
}
set.add(r);
}
System.out.println(set);
}
}
像在ImperativeRandoms中那样显示地编写迭代的方式称为外部迭代而在Randows中你看不到任何迭代的过程,所以被称为内部迭代,另外流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。
流操作
流操作的类型有三种:创建流,修改流元素(中间操作, Intermediate Operations),消费流元素(终端操作, Terminal Operations)。最后一种类型通常意味着收集流元素(通常是汇入一个集合)。
流创建
1.你可以通过 Stream.of() 很容易地将一组元素转化成为流。
import java.util.stream.*;
public class StreamOf {
public static void main(String[] args) {
Stream.of(new Bubble(1), new Bubble(2), new Bubble(3))
.forEach(System.out::println);
Stream.of("It's ", "a ", "wonderful ", "day ", "for ", "pie!")
.forEach(System.out::print);
System.out.println();
Stream.of(3.14159, 2.718, 1.618)
.forEach(System.out::println);
}
}
输出结果:
Bubble(1)
Bubble(2)
Bubble(3)
It's a wonderful day for pie!
3.14159
2.718
1.618
Bubble定义如下:
public class Bubble {
public final int i;
public Bubble(int n) {
i = n;
}
@Override
public String toString() {
return "Bubble(" + i + ")";
}
private static int count = 0;
public static Bubble bubbler() {
return new Bubble(count++);
}
}
2.除此之外,每个集合都可以通过调用 stream() 方法来产生一个流。代码示例:
import java.util.*;
import java.util.stream.*;
public class CollectionToStream {
public static void main(String[] args) {
List<Bubble> bubbles = Arrays.asList(new Bubble(1), new Bubble(2), new Bubble(3));
System.out.println(bubbles.stream()
.mapToInt(b -> b.i)
.sum());
Set<String> w = new HashSet<>(Arrays.asList("It's a wonderful day for pie!".split(" ")));
w.stream()
.map(x -> x + " ")
.forEach(System.out::print);
System.out.println();
Map<String, Double> m = new HashMap<>();
m.put("pi", 3.14159);
m.put("e", 2.718);
m.put("phi", 1.618);
m.entrySet().stream()
.map(e -> e.getKey() + ": " + e.getValue())
.forEach(System.out::println);
}
}
输出结果:
6
a pie! It's for wonderful day
phi: 1.618
e: 2.718
pi: 3.14159
在创建 List<Bubble> 对象之后,我们只需要简单地调用所有集合中都有的 stream()。中间操作 map() 会获取流中的所有元素,并且对流中元素应用操作从而产生新的元素,并将其传递到后续的流中。通常 map() 会获取对象并产生新的对象,但在这里产生了特殊的用于数值类型的流。例如,mapToInt() 方法将一个对象流(object stream)转换成为包含整型数字的 IntStream。同样,针对 Float 和 Double 也有类似名字的操作。细心的小伙伴会发现,map()函数它的参数是一个函数接口,这个就和前面的Lambda挂钩了。
<R> Stream<R> map(Function<? super T, ? extends R> var1);
3.使用Stream.generate()方法来生成,前提是,传递的参数必须满足与Supplier<T> 接口兼容。
上面的Bubble就和Supplier<T>接口满足,所以可以直接传递过去,代码如下:
import java.util.stream.*;
public class Bubbles {
public static void main(String[] args) {
Stream.generate(Bubble::bubbler)
.limit(5)
.forEach(System.out::println);
}
}
4.Stream.iterate() 产生的流的第一个元素是种子(iterate方法的第一个参数),然后将种子传递给方法(iterate方法的第二个参数)。方法运行的结果被添加到流(作为流的下一个元素),并被存储起来,作为下次调用 iterate()方法时的第一个参数,以此类推。我们可以利用 iterate() 生成一个斐波那契数列,代码示例:
import java.util.stream.Stream;
public class Fibonacci {
int x = 0;
Stream<Integer> numbers() {
return Stream.iterate(1,i -> {
int result = i + x;
x = i;
return result;
});
}
public static void main(String[] args) {
new Fibonacci()
.numbers()
.skip(20) // 过滤掉前20个
.limit(10) // 然后取 10 个
.forEach(System.out::println);
}
}
斐波那契数列将数列中最后两个元素进行求和以产生下一个元素。iterate() 只能记忆结果,因此我们需要利用一个变量 x 追踪另外一个元素。
中间操作
中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。
跟踪和调试
peek() 操作的目的是帮助调试。它允许你无修改地查看流中的元素。代码示例:
// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}
import java.nio.file.*;
import java.util.stream.*;
import java.util.regex.Pattern;
public class FileToWords {
public static Stream<String> stream(String filePath) throws Exception {
System.out.println(filePath);
return Files.lines(Paths.get(filePath))
.skip(1) // First (comment) line
.flatMap(line ->
Pattern.compile("\\W+").splitAsStream(line));
}
}
移除元素
distinct():在Randoms.java类中的distinct()可用于消除流中的重复元素。相比创建一个 Set 集合来消除重复,该方法的工作量要少得多。filter(Predicate):过滤操作,保留如下元素:若元素传递给过滤函数产生的结果为true。
在下例中,isPrime() 作为过滤函数,用于检测质数。
import java.util.stream.*;
import static java.util.stream.LongStream.*;
public class Prime {
public static Boolean isPrime(long n){
boolean b = rangeClosed(2, (long) Math.sqrt(n))
.noneMatch(i -> n % i == 0);
return b;
}
public LongStream numbers(){
return LongStream.iterate(2,i -> i + 1)
.filter(Prime::isPrime);
}
public static void main(String[] args) {
new Prime().numbers()
.limit(10)
.forEach(x -> System.out.printf("%d ",x));
System.out.println("=========================");
new Prime().numbers()
.skip(100)
.limit(10)
.forEach(x -> System.out.printf("%d ",x));
}
}
rangeClosed() 包含了上限值。如果不能整除,即余数不等于 0,则 noneMatch() 操作返回 true,如果出现任何等于 0 的结果则返回 false。 noneMatch() 操作一旦有失败就会退出。
应用函数到元素
map(Function):将函数操作应用在输入流的元素中,并将返回值传递到输出流中。mapToInt(ToIntFunction):操作同上,但结果是 IntStream。mapToLong(ToLongFunction):操作同上,但结果是 LongStream。mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream。
在这里,我们使用 map() 映射多种函数到一个字符串流中。代码示例:
// streams/FunctionMap.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class FunctionMap {
static String[] elements = { "12", "", "23", "45" };
static Stream<String>
testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println(" ---( " + descr + " )---");
testStream()
.map(func)
.forEach(System.out::println);
}
public static void main(String[] args) {
test("add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
}
catch(NumberFormatException e) {
return s;
}
}
);
test("Replace", s -> s.replace("2", "9"));
test("Take last digit", s -> s.length() > 0 ?
s.charAt(s.length() - 1) + "" : s);
}
}
输出结果:
---( add brackets )---
[12]
[]
[23]
[45]
---( Increment )---
13
24
46
---( Replace )---
19
93
45
---( Take last digit )---
2
3
5
在上面的自增示例中,我们用 Integer.parseInt() 尝试将一个字符串转化为整数。如果字符串不能被转化成为整数就会抛出 NumberFormatException 异常,此时我们就回过头来把原始字符串放到输出流中。
假设我们现在有了一个传入的元素流,并且打算对流元素使用 map() 函数。现在你已经找到了一些可爱并独一无二的函数功能,但是问题来了:这个函数功能是产生一个流。我们想要产生一个元素流,而实际却产生了一个元素流的流。
flatMap() 做了两件事:将产生流的函数应用在每个元素上(与 map() 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。
flatMap(Function):当 Function 产生流时使用。
flatMapToInt(Function):当 Function 产生 IntStream 时使用。
flatMapToLong(Function):当 Function 产生 LongStream 时使用。
flatMapToDouble(Function):当 Function 产生 DoubleStream 时使用。
为了弄清它的工作原理,我们从传入一个刻意设计的函数给 map() 开始。该函数接受一个整数并产生一个字符串流:
// streams/StreamOfStreams.java
import java.util.stream.*;
public class StreamOfStreams {
public static void main(String[] args) {
Stream.of(1, 2, 3)
.map(i -> Stream.of("Gonzo", "Kermit", "Beaker"))
.map(e-> e.getClass().getName())
.forEach(System.out::println);
}
}
输出结果:
java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head
java.util.stream.ReferencePipeline$Head
我们天真地希望能够得到字符串流,但实际得到的却是“Head”流的流。我们可以使用 flatMap() 解决这个问题:
// streams/FlatMap.java
import java.util.stream.*;
public class FlatMap {
public static void main(String[] args) {
Stream.of(1, 2, 3)
.flatMap(i -> Stream.of("Gonzo", "Fozzie", "Beaker"))
.forEach(System.out::println);
}
}
//等同于
public class StreamOfStreams {
public static void main(String[] args) {
Stream.of(1, 2, 3)
.map(i -> Stream.of("Gonzo", "Kermit", "Beaker"))
.forEach(x -> x.forEach(System.out::println));
}
}
输出结果:
Gonzo
Fozzie
Beaker
Gonzo
Fozzie
Beaker
Gonzo
Fozzie
Beaker
从映射返回的每个流都会自动扁平为组成它的字符串。
Optional类
我们必须考虑在一个空流中获取元素会发生什么,在流中放置 null 却会轻易令其中断。那么是否存在某种对象,可以在持有流元素的同时,即使在我们查找的元素不存在时,也能友好地对我们进行提示(也就是说,不会产生异常)?
Optional 可以实现这样的功能。一些标准流操作返回 Optional 对象,因为它们并不能保证预期结果一定存在。包括:
findFirst()返回一个包含第一个元素的 Optional 对象,如果流为空则返回 Optional.emptyfindAny()返回包含任意元素的 Optional 对象,如果流为空则返回 Optional.emptymax()和min()返回一个包含最大值或者最小值的 Optional 对象,如果流为空则返回 Optional.empty
import java.util.*;
import java.util.stream.*;
class OptionalsFromEmptyStreams {
public static void main(String[] args) {
System.out.println(Stream.<String>empty()
.findFirst());
System.out.println(Stream.<String>empty()
.findAny());
System.out.println(Stream.<String>empty()
.max(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty()
.min(String.CASE_INSENSITIVE_ORDER));
System.out.println(Stream.<String>empty()
.reduce((s1, s2) -> s1 + s2));
System.out.println(IntStream.empty()
.average());
}
}
输出结果:
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
OptionalDouble.empty
当流为空的时候你会获得一个 Optional.empty 对象,而不是抛出异常。Optional 拥有 toString() 方法可以用于展示有用信息。
便利函数
有许多便利函数可以解包 Optional ,这简化了上述“对所包含的对象的检查和执行操作”的过程:
ifPresent(Consumer):当值存在时调用 Consumer,否则什么也不做。orElse(otherObject):如果值存在则直接返回,否则生成 otherObject。orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数生成一个异常。
如下是针对不同便利函数的简单演示:
// streams/Optionals.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
public class Optionals {
static void basics(Optional<String> optString) {
if(optString.isPresent())
System.out.println(optString.get());
else
System.out.println("Nothing inside!");
}
static void ifPresent(Optional<String> optString) {
optString.ifPresent(System.out::println);
}
static void orElse(Optional<String> optString) {
System.out.println(optString.orElse("Nada"));
}
static void orElseGet(Optional<String> optString) {
System.out.println(
optString.orElseGet(() -> "Generated"));
}
static void orElseThrow(Optional<String> optString) {
try {
System.out.println(optString.orElseThrow(
() -> new Exception("Supplied")));
} catch(Exception e) {
System.out.println("Caught " + e);
}
}
static void test(String testName, Consumer<Optional<String>> cos) {
System.out.println(" === " + testName + " === ");
cos.accept(Stream.of("Epithets").findFirst());
cos.accept(Stream.<String>empty().findFirst());
}
public static void main(String[] args) {
test("basics", Optionals::basics);
test("ifPresent", Optionals::ifPresent);
test("orElse", Optionals::orElse);
test("orElseGet", Optionals::orElseGet);
test("orElseThrow", Optionals::orElseThrow);
}
}
输出结果:
=== basics ===
Epithets
Nothing inside!
=== ifPresent ===
Epithets
=== orElse ===
Epithets
Nada
=== orElseGet ===
Epithets
Generated
=== orElseThrow ===
Epithets
Caught java.lang.Exception: Supplied
test() 通过传入所有方法都适用的 Consumer 来避免重复代码。
创建 Optional
当我们在自己的代码中加入 Optional 时,可以使用下面 3 个静态方法:
empty():生成一个空 Optional。of(value):将一个非空值包装到 Optional 里。ofNullable(value):针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。
下面来看看它是如何工作的。代码示例:
// streams/CreatingOptionals.java
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class CreatingOptionals {
static void test(String testName, Optional<String> opt) {
System.out.println(" === " + testName + " === ");
System.out.println(opt.orElse("Null"));
}
public static void main(String[] args) {
test("empty", Optional.empty());
test("of", Optional.of("Howdy"));
try {
test("of", Optional.of(null));
} catch(Exception e) {
System.out.println(e);
}
test("ofNullable", Optional.ofNullable("Hi"));
test("ofNullable", Optional.ofNullable(null));
}
}
输出结果:
=== empty ===
Null
=== of ===
Howdy
java.lang.NullPointerException
=== ofNullable ===
Hi
=== ofNullable ===
Null
我们不能通过传递 null 到 of() 来创建 Optional 对象。最安全的方法是, 使用 ofNullable() 来优雅地处理 null。
Optional 对象操作
当我们的流管道生成了 Optional 对象,下面 3 个方法可使得 Optional 的后续能做更多的操作:
filter(Predicate):对 Optional 中的内容应用Predicate 并将结果返回。如果 Optional 不满足 Predicate ,将 Optional 转化为空 Optional 。如果 Optional 已经为空,则直接返回空Optional 。map(Function):如果 Optional 不为空,应用 Function 于 Optional 中的内容,并返回结果。否则直接返回 Optional.empty。flatMap(Function):同map(),但是提供的映射函数将结果包装在 Optional 对象中,因此flatMap()不会在最后进行任何包装。
以上方法都不适用于数值型 Optional。一般来说,流的 filter() 会在 Predicate 返回 false 时移除流元素。而 Optional.filter() 在失败时不会删除 Optional,而是将其保留下来,并转化为空。下面请看代码示例:
import java.util.*;
import java.util.stream.*;
import java.util.function.*;
class OptionalFilter {
static String[] elements = {
"Foo", "", "Bar", "Baz", "Bingo"
};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Predicate<String> pred) {
System.out.println(" ---( " + descr + " )---");
for(int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst()
.filter(pred));
}
}
public static void main(String[] args) {
test("true", str -> true);
test("false", str -> false);
test("str != \"\"", str -> str != "");
test("str.length() == 3", str -> str.length() == 3);
test("startsWith(\"B\")",
str -> str.startsWith("B"));
}
}
输出结果:
---( true )---
Optional[Foo]
Optional[]
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
---( false )---
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
Optional.empty
---( str != "" )---
Optional[Foo]
Optional.empty
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
---( str.length() == 3 )---
Optional[Foo]
Optional.empty
Optional[Bar]
Optional[Baz]
Optional.empty
Optional.empty
---( startsWith("B") )---
Optional.empty
Optional.empty
Optional[Bar]
Optional[Baz]
Optional[Bingo]
Optional.empty
即使输出看起来像流,要特别注意 test() 中的 for 循环。每一次的for循环都重新启动流,然后跳过for循环索引指定的数量的元素,这就是流只剩后续元素的原因。然后调用findFirst() 获取剩余元素中的第一个元素,并包装在一个 Optional对象中。
注意,不同于普通 for 循环,这里的索引值范围并不是 i < elements.length, 而是 i <= elements.length。所以最后一个元素实际上超出了流。方便的是,这将自动成为 Optional.empty,你可以在每一个测试的结尾中看到。
同 map() 一样 , Optional.map() 执行一个函数。它仅在 Optional 不为空时才执行这个映射函数。并将 Optional 的内容提取出来,传递给映射函数。代码示例:
import java.util.Arrays;
import java.util.function.Function;
import java.util.stream.Stream;
class OptionalMap {
static String[] elements = {"12", "", "23", "45"};
static Stream<String> testStream() {
return Arrays.stream(elements);
}
static void test(String descr, Function<String, String> func) {
System.out.println(" ---( " + descr + " )---");
for (int i = 0; i <= elements.length; i++) {
System.out.println(
testStream()
.skip(i)
.findFirst() // Produces an Optional
.map(func));
}
}
public static void main(String[] args) {
// If Optional is not empty, map() first extracts
// the contents which it then passes
// to the function:
test("Add brackets", s -> "[" + s + "]");
test("Increment", s -> {
try {
return Integer.parseInt(s) + 1 + "";
} catch (NumberFormatException e) {
return s;
}
});
test("Replace", s -> s.replace("2", "9"));
test("Take last digit", s -> s.length() > 0 ?
s.charAt(s.length() - 1) + "" : s);
}
// After the function is finished, map() wraps the
// result in an Optional before returning it:
}
输出结果:
---( Add brackets )---
Optional[[12]]
Optional[[]]
Optional[[23]]
Optional[[45]]
Optional.empty
---( Increment )---
Optional[13]
Optional[]
Optional[24]
Optional[46]
Optional.empty
---( Replace )---
Optional[19]
Optional[]
Optional[93]
Optional[45]
Optional.empty
---( Take last digit )---
Optional[2]
Optional[]
Optional[3]
Optional[5]
Optional.empty
终端操作
以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作(Terminal Operations)总是我们在流管道中所做的最后一件事。
数组
toArray():将流转换成适当类型的数组。toArray(generator):在特殊情况下,生成自定义类型的数组。
当我们需要得到数组类型的数据以便于后续操作时,上面的方法就很有用。假设我们需要复用流产生的随机数时,就可以这么使用。代码示例:
import java.util.*;
import java.util.stream.*;
public class RandInts {
private static int[] rints = new Random(47).ints(0, 1000).limit(100).toArray();
public static IntStream rands() {
return Arrays.stream(rints);
}
}
上例将100个数值范围在 0 到 1000 之间的随机数流转换成为数组并将其存储在 rints 中。这样一来,每次调用 rands() 的时候可以重复获取相同的整数流。
循环
forEach(Consumer)常见如System.out::println作为 Consumer 函数。forEachOrdered(Consumer): 保证forEach按照原始流顺序操作。
第一种形式:无序操作,仅在引入并行流时才有意义。 parallel():可实现多处理器并行操作。实现原理为将流分割为多个(通常数目为 CPU 核心数)并在不同处理器上分别执行操作
下例引入 parallel() 来帮助理解 forEachOrdered(Consumer) 的作用和使用场景。代码示例:
import java.util.*;
import java.util.stream.*;
import static streams.RandInts.*;
public class ForEach {
static final int SZ = 14;
public static void main(String[] args) {
rands().limit(SZ)
.forEach(n -> System.out.format("%d ", n));
System.out.println();
rands().limit(SZ)
.parallel()
.forEach(n -> System.out.format("%d ", n));
System.out.println();
rands().limit(SZ)
.parallel()
.forEachOrdered(n -> System.out.format("%d ", n));
}
}
输出结果:
258 555 693 861 961 429 868 200 522 207 288 128 551 589
551 861 429 589 200 522 555 693 258 128 868 288 961 207
258 555 693 861 961 429 868 200 522 207 288 128 551 589
为了方便测试不同大小的流,我们抽离出了 SZ 变量。然而即使 SZ 值为14也产生了有趣的结果。在第一个流中,未使用 parallel() ,因此以元素从 rands()出来的顺序输出结果。在第二个流中,引入parallel() ,即便流很小,输出的结果的顺序也和前面不一样。这是由于多处理器并行操作的原因,如果你将程序多运行几次,你会发现输出都不相同,这是多处理器并行操作的不确定性造成的结果。
在最后一个流中,同时使用了 parallel() 和 forEachOrdered() 来强制保持原始流顺序。因此,对非并行流使用 forEachOrdered() 是没有任何影响的。
匹配
allMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。anyMatch(Predicate):如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算。noneMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。
查找
findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty。findAny():返回含有任意流元素的 Optional,如果流为空返回 Optional.empty。
信息
count():流中的元素个数。max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。
数字流信息
average():求取流元素平均值。max()和min():数值流操作无需 Comparator。sum():对所有流元素进行求和。summaryStatistics():生成可能有用的数据。目前并不太清楚这个方法存在的必要性,因为我们其实可以用更直接的方法获得需要的数据。

浙公网安备 33010602011771号