原来 Lamda 表达式是这样写的

发表于 3年以前  | 总阅读数:336 次

Lamda 表达式非常方便,在项目中一般在 stream 编程中用的比较多。

List<Student> studentList = gen();
Map<String, Student> map = studentList .stream()
        .collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a));

理解一个 Lamda 表达式就三步:

1. 确认 Lamda 表达式的类型

2. 找到要实现的方法

3. 实现这个方法

就这三步,没其他的了。而每一步,都非常非常简单,以至于我分别展开讲一下,你就懂了。

确认 Lamda 表达式的类型

能用 Lamda 表达式来表示的类型,必须是一个函数式接口,而函数式接口,就是只有一个抽象方法的接口。

我们看下非常熟悉的 Runnable 接口在 JDK 中的样子就明白了。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

这就是一个标准的函数式接口。

因为只有一个抽象方法。而且这个接口上有个注解

@FunctionalInterface

这个仅仅是在编译期帮你检查你这个接口是否符合函数式接口的条件,比如你没有任何抽象方法,或者有多个抽象方法,编译是无法通过的。

// 没有实现任何抽象方法的接口
@FunctionalInterface
public interface MyRunnable {}

// 编译后控制台显示如下信息
Error:(3, 1) java: 
  意外的 @FunctionalInterface 注释
  MyRunnable 不是函数接口
    在 接口 MyRunnable 中找不到抽象方法

再稍稍复杂一点,Java 8 之后接口中是允许使用默认方法和静态方法的,而这些都不算抽象方法,所以也可以加在函数式接口里。

看看你可能不太熟悉又有点似曾相识的一个接口。

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {...}
}

看,只有一个抽象方法,还有一个默认方法(方法体的代码省略了),这个也不影响它是个函数式接口。再看一个更复杂的,多了静态方法,这同样也是个函数式接口,因为它仍然只有一个抽象方法。自行体会吧。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);

    default Predicate<T> and(Predicate<? super T> other) {...}
    default Predicate<T> negate() {...}
    default Predicate<T> or(Predicate<? super T> other) {...}

    static <T> Predicate<T> isEqual(Object targetRef) {...}
    static <T> Predicate<T> not(Predicate<? super T> target) {...}
}

先不用管这些方法都是干嘛的,这些类在 Stream 设计的方法中比比皆是,我们就先记住这么一句话,Lamda 表达式需要的类型为函数式接口,函数式接口里只有一个抽象方法,就够了,以上三个例子都属于函数式接口。

恭喜你,已经学会了 Lamda 表达式最难的部分,就是认识函数式接口。

找到要实现的方法

Lamda 表达式就是实现一个方法,什么方法呢?就是刚刚那些函数式接口中的抽象方法。

那就太简单了,因为函数式接口有且只有一个抽象方法,找到它就行了。我们尝试把刚刚那几个函数式接口的抽象方法找到。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    default Consumer<T> andThen(Consumer<? super T> after) {...}
}

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {...}
    default Predicate<T> negate() {...}
    default Predicate<T> or(Predicate<? super T> other) {...}
    static <T> Predicate<T> isEqual(Object targetRef) {...}
    static <T> Predicate<T> not(Predicate<? super T> target) {...}
}

好了,这就找到了,简单吧!

实现这个方法

Lamda 表达式就是要实现这个抽象方法,如果不用 Lamda 表达式,你一定知道用匿名类如何去实现吧?比如我们实现刚刚 Predicate 接口的匿名类。

Predicate<String> predicate = new Predicate<String>() {
    @Override
    public boolean test(String s) {
        return s.length() != 0;
    }
};

那如果换成 Lamda 表达式呢?就像这样。

Predicate<String> predicate = 
    (String s) -> {
        return s.length() != 0;
    };

看出来了么?这个 Lamda 语法由三部分组成:

参数块:就是前面的 (String s),就是简单地把要实现的抽象方法的参数原封不动写在这。

小箭头:就是 -> 这个符号。

代码块:就是要实现的方法原封不动写在这。

不过这样的写法你一定不熟悉,连 idea 都不会帮我们简化成这个奇奇怪怪的样子,别急,我要变形了!其实是对其进行格式上的简化。

首先看参数快部分,(String s) 里面的类型信息是多余的,因为完全可以由编译器推导,去掉它。

Predicate<String> predicate = 
    (s) -> {
        return s.length() != 0;
    };

当只有一个参数时,括号也可以去掉。

Predicate<String> predicate = 
    s -> {
        return s.length() != 0;
    };

再看代码块部分,方法体中只有一行代码,可以把花括号和 return 关键字都去掉。

Predicate<String> p = s -> s.length() != 0;

这样看是不是就熟悉点了?

来,再让我们实现一个 Runnable 接口。

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Runnable r = () -> System.out.println("I am running");

你看,这个方法没有入参,所以前面括号里的参数就没有了,这种情况下括号就不能省略。

通常我们快速新建一个线程并启动时,是不是像如下的写法,熟悉吧?

new Thread(() -> System.out.println("I am running")).start();

多个入参

之前我们只尝试了一个入参,接下来我们看看多个入参的。

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
    // default methods removed
}

然后看看一个用法,是不是一目了然。

BiConsumer<Random, Integer> randomNumberPrinter = 
        (random, number) -> {
            for (int i = 0; i < number; i++) {
                System.out.println("next random = " + random.nextInt());
            }
        };

randomNumberPrinter.accept(new Random(314L), 5));

刚刚只是多个入参,那我们再加个返回值。

@FunctionalInterface
public interface BiFunction<T, U, R> {
    R apply(T t, U u);
    // default methods removed
}

// 看个例子
BiFunction<String, String, Integer> findWordInSentence = 
    (word, sentence) -> sentence.indexOf(word);

发现规律了没

OK,看了这么多例子,不知道你发现规律了没?

其实函数式接口里那个抽象方法,无非就是入参的个数,以及返回值的类型。入参的个数可以是一个或者两个,返回值可以是 void,或者 boolean,或者一个类型。那这些种情况的排列组合,就是 JDK 给我们提供的java.util.function包下的类。

BiConsumer

BiFunction

BinaryOperator

BiPredicate

BooleanSupplier

Consumer

DoubleBinaryOperator

DoubleConsumer

DoubleFunction

DoublePredicate

DoubleSupplier

DoubleToIntFunction

DoubleToLongFunction

DoubleUnaryOperator

Function

IntBinaryOperator

IntConsumer

IntFunction

IntPredicate

IntSupplier

IntToDoubleFunction

IntToLongFunction

IntUnaryOperator

LongBinaryOperator

LongConsumer

LongFunction

LongPredicate

LongSupplier

LongToDoubleFunction

LongToIntFunction

LongUnaryOperator

ObjDoubleConsumer

ObjIntConsumer

ObjLongConsumer

Predicate

Supplier

ToDoubleBiFunction

ToDoubleFunction

ToIntBiFunction

ToIntFunction

ToLongBiFunction

ToLongFunction

UnaryOperator

别看晕了,我们分分类就好了。可以注意到很多类前缀是 Int,Long,Double 之类的,这其实是指定了入参的特定类型,而不再是一个可以由用户自定义的泛型,比如说 DoubleFunction。

@FunctionalInterface
public interface DoubleFunction<R> {
    R apply(double value);
}

这完全可以由更自由的函数式接口 Function 来实现。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

那我们不妨先把这些特定类型的函数式接口去掉(我还偷偷去掉了 XXXOperator 的几个类,因为它们都是继承了别的函数式接口),然后再排排序,看看还剩点啥。

Consumer

Function

Predicate

BiConsumer

BiFunction

BiPredicate

Supplier

哇塞,几乎全没了,接下来就重点看看这些。这里我就只把类和对应的抽象方法列举出来

Consumer void accept(T t) Function R apply(T t) Predicate boolean test(T t)

BiConsumer void accept(T t, U u) BiFunction R apply(T t, U u) BiPredicate boolean test(T t, U u)

Supplier T get() 看出规律了没?上面那几个简单分类就是:

supplier:没有入参,有返回值。 consumer:有入参,无返回值。 predicate:有入参,返回 boolean 值 function:有入参,有返回值

然后带 Bi 前缀的,就是有两个入参,不带的就只有一个如参。OK,这些已经被我们分的一清二楚了,其实就是给我们提供了一个函数的模板,区别仅仅是入参返参个数的排列组合。

用我们常见的 Stream 编程熟悉一下

下面这段代码如果你项目中有用 stream 编程那肯定很熟悉,有一个 Student 的 list,你想把它转换成一个 map,key 是 student 对象的 id,value 就是 student 对象本身。

List<Student> studentList = gen();
Map<String, Student> map = studentList .stream()
        .collect(Collectors.toMap(Student::getId, a -> a, (a, b) -> a));

把 Lamda 表达式的部分提取出来。

Collectors.toMap(Student::getId, a -> a, (a, b) -> a)

