Java8函数式编程深入理解
什么是匿名内部类?
无需知道类实现名,在内部实现接口方法,做类的定义。
Lambda表达式与匿名内部类的区别?
- 匿名内部类仍然是一个类,只是不需要程序员显示指定类名,编译器会自动为该类取名。
- Lambda表达式通过invokedynamic指令实现,书写Lambda表达式不会产生新的类。
- 简化匿名内部类的书写,取代部分匿名内部类,只能用来取代函数接口(Functional Interface)的简写。
Lambda表达式
Lambda表达式:参数 箭头(->)表达式
好处:简化匿名内部类写法,去掉样板代码。
原本设计匿名内部类的目的,就是为了方便 Java 程序员将代码作为数据传递。不过,匿名内部类还是不够简便。为了调用一行重要的逻辑代码,不得不加上 4 行冗繁的样板代码。若使用Lambda写法,就能直接用一行代码替代4行,去掉样板代码,只留下具体逻辑代码。
正常写法Runable
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};
Lambda写法
Runnable runnable = ()->System.out.println("run");
|
Lambda的几种写法
简写的依据
能够使用Lambda的依据是必须有相应的函数接口(函数接口,是指内部只有一个抽象方法的接口。这一点跟Java是强类型语言吻合,也就是说你并不能在代码的任何地方任性的写Lambda表达式。实际上Lambda的类型就是对应函数接口的类型。Lambda表达式另一个依据是类型推断机制,在上下文信息足够的情况下,编译器可以推断出参数表的类型,而不需要显式指名。
无参函数写法
使用空括号()表示无参数,使用Lambda表达式实现Runable接口,该接口只有一个run方法,没有参数,返回类型为void。
/*Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("run");
}
};*/
Runnable runnable = ()->System.out.println("run");
|
带参函数写法
使用括号(参数,参数)表示有多个参数,当只有一个参数时可省略括号。
/*BinaryOperator<Long> add = new BinaryOperator<Long>() {
@Override
public Long apply(Long x, Long y) {
return x + y;
}
};*/
BinaryOperator<Long> add = (x, y) -> x + y;
/*ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked");
}
};*/
ActionListener listener = event -> System.out.println("button clicked");
|
复杂逻辑写法
若不止一行代码,需要加上大括号。(参数)->{代码逻辑}
/*ActionListener actionListener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked");
System.out.println("notice other");
}
};*/
ActionListener actionListener = event -> {
System.out.println("button clicked");
System.out.println("notice other");
};
|
注:错误写法
在只有一行代码逻辑的时候,若有程序逻辑有返回时,编译器会自动识别返回类型,无需加上return关键字。加上大括号时需要加上return关键字。
BinaryOperator<Long> add = (x, y) -> x + y;
BinaryOperator<Long> add = (x, y) -> return x + y;
BinaryOperator<Long> add = (x, y) -> {
return x + y;
};
|
自定义函数接口
只需要编写一个抽象方法的接口即可。@FunctionalInterface注释是可选的,其目的是为了标注编译器帮你检查接口是否符合函数接口规范,就像@Override标注会检查是否重载了函数一样。注意:函数接口中只允许一个抽象方法的接口,若出现多个的情况下,编译器无法根据上下文进行推算当前接口。
@FunctionalInterface
public interface Consumer<T> {
|
错误定义:
报错:Operator cannot be applied to lambda parameter
public interface TestComsumer<T> {
void accept(T t);
void apply(T x,T y);
}
|
|
类型推断
程序员可省略Lambda表达式中所有的参数类型,javac 根据 Lambda 表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断。
使用 Lambda 表达式示例1检测一个 Integer 是否大于 5,示例2检测一个String是否等于”x” 。这实际上是一个 Predicate ——用来判断真假的函数接口。
类型推断:
Predicate<Integer> predicate = x -> x > 5;
Predicate<String> predicate = x -> x.equals("x");
|
Predicate源码:接受一个对象,返回一个布尔值
@FunctionalInterface
public interface Predicate<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() skip() sorted() parallel() sequential() unordered() |
及早求值 | allMatch() anyMatch() collect() count() findAny() findFirst() forEach() forEachOrdered() max() min() noneMatch() reduce() toArray() |
惰性求值方法:只描述stream流,不产生新的集合。
及早求值方法:从stream流中产生值。
是否是惰性求值还是及早求值有一个简单的判断依据,当返回的值是stream流,那么就是惰性求值,若返回值是具体的值或空,那么就是及早求值。
注:只执行惰性求值方法,而不使用及早求值方法是不会真正的执行stream流。惰性求值可以比作“我要做“,及早求值比作”我要得到“。当”我要做“的时候,代码只是将代码执行流程都准备好,只有执行”我要得到“指令时,才会去执行”我要做“指令。
惰性求值
concat
distinct
distinct可以进行去重操作。
Stream<String> stream= Stream.of("I", "love", "you", "too", "too");
stream.distinct()
.forEach(str -> System.out.println(str));
|
filter
filter是将一种Stream流中过滤中需要的新Stream流。
Stream<T> filter(Predicate<? super T> predicate);
//代码示例 将oldList中大于3的元素转换成newList
List<Integer> oldList = Arrays.asList(1,2,3,4,5);
/*List<Integer> newList = new ArrayList<Integer>();
for(Integer integer:oldList){
if(integer>3){
newList.add(integer);
}
}*/
List<Integer> newList = oldList.stream()
.filter(x -> x > 3).collect(toList());
|
flatMap
flatMap 方法可用 Stream 替换值,然后将多个 Stream 连接成一个 Stream。
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
List<Integer> list1 = asList(1, 2);
List<Integer> list2 = asList(3, 4);
Stream<List<Integer>> listStream = Stream.of(list1, list2);
List<Integer> together = listStream
.flatMap(numbers -> numbers.stream())
.collect(toList());
|
limit
map
map是将一种Stream流转换成另外一种Stream流。
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//示例代码将Integer集合转换成String集合
List<String> list = Stream.of(1, 2, 3)
.map(x -> String.valueOf(x)).collect(toList());
|
peek
skip
sorted
sorted有两种排序,一种是自然顺序排序,一种是自定义比较器排序。
Stream<T> sorted(Comparator<? super T> comparator);
Stream<String> stream= Stream.of("I", "love", "you", "too");
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().
<R, A> R collect(Collector<? super T, A, R> collector);
List<String> list = Stream.of("a", "b" , "c")
.collect(Collectors.toList());
Map<String, String> map = Stream.of("a", "b", "c")
.collect(Collectors.toMap(str -> str, Function.identity()));
// Function.identity()表示返回自身,等同str -> str à源码
static <T> Function<T, T> identity() {
return t -> t;
}
Set<String> set = Stream.of("a", "b", "c").collect(Collectors.toSet());
|
forEach
forEach是一个遍历集合的接口方法,作用是对容器中每个元素执行action指定动作,也就是对元素进行遍历。
void forEach(Consumer<? super T> action)
|
Stream<String> stream = Stream.of("I", "love", "you", "too");
/*stream.forEach(new Consumer<String>() {
@Override
public void accept(String str) {
System.out.println(str);
}
});*/
stream.forEach(str -> System.out.println(str));
|
max和min
max和min是查找Stream流中按照Comparator对象对比出来的max与min对象值。
Optional<T> max(Comparator<? super T> comparator);
String maxStr = Stream.of("I", "love", "you", "too")
.max(Comparator.comparing(x -> x.length())).get();
|
reduce
reduce 可以实现从一组值中生成一个值。reduce可以做count , max , min等操作。
T reduce(T identity, BinaryOperator<T> accumulator);
Optional<T> reduce(BinaryOperator<T> accumulator);
<U> U reduce(U identity,
BiFunction<U, ? super T, U> accumulator,
BinaryOperator<U> combiner);
Optional<Integer> reduce = Stream.of(1, 2, 3)
.reduce((acc, element) -> acc + element);
//默认给了起始值,所以返回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 中可能为空的值文档化,这比阅读实现代码要简单很多。
Optional<String> optional = Optional.of("a");
//创建一个空的 Optional 对象,并检查其是否有值
Optional emptyOptional = Optional.empty();
Optional alsoEmpty = Optional.ofNullable(null);
//使用 orElse 和 orElseGet 方法
emptyOptional.orElse("b");
emptyOptional.orElseGet(() -> "c");
|
方法引用
数据并行化
参考文献