Java8中的匿名函数(Lambda)

最近在二刷《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的好处是行为参数化,可以写得更加简洁并且方便阅读。如果配合流和集合使用,将更加的方便。在行为参数化中,还可以传递方法引用。

欢迎关注我的公众号

只说一点点点点

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据