最近在二刷《Java 8 in action》,主要是想记录一些东西,Just很简单的东西。
我觉得Java8引入的Lambda表达式和默认方法是对程序员来说影响比较大的特性。当然还有流~~
匿名函数
引入匿名函数-Lambda主要是为了行为参数化,它可以帮助我们处理频繁变更的需求,我们来看一个需求,然后再引出Lambda表达式。
问题:我们需要从一个列表里筛选出绿苹果
先定义Apple这个对象
class Apple {
String color;
double weight;
// 省略getset方法
}
这个很简单,我们根据color进行判断就可以了
- 第一次尝试
public static List<Apple> filterGreenApple(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
但是如果要进行根据其他颜色进行筛选呢?这个也很容易,把要筛选颜色作为参数就行,我们得到了这个方法
- 第二次尝试
public static List<Apple> filterApple(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor())) {
result.add(apple);
}
}
return result;
}
但是如果我们需要筛选重量大于150g的苹果呢?我们是不是也要重写一个方法,有了上次的经验,我们直接把weight作为了参数就增加了一个新的方法。
public static List<Apple> filterWeightApple(List<Apple> inventory, double weight) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
但是我们发现我们代码很多重复,里面只有if条件是不一样的,难道不能重用其他代码吗
- 第三次尝试
public static List<Apple> filterApple(List<Apple> inventory, String color, double weight, boolean flag) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ( flag && color.equals(apple.getColor())
|| !flag && apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}
这次我们通过flag来判断,如果flag为true,那就根据颜色进行判断,否则使用重量筛选。这里好像不错了。但是到了客户端,他们调用的时候就头大了,这么多参数,我只是想要个绿苹果,,,而且,如果还需要增加形状,产地等筛选条件呢。
其实我们仔细观察发现,我们需要if的判断,有没有方法传递这个判断条件呢?
当然有,增加接口就行,和策略模式一样,根据传入不同的策略进行不同的筛选。
我们定义一个接口,我们称它为谓词(即返回一个boolean的函数)
public interface ApplePredicate {
boolean test (Apple apple);
}
然后我们可以给不同的条件添加实现了
class AppleWeightPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return apple.getWeight() > 150;
}
}
class AppleColorPredicate implements ApplePredicate {
public boolean test(Apple apple) {
return "green".equals(apple.getColor());
}
}
- 第四次尝试
public static List<Apple> filterApple(List<Apple> inventory, ApplePredicate p) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ( p.test(apple)) {
result.add(apple);
}
}
return result;
}
使用很简单
List<Apple> greenApple = filterApple(inventory, new AppleColorPredicate());
这里我们就没有了重复代码了,而且增加筛选条件也方便很多了,而且还用到了策略模式。但是还是有点啰嗦,为了写个条件,还要增加一个类,但是java不是有匿名类么,我们可以这样写呀!
- 第五次尝试
List<Apple> redApple = filterApple(inventory, new ApplePredicate(){
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});
这样我们可以不用新建类了,不过匿名类的局部变量经常 会让人混淆,还有this的使用等。
如果我们使用Java8,智能一点的IDE就会提示我们,刚才的代码可以替换成Lambda表达式。那么如果使用lambda表达式该怎么写呢
- 第六次尝试
List<Apple> redApple = filterApple(inventory, apple -> "red".equals(apple.getColor()));
这样子,很简单。
当然我们不能局限于筛选苹果,应该做一个通用的过滤器。改写一下
- 第七次尝试
interface Predicate<T> {
boolean test (T t);
}
public static<T> List<T> filter(List<T> inventory, Predicate<T> p) {
List<T> result = new ArrayList<>();
for (T t : inventory) {
if ( p.test(t)) {
result.add(t);
}
}
return result;
}
这样我们使用泛型做了一个通用的筛选器。
刚才我们已经使用了lambda表达式了,它的特点有 匿名,函数,传递,简洁。
Lambda表达式由三部分组成
1、参数
2、箭头
3、lambda主体
例如下面两个都是合法的:
a, b -> a+b
a, b -> {return a+b;}
- 函数式接口
为啥我们刚才的Predicate可以使用Lambda表达式呢,因为这是个函数式接口。
函数式接口定义且定义了一个抽象方法,可以使用@FunctionalInterface注解声明,但是不是必须的,这个和override类似,不是必须的,但是如果我们定义错误,IDE可以有提示。
在java.util.function包下面已经定义了多个函数式接口。
Predicate
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Function
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
Consumer
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
这里列出了常用的3个,并列出主要方法,还有很多函数式接口在里面,有兴趣可以看一下。
-
常见的列子
定义一个线程
Thread t = new Thread(()-> System.out.println("Hello Jam!"));
苹果按照颜色进行排序
inventory.sort(Comparator.comparing(Apple::getColor));
去掉列表中红色的苹果
inventory.removeIf(apple -> apple.getColor().equals("red"));
注意,使用lambda表达式时,如果使用了外部的变量,那么这个变量时隐式final的,因为局部变量保存在栈上,这样表明它们只能在它们所在的线程上,如果允许修改,那么就会产生线程安全问题。
例如
int a = 1;
new Thread(() -> System.out.println(a++));
上面IDEA会提示Variable used in lambda expression should be final or effectively final
Lambda的好处是行为参数化,可以写得更加简洁并且方便阅读。如果配合流和集合使用,将更加的方便。在行为参数化中,还可以传递方法引用。
欢迎关注我的公众号