你真的了解java的lambda吗?- java lambda用法与源码分析

转载请注明来源:cmlanche.com

用法

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

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

由此引发的一些案例

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

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。

疑问

  1. 上面的示例我们看到接口都有个@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注解就可以实现

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

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

源码解析

必须了解注解 @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表达式的使用协议的,那么注释中都说了哪些呢?

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

    * 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.
  2. 一个被它注解的接口只能有一个抽象方法,有两种例外。

    1. 第一是接口允许有实现的方法,这种实现的方法是用default关键字来标记的(java反射中java.lang.reflect.Method#isDefault()方法用来判断是否是default方法),例如:

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

    1. 如果声明的方法和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.
  3. 如果一个类型被这个注解修饰,那么编译器会要求这个类型必须满足如下条件

    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>
  4. 编译器会自动把满足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是怎么把一个函数当做一个对象作为参数使用的。

穿越:对象变身函数

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

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. 返回接口对象

关于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它可以接受两个同类型参数,并返回同类型参数的值。

关于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的的作用是Represents a function that accepts one argument and produces a result.意思是接受一个参数,并产生(返回)一个结果(类型可不同)。

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

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

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

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

你真的了解java的lambda吗?- java lambda用法与源码分析的更多相关文章

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

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

  2. java io系列02之 ByteArrayInputStream的简介,源码分析和示例(包括InputStream)

    我们以ByteArrayInputStream,拉开对字节类型的“输入流”的学习序幕.本章,我们会先对ByteArrayInputStream进行介绍,然后深入了解一下它的源码,最后通过示例来掌握它的 ...

  3. java io系列03之 ByteArrayOutputStream的简介,源码分析和示例(包括OutputStream)

    前面学习ByteArrayInputStream,了解了“输入流”.接下来,我们学习与ByteArrayInputStream相对应的输出流,即ByteArrayOutputStream.本章,我们会 ...

  4. Java 序列化和反序列化(三)Serializable 源码分析 - 2

    目录 Java 序列化和反序列化(三)Serializable 源码分析 - 2 1. ObjectStreamField 1.1 数据结构 1.2 构造函数 2. ObjectStreamClass ...

  5. Java 序列化和反序列化(二)Serializable 源码分析 - 1

    目录 Java 序列化和反序列化(二)Serializable 源码分析 - 1 1. Java 序列化接口 2. ObjectOutputStream 源码分析 2.1 ObjectOutputSt ...

  6. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  7. Java高并发之无锁与Atomic源码分析

    目录 CAS原理 AtomicInteger Unsafe AtomicReference AtomicStampedReference AtomicIntegerArray AtomicIntege ...

  8. 【Java入门提高篇】Day26 Java容器类详解(八)HashSet源码分析

    前面花了好几篇的篇幅把HashMap里里外外说了个遍,大家可能对于源码分析篇已经讳莫如深了.别慌别慌,这一篇来说说集合框架里最偷懒的一个家伙——HashSet,为什么说它是最偷懒的呢,先留个悬念,看完 ...

  9. 【Java入门提高篇】Day21 Java容器类详解(四)ArrayList源码分析

    今天要介绍的是List接口中最常用的实现类——ArrayList,本篇的源码分析基于JDK8,如果有不一致的地方,可先切换到JDK8后再进行操作. 本篇的内容主要包括这几块: 1.源码结构介绍 2.源 ...

随机推荐

  1. 用JavaScript带你体验V8引擎解析标识符

    上一篇讲了字符串的解析过程,这一篇来讲讲标识符(IDENTIFIER)的解析. 先上知识点,标识符的扫描分为快解析和慢解析,一旦出现Ascii编码大于127的字符或者转义字符,会进入慢解析,略微影响性 ...

  2. digital clock based C

    /********************************** * Name : timeDisplay.cpp * Purpose: Display digital clock accord ...

  3. Delphi - 采用第三方控件TMS、SPComm开发串口调试助手

    第三方控件TMS.SPComm的下载与安装 盒子上可搜索关键字进行下载,TMS是.dpk文件,SPComm.pas文件: 安装方法自行百度,不做赘述. 通过TMS控件进行界面布局 界面预览: Delp ...

  4. redis集群cluster简单设置

    环境: 这里参考官方使用一台服务器:Centos 7  redis-5.0.4    192.168.10.10 redis集群cluster最少要3个主节点,所以本次需要创建6个实例:3个主节点,3 ...

  5. Android源码分析(八)-----系统启动流程&IPC简述

    一 :系统启动流程图 从下往上依次启动linux kernel -->zygote-->SystemServer-->NativeService-->AndroidServic ...

  6. Kubernetes概念之RC

    感觉自己浪费了一年的时间,种一棵树最好的时间是十年前,还有就是现在,虽然这颗树种了又种,种了又种,这次真的要种了......   本文通过<Kubernetes权威指南>的概念部分学习总结 ...

  7. 剑指:包含min函数的栈(min栈)

    题目描述 设计一个支持 push,pop,top 等操作并且可以在 O(1) 时间内检索出最小元素的堆栈. push(x)–将元素x插入栈中 pop()–移除栈顶元素 top()–得到栈顶元素 get ...

  8. 利用 FluentScheduler 启动定时器计划任务

    FluentScheduler 是什么? Automated job scheduler with fluent interface. 这是作者在 Github 上的介绍,就是一个定时任务管理器.在 ...

  9. V4L2视频采集原理

    一.简介 Video for Linuxtwo(Video4Linux2)简称V4L2,是V4L的改进版.V4L2是linux操作系统下用于采集图片.视频和音频数据的API接口,配合适当的视频采集设备 ...

  10. win10 任务栏上的工具栏,重启消失的解决方法

    首先谈下 <任务栏的工具栏> 对于很多人来言,还是有可取性的 任务栏的工具栏对编程者的作用 一般来说,我们会经常查看某些API文档,虽然现在是联网也很方便,但如果都下载下来,整理到一个文件 ...