来源:http://uee.me/aWWS2

1. 用法

示例:最普遍的一个例子,执行一个线程

new Thread(() -> System.out.print("hello world")).start();

->  我们发现它指向的是 Runnable接口

@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();}

分析

  1. ->这个箭头是lambda表达式的关键操作符

  2. ->把表达式分成两截,前面是函数参数,后面是函数体。

  3. Thread的构造函数接收的是一个Runnable接口对象,而我们这里的用法相当于是把一个函数当做接口对象传递进去了,这点理解很关键,这正是函数式编程的含义所在。

  4. 我们注意到Runnable有个注解 @FunctionalInterface,它是jdk8才引入,它的含义是函数接口。它是lambda表达式的协议注解,这个注解非常重要,后面做源码分析会专门分析它的官方注释,到时候一目了然。

/* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

2. 由此引发的一些案例

有参数有返回值的实例:集合排序

List<String> list = new ArrayList<>();
Collections.sort(list, (o1, o2) -> {if(o1.equals(o2)) {return 1;}return -1;
})

我们知道Collections.sort方法的第二个参数接受的是一个 Comparator<T>的对象,它的部分关键源码是这样的:

@FunctionalInterface
public interface Comparator<T> {int compare(T o1, T o2);
}

如上已经去掉注释和部分其他方法。

我们可以看到sort的第二个参数是Comparator的compare方法,参数类型是T,分别是o1和o2,返回值是一个int。

3. 疑问

  • 上面的示例我们看到接口都有个 @FunctionalInterface的注解,但是我们在实际编程中并没有加这个注解也可以实现lambda表达式,例如:

public class Main {interface ITest {int test(String string);}static void Print(ITest test) {test.test("hello world");}public static void main(String[] args) {Print(string -> {System.out.println(string);return 0;});}
}

如上所示,确实不需要增加 @FunctionInterface注解就可以实现。

  • 如果在1中的示例的ITest接口中增加另外一个接口方法,我们会发现不能再用lambda表达式。

我们带着这两个疑问来进入源码解析。

4. 源码解析

必须了解注解 @FunctionInterface

上源码:

package java.lang;import java.lang.annotation.*;/**
* An informative annotation type used to indicate that an interface
* type declaration is intended to be a <i>functional interface</i> as
* defined by the Java Language Specification.
*
* Conceptually, a functional interface has exactly one abstract
* method.  Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract.  If
* an interface declares an abstract method overriding one of the
* public methods of {@code java.lang.Object}, that also does
* <em>not</em> count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
*
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
*
* <p>If a type is annotated with this annotation type, compilers are
* required to generate an error message unless:
*
* <ul>
* <li> The type is an interface type and not an annotation type, enum, or class.
* <li> The annotated type satisfies the requirements of a functional interface.
* </ul>
*
* <p>However, the compiler will treat any interface meeting the
* definition of a functional interface as a functional interface
* regardless of whether or not a {@code FunctionalInterface}
* annotation is present on the interface declaration.
*
* @jls 4.3.2. The Class Object
* @jls 9.8 Functional Interfaces
* @jls 9.4.3 Interface Method Body
* @since 1.8
*/@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

我们说过这个注解用来规范lambda表达式的使用协议的,那么注释中都说了哪些呢?

  • 一种给interface做注解的注解类型,被定义成java语言规范。

* An informative annotation type used to indicate that an functional interface

* type declaration is intended to be a <b>functional interface</b> as

* defined by the Java Language Specification.

  • 一个被它注解的接口只能有一个抽象方法,有两种例外。

第一是接口允许有实现的方法。

这种实现的方法是用default关键字来标记的

(java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法),例如:

当然这是jdk8才引入的特性,到此我们才知道,知识是一直在变化的,我们在学校中学到interface接口不允许有实现的方法是错误的,随着时间推移,一切规范都有可能发生变化。

如果声明的方法和java.lang.Object中的某个方法一样,它可以不当做未实现的方法,不违背这个原则:一个被它注解的接口只能有一个抽象方法

例如同样是Compartor接口中,它重新声明了equals方法:

这些是对如下注释的翻译和解释。

* Conceptually, a functional interface has exactly one abstract
* method.  Since {@linkplain java.lang.reflect.Method#isDefault()
* default methods} have an implementation, they are not abstract.  If
* an interface declares an abstract method overriding one of the
* public methods of {@code java.lang.Object}, that also does
* <em>not</em> count toward the interface's abstract method count
* since any implementation of the interface will have an
* implementation from {@code java.lang.Object} or elsewhere.
  • 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件:

  1. 这个类型必须是一个interface,而不是其他的注解类型、枚举enum或者类class

  2. 这个类型必须满足function interface的所有要求,如你个包含两个抽象方法的接口增加这个注解,会有编译错误。

