为了账号安全,请及时绑定邮箱和手机立即绑定

【Java8】Java8实战之行为参数化与Lambda

标签:
Java

Java8实战之行为参数化与Lambda

前言

现在Java的迭代速度比以前快了很多,然而,本渣渣最近才开始学习Java8,相比于之前的版本,Java8中引入了许多新的特性,其中最主要的是LambdaStreamOptional<T>、新的时间日期API,接下来将分成几个小节,分别学习这些内容,并且将学习笔记整理出来,参考书籍为《Java8实战》。

本小节主要学习行为参数化以及Lambda

行为参数化

在Java8之前,Java中的方法或者说函数是二等公民,也就是说,方法或者函数是无法作为参数进行传递的,随着编程思想的逐步发展,函数式编程的思想越来越受到重视,其优势也逐渐凸显出来,Java8引入了对应的解决方法,提供了一种类似的方式来处理,使得可以将函数作为参数进行传递,从而实现了类似函数式编程的功能,亦即Lamda。

所谓的形式参数化,就是将行为,通常表现为函数或者方法做为参数进行传递,这种编程方式的好处在于,可以将变化的部分抽取出来,形成函数,然后根据情况传递实现的内容,使得整体具有更高的灵活性。

下面的举例内容大致来自于书中的例子,通过该例子,可以看出行为参数化的优势

class Apple {    private int weight;    private String color;    // 省略set、get方法}

对于上面的Apple类,如果我们想根据不同的情况进行筛选,实现方式有多种,一种是根据需要筛选的条件提供对应的筛选方法,如下所示

public List<Apple> filterAppleByWeight(List<Apple> apples) {
        List<Apple> result = new ArrayList<>();        for (Apple apple : apples) {            if (apple.getWeight() > 30) {
                result.add(apple);
            }
        }        return result;
    }public List<Apple> filterAppleByColor(List<Apple> apples) {
    List<Apple> result = new ArrayList<>();    for (Apple apple : apples) {        if (apple.getColor().equals("green")) {
            result.add(apple);
        }
    }    return result;
}

上面的方式看起来没有什么问题,而且非常直观,当需要增加新的过滤条件时,只需要复制一份,然后更改过滤条件就行,但实际上这种方式问题是比较大的

  • 存在着明显的代码冗余,代码冗余就意味着如果需要修改,需要注意的地方会比较多

  • 不利于变化,比如说现在需要过滤weight > 30 && color == "green"的,那么就需要为其再编写对应的处理函数,而改变的仅仅是其中的一行代码。尤其是当属性比较多的时候,各种情况组合起来,需要考虑的情况非常多,代码也会变得非常复杂。

由于上面的方式比较难以应对各种情况,所以,更好地方式是采用类似策略模式的形式,将其中可能经常变化的部分抽取出来放在接口里面,在运行时通过传入不同的实现类来处理

// 过滤器接口interface AppleFilter {    boolean filter(Apple apple);
}// 过滤器实现class GreenAppleFilter implements AppleFilter {    @Override
    public boolean filter(Apple apple) {        return "green".equals(apple.getColor());
    }
}// 过滤苹果操作public List<Apple> static filterApple(List<Apple> apples, AppleFilter appleFilter) {
    List<Apple> result = new ArrayList<>();    for (Apple apple : apples) {        if (appleFilter.filter(apple)) {
            result.add(apple);
        }
    }    return result;
}public void operation() {
    AppleFilter appleFilter = new GreenAppleFilter();
    filterApple(apples, appleFilter);
}

通过上面的方式,可以在需要的时候创建对应的过滤器实现类,然后传递给过滤操作函数即可,通过这种方式,可以避免前面出现的修改问题。

然而,上面的这种方式其实并不优雅,也不方便,由于我们需要根据不同的情况,编写不同的实现类,这虽然方便解耦,但却增大了不必要的工作量。

通过匿名内部类的形式,我们就可以在不编写对应实现类的情况下,直接将对应的实现传递给对应的方法,所以,上面的代码可以直接简化为

public void operation() {        // 匿名内部类实现
    filterApple(apples, new AppleFilter() {        @Override
        public boolean filter(Apple apple) {            return "green".equals(apple.getColor());
        }
    });
}

这样就可以不用编写实现类,然后再来实现对应的操作了,这种方式已经是非常优雅了,不过,仍然不是最优雅的方式,因为我们还需要new一个对象,然后实现其方法。

上面的实现方式是在Java8之前的方式,在Java8之后,通过Lambda,我们可以将上面的方式更佳地简化,最终代码如下所示

