1.为什么关心Java8
和大多数书本一样,书本的第一章都起统领全书的作用,上来就是一个疑问?Java8怎么还在变,语言的变化离不开程序员对性能和代码简化的需求,进而引出了流处理、Lambda表达式、方法引用、并行化的相关概念
1.1 Java 怎么还在变
- Java 在编程语言生态系统中的位置
- 流处理 ——流是一系列数据项,一次只生成一项
- 用行为参数化把代码传递给方法
- 并行与共享的可变数据
- Java 需要演变
1.2 Java中的函数
Java8中新增了函数——值的一种新形式
前提是:listFiles
方法接收一个方法作为参数
谓词(predicate):在数学上常常用来代表一个类似函数的东西,它接受一个参数值,并返回 true 或 false 。你在后面会看到,Java 8也会允许你写 Function<Apple,Boolean> ——在学校学过函数却没学过谓词的读者对此可能更熟悉,但用 Predicate 是更标准的方式,效率也会更高一点儿,这避免了把 boolean 封装在 Boolean 里面。(通俗来讲就是一个返回boolean值的函数)
1.3 流
你需要从一个列表中筛选金额较高的交易,然后按货币分组。
在Stream
中
有了Stream API,你根本用不着操心循环的事情。数据处理完全是在库内部进行的。我们把这种思想叫作内部迭代。
1.3.2 并行流
Collection主要是为了存储和访问数据,而Stream则主要用于描述对数据的计算。
如何利用Stream和Lambda表达式顺序或并行地从一个列表里筛选比较重的苹果。
使用并行
理想的情况下,你会希望做 list.sort(comparator) ,而不是 Collections.sort(list, comparator) 。前者不是并行,后者既不是并行也不是面向对象语法。
Java中从函数式编程中引入的两个核心思想:将方法和Lambda作为一等值,以及在没有可变共享状态时,函数或方法可以有效、安全地并行执行。
2. 通过行为参数化传递代码
2.1 行为参数化
让我们定义一个接口来对选择标准建模:
你刚做的这些和“策略设计模式” 相关,它让你定义一族算法,把它们封装起来(称为“策略”),然后在运行时选择一个算法。
最终的代码
2.2 可以使用匿名类
策略模式其实挺麻烦的,因为每一个策略都是一个实现类,会造成大量的文件,而且如果不是经常重复使用的话,对系统来说未免有点浪费。所以,对使用一次的策略,建议直接使用匿名类。
匿名类:面试中常问的接口可以实现吗?其实说的就是匿名类
匿名类谜题
答案是5,因为this指的是包含它的Runnable,而不是外面的类MeaningOfThis
3. Lambda表达式
可以把Lambda表达式理解为简介地表示可以传递的匿名函数的一种方式:它没有名称,但他有参数列表、函数主体、返回类型,可能还有一个可以跑出的异常列表。
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确地名称:写得少而想得多(鸡肋)
- 函数——我们说它是函数,是因为Lambda函数不像方法属于某个特定的类。但和方法一样,Lambda有参数列表、函数主体、返回类型,还有可能跑出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中
- 简介——无需像匿名类那样写很多模板代码。
Lambda表达式
Lambda有三个组成部分
- 参数列表
- 箭头
- Lambda主体
注意:使用return显式返回的时候,要有花括号{};同理,有{},就要有return
对比匿名类
3.1 Lambda示例
3.2 在哪里以及如何使用Lambda
3.2.1 函数式接口
哪怕有再多的默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口
3.2.2 函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符。。例如, Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )
**@FunctionalInterface又是怎么回事? **
如果你去看看新的Java API,会发现函数式接口带有 @FunctionalInterface 的标注。这个标注用于表示该接口会设计成一个函数式接口。如果你用 @FunctionalInterface 定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。例如,错误消息可能是“Multiple non-overriding abstract methods found in interface Foo”,表明存在多个抽象方法。请注意, @FunctionalInterface 不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是 @Override标注表示方法被重写了。
3.2.3 异常、Lambda,还有函数式接口
请注意,任何函数式接口都不允许抛出受检异常(checked exception)。如果你需要Lambda表达式来抛出异常,有两种办法:定义一个自己的函数式接口,并声明受检异常,或者把Lambda包在一个 try/catch 块中。
3.3 类型检查、类型推断
Lambda的类型是从使用Lambda的上下文推断出来的。上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。
3.4 使用局部变量
3.5 方法引用
方法引用让你可以重复使用现有的方法定义,并像Lambda一样传递它们。
3.5.1 构建方法引用
- 指向静态方法的方法引用(例如 Integer 的 parseInt 方法,写作 Integer::parseInt)
- 指 向 任 意 类 型 实 例 方 法 的 方 法 引 用 ( 例 如 String 的 length 方 法 ,写 作String::length )。
- 指向现有对象的实例方法的方法引用(假设你有一个局部变量 expensiveTransaction用于存放 Transaction 类型的对象,它支持实例方法 getValue ,那么你就可以写 expensiveTransaction::getValue )。
3.5.2 构造函数引用
3.5.3 奥义:工厂
3.6 Lambda表达式的符合方法
//1. 逆序
inventory.sort(comparing(Apple::getWeight).reversed());
//2. 比较器链
inventory.sort(comparing(Apple::getWeight) .reversed() .thenComparing(Apple::getCountry));
附录
常见函数接口
接口 | 描述 | 原始类型特化 |
---|---|---|
Predicate<T> | 定义了一个test的抽象方法,(T)->boolean | IntPredicate, LongPredicate, DoublePredicate |
Consumer<T> | 定义了一个accept的抽象方法,(T)->void | IntConsumer, LongConsumer, DoubleConsumer |
Function<T,R> | 定义了一个apply的抽象方法,(T)->R | IntFunction<R> , IntToDoubleFunction, IntToLongFunction, LongFunction<R> , LongToDoubleFunction, LongToIntFunction, DoubleFunction<R> , ToIntFunction<T> , ToDoubleFunction<T> , ToLongFunction<T> |
Supplier<T> | 定义了一个get的抽象方法 () -> T | BooleanSupplier, IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator<T> | 继承自Function<T,R> ,独有的UnaryOperator方法(未知用处) | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator<T> | 继承自BiFunction<T,T,T> | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L,R> | test抽象方法,(T,U)->boolean | |
BiConsumer<T,U> | accept,(T,U)->void | ObjIntConsumer<T> , ObjLongConsumer<T> , ObjDoubleConsumer<T> |
BiFunction<T,U,R> | 定义了一个apply抽象方法,(T,U)->R | ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U> |
练习
- 把int数组变为integer数组(1.5.反过来)
int[] nums = new int[]{1,2,3,4,5,6};
Integer[] integers = Arrays.stream(nums).boxed().toArray(Integer[]::new);
//====================
Integer[] nums = new Integer[]{1,2,3,4,5,6};
int[] ints = Arrays.stream(nums).mapToInt(Integer::valueOf).toArray();
class Solution {public int[][] allCellsDistOrder(int R, int C, int r0, int c0) {int[][] res = new int[R*C][2];int k = 0;for(int i = 0;i<R;i++){for(int j=0;j<C;j++){res[k++] = new int[]{i,j};}}Arrays.sort(res, (o1, o2) -> {return (Math.abs(o1[0] - r0) + Math.abs(o1[1] - c0)) - (Math.abs(o2[0] - r0) + Math.abs(o2[1] - c0));});return res;}
}