* <p>If a type is annotated with this annotation type, compilers are
* required to generate an error message unless:
*
* <ul>
* <li> The type is an interface type and not an annotation type, enum, or class.
* <li> The annotated type satisfies the requirements of a functional interface.
* </ul>
  • 编译器会自动把满足function interface要求的接口自动识别为function interface,所以你才不需要对上面示例中的 ITest接口增加@FunctionInterface注解。

* <p>However, the compiler will treat any interface meeting the
* definition of a functional interface as a functional interface
* regardless of whether or not a {@code FunctionalInterface}
* annotation is present on the interface declaration.

通过了解function interface我们能够知道怎么才能正确的创建一个function interface来做lambda表达式了。接下来的是了解java是怎么把一个函数当做一个对象作为参数使用的。

5. 穿越:对象变身函数

让我们重新复盘一下上面最开始的实例:

new Thread(() -> System.out.print("hello world")).start();

我们知道在jdk8以前我们都是这样来执行的:

Runnable r = new Runnable(){System.out.print("hello world");
};
new Thread(r).start();

我们知道两者是等价的,也就是说 r 等价于 ()->System.out.print("hello world"),一个接口对象等于一个lambda表达式?那么lambda表达式肯定做了这些事情(未看任何资料,纯粹推理,有误再改正):

  1. 创建接口对象

  2. 实现接口对象

  3. 返回接口对象

6. 关于 UnaryOperator

上篇文章(聊一聊JavaFx中的TextFormatter以及一元操作符UnaryOperator)关于 UnaryOperator草草收尾,在这里给大家重新梳理一下,关于它的使用场景以及它与lambda表达式的关系。

  • 使用场景

要先理解它的作用,它是接受一个参数并返回与该类型同的值,来看一个List怎么用它的,java.util.List中的replaceAll就用它了:

 

default void replaceAll(UnaryOperator<E> operator) {Objects.requireNonNull(operator);final ListIterator<E> li = this.listIterator();while (li.hasNext()) {li.set(operator.apply(li.next()));}}

我们可以看到这个方法的目的是把list中的值经过operator操作后重新返回一个新值,例如具体调用。

 

       List<String> list = new ArrayList<>();list.add("abc");list.replaceAll(s -> s + "efg");System.out.println(list);

其中lambda表达式 s->s+"efg"就是这个operator对象,那么最终list中的值就变成了["abcefg"],由此我们可以知道它的作用就是对输入的值再加工,并返回同类型的值,怎么用就需要你自己扩展发挥了。

  • 与lambda表达式的关系?

在我看来,它跟lambda表达式的关系并不大,只是它是jdk内置的一种标准操作,类似的二元操作符 BinaryOperator它可以接受两个同类型参数,并返回同类型参数的值。

7. 关于UnaryOperator,我们百尺竿头更进一步,深入到核心

先贴出它的源码:

@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {/*** Returns a unary operator that always returns its input argument.** @param <T> the type of the input and output of the operator* @return a unary operator that always returns its input argument*/static <T> UnaryOperator<T> identity() {return t -> t;}}

我们看到这个function interface居然没有抽象方法,不,不是没有,我们继续看Function接口。

@FunctionalInterface
public interface Function<T, R> {/*** Applies this function to the given argument.** @param t the function argument* @return the function result*/R apply(T t);/*** Returns a composed function that first applies the {@code before}* function to its input, and then applies this function to the result.* If evaluation of either function throws an exception, it is relayed to* the caller of the composed function.** @param <V> the type of input to the {@code before} function, and to the*           composed function* @param before the function to apply before this function is applied* @return a composed function that first applies the {@code before}* function and then applies this function* @throws NullPointerException if before is null** @see #andThen(Function)*/default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {Objects.requireNonNull(before);return (V v) -> apply(before.apply(v));}/*** Returns a composed function that first applies this function to* its input, and then applies the {@code after} function to the result.* If evaluation of either function throws an exception, it is relayed to* the caller of the composed function.** @param <V> the type of output of the {@code after} function, and of the*           composed function* @param after the function to apply after this function is applied* @return a composed function that first applies this function and then* applies the {@code after} function* @throws NullPointerException if after is null** @see #compose(Function)*/default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {Objects.requireNonNull(after);return (T t) -> after.apply(apply(t));}/*** Returns a function that always returns its input argument.** @param <T> the type of the input and output objects to the function* @return a function that always returns its input argument*/static <T> Function<T, T> identity() {return t -> t;}}

既然他们都被注解为 @FunctionInterface了,那么他们肯定有一个唯一的抽象方法,那就是 apply。

我们知道 ->lambda表达式它是不需要关心函数名字的,所以不管它叫什么, apply也好, apply1也好都可以,但jdk肯定要叫一个更加合理的名字,那么我们知道 s->s+"efg"中 ->调用的就是 apply方法。

而且我们注意到这里有一个 identity()的静态方法,它返回一个Function对象,它其实跟lambda表达式关系也不大,它的作用是返回当前function所要表达的lambda含义。相当于创建了一个自身对象。

Function算是lambda的一种扩展应用,这个Function的的作用,是 Representsafunctionthat accepts one argumentandproduces a result.意思是接受一个参数,并产生(返回)一个结果(类型可不同)。

