12

Java8中的匿名函数(Lambda)

 3 years ago
source link: https://www.okayjam.com/java8%e4%b8%ad%e7%9a%84%e5%8c%bf%e5%90%8d%e5%87%bd%e6%95%b0%ef%bc%88lambda%ef%bc%89/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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表达式由三部分组成

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

欢迎关注我的公众号

只说一点点点点

此项目被张贴在JAVA技术设计模式和标记javalambda匿名函数技术 。书签的 permalink

发表评论 取消回复

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

评论

姓名 *

电子邮件 *

站点

在此浏览器中保存我的名字、电邮和网站。

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK