使用 Lambda 表达式的正确姿势,写得太好了叭(1)

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<TU> {

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<TUR> {

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 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 studentList = gen();

Map<String, Student> map = studentList .stream()

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

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

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 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 Function<T, T> identity() {

return t -> t;

}

}

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

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

@FunctionalInterface

public interface Predicate {

boolean test(T t);

default Predicate and(Predicate<? super T> other) {

Objects.requireNonNull(other);

return (t) -> test(t) && other.test(t);

}

}

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

Predicate p =

s -> (s != null) &&

!s.isEmpty() &&

s.length() < 5;

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

Predicate nonNull = s -> s != null;

Predicate nonEmpty = s -> s.isEmpty();

Predicate shorterThan5 = s -> s.length() < 5;

Predicate 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 printer = s -> System.out.println(s);

Consumer printer = System.out::println;

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

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

Supplier<List> newListOfStrings = ArrayList::new;

总结

==

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

1. 确认 Lambda 表达式的类型

2. 找到要实现的方法

3. 实现这个方法

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

是不是很简单了!
以上代码例子,都来源于官方的教程,英语好的同学可以看看,是最科学的 Lambda 表达式教程了。
https://dev.java/learn/tutorial/getting-to-know-the-language/lambda-expressions/lambdas.html
如果觉得本文对你有帮助,就点赞关注支持一下吧!

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。

以上就是蚂蚁技术四面和HR面试题目,以下最新总结的最全,范围包含最全MySQL、Spring、Redis、JVM等最全面试题和答案,仅用于参考

一份还热乎的蚂蚁金服面经(已拿Offer)面试流程4轮技术面+1轮HR

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
1713522317070)]

[外链图片转存中…(img-e0RGYpN6-1713522317070)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

总结

面试建议是,一定要自信,敢于表达,面试的时候我们对知识的掌握有时候很难面面俱到,把自己的思路说出来,而不是直接告诉面试官自己不懂,这也是可以加分的。

以上就是蚂蚁技术四面和HR面试题目,以下最新总结的最全,范围包含最全MySQL、Spring、Redis、JVM等最全面试题和答案,仅用于参考

[外链图片转存中…(img-i1RVvBkm-1713522317071)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值