本文要讲几个知识点:
- 关于lambda表达式的东西
- 来由
- 匿名内部类访问外部变量
- 什么时候能够使用lambda
- 讲讲几个基础的函数式接口的使用
- Function
- Predicate
- Supplier & Consumer
- 流的使用
- 方法引用
- 流的副作用(side-effects)
- 流的一些有用却有些陌生的操作
本文不再赘述,lambda表达式的语法,网上有很多哦
1. 关于lambda表达式的东西
1.1 来由
我们在JDK8前(只是想说明,lambda是JDK8引入进来的)常常会看到以下代码
1 |
|
上面的内部实现是不是看起来很丑,而lambda让上面的代码看上去更加的紧凑(compactly).将上面的实现,改为lambda表达式
1 |
|
1.2 匿名内部类访问外部变量
先下结论,匿名内部类可以修改外部类的成员变量,但是不能修改外部类的局部变量
1.2.1 修改外部类的成员变量
1 |
|
将以上代码编译一下
class LambdaScopeTest$1 implements MyFunction {
LambdaScopeTest$1(LambdaScopeTest var1) {
this.this$0 = var1;
}
public int add(int var1, int var2) {
this.this$0.x = 3;
return this.this$0.x + var1 + var2;
}
}
我们可以看见,内部类(LambdaScopeTest$1)拿到了外部类(LambdaScopeTest)的引用,只是改变了引用下的属性x, 但是外部类的引用指向的地址并没有改变,这是允许的。
1.2.2 访问局部变量
1 |
|
使用同样的方法我们编译一下
class LambdaScopeTest$1 implements MyFunction {
LambdaScopeTest$1(LambdaScopeTest var1, int var2) {
this.this$0 = var1;
// 将外部的局部变量直接复制一份到内部来
this.val$x = var2;
}
public int add(int var1, int var2) {
return this.val$x + var1 + var2;
}
}
因为,内部的值改变了,外部的值并没有改变,如果该类型是引用类型,其内部类的引用地址改变了,外部的引用地址也不会变的(指向的地址都不同了)。
1 |
|
从编译的代码我们可以看见,对外部的值进行了拷贝,为了不必去考虑外部与内部变量修改后的可见性,将外部的变量定义为了final,在JDK8之后,不加final也行,java默认将变量修饰为 effectively final (相当于隐式的加了一个final在变量前面)。
1.3 什么时候能够使用lambda
当某个接口上使用了 @FunctionalInterface 就可以用lambda了;functional interface其实就是指的,只包含一个抽象方法的接口,可以包含一个或多个的默认方法或静态方法。 只要接口只包含一个需要去实现的方法(不管这个接口是否被@FunctionalInterface 给标识),就可以省略其方法名,使用lambda
2. 讲讲几个基础的函数式接口的使用
这也是JDK8以后引入的新玩意儿,建议配合lambda一起使用。讲四个函数式的接口,Function、Predicate、Consumer、Supplier,其余的类似的函数式接口都是这四个的一个扩展。
2.1 Function
表示接受一个参数并且生成一个结果的一个函数 –jdk
1 |
|
该接口除了apply()方法,还有andThen()’之后’, compose()’之前’,identity(),我们再讲讲identity()这个方法
1 |
|
identity() 相等于f(x) = x, 传入什么值就返回什么值。恒等函数。 更多的可以看看Usage of Function.identity with Examples
2.2 Predicate
表示一个值的断言(布尔值函数) –jdk
1 |
|
Predicate还有and、negate、or和一个静态方法isEqual,这里不再多讲。
2.3 Supplier & Consumer
一个提供者,一个消费者,这两个一起写一个例子
1 |
|
3. 流(stream)的使用
先看看下面的例子, 参考lambdaexpressions
1 |
|
我们可以使用’stream’,来替换以上的lambda操作
1 |
|
流让我想起了“曲水流觞”的感觉,添加一系列的操作,然后得到我们想要的结果。
An operation on a stream produces a result, but does not modify its source.
下面科普一下关于流的操作可以稍稍看一下
1 |
|
3.1 方法引用
若你装了Alibaba Java Coding Guidelines这个插件,上述代码它应该建议你这样写
1 |
|
当使用lambda表达式时,若表达式没有做任何其他事却调用了某个方法,最好使用方法引用,看上去更加清晰
1 |
|
3.2 流的副作用(side-effects)
我们看看下面的例子
1 |
|
side-effects就是在操作流的同时,还改变了其他外部的状态(除流之外的);更多的了解,可以查看副作用 (计算机科学)
3.3 流的一些有用却有些陌生的操作
- peek
1
2
3
4
5
6
7
8
9
10
11
12
13// peek(); 主要支持调试,访问一个元素就执行peek中的元素 Stream.of("one", "two", "three", "four") .filter(e -> e.length() > 3) .peek(e -> System.out.println("Filtered value: " + e)) .map(String::toUpperCase) .peek(e -> System.out.println("Mapped value: " + e)) .collect(Collectors.toList()).stream(); // output: // Filtered value: three // Mapped value: THREE // Filtered value: four // Mapped value: FOUR
- iterate
1 |
|
- joining
1 |
|
流有很多有用的方法,这篇文章也相当于抛砖引玉,感兴趣的可以去看看Stream以及Collectors其余的方法,并拿来写一些小小的demo。
对自己的现状不满意只有付出更多的努力去改变它
如果有不对的地方或建议,请指出,谢谢啦