- 前言
- 函数式编程
- 新旧比对
- Lambda表达式
- 方法引用
- 未绑定的方法引用
- 构造函数引用
- 函数式接口
- 多参数函数式接口
- 高阶函数
- 闭包
- List
- 闭包的内部类
- 函数组合
- 柯里化函数
- 纯函数式编程
- 流式编程
- 新旧比对
- 创建流
- 中间 *** 作
- Optional类
- 创建Optional
- 便利函数
- 对象 *** 作
- 终端 *** 作
- 本章小结
函数式编程函数式编程语言 *** 纵代码片段就像 *** 作数据一样容易。 虽然 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) { //Java7 new Thread(new Runnable() { @Override public void run() { System.out.println("hello"); } }).start(); //Java8 new Thread(Hello::hello).start(); }
- Java 8 的 Lambda 表达式。由箭头 -> 分隔开参数和函数体,箭头左边是参数,箭头右侧是从 Lambda 返回的表达式,即函数体。这实现了与定义类、匿名内部类相同的效果,但代码少得多。
- Java 8 的方法引用,由 :: 区分。在 :: 的左边是类或对象的名称,在 :: 的右边是方法的名称,但没有参数列表。
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;//Cannot resolve method '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 = new One()::consumer; 实例化对象的方法的引用,有时称为绑定方法引用 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。
下面列举了一些示例:
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);//Output: 1 Callable callable = One::consumer1; Object call = callable.call(); System.out.println(call);//Output: 1 Consumerconsumer = s -> System.out.println(s + "world"); consumer.accept("hello");//Output: helloworld BiConsumer biConsumer = (a,b) -> System.out.println(a+" "+b); biConsumer.accept("hello","world");//Output: hello world ObjIntConsumer objIntConsumer = (a,b) -> System.out.println(a+" "+b); objIntConsumer.accept(1,2);//Output: 1 2 IntFunction intFunction = One::consumer2; System.out.println(intFunction.apply(1));//Output: 1 UnaryOperator unaryOperator = s -> s + " world"; System.out.println(unaryOperator.apply("hello"));//Output: hello world BinaryOperator binaryOperator = (a,b) -> a+" "+b; String apply = binaryOperator.apply("hello", "world"); System.out.println(apply);//Output: hello world Comparator comparator = (a,b)-> a+b; int compare = comparator.compare(1, 2); System.out.println(compare);//Output: 3 Predicate predicate = s -> s.length() >0; boolean hello = predicate.test("hello"); System.out.println(hello);//Output: true IntToLongFunction intToLongFunction = a -> a; System.out.println(intToLongFunction.applyAsLong(2));//Output: 2 BiFunction biFunction = (a,b) -> a>b; System.out.println(biFunction.apply(1,2));//Output: false } }
只要参数类型、返回类型也可以换成方法绑定引用,这些 Lambda 表达式尝试生成适合函数签名的最简代码。 在某些情况下,有必要进行强制类型转换,否则编译器会报截断错误。
多参数函数式接口接口是有限的。比如有了 BiFunction,但它不能变化。 如果需要三参数函数的接口怎么办? 其实这些接口非常简单,很容易查看 Java 库源代码并自行创建。代码示例:
@FunctionalInterface public interface One高阶函数{ 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 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{ } 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);//3 return o; }); } public static void main(String[] args){ Function function = product(i->{ System.out.println(i);//2 return new One(); }); One one = (One) function.apply(new Two());//1 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; Variable 'i' might not have been initialized int i = 0; x++; i++; //Variable used in lambda expression should be final or effectively final 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 SuppliermakeFun(){ 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 接口中包含支持函数组合的方法。
下面介绍不同函数的用法:
public static void main(String[] args) { Functionfunction = (i) -> i.trim(); Function 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 之前调用。
Predicatepredicate = (i)->i.length() >1; Predicate 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) { Functionfunction = 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) { //第一种 命令式编程 Listlist = Arrays.asList("c","b","a","a"); Collections.sort(list); Set stringSet = new HashSet<>(list); Iterator 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) { Streamstream = Stream.of("ab", "bc", "cd"); stream.forEach(System.out::println); System.out.println("================="); List list = Arrays.asList("ab", "bc", "cd"); list.stream().mapToInt(i->i.length()).forEach(System.out::println); System.out.println("================="); Set set = new HashSet<>(list); set.stream().mapToInt(i->i.length()).forEach(System.out::println); System.out.println("================="); Map 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);//Output: 45 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) { Listones = 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();//Output: a }
- 排序。关键字sorted(),传入一个 Comparator 参数,可以使得遍历顺序反转。
public static void main(String[] args) { Listlist = Arrays.asList("a","b","c","c"); list.stream().sorted().forEach(System.out::print);//Output: abcc System.out.print("n"); List 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) { //基本写法 Listlist = Arrays.asList("a","b","c","c"); list.stream().distinct().forEach(System.out::print);//Output: abc System.out.print("n"); //实战写法 List 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) { ListOptional类list = Arrays.asList("a", "b", "c", "c"); list.stream().map(String::hashCode).forEach(System.out::print);//Output: 97989999 list.stream().mapToInt(String::hashCode).forEach(System.out::println);//映射为int类型的流 list.stream().mapToDouble(String::hashCode).forEach(System.out::println);//映射为double类型的流 list.stream().mapToLong(String::hashCode).forEach(System.out::println)//;映射为long类型的流 List 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)); //提取出val字段成为一个list List collect = ones.stream().map(One::getVal).collect(Collectors.toList()); List 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分割后遍历,map()映射为一个数组 List collect = ones.stream() .map(map -> map.getVal().split(" ")).collect(Collectors.toList()); //flatMap()映射完重新组合为一个list ones.stream().map(i -> i.getVal().split(" ")) .flatMap(Arrays::stream).forEach(System.out::print); List ones1 = new ArrayList<>(); ones1.add(new One("a b", 1)); //合并两个集合 Stream.of(ones,ones1).flatMap(Collection::stream) .forEach(i-> System.out.println(i.getVal())); }
我们必须考虑如果你在一个空流中获取元素会发生什么。在流中放置 null 是很好的中断方法。那么是否有某种对象,可作为流元素的持有者,即使查看的元素不存在也能友好地提示我们(也就是说,不会发生异常)?Optional 可以实现这样的功能。一些标准流 *** 作返回 Optional 对象,因为它们并不能保证预期结果一定存在,如果流为空则返回 Optional.empty
public static void main(String[] args) { Listones = new ArrayList<>(); System.out.println(ones.stream().findAny());//Output: Optional.empty System.out.println(ones.stream().findFirst());//Output: Optional.empty }
注意,空流是通过 Stream.empty() 创建的。如果你在没有任何上下文环境的情况下调用 Stream.empty(),Java 并不知道它的数据类型;这个语法解决了这个问题。如果编译器拥有了足够的上下文信息,比如:
Stream创建Optional
empty()生成一个空 Optional。
public static void main(String[] args) { Optionals = Optional.empty(); boolean present = s.isPresent(); System.out.println(present);//Output: false }
of(value)将一个非空值包装到 Optional 里。
public static void main(String[] args) { Optionals = Optional.of("a"); boolean present = s.isPresent(); System.out.println(present);//Output: true }
ofNullable(value)针对一个可能为空的值,为空时自动生成 Optional.empty,否则将值包装在 Optional 中。
public static void main(String[] args) { Optional便利函数s1 = Optional.ofNullable(null); System.out.println(s1.orElse(null));//Output: null }
有许多便利函数可以解包 Optional ,这简化了上述“对所包含的对象的检查和执行 *** 作”的过程:
- ifPresent(Consumer):当值存在时调用 Consumer,否则什么也不做。
public static void main(String[] args) { Listones = new ArrayList<>(); ones.add(new One("a", 1)); System.out.println(ones.stream().findFirst().isPresent());//Output: true ones = new ArrayList<>(); System.out.println(ones.stream().findFirst().isPresent());//Output: false }
- orElse(otherObject):如果值存在则直接返回,否则生成 otherObject。
public static void main(String[] args) { Listones = new ArrayList<>(); ones.add(new One("a", 1)); System.out.println(ones.stream().findFirst().orElse(new One("d",5)).getVal());//Output: a ones = new ArrayList<>(); System.out.println(ones.stream().findFirst().orElse(new One("d",5)).getVal());//Output: d }
- orElseGet(Supplier):如果值存在则直接返回,否则使用 Supplier 函数生成一个可替代对象。
public static void main(String[] args) { Listones = new ArrayList<>(); ones.add(new One("a", 1)); System.out.println(ones.stream().findFirst() .orElseGet(()->new One("d",5)).getVal());//Output: a }
- orElseThrow(Supplier):如果值存在直接返回,否则使用 Supplier 函数生成一个异常。
public static void main(String[] args) throws Exception { List对象 *** 作ones = new ArrayList<>(); ones.add(new One("a", 1)); System.out.println(ones.stream().findFirst() .orElseThrow(()->new Exception("null")).getVal());//Output: a ones = new ArrayList<>(); System.out.println(ones.stream().findFirst() .orElseThrow(()->new Exception("null")).getVal());//java.lang.Exception: null }
- 过滤。关键字filter()类似于if判断,获取为true的数据
public static void main(String[] args) { Listlist = Arrays.asList("a","b","c","c"); list.stream().filter(item ->!item.equals("c")).forEach(System.out::print);//Output: ab System.out.print("n"); List 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数据 List a = ones.stream() .filter(item -> !item.getVal().equals("a")).collect(Collectors.toList()); //遍历过滤后的list数据 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终端 *** 作list = Arrays.asList("a","b","c","c"); list.stream().limit(2).forEach(System.out::print);//Output: ab System.out.print("n"); list.stream().skip(2).forEach(System.out::print);//Output: cc System.out.print("n"); List 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) { Listlist = 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) { Listones = 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)); //转换为set ones.stream().collect(Collectors.toSet()) .forEach(item -> System.out.println(item.getVal()+","+item.getVal2())); //提取指定属性转为linkedList ones.stream().map(One::getVal).collect(Collectors.toCollection(linkedList::new)) .forEach(System.out::print);//Output: abcc //转换为map value=val,key=val2 ones.stream().collect(Collectors.toMap(One::getVal,One::getVal2,(k1,k2)->k1)).entrySet() .forEach(System.out::print);//Output: a=1b=2c=3 //转换为map value=val,key=对象 ones.stream().collect(Collectors.toMap(One::getVal, Function.identity(),(k1, k2)->k1)) .entrySet().forEach(item -> System.out.println(item.getKey()+","+item.getValue())); //指定map类型 ones.stream() .collect(Collectors.toMap(One::getVal,One::getVal2,(k1,k2)->k1,linkedHashMap::new)) .entrySet().forEach(System.out::print);//Output: a=1b=2c=3 }
- 数值计算。关键字reduce()
public static void main(String[] args) { Listones = 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);//Output: 1 //获取最大值 one = ones.stream().map(One::getVal2).reduce(Integer::max).get(); System.out.println(one);//Output: 3 //计算总和 one = ones.stream().map(One::getVal2).reduce(Integer::sum).get(); System.out.println(one);//Output: 6 }
另一种写法
public static void main(String[] args) { Listones = 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);//Output: 2.5 //计算总和 int sum = ones.stream().mapToInt(One::getVal2).sum(); System.out.println(sum);//Output: 10 //计算总数 long count = ones.stream().mapToInt(One::getVal2).count(); System.out.println(count);//Output: 4 //获取最大数 int max = ones.stream().mapToInt(One::getVal2).max().getAsInt(); //One one = ones.stream().max(Comparator.comparing(One::getVal2)).get();获取最大对象 System.out.println(max);//Output:4 //获取最小数 int min = ones.stream().mapToInt(One::getVal2).min().getAsInt(); //One one = ones.stream().min(Comparator.comparing(One::getVal2)).get();获取最小对象 System.out.println(min);//Output: 1 }
- 匹配。关键字anyMatch()判断流中是否含有匹配元素、allMatch()判断流中所有元素匹配、noneMatch()判断流中不包含此元素
public static void main(String[] args) { Listlist = Arrays.asList("a", "b", "c", "c"); //判断是否全部元素等于a boolean a = list.stream().allMatch(item -> item.contains("a")); //判断是否匹配元素a boolean b = list.stream().anyMatch(item -> item.contains("b")); //判断是否不包含元素e boolean c = list.stream().noneMatch(item -> item.contains("e")); System.out.println(a);//Output: false System.out.println(b);//Output: true System.out.println(c);//Output: true List 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);//Output: false b1 = ones.stream().anyMatch(item -> item.getVal2() == 3); System.out.println(b1);//Output: true b1 = ones.stream().noneMatch(item -> item.getVal2() == 4); System.out.println(b1);//Output: true }
- 查找。关键字findAny()找到一个元素就返回、findFirst()返回第一个元素、get()返回对应值,若值为null,报空异常、orElse()如果没有数据返回指定类型值
public static void main(String[] args) { Listlist = Arrays.asList("a", "b", "c", "c"); //获取对应元素,为null报异常 String a = list.stream().findAny().get(); System.out.println(a);//Output: a //为null则返回null a = list.stream().findFirst().orElse(null); System.out.println(a);//Output: a List 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());//Output: a,1 one = ones.stream().findFirst().get(); System.out.println(one.getVal()+","+one.getVal2());//Output: a,1 }
- 总数。关键字count()计算元素总数
public static void main(String[] args) { Listlist = Arrays.asList("a", "b", "c", "c"); List 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);//Output: 3 }
- 连接字符串。关键字joining()
public static void main(String[] args) { Listones = 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);//Output: a,b,c,c }
- 分组。关键字groupingBy()
public static void main(String[] args) { List本章小结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)); //通过val进行分组,为了显示方便处理了输出结果 Map > collect = ones.stream().collect(Collectors.groupingBy(One::getVal)); System.out.println(collect);//Output: {a=[Object], b=[Object], c=[Object, Object]} //分组后的元素数量 Map collect1 = ones.stream() .collect(Collectors.groupingBy(One::getVal,Collectors.counting())); System.out.println(collect1);//Output: {a=1, b=1, c=2} //将分组的value转换为set接收,为了显示方便处理了输出结果 Map > toSet = ones.stream() .collect(Collectors.groupingBy(One::getVal,Collectors.toSet())); System.out.println(toSet);//Output: {a=[Object], b=[Object], c=[Object, Object]} //将map转换为TreeMap接收,为了显示方便处理了输出结果 TreeMap > toTreeMap = ones.stream() .collect(Collectors.groupingBy(One::getVal,TreeMap::new,Collectors.toSet())); System.out.println(toTreeMap);//Output: {a=[Object], b=[Object], c=[Object, Object]} }
流式 *** 作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)