深入理解 Java Lambda

Java Lambda

  • Java Lambda
    • Lambda
      • Lambda
      • 函数式接口
      • Lambda为什么可以做到类型推导?可以做到什么程度?
      • 方法应用
      • Lambda和匿名类是什么关系?仅仅是语法糖? Java编译器如何处理Lambda?

Lambda

lambda名称的由来
许多年前,在计算机出来之前,有位名叫 Alonzo Church 的逻辑学家,他想证明
什么样的数学函数是可以有效计算的(奇怪的是,当时已经存在了许多已知的函数,
但是没有人知道怎样去计算他们的值。)它使用希腊字母lambda (λ)来标记参数。
至于为什么非要用λ 是因为《数学原理》中使用‘^’来表示自由变量,所以Church就用
大写的 lambda Λ 来表示参数,但是最终还是换回了小写。
于是从那时起,带参数变量的表达式都被叫做lambda表达式。

Lambda

Java不是一种函数式语言,函数无法独立的存在。可这带来的问题就是Java过于的“重”。Lambda出现之前通常情况你的参数只能传递值而不能很方便的传递行为。
Java提供了匿名类来达到传递一组行为的效果,但是匿名内部类语法过于冗余;匿名内部类中的this和变量名容易让人产生误解;无法获取非final的局部变量。

:创建一个比较器

匿名类

Comparator comparator = new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o1 - o2;
            }
        };

Lambda

Comparator comparator = (i1, i2) -> i1 - i2;

++Lambda 表达式的结构++:
- 一个 Lambda 表达式可以有零个或多个参数
- 参数的类型既可以明确声明,也可以根据上下文来推断。例如:int aa效果相同
- 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:a, bint a, int bString a, int b, float c
- 空圆括号代表参数集为空。例如:() -> 42
- 当只有一个参数,且其类型可推导时,圆括号()可省略。例如:a -> return a * a
- Lambda 表达式的主体可包含零条或多条语句
- 如果 Lambda 表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致
- 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码- 块的返回类型一致,若没有返回则为空

函数式接口

匿名内部类有一个特点,每一个匿名类都对应一个接口。接口作为Java的一个特性,可以通过接口来作为和函数特性的连接。通过 @FunctionalInterface 来创建函数式接口可以达到一定的约束规范。再根据常用函数的特征进行抽象可以避免不必要的接口创建。

Java8通过@FunctionalInterface来创建函数式接口,当然如果符合潜在的标准(函数式接口只能有一个抽象方法),也可以不适用注解。在自己定义函数式接口的时候,可以加上这个注解来检测是否和jdk已有的函数式接口重复,避免自己创建冗余的接口。

Java8中对函数式接口的抽象都放在了 function包中大致分为几类:
- consumer 一个或多个入参,无返回类型
- supplier 无入参,返回对象或基础类型
- function 一个或两个入参,返回不同类型或不同的基础类型
- operator 一个或两个相同类型的入参,返回相同的类型
- predicate 一个或两个入参,返回布尔型
- run 无入参,无返回

这些函数式接口基本涵盖了日常工作中大多数对函数式接口的需求。

Lambda为什么可以做到类型推导?可以做到什么程度?

Lambda 本身就是类型推导

当我们定义了类似(int a) -> "abc" 的lambda表达式后,系统自动为它推导出类型。

编译器负责推导lambda的类型,它利用上下文被期待的类型当做推导的目标类型,当满足下面条件时,就会被赋予目标类型:
- 被期待的目标类型是一个函数式接口
- lambda的入参类型和数量与该接口一致
- 返回类型一致
- 抛出异常类型一致

(int a) -> "abc" 通常会被推导为 function类型

刚才我们提到了目标对象的上下文,上下文环境一般为:++变量声明++,++返回语句++,++赋值++,++方法的参数++,++强制转换类型等++。

Lambda表达式具有显式类型隐式类型,显式类型是由我们指定的类型,比如 Function f = (int a) -> "abc",隐式类型是由编译器推导而来,对于返回类型不明确的情况,即存在二义性,则我们需要手动给出目标类型。

// Object o = () -> System.out.println("123"); 编译器无法识别,提示语法错误
Object o = (Runnable) () -> System.out.println("123");

方法应用

