彻底搞明白JDK 1.8 Lambda 表达式
发布日期:2021-06-30 16:14:31 浏览次数:2 分类:技术文章

本文共 3008 字,大约阅读时间需要 10 分钟。

Lambda表达式

为什么

背景

Java是面向对象语言,所以java对行为的封装都是基于对象。什么意思,简单说,我们要定义个行为,那么需要一个函数,只有函数就行了吗?在java里面不行,因为java是面向对象的语言,

所以我们需要定义对象,看下面的代码:

public interface ActionListener {void actionPerformed(ActionEvent e);}

最早接触过java swing编程的老程序员肯定不陌生,定义一个按钮触发时间,每当时间发生时,调用这个函数actionPerformed里面逻辑。

其实大家发现没有,这个类ActionListener其实完全没有必要。所以出现了匿名内部类。改进的代码如下:

button.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {ui.dazzle(e.getModifiers());}});

后来我们一直用匿名内部类定义行为,不用去定义对象了。但是仍然有问题,主要存在以下问题:

  1. 语法过于冗余
  2. 匿名类中的 this 和变量名容易使人产生误解
  3. 类型载入和实例创建语义不够灵活
  4. 无法捕获非 final 的局部变量
  5. 无法对控制流进行抽象

随着回调的大量使用和函数式编程的流行,java需要一种便捷的方式匿名内部类。

函数式接口

对于上面的,只有一个抽象方法的接口,我们定义为函数式接口。只有一个方法的接口,在jdk里面很多,比如Runnable,Compartor等。看下面的源码:

package java.lang;@FunctionalInterfacepublic interface Runnable {    /**     * When an object implementing interface Runnable is used     * to create a thread, starting the thread causes the object's     * run method to be called in that separately executing     * thread.     * 

* The general contract of the method run is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run();}

可以通过 @FunctionalInterface 注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。

Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例如:

  • Predicate<T>——接收 T 并返回 boolean
  • Consumer<T>——接收 T,不返回值
  • Function<T, R>——接收 T,返回 R
  • Supplier<T>——提供 T 对象(例如工厂),不接收值
  • UnaryOperator<T>——接收 T 对象,返回 T
  • BinaryOperator<T>——接收两个 T,返回 T

除了上面的这些基本的函数式接口,我们还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如 IntSupplier 和 LongBinaryOperator

函数式接口与lambda表达式

看下面的代码:

//使用com.google.guava包创建集合    List
list =Lists.newArrayList("a","b","c","d"); //1、正常遍历 list.forEach(item->System.out.println(item)); //2、根据条件遍历 list.forEach(item->{ if("b".equals(item)){ System.out.println(item); }

foreach里面利用的就是lamabda表达式,我们看看foreach的源代码:

public interface Iterable
{ Iterator
iterator(); default void forEach(Consumer
action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } } default Spliterator
spliterator() { return Spliterators.spliteratorUnknownSize(iterator(), 0); }

foreach的入参是Consumer类型,再看Consumer是个函数式接口,不需要指定方法,因为函数式接口只有一个方法。

函数式接口可以被隐式地转换为lamadba表达式

总结

下面的图片来自:

这样,我们就成功的非常优雅的把"一块代码"赋给了一个变量。而"这块代码",或者说"这个被赋给一个变量的函数",就是一个Lambda表达式

但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?

在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是"那段代码",需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型:

 我们看一下有返回值的情况,函数式接口返回值,其实是在函数式接口定义的,我们看compartor类:

@FunctionalInterfacepublic interface Comparator
{

使用方式如下:

Comparator
byNameLambda = (Developer developer, Developer compareDeveloper)->developer.getName().compareTo(compareDeveloper.getName());

或者:

Comparator
byNameLambdaSimple = Comparator.comparing(Developer::getName);

 

转载地址:https://kerry.blog.csdn.net/article/details/114027146 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:MySQL在实际应用中的规范(表定义、SQL、索引等)
下一篇:JDK 8 的optional类

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月18日 17时10分31秒