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

}

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

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

找到要实现的方法

========

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

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

@FunctionalInterface

public interface Runnable {

public abstract void run();

}

@FunctionalInterface

public interface Consumer<T> {

void accept(T t);

default Consumer andThen(Consumer<? super T> after) {…}

}

@FunctionalInterface

public interface Predicate<T> {

boolean test(T t);

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

default Predicate negate() {…}

default Predicate or(Predicate<? super T> other) {…}

static  Predicate isEqual(Object targetRef) {…}

static  Predicate not(Predicate<? super T> target) {…}

}

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

实现这个方法

======

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

Predicate predicate = new Predicate() {

@Override

public boolean test(String s) {

return s.length() != 0;

}

};

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

Predicate predicate =

(String s) -> {

return s.length() != 0;

};

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

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

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

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

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

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

Predicate predicate =

(s) -> {

return s.length() != 0;

};

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

Predicate predicate =

s -> {

return s.length() != 0;

};

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

Predicate 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<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 值,就用原来的对象自己,你肯定还见过这样的写法。

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

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

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

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

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

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

img

最后

一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。

这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。

image

请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
tps://img-community.csdnimg.cn/images/e5c14a7895254671a72faed303032d36.jpg" alt=“img” style=“zoom: 33%;” />

最后

一次偶然,从朋友那里得到一份“java高分面试指南”,里面涵盖了25个分类的面试题以及详细的解析:JavaOOP、Java集合/泛型、Java中的IO与NIO、Java反射、Java序列化、Java注解、多线程&并发、JVM、Mysql、Redis、Memcached、MongoDB、Spring、Spring Boot、Spring Cloud、RabbitMQ、Dubbo 、MyBatis 、ZooKeeper 、数据结构、算法、Elasticsearch 、Kafka 、微服务、Linux。

这不,马上就要到招聘季了,很多朋友又开始准备“金三银四”的春招啦,那我想这份“java高分面试指南”应该起到不小的作用,所以今天想给大家分享一下。

[外链图片转存中…(img-mIZOy9Mn-1713522349987)]

请注意:关于这份“java高分面试指南”,每一个方向专题(25个)的题目这里几乎都会列举,在不看答案的情况下,大家可以自行测试一下水平 且由于篇幅原因,这边无法展示所有完整的答案解析
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值