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

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

总结

本小节主要学习了行为参数化的作用以及意义,Lambda表达式的写法,函数接口的限制以及作用,常见的几个函数接口。关于Lambda表达式的写法,多练习几次就熟悉了,在Java8中引入的其他几个新功能,基本都是构建在Lambda表示式之上的,所以,目前来说,对Lambda表示式只需要有个概念性的理解即可,后面经过Stream的练习之后,就会熟悉Lambda表达式了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容

  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,231评论 1 2
  • 原文链接: Lambdas 原文作者: shekhargulati 译者: leege100 lambda表达式是...
    忽来阅读 6,587评论 8 129
  • 注:之前关于Java8的认知一直停留在知道有哪些修改和新的API上,对Lambda的认识也是仅仅限于对匿名内部类的...
    mualex阅读 2,823评论 1 4
  • lambda表达式(又被成为“闭包”或“匿名方法”)方法引用和构造方法引用扩展的目标类型和类型推导接口中的默认方法...
    183207efd207阅读 1,483评论 0 5
  • Anna艳娜 2018年5月11日复盘 六点过起床,最后收拾了下东西,早早出门,担心拉着箱子会很挤。上班高峰期,地...
    Anna艳娜阅读 168评论 0 0