方法应用使用 :: 双冒号,是lambda的简化

  • 静态方法引用 Class::method
  • 实例方法引用 instance::method this::method supper::method
  • 构造方法引用 Class::new String[]::new int[]::new

方法引用会生成一个lambda表达式,构造方法引用必然生成的是 supplier类型,其他情况则需要根据具体类型推导。

Lambda和匿名类是什么关系?仅仅是语法糖? Java编译器如何处理Lambda?

文章

java虚拟机里调用方法的字节码指令有5种:
- invokestatic  //调用静态方法
- invokespecial  //调用私有方法、实例构造器方法、父类方法
- invokevirtual  //调用实例方法
- invokeinterface  //调用接口方法,会在运行时再确定一个实现此接口的对象
- ==invokedynamic==  //先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。

graph LR
a(indy)---b(call sit specifier);
b---c(bootstrap method);
c---d(callsit);
e(Method Handle)---d;
f(method)---e

==表面==

public class Illusory {
    public void say(){
        System.out.println("Hi");
    }
}

==真实==

import java.lang.invoke.*;

public class Real {

    private static void hello() {
        System.out.println("Hello!");
    }

    public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        Class thisClass = lookup.lookupClass();
        MethodHandle mh = lookup.findStatic(thisClass, "hello", MethodType.methodType(void.class));
        return new ConstantCallSite(mh.asType(type));
    }
}

==变化==

import org.objectweb.asm.*;

import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

//Opcodes接口里定义了几乎全部的java字节码命令
public class Change implements Opcodes {

    public static void main(String[] args) throws Exception {
        byte[] codes = dump();
        Class clazz = new MyClassLoader().defineClass("Illusory", codes);
        clazz.getMethod("say").invoke(clazz.newInstance());
    }

    public static byte[] dump() throws Exception {

        ClassWriter classWriter = new ClassWriter(0);
        MethodVisitor visitor;

        classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "Illusory", null, "java/lang/Object", null);

        // 默认构造方法
        visitor = classWriter.visitMethod(ACC_PUBLIC, "", "()V", null, null);
        visitor.visitCode();
        visitor.visitVarInsn(ALOAD, 0);
        visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(1, 1);
        visitor.visitEnd();

        // say 方法的改变
        visitor = classWriter.visitMethod(ACC_PUBLIC, "say", "()V", null, null);
        visitor.visitCode();
        //---------Unknown world!--------------
//        visitor.visitCode();
//        visitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
//        visitor.visitLdcInsn("Unknown world!");
//        visitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
//        visitor.visitInsn(RETURN);
//        visitor.visitMaxs(2, 1);
        //-----------------------
        //----------Real world!-------------
        MethodType methodType = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
                MethodType.class);
        Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "Real", "bootstrap", methodType.toMethodDescriptorString(), false);
        visitor.visitInvokeDynamicInsn("dynamicInvoke", "()V", bootstrap);
        visitor.visitInsn(RETURN);
        visitor.visitMaxs(0, 1);
        //-----------------------
        visitor.visitEnd();
        classWriter.visitEnd();

        return classWriter.toByteArray();
    }


    private static class MyClassLoader extends ClassLoader implements Opcodes {
        public Class defineClass(String name, byte[] b) {
            return super.defineClass(name, b, 0, b.length);
        }

    }
}

TODO
lambda最后会由编译器生成static 方法在当前类中,利用了invokedynamic命令脱离了内部类实现的优化。

public class LambdaTest {

    public static void main(String[] args) {
        List list = Arrays.asList("a","b");

        list.forEach(System.out::println);
    }
}
public class LambdaTest {
  public LambdaTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: anewarray     #2                  // class java/lang/String
       4: dup
       5: iconst_0
       6: ldc           #3                  // String a
       8: aastore
       9: dup
      10: iconst_1
      11: ldc           #4                  // String b
      13: aastore
      14: invokestatic  #5                  // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
      17: astore_1
      18: aload_1
      19: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;
      22: dup
      23: invokevirtual #7                  // Method java/lang/Object.getClass:()Ljava/lang/Class;
      26: pop
      27: invokedynamic #8,  0              // InvokeDynamic #0:accept:(Ljava/io/PrintStream;)Ljava/util/function/Consumer;
      32: invokeinterface #9,  2            // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
      37: return
}

你可能感兴趣的:(java8)