在学习 Lambda之前我们得学习下通过行为参数化传递代码,如何理解?举个例子就是
先定义苹果类
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在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了 这个限制。
登录 | 立即注册