类似的还有很多Function,都在包java.util.Function中。

你也可以创建自己的Function,它是用来表达操作是怎样的。如传入的参数是什么,返回的是什么。

其实你只要明白它抽象的是操作就可以了。

到此就知道,原来UnaryOperator没啥神秘的,jdk把这些操作放在java.util.function中也正说明了它是一个工具类,是为了提取重复代码,让它可以重用,毕竟需要用到这样的操作的地方太多了,提取是有必要的。

你真的了解 lambda 吗(纠错篇)?相关推荐

  1. 深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

    作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-language- ...

  2. Java Lambda(语言篇——lambda,方法引用,目标类型,默认方法,函数接口,变量捕获)

    深入理解Java 8 Lambda(语言篇--lambda,方法引用,目标类型和默认方法) 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout ...

  3. [转]深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)...

    以下内容转自: 作者:Lucida 微博:@peng_gong 豆瓣:@figure9 原文链接:http://zh.lucida.me/blog/java-8-lambdas-insideout-l ...

  4. 真的很干!这篇4万字超长干货把“设计模式六大原则”解析透了!建议反复阅读。

    今天整理更新一篇关于设计模式六大原则的文章,主要是学习路径类的. 以后会找机会再更面试类的文章.所以,点关注,不迷路,可以最先看到我的更新呦! 壹 | 超 级 干 货 一.单一职责原则 1.定义 首先 ...

  5. 你真的了解lambda吗?一文让你明白lambda用法与源码分析

    本文作者: cmlanche 本文链接: http://www.cmlanche.com/2018/07/22/lambda用法与源码分析/ 转载来源:cmlanche.com 用法 示例:最普遍的一 ...

  6. 你真的了解Lambda表达式吗?

    简介 Lambda 是一种可用于创建委托或表达式目录树类型的匿名函数.通过使用 lambda 表达式,可以写入可作为参数传递或作为函数调用值返回的本地函数! 场景 比如有这样一个场景,在给出的List ...

  7. 关于Python中的lambda,这篇阅读量10万+的文章可能是你见过的最完整的讲解

    lambda是Python编程语言中使用频率较高的一个关键字.那么,什么是lambda?它有哪些用法?网上的文章汗牛充栋,可是把这个讲透的文章却不多.这里,我们通过阅读各方资料,总结了关于Python ...

  8. 保险丝的作用,参数及选型应用,你真的懂了吗——电子元器件篇

    最详细,通俗易懂的保险丝保姆级讲解.着重讲下额定电流和分断能力,分断能力很少被重视,在选型中也比较易被忽略,然而分断能力也是保险丝安全保护电路的重要参数之一,很多新手工程师对这一参数并不理解. 文章目 ...

  9. Android <Textview>的高级应用:真的很高级(提高篇)

    学会了textview的基本用法,我最近有看到了一个好玩的:跑马灯实现文字滚动,作为一个基本的组件,它的作用当然巨大,所以我们有必要提高一下对textview的认知,于是我今天就利用课余时间做了一个跑 ...

最新文章

  1. python中如何输出中文_python中怎么输出中文-问答-阿里云开发者社区-阿里云
  2. VC++ HIDAPI实现USB数据读写
  3. 不附加数据库 ASP.NET调用.sql文件
  4. 数论之勾股数组(毕达哥拉斯三元组)
  5. 云信api_服务端API文档
  6. ecshop 首页调用多个促销,显示到计时
  7. 联想ThinkBook解锁FN键
  8. 湖南省大学生程序设计竞赛系统设计
  9. 开源表单推荐:Tduck 填鸭 —— 表单收集器
  10. C语言--实现汉诺塔【图文讲解,附代码】
  11. 在微信小程序中如何下载APP?
  12. Linux下查看CPU型号,内存大小,硬盘空间的命令(详解)
  13. 数位板时不时失控_当事情失控时进行网络分析
  14. JS Object() 与 new Object()的区别
  15. GD32F407之LWIP+RTL8201F
  16. 动态库装载及 dlsym的RTLD_NEXT参数详解
  17. Keepass2Android病毒吗,带有OTP的Keepass2Android无法正常工作
  18. All in One RapidShare Pack 3.0
  19. win10自带虚拟机好用吗_win10自带虚拟机好用吗?
  20. 不同编程语言代码转换网站,在线编程语言代码转换,C#转VB.NET,VB.NET转C#

热门文章

  1. ElementUI的组件拆解之Tooltip
  2. 刷固件Layer1到手机FLASH(硬刷)
  3. [洛谷1390]公约数的和
  4. [BZOJ 4551][Tjoi2016Heoi2016]树(并查集)
  5. Which one is faster: Java heap or native memory?
  6. Matlab中legend位置
  7. 电感器在交流电路中的作用
  8. linux route命令详解
  9. linux 的set,env和export的区别
  10. Linux下epoll如何实现高效处理百万句柄的