Java8函数式编程深入理解
什么是匿名内部类?
无需知道类实现名,在内部实现接口方法,做类的定义。
Lambda表达式与匿名内部类的区别?
- 匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。
- Lambda表达式通过invokedynamic指令实现,书写Lambda表达式不会产生新的类。
- 简化匿名内部类的书写,取代部分匿名内部类,只能用来取代函数接口(Functional Interface)的简写。
Lambda表达式
Lambda表达式:参数 箭头(->)表达式
好处:简化匿名内部类写法,去掉样板代码。
原本设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部类还是不够简便。为了调用一行重要的逻辑代码,不得不加上 4 行冗繁的样板代码。若使用Lambda写法,就能直接用一行代码替代4行,去掉样板代码,只留下具体逻辑代码。
正常写法Runable
| Runnable runnable = new Runnable() { |
Lambda写法
Runnable runnable = ()->System.out.println("run"); |
Lambda的几种写法
简写的依据
能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口。这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。
无参函数写法
使用空括号()表示无参数,使用Lambda表达式实现Runable接口,该接口只有一个run方法,没有参数,返回类型为void。
Runnable runnable = ()->System.out.println("run"); |
带参函数写法
使用括号(参数,参数)表示有多个参数,当只有一个参数时可省略括号。
BinaryOperator ActionListener listener = event -> System.out.println("button clicked"); |
复杂逻辑写法
若不止一行代码,需要加上大括号。(参数)->{代码逻辑}
ActionListener actionListener = event -> {
System.out.println("button clicked");
System.out.println("notice other");
}; |
注:错误写法
在只有一行代码逻辑的时候,若有程序逻辑有返回时,编译器会自动识别返回类型,无需加上return关键字。加上大括号时需要加上return关键字。
//正确写法 BinaryOperator // 错误写法 BinaryOperator //正确写法 BinaryOperator |
自定义函数接口
只需要编写一个抽象方法的接口即可。@FunctionalInterface注释是可选的,其目的是为了标注编译器帮你检查接口是否符合函数接口规范,就像@Override标注会检查是否重载了函数一样。注意:函数接口中只允许一个抽象方法的接口,若出现多个的情况下,编译器无法根据上下文进行推算当前接口。
@FunctionalInterface public interface Consumer void accept(T t); } |
错误定义:
报错:Operator cannot be applied to lambda parameter
public interface TestComsumer |
类型推断
程序员可省略Lambda表达式中所有的参数类型,javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断。
使用 Lambda 表达式示例1检测一个 Integer 是否大于 5,示例2检测一个String是否等于”x” 。这实际上是一个 Predicate ——用来判断真假的函数接口。
类型推断:
//示例1 Predicate //示例2 Predicate |
Predicate源码:接受一个对象,返回一个布尔值
@FunctionalInterface public interface Predicate boolean test(T t); } |
Predicate 只有一个泛型类型的参数, Integer 或String用于其中。Lambda表达式实现了 Predicate 接口,因此它的单一参数被推断为 Integer或String 类型。 javac 还可检查Lambda 表达式的返回值是不是 boolean ,这正是 Predicate 方法的返回类型。
没有泛型,代码不通过编译。当代码没有给与任何泛型时,编译器会将参数识别成java.lang.Object,Object是无法直接进行大小比较。
//当将x进行toString后,再转成Integer就能进行大小比较,这样就不报错。 Predicate predicate = x -> Integer.parseInt(x.toString()) > 5; //当将x进行toString后,就能跟字符”x”进行比较 Predicate predicate = x -> x.toString().equals("x"); |
Stream流
Stream流部分常见接口方法
| 操作类型 | 接口方法 |
| 惰性求值 | concat() distinct() filter() flatMap() limit() map() peek() |
| 及早求值 | allMatch() anyMatch() collect() count() findAny() findFirst() |
惰性求值方法:只描述stream流,不产生新的集合。
及早求值方法:从stream流中产生值。
是否是惰性求值还是及早求值有一个简单的判断依据,当返回的值是stream流,那么就是惰性求值,若返回值是具体的值或空,那么就是及早求值。
注:只执行惰性求值方法,而不使用及早求值方法是不会真正的执行stream流。惰性求值可以比作“我要做“,及早求值比作”我要得到“。当”我要做“的时候,代码只是将代码执行流程都准备好,只有执行”我要得到“指令时,才会去执行”我要做“指令。
惰性求值
concat
distinct
distinct可以进行去重操作。
//Stream中distinct方法签名 Stream //代码示例 Stream |
filter
filter是将一种Stream流中过滤中需要的新Stream流。
// Stream中filter方法签名 Stream //代码示例 将oldList中大于3的元素转换成newList List List .filter(x -> x > 3).collect(toList()); |
flatMap
flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream。
// Stream中flatMap方法签名
//将多个list中的值转换成一个list List |
limit
map
map是将一种Stream流转换成另外一种Stream流。
//Stream中map方法签名
//示例代码将Integer集合转换成String集合 List .map(x -> String.valueOf(x)).collect(toList()); |
peek
skip
sorted
sorted有两种排序,一种是自然顺序排序,一种是自定义比较器排序。
//Stream中sorted方法签名 Stream Stream //代码示例 Stream //自然顺序排序 stream.sorted().forEach(str -> System.out.println(str)); //自定义排序 stream.sorted((str1, str2) -> str1.length()-str2.length()) .forEach(str -> System.out.println(str)); |
parallel
sequential
unordered
及早求值
collect
collect(toList()) 方法由 Stream 里的值生成一个列表。不单单是toList(),还可以toMap(),toSet(),toConcurrentMap(),toCollection().
//Stream中collect方法签名
//toList()代码示例 List .collect(Collectors.toList()); //toMap()代码示例 Map .collect(Collectors.toMap(str -> str, Function.identity())); // Function.identity()表示返回自身,等同str -> str à源码 static //toSet()代码示例 Set |
forEach
forEach是一个遍历集合的接口方法,作用是对容器中每个元素执行action指定动作,也就是对元素进行遍历。
// Stream中forEach方法签名。 void forEach(Consumer super T> action) //代码示例 |
Stream stream.forEach(str -> System.out.println(str)); |
max和min
max和min是查找Stream流中按照Comparator对象对比出来的max与min对象值。
//Stream中max方法签名 Optional //查找字符长度最长的元素 String maxStr = Stream.of("I", "love", "you", "too") .max(Comparator.comparing(x -> x.length())).get(); |
reduce
reduce 可以实现从一组值中生成一个值。reduce可以做count , max , min等操作。
//Stream中reduce方法签名 T reduce(T identity, BinaryOperator Optional U reduce(U identity, BiFunction accumulator, BinaryOperator combiner); //代码示例 //Optional可能为空 Optional //默认给了起始值,所以返回integer Integer reduce = Stream.of(1, 2, 3) .reduce(0, (acc, element) -> acc + element); |
Optional
Optional 是为核心类库新设计的一个数据类型,用来替换 null 值。人们常常使用 null 值表示值不存在, Optional 对象能更好地表达这个概念。使用 null 代表值不存在的最大问题在于 NullPointerException 。一旦引用一个存储 null 值的变量,程序会立即崩溃。使用 Optional 对象有两个目的:
首先, Optional 对象鼓励程序员适时检查变量是否为空,以避免代码缺陷;
其次,它将一个类的 API 中可能为空的值文档化,这比阅读实现代码要简单很多。
//创建一个字符”a”的optional Optional //创建一个空的 Optional 对象,并检查其是否有值 Optional emptyOptional = Optional.empty(); Optional alsoEmpty = Optional.ofNullable(null); //使用 orElse 和 orElseGet 方法 emptyOptional.orElse("b");
emptyOptional.orElseGet(() -> "c"); |
方法引用
数据并行化
参考文献
- 深入理解Java函数式编程https://www.cnblogs.com/CarpenterLee/p/6729368.html
- 《Java 8函数式编程 [英]沃伯顿》https://www.amazon.cn/Java-8%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B-%E8%8B%B1-%E6%B2%83%E4%BC%AF%E9%A1%BF/dp/B00VDSW7AE
- 深入理解Java函数式编程https://www.cnblogs.com/CarpenterLee/p/6729368.html
- 《Java 8函数式编程 [英]沃伯顿》https://www.amazon.cn/Java-8%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B-%E8%8B%B1-%E6%B2%83%E4%BC%AF%E9%A1%BF/dp/B00VDSW7AE



