彻底搞明白JDK 1.8 Lambda 表达式
Posted hanruikai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底搞明白JDK 1.8 Lambda 表达式相关的知识,希望对你有一定的参考价值。
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());
);
后来我们一直用匿名内部类定义行为,不用去定义对象了。但是仍然有问题,主要存在以下问题:
- 语法过于冗余
- 匿名类中的
this
和变量名容易使人产生误解 - 类型载入和实例创建语义不够灵活
- 无法捕获非
final
的局部变量 - 无法对控制流进行抽象
随着回调的大量使用和函数式编程的流行,java需要一种便捷的方式匿名内部类。
函数式接口
对于上面的,只有一个抽象方法的接口,我们定义为函数式接口。只有一个方法的接口,在jdk里面很多,比如Runnable,Compartor等。看下面的源码:
package java.lang;
@FunctionalInterface
public interface Runnable
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> 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<String> 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<T>
Iterator<T> iterator();
default void forEach(Consumer<? super T> action)
Objects.requireNonNull(action);
for (T t : this)
action.accept(t);
default Spliterator<T> spliterator()
return Spliterators.spliteratorUnknownSize(iterator(), 0);
foreach的入参是Consumer类型,再看Consumer是个函数式接口,不需要指定方法,因为函数式接口只有一个方法。
函数式接口可以被隐式地转换为lamadba表达式
总结
下面的图片来自:https://www.cnblogs.com/PomeloYe/p/11807059.html
这样,我们就成功的非常优雅的把"一块代码"赋给了一个变量。而"这块代码",或者说"这个被赋给一个变量的函数",就是一个Lambda表达式。
但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?
在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是"那段代码",需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型:
我们看一下有返回值的情况,函数式接口返回值,其实是在函数式接口定义的,我们看compartor类:
@FunctionalInterface
public interface Comparator<T>
使用方式如下:
Comparator<Developer> byNameLambda =
(Developer developer, Developer compareDeveloper)->developer.getName().compareTo(compareDeveloper.getName());
或者:
Comparator<Developer> byNameLambdaSimple = Comparator.comparing(Developer::getName);
以上是关于彻底搞明白JDK 1.8 Lambda 表达式的主要内容,如果未能解决你的问题,请参考以下文章
JDK 1.8 Lambda表达式集合分组条件过滤组装去重排序转换求和最值