在学习 Lambda之前我们得学习下通过行为参数化传递代码,如何理解?举个例子就是,在我们进行音乐程序开发的时候,有时候需求是不停的增加和修盖的,如“我们需要寻找某个苹果仓库里面的红苹果”,又变成“需要寻找重量大于300g 的苹果”等等,这个时候我们如果按照之前的思路来就会变得臃肿以及后续难易维护。我们上干货。
好吧,先开始第一个需求,我们要寻找红苹果,不要绿苹果
先定义苹果类
public class Apple {
private Integer weight;
private String color;
public Integer getWeight() {
return weight;
}
public void setWeight(Integer weight) {
this.weight = weight;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Apple(Integer weight, String color) {
super();
this.weight = weight;
this.color = color;
}
public Apple() {
// TODO Auto-generated constructor stub
}
@Override
public String toString() {
return "Apple [weight=" + weight + ", color=" + color + "]";
}
}
之后我们轻而易举的写下寻找苹果的代码
public class Demo1 {
public static void main(String[] args) {
List <Apple> apples=new ArrayList<Apple>();
apples.add(new Apple(10,"red"));
apples.add(new Apple(15,"red"));
apples.add(new Apple(8,"green"));
List<Apple> filterApples=new ArrayList<Apple>();
for(Apple apple:apples){
if("red".equals(apple.getColor())){
filterApples.add(apple);
}
}
for(Apple apple:filterApples){
System.out.println(apple);
}
}
}
非常简单,那么问题来了,如果这个时候需求变动,要求选择的是红色的并且重量大于10的,那么怎么办。小 case,不就一行代码的事
if("red".equals(apple.getColor()) && apple.getWeight>10){
filterApples.add(apple);
}
不错,的确做出来了,这个时候是不是心里有点虚,那么又有新的变动咋办,每次修改类重新部署这开销有点大啊。这个时候,我们想到了设计模式的对修改关闭对新增开发的开闭原则。
好吧,既然想到了就开始实现吧,先构建一个接口来做所有苹果的判断
public interface ApplePredicate {
public boolean test(Apple apple);
}
接着把需要实现都写出来,这样不管有什么新的需求就都可以直接新增,这里先把颜色和重量的实现下
public class RedApplePredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
}
public class WeightApplePredicate implements ApplePredicate{
@Override
public boolean test(Apple apple) {
return apple.getWeight()>10;
}
}
接下来写个方法,来进行判断
public static List<Apple> filterApples(List<Apple> apples,ApplePredicate predicate){
List<Apple> filterApples=new ArrayList<Apple>();
for(Apple apple:apples){
if(predicate.test(apple)){
filterApples.add(apple);
}
}
return filterApples;
}
到此为止,我们很好的把变动的部分单独抽取出来,我们的应用如下
public static void main(String[] args) {
List <Apple> apples=new ArrayList<Apple>();
apples.add(new Apple(10,"red"));
apples.add(new Apple(15,"red"));
apples.add(new Apple(8,"green"));
List<Apple> filterApples=filterApples(apples,new WeightApplePredicate());
for(Apple apple:filterApples){
System.out.println(apple);
}
}
想要什么样的 Prdicate 就在代码List<Apple> filterApples=filterApples(apples,new WeightApplePredicate());
处修正自己的Prdicate实现。
更有甚者,可以用匿名类代替new WeightApplePredicate()
的实现。
List<Apple> filterApples=filterApples(apples,new ApplePredicate(){
@Override
public boolean test(Apple apple) {
return apple.getWeight()>10;
}
});
这样看来貌似不错,但是说到现在貌似和 Lambda 一点关系都没有,的确,这才是开始,我们的 Lambda终于要上场了。
就在我们要传递new WeightApplePredicate()
的时候,并且这个类的接口有唯一的方法,那么这个接口可以说是函数式接口
,可以使用标注@FunctionalInterface
在接口上进行标注,这样有错误在编译的时候就能发现。
有了 Lambda 的表达式,我们可以这样写
List<Apple> filterApples=filterApples(apples,(Apple apple)->"red".equals(apple.getColor()));
Lambda 表达式有3块,分别是参数、箭头、主题
形如
(parameters) -> expression
或者(parameters) -> { statements; }
仔细观察他们的区别,不在花括号内的是表达式;花括号里面是声明语句,有返回值需要 return 关键字
参数就是(Apple apple)
箭头就是 ->
主体部分是"red".equals(apple.getColor())
Lambda使用场景:在函数式接口的地方使用
一些JDK内置的函数式接口有如下
Predicate
:java.util.function.Predicate<T>接口定义了一个名叫test的抽象方法,它接受泛型 T对象,并返回一个boolean。
Consumer
:java.util.function.Consumer<T>定义了一个名叫accept的抽象方法,它接受泛型T的对象,没有返回(void)。你如果需要访问类型T的对象,并对其执行某些操作,就可以使用 这个接口。
Function
:java.util.function.Function<T, R>接口定义了一个叫作apply的方法,它接受一个 泛型T的对象,并返回一个泛型R的对象。
Supplier
:Supplier<T>具有唯一一个抽象方法叫作get,代表的函数描述符是()-> T。
Lambda 还可以进行类型推断
比如我们的List<Apple> filterApples1=filterApples(apples,a->"red".equals(a.getColor()));
这里的 a
变量会自己推断类型为 Apple
关于变量,Lambda 里面如果需要接受局部的参数变量,那么该参数必须是 final
的,为什么?
- 第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了 这个限制。
- 第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式.
关于 Lambda 的方法引用见下次了 _
网友评论