public void operation() {
    filterApple(apples, apple -> "green".equals(apple.getColor()));
}

可以看到,Lambda表达式以及其简洁的方式来实现我们的需求,并且满足了对改动等的要求,所以,下面我们就详细地学习Lambda表达式。

Lambda表达式

Lambda表达式是在Java8之后引入的一种新的代码编写方式,通过Lambda表达式,可以在某种程度上使得代码变得更加简洁,尤其是在某个函数只需要使用一次的时候,无需再为他们设计专门的类以及方法。

Lambda表达式可以简单地理解匿名函数,所以Lambda表达式具有函数除了名字之外的的所有特性

  • 可以有一个或者多个参数

  • 可以具有返回值也可以没有

  • 具有方法体,也就是Lambda所要执行的内容

将Lambda表达式理解为匿名函数之后,关于Lambda表达式的写法就比较好理解了,Lambda具有两种最基本的写法

// 表达式() -> EXP// 语句() -> {STATEMENT1; STATEMENT2; ...}

其中的->用于分隔参数以及方法体,是必须具备的,如果只有一个参数,()可以不用,如apple -> {}(apple) -> {}是等价的,而且,在Lambda表达式参数中,可以不用写上类型,原因在于lambda可以根据使用的场景进行推导,后面展开。

注意上面的两种形式,其中的表达式没有{},并且不需要;只能有一个表达式(表达式的返回值就是Lambda的值),而对于语句来讲可以有多个表达式,每个表达式后面需要有;,最后一个表达式作为Lambda表达式的返回值。

函数接口

上面我们看到了Lambda的写法,接下来我们来看下Lambda表达式的应用场景,并不是说每个Lambda表达式都可以作为参数传递到任意的方法中,Lambda表达式需要符合某种规则(参数,返回值),而这些规则,就来自于函数接口。

函数接口是一种特别的接口,这种接口是专门为Lambda表达式创建的,其特性表现为

  • 只能有一个抽象方法(可以有多个默认方法,关于默认方法,将在后面的小节学习到)

  • 可以使用@FunctionalInterface,进行修饰

比如上面的AppleFilter就是一个函数接口,而下面的接口就不是了,因为有不止一个抽象方法

interface AppleFilter {    boolean filter(Apple apple);    boolean match(Apple apple);
}

在传递Lambda表达式的时候,在方法的参数中,需要提供一个函数接口,才能将Lambda表达式传递给该方法,并且该Lambda表达式必须与函数接口中的抽象方法签名一致,可以简单地理解为该Lambda表达式就是该接口的一个实现。

public void operation() {
    filterApple(apples, apple -> "green".equals(apple.getColor()));    // 其中的Lambda表达式 apple -> "green".equals(apple.getColor());
    // 跟接口中的抽象方法签名 boolean filter(Apple apple); 是一致的
    // 一个参数,一个Boolean类型的返回值}

同时我们也提到了Lambda表达式参数可以不用写类型,可以根据情况进行推导,其原因也在于此,当我们把一个Lambda表达式传递给一个方法的时候,由于该方法中的函数接口中的匿名方法签名已经是确定的了,比如上面的boolean filter(Apple apple),所以,当使用Lambda表达式 apple -> "green".equals(apple.getColor())的时候,编译器可以根据方法签名中的方法类型来推导出Lambda表达式中的参数签名,进而对后面的内容进行验证,参数个数以及返回值也是如此,而且可以验证整个Lambda表示式是否是符合对应抽象方法签名的。

这里有个地方需要注意,同一个Lambda表示式可以用在不同的函数接口的,只需要符合其方法签名即可,同时,一个Lambda表达式可以直接赋值给其对应的函数接口对象

interface AppleFilter {    boolean filter(Apple apple);
}interface Predict {    boolean test(Apple apple);
}// 上面的Lambda表达式可以用于这两个接口// 如果结合泛型的话,则同一个Lambda表达式可以使用的场景就更多了

由于如果每次需要,都要设计对应的函数接口,也是一件非常麻烦的事情,所以,在JDK中提供了基本上涵盖了我们所需要的函数接口了,可以根据需要使用即可,其中常用的几个如下所示

public interface Consumer<T> {    void accept(T t);
}public interface Function<T, R> {    R apply(T t);
}public interface Supplier<T> {    T get();
}public interface Predicate<T> {    boolean test(T t);
}// 更多的函数接口可以参考 package java.util.function



作者:颜洛滨
链接:https://www.jianshu.com/p/3db415bb6751


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消