前言
函数式编程语言操纵代码片段就像操作数据一样容易。 虽然 Java 不是函数式语言,但 Java 8 Lambda 表达式和方法引用 (Method References) 允许你以函数式编程。 集合优化了对象的存储,而流和对象的处理有关。
函数式编程
在计算机时代早期,内存是稀缺和昂贵的。为了使程序能在有限的内存上运行,程序员通过修改内存中的代码,使程序可以执行不同的操作,用这种方式来节省代码空间。这种技术被称为自修改代码 (self-modifying code),使用纯粹的自修改代码造成的结果就是:我们很难确定程序在做什么。它也难以测试:除非你想一点点测试输出,代码转换和修改等等过程?。随着内存和处理器变得更便宜、更快。C 语言出现并被大多数汇编程序员认为更“高级”。人们发现使用 C 可以显著提高生产力。同时,使用 C 创建自修改代码仍然不难。
函数式编程(FP)。通过合并现有代码来生成新功能而不是从头开始编写所有内容,我们可以更快地获得更可靠的代码。至少在某些情况下,这套理论似乎很有用。在这一过程中,函数式语言已经产生了优雅的语法,这些语法对于非函数式语言也适用。你也可以这样想:OO(object oriented,面向对象)是抽象数据,FP(functional programming,函数式编程)是抽象行为。
纯粹的函数式语言在安全性方面更进一步。它强加了额外的约束,即所有数据必须是不可变的:设置一次,永不改变。更好的是,“不可变对象和无副作用”范式解决了并发编程中最基本和最棘手的问题之一(当程序的某些部分同时在多个处理器上运行时)。这是可变共享状态的问题,这意味着代码的不同部分(在不同的处理器上运行)可以尝试同时修改同一块内存(谁赢了?没人知道)。如果函数永远不会修改现有值但只生成新值,则不会对内存产生争用,这是纯函数式语言的定义。 因此,经常提出纯函数式语言作为并行编程的解决方案(还有其他可行的解决方案)。
需要提醒大家的是,函数式语言背后有很多动机,这意味着描述它们可能会有些混淆。它通常取决于各种观点:为“并行编程”,“代码可靠性”和“代码创建和库复用”。关于函数式编程能高效创建更健壮的代码这一观点仍存在部分争议。虽然已有一些好的范例,但还不足以证明纯函数式语言就是解决编程问题的最佳方法。
新旧比对
下面我们用创建线程的方式来展示传统形式和Java8的区别:
class Hello{
static void hello(){
System.out.println("hello world");
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
}).start();
new Thread(Hello::hello).start();
}
- Java 8 的 Lambda 表达式。由箭头 -> 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。
- Java 8 的方法引用,由 :: 区分。在 :: 的左边是类或对象的名称,在 :: 的右边是方法的名称,但没有参数列表。
Lambda表达式
Lambda 表达式是使用最小可能语法编写的函数定义:
- Lambda 表达式产生函数,而不是类。 在 JVM(Java Virtual Machine,Java 虚拟机)上,一切都是一个类,因此在幕后执行各种操作使 Lambda 看起来像函数 —— 但作为程序员,你可以高兴地假装它们“只是函数”。
- Lambda 语法尽可能少,这正是为了使 Lambda 易于编写和使用。
public interface One {
String consumer();
}
public interface Two {
String consumer(String param);
}
public interface Three {
String consumer(String param,String param1);
}
public class FunFP {
static One one = () -> "one";
static One one1 = () ->{
System.out.println("form moreLines");
return "one1";
};
static Two two = t -> t + "two";
static Two two1 = (t) -> t + "two1";
static Three three = (t,t1) -> t+","+t1;
public static void main(String[] args) {
String consumer = one.consumer();
System.out.println("one = "+consumer);
String consumer1 = one1.consumer();
System.out.println("one1 = "+consumer1);
String consumer2 = two.consumer("hello");
System.out.println("two = "+consumer2);
String consumer3 = two1.consumer("hello");
System.out.println("two1 = "+consumer3);
String consumer4 = three.consumer("hello","world");
System.out.println("three = "+consumer4);
}
}
输出结果:
one = one
form moreLines
one1 = one1
two = hellotwo
two1 = hellotwo1
three = hello,world
我们从三个接口开始,每个接口都有一个单独的方法(很快就会理解它的重要性)。但是,每个方法都有不同数量的参数,以便演示 Lambda 表达式语法。
任何 Lambda 表达式的基本语法是:
- 如果没有参数,则必须使用括号 () 表示空参数列表。
- 当只用一个参数,可以不需要括号 ()。
- 正常情况使用括号 () 包裹参数。 为了保持一致性,也可以使用括号 () 包裹单个参数,虽然这种情况并不常见。
- 对于多个参数,将参数列表放在括号 () 中,逗号区分。
- 如果在 Lambda 表达式中确实需要多行,则必须将这些行放在花括号中。 在这种情况下,就需要使用 return。
Lambda 表达式通常比匿名内部类产生更易读的代码,所有 Lambda 表达式方法体都是单行。 该表达式的结果自动成为 Lambda 表达式的返回值,在此处使用 return 关键字是非法的。 这是 Lambda 表达式缩写用于描述功能的语法的另一种方式。
方法引用
方法引用组成:类名或对象名,后面跟 :: ,然后跟方法名称。
public interface Callable {
void call(String s);
}
public class One {
public static void consumer(){
System.out.println("hello world");
}
}
public static void main(String[] args) {
Callable callable = One::consumer;
callable.call("1");
}
参数或参数类型不匹配、非静态时,无法解析方法。方法的参数类型和返回类型要与接口符合
public interface Callable {
void call(String s);
}
public class One {
public static void consumer(String param){
System.out.println("hello "+param);
}
static class One1{
One1(String param){
System.out.println("hello "+param);
}
public void consumer(String param){
System.out.println("hello "+param);
}
}
public static void main(String[] args) {
Callable callable = One::consumer;
callable.call("world");
callable = new One.One1("world")::consumer;
callable.call("world");
}
}
通过调用 call() 来调用 consumer() ,因为 Java 将 call() 映射到 consumer() 。
未绑定的方法引用
未绑定的方法引用是指没有关联对象的普通(非静态)方法。 使用未绑定的引用之前,我们必须先提供对象:
public interface Callable {
String call(One s);
}
public class One {
public String consumer(){
return "hello world";
}
public static void main(String[] args) {
Callable callable = One::consumer;
String world = callable.call(new One());
System.out.println(world);
One one = new One();
System.out.println(one.consumer());
}
}
前面我们知道了与接口方法同名的方法引用。使用未绑定的引用时,函数式方法的签名(接口中的单个方法)不再与方法引用的签名完全匹配。 原因是:你需要一个对象来调用方法。我拿到未绑定的方法引用,并且调用它的call() 方法,将类的对象传递给它,然后就以某种方式导致了对 consumer() 方法的调用。
构造函数引用
你还可以捕获构造函数的引用,然后通过引用调用该构造函数。
public interface Callable {
One call();
}
public interface Callable1 {
One call(String param);
}
public class One {
One(){
System.out.println("hello world");
}
One(String param){
System.out.println(param);
}
public static void main(String[] args) {
Callable callable = One::new;
One one = callable.call();
Callable1 callable1 = One::new;
One one1 = callable1.call("hello world");
}
}
函数式接口
Java 8 引入了 java.util.function 包。它包含一组接口,这些接口是 Lambda 表达式和方法引用的目标类型,使得我们一般情况下不需再定义自己的接口。这主要是因为基本类型会产生一小部分接口。 如果你了解命名模式,顾名思义就能知道特定接口的作用。
- 如果只处理对象而非基本类型,名称则为 Function,Consumer,Predicate 等。参数类型通过泛型添加。
- 如果接收的参数是基本类型,则由名称的第一部分表示,如 LongConsumer,DoubleFunction,IntPredicate 等,但基本 Supplier 类型例外。
- 如果返回值为基本类型,则用 To 表示,如 ToLongFunction 和 IntToLongFunction。
- 如果返回值类型与参数类型一致,则是一个运算符:单个参数使用 UnaryOperator,两个参数使用 BinaryOperator。
- 如果接收两个参数且返回值为布尔值,则是一个谓词(Predicate)。
- 如果接收的两个参数类型不同,则名称中有一个 Bi。
特征 | 函数式方法名 | 示例接口 |
---|
无参数;无返回值 | run() | Runnable | 无参数;返回类型任意 | get()、getAs类型() | Supplier、BooleanSupplier、IntSupplier、LongSupplier、DoubleSupplier | 无参数;返回类型任意 | call() | Callable | 一个参数;无返回值 | accept() | Consumer、IntConsumer、LongConsumer、DoubleConsumer | 两个参数 | accept() | BiConsumer | 两个参数;一个引用;一个基本类型 | accept() | ObjIntConsumer、ObjLongConsumer、ObjDoubleConsumer | 一个参数;返回类型不同 | apply()、applyAs类型() | Function、IntFunction、LongFunction、DoubleFunction、ToIntFunction、ToLongFunction、ToDoubleFunction、IntToLongFunction、IntToDoubleFunction、LongToIntFunction、LongToDoubleFunction、DoubleToIntFunction、DoubleToLongFunction | 一个参数;返回类型相同 | apply() | UnaryOperator、IntUnaryOperator、LongUnaryOperator、DoubleUnaryOperator | 两个参数类型相同;返回类型相同 | apply() | BinaryOperator、IntBinaryOperator、LongBinaryOperator、DoubleBinaryOperator | 两个参数类型相同;返回整型 | compare() | Comparator | 两个参数;返回布尔型 | test() | Predicate、BiPredicate、IntPredicate、LongPredicate、DoublePredicate | 参数基本类型;返回基本类型 | applyAs类型() | IntToLongFunction、IntToDoubleFunction、LongToIntFunction、LongToDoubleFunction、DoubleToIntFunction、DoubleToLongFunction | 两个参数参数类型不同 | Bi操作(不同方法名) | BiFunction、BiConsumer、BiPredicate、ToIntBiFunction、ToLongBiFunction、ToDoubleBiFunction |
下面列举了一些示例:
public class One {
public static int consumer1(){
return 1;
}
public static String consumer2(int value){
return value+"";
}
public static void main(String[] args) throws Exception {
IntSupplier intSupplier = One::consumer1;
int asInt = intSupplier.getAsInt();
System.out.println(asInt);
Callable callable = One::consumer1;
Object call = callable.call();
System.out.println(call);
Consumer<String> consumer = s -> System.out.println(s + "world");
consumer.accept("hello");
BiConsumer<String,String> biConsumer = (a,b) -> System.out.println(a+" "+b);
biConsumer.accept("hello","world");
ObjIntConsumer<Integer> objIntConsumer = (a,b) -> System.out.println(a+" "+b);
objIntConsumer.accept(1,2);
IntFunction<String> intFunction = One::consumer2;
System.out.println(intFunction.apply(1));
UnaryOperator<String> unaryOperator = s -> s + " world";
System.out.println(unaryOperator.apply("hello"));
BinaryOperator<String> binaryOperator = (a,b) -> a+" "+b;
String apply = binaryOperator.apply("hello", "world");
System.out.println(apply);
Comparator<Integer> comparator = (a,b)-> a+b;
int compare = comparator.compare(1, 2);
System.out.println(compare);
Predicate<String> predicate = s -> s.length() >0;
boolean hello = predicate.test("hello");
System.out.println(hello);
IntToLongFunction intToLongFunction = a -> a;
System.out.println(intToLongFunction.applyAsLong(2));
BiFunction<Integer,Integer,Boolean> biFunction = (a,b) -> a>b;
System.out.println(biFunction.apply(1,2));
}
}
只要参数类型、返回类型也可以换成方法绑定引用,这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。
多参数函数式接口
接口是有限的。比如有了 BiFunction,但它不能变化。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例:
@FunctionalInterface
public interface One<T,U,V,R> {
R apply(T t,U u,V v);
}
public class FunFP {
static int test(int a,long b,double c){
return (int) (a+b+c);
}
public static void main(String[] args) throws Exception {
One<Integer,Long,Double,Integer> one = FunFP::test;
Integer apply = one.apply(1, 2L, 3.2);
System.out.println(apply);
one = (a,b,c)-> 12;
System.out.println(one.apply(1,2L,3.2));
}
}
高阶函数
这个名字可能听起来令人生畏,但是:高阶函数(Higher-order Function)只是一个消费或产生函数的函数。
public interface One extends Function<String,String> {
}
public class FunFP {
static One product(){
return s->s.toUpperCase();
}
public static void main(String[] args){
One one = product();
String apply = one.apply("hello");
System.out.println(apply);
}
}
product() 就是一个高阶函数
要消费一个函数成新函数时,消费函数需要在参数列表正确地描述函数类型。代码示例:
public class One {
@Override
public String toString() {
return "ONE";
}
}
public class Two {
@Override
public String toString() {
return "TWO";
}
}
public class FunFP {
static Function product(Function function){
return function.andThen(o ->{
System.out.println(o);
return o;
});
}
public static void main(String[] args){
Function function = product(i->{
System.out.println(i);
return new One();
});
One one = (One) function.apply(new Two());
System.out.println(one);
}
}
在这里,product() 生成一个与传入的函数具有相同签名的函数,但是你可以生成任何你想要的类型。
这里使用到了 Function 接口中名为 andThen() 的默认方法,该方法专门用于操作函数。 顾名思义,在调用 function 函数之后调用 andThen() (还有个 compose() 方法,它在 function 函数之前应用新函数)。 要附加一个 andThen() 函数,我们只需将该函数作为参数传递。 product() 产生的是一个新函数,它将 function 的动作与 andThen() 参数的动作结合起来。
闭包
闭包(Closure)一词总结了这些问题。 它非常重要,利用闭包可以轻松生成函数。
考虑一个更复杂的 Lambda,它使用函数作用域之外的变量。 返回该函数会发生什么? 也就是说,当你调用函数时,它对那些 “外部 ”变量引用了什么? 如果语言不能自动解决这个问题,那将变得非常具有挑战性。 能够解决这个问题的语言被称为支持闭包,或者叫作在词法上限定范围( 也使用术语变量捕获 )。Java 8 提供了有限但合理的闭包支持,我们将用一些简单的例子来研究它。
首先,下列方法返回一个函数,该函数访问对象字段和方法参数:
public class One {
static int i;
static IntSupplier makeFun(int x){
return () -> x + i++;
}
public static void main(String[] args) {
IntSupplier intSupplier = makeFun(0);
IntSupplier intSupplier1 = makeFun(0);
IntSupplier intSupplier2 = makeFun(0);
IntSupplier intSupplier3 = makeFun(0);
System.out.println(intSupplier.getAsInt());
System.out.println(intSupplier1.getAsInt());
System.out.println(intSupplier2.getAsInt());
System.out.println(intSupplier3.getAsInt());
}
}
每次调用 getAsInt() 都会增加 i,表明存储是共享的。在 Java 8 开始出现等同 final 效果(Effectively Final)虽然没有明确地声明变量是 final 的,但是因变量值没被改变过而实际有了 final 同等的效果。 如果局部变量的初始值永远不会改变,那么它实际上就是 final 的。
为什么变量 i 被修改编译器却没有报错呢。 它既不是 final 的,也不是等同 final 效果的。因为 i 是外围类的成员,所以这样做肯定是安全的(除非你正在创建共享可变内存的多个函数)。如果它是对象中的字段,那么它拥有独立的生存周期,并且不需要任何特殊的捕获,以便稍后在调用 Lambda 时存在。
如果 i 是 makeFun() 的局部变量怎么办?会产生编译时错误(Variable ‘i’ might not have been initialized)。如果将 i 放到内部,则编译器仍将视其为错误。每个递增操作则会分别产生错误消息。代码示例:
static IntSupplier makeFun(int x){
int i = 0;
x++;
i++;
return () -> x + i;
}
为了解决这种错误,x1和 i1的值在赋值后并没有改变过,因此在这里使用 final 是多余的。
static IntSupplier makeFun(int x){
int i = 0;
x++;
i++;
final int x1 = x;
final int i1 = i;
return () -> x1+i1;
}
List
static Supplier<List> makeFun(){
final List ai = new ArrayList<>();
return () -> ai;
}
public static void main(String[] args) {
List intSupplier = makeFun().get();
intSupplier.add(0,"a");
List intSupplier1 = makeFun().get();
intSupplier1.add(0,"a");
intSupplier1.add(1,"b");
System.out.println(intSupplier);
System.out.println(intSupplier1);
}
我们改变了 List 的内容却没产生编译时错误。通过观察本例的输出结果,我们发现这看起来非常安全。这是因为每次调用 makeFun() 时,其实都会创建并返回一个全新而非共享的 ArrayList。也就是说,每个闭包都有自己独立的ArrayList,它们之间互不干扰。尽管在这个例子中你可以去掉 final 并得到相同的结果。 应用于对象引用的 final 关键字仅表示不会重新赋值引用。 它并不代表你不能修改对象本身。
闭包的内部类
实际上只要有内部类,就会有闭包(Java 8 只是简化了闭包操作)。在 Java 8 之前,变量 x 和 i 必须被明确声明为 final。在 Java 8 中,内部类的规则放宽,包括等同 final 效果。
static IntSupplier makeFun(){
int i = 0;
return new IntSupplier(){
@Override
public int getAsInt() {
return i+1;
}
};
}
函数组合
函数组合(Function Composition)意为“多个函数组合成新函数”。它通常是函数式编程的基本组成部分。在前面的 TransformFunction.java 类中,有一个使用 andThen() 的函数组合示例。一些 java.util.function 接口中包含支持函数组合的方法。
函数式方法名 | 示例接口 |
---|
andThen(argument) | Function、BiFunction、Consumer、BiConsumer、IntConsumer、LongConsumer、DoubleConsumer、UnaryOperator、IntUnaryOperator、LongUnaryOperator、DoubleUnaryOperator、BinaryOperator | compose(argument) | Function、UnaryOperator、IntUnaryOperator、LongUnaryOperator、DoubleUnaryOperator | and(argument) | Predicate、BiPredicate、IntPredicate、LongPredicate、DoublePredicate | or(argument) | Predicate、BiPredicate、IntPredicate、LongPredicate、DoublePredicate | negate() | Predicate、BiPredicate、IntPredicate、LongPredicate、DoublePredicate |
下面介绍不同函数的用法:
public static void main(String[] args) {
Function<String,String> function = (i) -> i.trim();
Function<String, String> function1 = function.andThen(i -> i.toUpperCase())
.compose(i->i.replace(" ","-"));
String a = function1.apply(" hello world ");
System.out.println(a);
function = (i) -> i.trim();
function1 = function.andThen(i -> i.toUpperCase())
.andThen(i->i.replace(" ","-"));
a = function1.apply(" hello world ");
System.out.println(a);
}
compose() 表示在 function 之前调用。
Predicate<String> predicate = (i)->i.length() >1;
Predicate<String> ok = predicate.and(i -> i.contains("hello")).negate()
.or(i -> i.isEmpty());
boolean bo = ok.test("hello world");
System.out.println(bo);
逻辑运算。判断传入的值长度大于1且不包含hello关键字或者等于null。
柯里化函数
柯里化(Currying)的名称来自于其发明者之一 Haskell Curry。他可能是计算机领域唯一名字被命名重要概念的人(另外就是 Haskell 编程语言)。 柯里化意为:将一个多参数的函数,转换为一系列单参数函数。
public static void main(String[] args) {
Function<String,Function> function = a->b->a+b ;
Function hello = function.apply("hello");
Object world = hello.apply(" world");
System.out.println(world);
}
这一连串的箭头很巧妙。注意,在函数接口声明中,第二个参数是另一个函数。柯里化的目的是能够通过提供一个参数来创建一个新函数,所以现在有了一个“带参函数”和剩下的 “无参函数” 。实际上,你从一个双参数函数开始,最后得到一个单参数函数。
纯函数式编程
Java 8 让函数式编程更简单,不过我们要确保一切是 final 的,同时你的所有方法和函数没有副作用。因为 Java 在本质上并非是不可变语言,我们无法通过编译器查错。这种情况下,我们可以借助第三方工具,但使用 Scala 或 Clojure 这样的语言可能更简单。因为它们从一开始就是为保持不变性而设计的。你可以采用这些语言来编写你的 Java 项目的一部分。如果必须要用纯函数式编写,则可以用 Scala(需要一些规则) 或 Clojure (需要的规则更少)。虽然 Java 支持并发编程,但如果这是你项目的核心部分,你应该考虑在项目部分功能中使用 Scala 或 Clojure 之类的语言。
流式编程
流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。利用流,我们无需迭代集合中的元素,就可以提取和操作它们。这些管道通常被组合在一起,在流上形成一条操作管道。在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将把编程的主要焦点从集合转移到了流上。流的一个核心好处是,它使得程序更加短小并且更易理解。当 Lambda 表达式和方法引用(method references)和流一起使用的时候会让人感觉自成一体。流使得 Java 8 更具吸引力。
新旧比对
举个例子,对比一下使用流操作后的代码可以多简洁。如下所示:创建一个list,先进行排序然后再去重,再输出。
public static void main(String[] args) {
List<String> list = Arrays.asList("c","b","a","a");
Collections.sort(list);
Set<String> stringSet = new HashSet<>(list);
Iterator<String> iterator = stringSet.iterator();
while (iterator.hasNext()){
System.out.print(iterator.next());
}
System.out.println("");
list.stream().sorted().distinct().forEach(System.out::print);
}
第一种写法的遍历方式称为外部迭代。流式编程采用内部迭代,这是流式编程的核心特性之一。这种机制使得编写的代码可读性更强,也更能利用多核处理器的优势。通过放弃对迭代过程的控制,我们把控制权交给并行化机制;另一个重要方面,流是懒加载的。这代表着它只在绝对必要时才计算。你可以将流看作“延迟列表”。由于计算延迟,流使我们能够表示非常大(甚至无限)的序列,而不需要考虑内存问题。流可以在不使用赋值或可变数据的情况下对有状态的系统建模,这非常有用。
创建流
你可以通过 Stream.of() 很容易地将一组元素转化成为流,除此之外,每个集合都可以通过调用 stream() 方法来产生一个流。代码示例:
public static void main(String[] args) {
Stream<String> stream = Stream.of("ab", "bc", "cd");
stream.forEach(System.out::println);
System.out.println("=================");
List<String> list = Arrays.asList("ab", "bc", "cd");
list.stream().mapToInt(i->i.length()).forEach(System.out::println);
System.out.println("=================");
Set<String> set = new HashSet<>(list);
set.stream().mapToInt(i->i.length()).forEach(System.out::println);
System.out.println("=================");
Map<String,String> map = new HashMap<>();
map.put("a","123");
map.put("b","456");
map.put("c","789");
map.entrySet().stream().map(item->item.getKey()+":"+item.getValue()).forEach(System.out::println);
}
输出结果:
ab
bc
cd
=================
2
2
2
=================
2
2
2
=================
a:123
b:456
c:789
Stream.generate() 可以生成指定对象数据,一定要配合limit截断,不然会无限制创建下去;Stream.builder() 可以不断的追加参数;Stream.iterate() 以种子(第一个参数)开头,并将其传给方法(第二个参数)。
public static void main(String[] args) {
Stream.generate(()->"abc").limit(5).forEach(System.out::println);
Stream.builder().add("a").add("b").add("c").build().forEach(System.out::println);
int a = 1;
Stream.iterate(0,i->{
int b = a + i++;
return b;
}).limit(5).forEach(System.out::println);
}
除了Stream之外,还有IntStream、DoubleStream、LongStream流等。
public static void main(String[] args) {
int sum = IntStream.range(1, 10).sum();
System.out.println(sum);
DoubleStream.of(1.1,1.25,1.3).count();
LongStream.range(1,10).count();
}
中间操作
顾名思义,就是流中间的操作咯。
public class One {
private String val;
private int val2;
public String getVal() {
return val;
}
public void setVal(String val) {
this.val = val;
}
public int getVal2() {
return val2;
}
public void setVal2(int val2) {
this.val2 = val2;
}
public One(String val, int val2) {
this.val = val;
this.val2 = val2;
}
}
- 跟踪和调试。
peek() 操作的目的是帮助调试。它允许你无修改地查看流中的元素,与map() 不同,他无需返回值。代码示例:
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
ones.add(new One("c", 4));
ones.stream().peek(i->System.out.println(i.getVal())).findAny().get();
}
- 排序。关键字
sorted() ,传入一个 Comparator 参数,可以使得遍历顺序反转。
public static void main(String[] args) {
List<String> list = Arrays.asList("a","b","c","c");
list.stream().sorted().forEach(System.out::print);
System.out.print("\n");
List<One> ones = new ArrayList<>();
ones.add(new One("a",1));
ones.add(new One("a",2));
ones.add(new One("b",3));
ones.add(new One("c",4));
ones.stream().sorted(Comparator.comparing(One::getVal))
.forEach(one -> System.out.println(one.getVal()+","+one.getVal2()));
ones.stream().sorted(Comparator.comparing(One::getVal).reversed())
.forEach(one -> System.out.println(one.getVal()+","+one.getVal2()));
}
- 去重。关键字:
distinct() 相比创建一个 Set 集合,该方法的工作量要少得多。
public static void main(String[] args) {
List<String> list = Arrays.asList("a","b","c","c");
list.stream().distinct().forEach(System.out::print);
System.out.print("\n");
List<One> ones = new ArrayList<>();
ones.add(new One("a",1));
ones.add(new One("a",2));
ones.add(new One("b",3));
ones.add(new One("c",4));
ones.stream().collect(
Collectors.collectingAndThen(Collectors.toCollection(() ->
new TreeSet<>(Comparator.comparing(One::getVal))), ArrayList::new))
.forEach(i-> System.out.println(i.getVal()+","+i.getVal2()));
}
- 映射和组合。关键字
map() 映射一个新的流和flatMap() 将处理的流映射为一个新的组合流
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "c");
list.stream().map(String::hashCode).forEach(System.out::print);
list.stream().mapToInt(String::hashCode).forEach(System.out::println);
list.stream().mapToDouble(String::hashCode).forEach(System.out::println);
list.stream().mapToLong(String::hashCode).forEach(System.out::println)
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("a", 2));
ones.add(new One("b", 3));
ones.add(new One("c", 4));
List<String> collect = ones.stream().map(One::getVal).collect(Collectors.toList());
List<One> ones = new ArrayList<>();
ones.add(new One("a b", 1));
ones.add(new One("b c", 2));
ones.add(new One("c c", 3));
List<String[]> collect = ones.stream()
.map(map -> map.getVal().split(" ")).collect(Collectors.toList());
ones.stream().map(i -> i.getVal().split(" "))
.flatMap(Arrays::stream).forEach(System.out::print);
List<One> ones1 = new ArrayList<>();
ones1.add(new One("a b", 1));
Stream.of(ones,ones1).flatMap(Collection::stream)
.forEach(i-> System.out.println(i.getVal()));
}
Optional类
我们必须考虑如果你在一个空流中获取元素会发生什么。在流中放置 null 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)?Optional 可以实现这样的功能。一些标准流操作返回 Optional 对象,因为它们并不能保证预期结果一定存在,如果流为空则返回 Optional.empty
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
System.out.println(ones.stream().findAny());
System.out.println(ones.stream().findFirst());
}
注意,空流是通过 Stream.empty() 创建的。如果你在没有任何上下文环境的情况下调用 Stream.empty() ,Java 并不知道它的数据类型;这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:
Stream<Object> a=Stream.empty();
创建Optional
empty() 生成一个空 Optional。
public static void main(String[] args) {
Optional<String> s = Optional.empty();
boolean present = s.isPresent();
System.out.println(present);
}
of(value) 将一个非空值包装到 Optional 里。
public static void main(String[] args) {
Optional<String> s = Optional.of("a");
boolean present = s.isPresent();
System.out.println(present);
}
ofNullable(value) 针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。
public static void main(String[] args) {
Optional<String> s1 = Optional.ofNullable(null);
System.out.println(s1.orElse(null));
}
便利函数
有许多便利函数可以解包 Optional ,这简化了上述“对所包含的对象的检查和执行操作”的过程:
ifPresent(Consumer) :当值存在时调用 Consumer,否则什么也不做。
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
System.out.println(ones.stream().findFirst().isPresent());
ones = new ArrayList<>();
System.out.println(ones.stream().findFirst().isPresent());
}
orElse(otherObject) :如果值存在则直接返回,否则生成 otherObject。
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
System.out.println(ones.stream().findFirst().orElse(new One("d",5)).getVal());
ones = new ArrayList<>();
System.out.println(ones.stream().findFirst().orElse(new One("d",5)).getVal());
}
orElseGet(Supplier) :如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
System.out.println(ones.stream().findFirst()
.orElseGet(()->new One("d",5)).getVal());
}
orElseThrow(Supplier) :如果值存在直接返回,否则使用 Supplier 函数生成一个异常。
public static void main(String[] args) throws Exception {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
System.out.println(ones.stream().findFirst()
.orElseThrow(()->new Exception("null")).getVal());
ones = new ArrayList<>();
System.out.println(ones.stream().findFirst()
.orElseThrow(()->new Exception("null")).getVal());
}
对象操作
- 过滤。关键字
filter() 类似于if判断,获取为true的数据
public static void main(String[] args) {
List<String> list = Arrays.asList("a","b","c","c");
list.stream().filter(item ->!item.equals("c")).forEach(System.out::print);
System.out.print("\n");
List<One> ones = new ArrayList<>();
ones.add(new One("a",1));
ones.add(new One("a",2));
ones.add(new One("b",3));
ones.add(new One("c",4));
List<One> a = ones.stream()
.filter(item -> !item.getVal().equals("a")).collect(Collectors.toList());
ones.stream().
filter(item -> !item.getVal().equals("a"))
.forEach(one -> System.out.println(one.getVal()+","+one.getVal2()));
}
- 限制和跳过。关键字
limit() 获取指定条数数据和skip() 跳过指定条数数据
public static void main(String[] args) {
List<String> list = Arrays.asList("a","b","c","c");
list.stream().limit(2).forEach(System.out::print);
System.out.print("\n");
list.stream().skip(2).forEach(System.out::print);
System.out.print("\n");
List<One> ones = new ArrayList<>();
ones.add(new One("a",1));
ones.add(new One("a",2));
ones.add(new One("b",3));
ones.add(new One("c",4));
ones.stream().limit(2)
.forEach(item -> System.out.println(item.getVal()+","+item.getVal2()));
ones.stream().skip(2)
.forEach(item -> System.out.println(item.getVal()+","+item.getVal2()));
}
终端操作
以下操作将会获取流的最终结果。至此我们无法再继续往后传递流。可以说,终端操作(Terminal Operations)总是我们在流管道中所做的最后一件事。
- 遍历。关键字
forEach() 、forEachOrdered(Consumer) :保证 forEach 按照原始流顺序操作。
public static void main(String[] args) {
List<String> list = Arrays.asList("a","b","c","c");
list.stream().forEach(System.out::print);
System.out.println();
list.stream().forEachOrdered(item-> System.out.println(item.hashCode()));
}
- 收集。关键字
collect() 将流转换为其他形式,比如转换成List、Set、Map,除了这些之外还可进行数值累加、分组等,也就是方法解决的方案有很多种,需要你灵活使用。
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
ones.add(new One("c", 4));
ones.stream().collect(Collectors.toSet())
.forEach(item -> System.out.println(item.getVal()+","+item.getVal2()));
ones.stream().map(One::getVal).collect(Collectors.toCollection(LinkedList::new))
.forEach(System.out::print);
ones.stream().collect(Collectors.toMap(One::getVal,One::getVal2,(k1,k2)->k1)).entrySet()
.forEach(System.out::print);
ones.stream().collect(Collectors.toMap(One::getVal, Function.identity(),(k1, k2)->k1))
.entrySet().forEach(item -> System.out.println(item.getKey()+","+item.getValue()));
ones.stream()
.collect(Collectors.toMap(One::getVal,One::getVal2,(k1,k2)->k1,LinkedHashMap::new))
.entrySet().forEach(System.out::print);
}
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
Integer one = ones.stream().map(One::getVal2).reduce(Integer::min).get();
System.out.println(one);
one = ones.stream().map(One::getVal2).reduce(Integer::max).get();
System.out.println(one);
one = ones.stream().map(One::getVal2).reduce(Integer::sum).get();
System.out.println(one);
}
另一种写法
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
ones.add(new One("c", 4));
double average = ones.stream().mapToInt(One::getVal2).average().getAsDouble();
System.out.println(average);
int sum = ones.stream().mapToInt(One::getVal2).sum();
System.out.println(sum);
long count = ones.stream().mapToInt(One::getVal2).count();
System.out.println(count);
int max = ones.stream().mapToInt(One::getVal2).max().getAsInt();
System.out.println(max);
int min = ones.stream().mapToInt(One::getVal2).min().getAsInt();
System.out.println(min);
}
- 匹配。关键字
anyMatch() 判断流中是否含有匹配元素、allMatch() 判断流中所有元素匹配、noneMatch() 判断流中不包含此元素
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "c");
boolean a = list.stream().allMatch(item -> item.contains("a"));
boolean b = list.stream().anyMatch(item -> item.contains("b"));
boolean c = list.stream().noneMatch(item -> item.contains("e"));
System.out.println(a);
System.out.println(b);
System.out.println(c);
List<One> ones = new ArrayList<>();
ones.add(new One("a b", 1));
ones.add(new One("b c", 2));
ones.add(new One("c c", 3));
boolean b1 = ones.stream().allMatch(item -> item.getVal2() == 3);
System.out.println(b1);
b1 = ones.stream().anyMatch(item -> item.getVal2() == 3);
System.out.println(b1);
b1 = ones.stream().noneMatch(item -> item.getVal2() == 4);
System.out.println(b1);
}
- 查找。关键字
findAny() 找到一个元素就返回、findFirst() 返回第一个元素、get() 返回对应值,若值为null,报空异常、orElse() 如果没有数据返回指定类型值
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "c");
String a = list.stream().findAny().get();
System.out.println(a);
a = list.stream().findFirst().orElse(null);
System.out.println(a);
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
One one = ones.stream().findAny().get();
System.out.println(one.getVal()+","+one.getVal2());
one = ones.stream().findFirst().get();
System.out.println(one.getVal()+","+one.getVal2());
}
public static void main(String[] args) {
List<String> list = Arrays.asList("a", "b", "c", "c");
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
long one = ones.stream().count();
System.out.println(one);
}
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
ones.add(new One("c", 4));
String collect = ones.stream().map(One::getVal).collect(Collectors.joining(","));
System.out.println(collect);
}
public static void main(String[] args) {
List<One> ones = new ArrayList<>();
ones.add(new One("a", 1));
ones.add(new One("b", 2));
ones.add(new One("c", 3));
ones.add(new One("c", 4));
Map<String, List<One>> collect = ones.stream().collect(Collectors.groupingBy(One::getVal));
System.out.println(collect);
Map<String, Long> collect1 = ones.stream()
.collect(Collectors.groupingBy(One::getVal,Collectors.counting()));
System.out.println(collect1);
Map<String, Set<One>> toSet = ones.stream()
.collect(Collectors.groupingBy(One::getVal,Collectors.toSet()));
System.out.println(toSet);
TreeMap<String, Set<One>> toTreeMap = ones.stream()
.collect(Collectors.groupingBy(One::getVal,TreeMap::new,Collectors.toSet()));
System.out.println(toTreeMap);
}
本章小结
流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。
|