由于我们还没见过 :: 这种形式,先打回原样,这里只是让你预热一下。

Collectors.toMap(a -> a.getId(), a -> a, (a, b) -> a)

为什么它被写成这个样子呢?我们看下 Collectors.toMap 这个方法的定义就明白了。

public static <T, K, U> Collector<T, ?, Map<K,U>> toMap(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends U> valueMapper,
        BinaryOperator<U> mergeFunction) 
{
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

看,入参有三个,分别是Function,Function,BinaryOperator,其中 BinaryOperator 只是继承了 BiFunction 并扩展了几个方法,我们没有用到,所以不妨就把它当做BiFunction

还记得 Function 和 BiFunction 吧?

Function R apply(T t) BiFunction R apply(T t, U u) 那就很容易理解了。

第一个参数a -> a.getId()就是 R apply(T t) 的实现,入参是 Student 类型的对象 a,返回 a.getId()

第二个参数a -> a也是 R apply(T t) 的实现,入参是 Student 类型的 a,返回 a 本身

第三个参数(a, b) -> a是 R apply(T t, U u) 的实现,入参是Student 类型的 a 和 b,返回是第一个入参 a,Stream 里把它用作当两个对象 a 和 b 的 key 相同时,value 就取第一个元素 a

其中第二个参数 a -> a 在 Stream 里表示从 list 转为 map 时的 value 值,就用原来的对象自己,你肯定还见过这样的写法。

Collectors.toMap(a -> a.getId(), Function.identity(), (a, b) -> a)

为什么可以这样写,给你看 Function 类的全貌你就明白了。

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t); 
    ...
    static <T> Function<T, T> identity() {
        return t -> t;
    }
}

看到了吧,identity 这个方法,就是帮我们把表达式给实现了,就不用我们自己写了,其实就是包了个方法。这回知道一个函数式接口,为什么有好多还要包含一堆默认方法和静态方法了吧?就是干这个事用的。

我们再来试一个,Predicate 里面有这样一个默认方法。

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
}

它能干嘛用呢?我来告诉你,如果没有这个方法,有一段代码你可能会这样写。

Predicate<String> p = 
    s -> (s != null) && 
    !s.isEmpty() && 
    s.length() < 5;

如果利用上这个方法,就可以变成如下这种优雅形式。

Predicate<String> nonNull = s -> s != null;
Predicate<String> nonEmpty = s -> s.isEmpty();
Predicate<String> shorterThan5 = s -> s.length() < 5;

Predicate<String> p = nonNull.and(nonEmpty).and(shorterThan5);

自行体会吧。

方法引用

那我们回过头再看刚刚的 Student::getId 这种写法。当方法体中只有一个方法调用时,就可以作这样的简化。

比如这个 a -> a.getId() 就只是对 Student 对象上 getId() 这个方法的调用,那么就可以写成 Student::getId 这种形式。

再看几个例子

Function<String, Integer> toLength = s -> s.length();
Function<String, Integer> toLength = String::length;

Function<User, String> getName = user -> user.getName();
Function<String, Integer> toLength = User::getName;

Consumer<String> printer = s -> System.out.println(s);
Consumer<String> printer = System.out::println;

如果是构造方法的话,也可以简化。

Supplier<List<String>> newListOfStrings = () -> new ArrayList<>();
Supplier<List<String>> newListOfStrings = ArrayList::new;

总结

学会理解和写 Lamda 表达式,别忘了最开始的三步。

1. 确认 Lamda 表达式的类型

2. 找到要实现的方法

3. 实现这个方法

Lamda 表达式的类型就是函数式接口,要实现的方法就是函数式接口里那个唯一的抽象方法,实现这个方法的方式就是参数块 + 小箭头 + 方法体,其中参数块和方法体都可以一定程度上简化它的写法。

是不是很简单了!

以上代码例子,都来源于官方的教程,英语好的同学可以看看,是最科学的 Lamda 表达式教程了。

https://dev.java/learn/tutorial/getting-to-know-the-language/lambda-expressions/lambdas.html

的今天的文章主要就是讲怎么写出 Lamda 表达式,至于原理,之后再说。这里提个引子,你觉得 Lamda 表达式是匿名类的简化么?按照官方的说法,Lamda 表达式在某些情况下就是匿名类的一种更简单的写法,但是从字节码层面看,完全不同,这又是怎么回事呢?

带着这个问题,给自己埋个坑,我们下讲说说 Lamda 表达式背后的实现原理。

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/IBTq0EspL5tlLupPDwcbEQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237278次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8114次阅读
 目录