关于java8:Java8新特性-Lambda底层实现原理

39次阅读

共计 6389 个字符,预计需要花费 16 分钟才能阅读完成。

前言

常常会在写业务代码的时候,有这样的需要:

筛选出条件为 XX 的实体类 ID List

List<Long> waitTaskList = wflInsTaskList.stream().filter(wflInsTask -> {return wflInsTask.getArrivalStatus().equals(WflInsTask.ARRIVAL_STATUS_NOT_ARRIVED);
            }).map(WflInsTask::getTaskId).distinct().collect(Collectors.toList());

Java8 之 Lambda

下文内容默认以 JDK8 为前提

什么是 Lambda

Lambda 表达式是 JDK8 的一个新个性,能够取代大部分的匿名外部类,写出更优雅的 Java 代码,尤其在汇合的遍历和其余汇合操作中,能够极大地优化代码构造。

Lambda 表达式形容了一个代码块(或者叫匿名办法),能够将其作为参数传递给构造方法或者一般办法以便后续执行。如:

() -> System.out.println("hello");

() 为 Lambda 表达式的参数列表(容许没有参数),-> 标识这串代码为 Lambda 表达式(也就是说,看到 -> 就晓得这是 Lambda,Groovy 的闭包语法也相似),System.out.println("hello") 就是执行的代码,将“hello”打印到规范输入流。

以 Runnable 接口为例,原来咱们创立一个线程并启动它是这样的:

public class Test {public static void main(String[] args) {new Thread(new Runnable() {
            @Override
            public void run() {System.out.println("hello");
            }
        }).start();}
}

通过 Lambda 只须要这样:

public class Test {public static void main(String[] args) {new Thread(() -> System.out.println("hello")).start();}
}

咱们看 Runnable 接口源码:

@FunctionalInterface
public interface Runnable {public abstract void run();
}

其中 @FunctionalInterface 注解中有这样一段形容:

/*
 * <p>Note that instances of functional interfaces can be created with
 * lambda expressions, method references, or constructor references.
 */

阐明通过 @FunctionalInterface 标记的接口能够通过 Lambda 表达式创立实例。

@FunctionalInterface润饰函数式接口的,要求接口中的形象办法只有一个。有多个形象办法编译将报错

加不加 @FunctionalInterface 对于接口是不是函数式接口没有影响,该注解常识揭示编译器去查看该接口是否仅蕴含一个形象办法

例如:

public interface Comparator<T> {int compare(T o1, T o2);
}
public interface Runnable {void run();
}
public interface Callable<V> {V call() throws Exception;
}

下面三个接口都只有一个形象办法,然而三个办法的签名都不一样,这要求 Lambda 表达式与实现接口的办法签名要统一。上面用 函数描述符 来示意上述三个办法的签名,箭头后面是办法的入参类型,前面是返回类型。

  1. compare:(T, T) -> int,两个泛型 T 类型的入参,返回 int 类型

    Lambda 表达式:(User u1, User u2)-> u1.getAge - u2.getAge

  2. run:() -> void,无入参,无返回值

    Lambda 表达式:() -> { System.out.println("hello"); }

  3. call:() -> V,无入参,返回一个泛型 V 类型的对象

    Lambda 表达式:() -> new User() // 不须要用括号盘绕返回值为 void 的单行办法调用。

语法

Lambda 表达式由 三局部 组成:

  1. 参数列表
  2. 箭头
  3. 主体

两种格调,别离是:

  1. 表达式 - 格调

    (parameters) -> expression

  2. 块 - 格调

    (parameters) -> {statements;}

其中 () 用来形容参数列表,{} 用来形容办法体,-> 为 lambda 运算符,读作(goes to)。

例:

  1. () -> {}
  2. () -> "hello"
  3. () -> { return "hello";}

Java 8 中外部类或者 Lambda 表达式对外部类变量的援用条件放松了,不要求强制的加上 final 关键字了,然而 Java 8 中要求这个变量是 effectively final

public class LambdaTest {public static void main(String[] args) {
        String str = "hello";
        List<String> list = new ArrayList<>();
        list.forEach(s -> {
            // 这行赋值报错 Variable used in lambda expression should be final or effectively final
            // 正文掉,不赋值就是 effectively final
            str = "hi";
            System.out.println(str);
        });
    }
}

罕用函数式接口

随 Lambda 一起减少的还有一个 java.util.function 包,其中定义了一些常见的函数式接口的。比方:

  • Function,承受一个输出参数,返回一个后果。参数与返回值的类型能够不同,咱们之前的 map 办法内的 lambda 就是示意这个函数式接口的;
  • Consumer,承受一个输出参数并且无返回的操作。比方咱们针对数据流的每一个元素进行打印,就能够用基于 Consumer 的 lambda;
  • Supplier,无需输出参数,只返回后果。看接口名就晓得是施展了对象工厂的作用;
  • Predicate,承受一个输出参数,返回一个布尔值后果。比方咱们在对数据流中的元素进行筛选的时候,就能够用基于 Predicate 的 Lambda;

这里只解释其中一种,更多的本人去理解吧。

public interface Predicate<T> {boolean test(T t);
}

test:T -> boolean,接管一个泛型 T 对象,返回一个 boolean。

实用场景:示意一个波及类型 T 的布尔表达式。

// 判断空白字符串
Predicate<String> isBlankPredicate = s -> s != null && s.trim().length() == 0;
isBlankPredicate.test(" "); // true
// 判断用户年龄是否大于 20
Predicate<User> agePredicate = u -> u.getAge() > 20;
agePredicate.test(new User(18)); // false

Java 是一个强类型的语言,因而参数必须要有类型,如果编译器可能揣测出 Lambda 表达式的参数类型,则不须要咱们显示的进行指定。

上述代码中 没有指定 s 参数类型 Stirng,因为编译器会依据 Lambda 表达式对应的函数式接口 Predicate<String> 进行主动推断。

复合 Lambda 表达式— 以比拟器复合(Comparator)为例

应用 Comparator 对用户年龄按从大到小进行排序:

List<User> users = Arrays.asList(new User("zhangsan", 30), 
                                   new User("lisi", 22), 
                                   new User("wangwu", 18));
users.sort(Comparator.comparing(User::getAge).reversed());

Comparator.comparing 源码:

@FunctionalInterface
public interface Comparator<T> {
    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T, ? extends U> keyExtractor)
    {Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }
   default Comparator<T> reversed() {return Collections.reverseOrder(this);
   }
}

Java 8 中在接口中减少了默认实现这种函数,其实在很大程序上违反了接口具备形象这种特色的,减少 default 实现次要起因是因为思考兼容及代码的更改老本,例如,在 Java 8 中向 iterator 这种接口减少一个办法,那么实现这个接口的所有类都要需实现一遍这个办法,那么 Java 8 须要更改的类就太多的,因而在 Iterator 接口里减少一个 default 实现,那么实现这个接口的所有类就都具备了这种实现

办法援用

当在 Lambda 表达式中间接调用了一个办法 时能够应用,其写法为 指标援用:: 办法名称

  1. 指向 静态方法 的办法援用

    Function<String, Integer> fun = s -> Integer.parseInt(s);
    Function<String, Integer> fun = Integer::parseInt;
  2. 指向 任意类型实例办法 的办法援用

    Function<String, Integer> fun = s -> s.length();
    Function<String, Integer> fun = String::length;
  3. 指向 现存内部对象实例办法 的办法援用

    String s = "hello";
    Supplier<Integer> len = () -> s.length();
    Supplier<Integer> len = s::length;

实现原理

以下文代码为例:

public class LambdaTest {
    @FunctionalInterface
    public interface LambdaDemo{public void runLambda();
    }
    public static void doSomething(LambdaDemo demo){demo.runLambda();
    }

    public static void main(String[] args) {doSomething(()->System.out.println("hello world!"));
    }
}

执行 javac LambdaTest.java 编译后生成两个 class 文件

javap -p LambdaTest 输入所有类和成员

由此能够看出 Lambda 表达式在 Java 8 中首先会生成一个公有的动态函数,这个公有的动态函数干的就是 Lambda 表达式外面的内容。

javap -v LambdaTest输入行号、本地变量表信息、反编译汇编代码、以后类用到的常量池等信息。

其中 main 办法执行了一条 invokedynamic 指令。invokedynamic 呈现的地位代表一个动静调用点,invokedynamic 指令前面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动静调用点。根据这个能够找到对应的动静调用疏导办法 java.lang.invoke.CallSite。

对应常量池中的静态方法:

而 #0 在字节码最初的 BootstrapMethods 中,Method arguments#29 代表这个 Lambda 表达式调用代码

依据 BootstrapMethods 对应的 #27 能够找到此处 Lambda InvokeDynamic 指令对应的疏导办法是LambdaMetafactory.metafactory,返还一个 CallSite

LambdaMetafactory.java

    public static CallSite metafactory(MethodHandles.Lookup caller,
                                       String invokedName,
                                       MethodType invokedType,
                                       MethodType samMethodType,
                                       MethodHandle implMethod,
                                       MethodType instantiatedMethodType)
            throws LambdaConversionException {
        AbstractValidatingLambdaMetafactory mf;
        // 通过 new 一个 InnerClassLambdaMetafactory 并调用 buildCallSite
        // 为 Lambda 表达式生成了一个外部类
        mf = new InnerClassLambdaMetafactory(caller, invokedType,
                                             invokedName, samMethodType,
                                             implMethod, instantiatedMethodType,
                                             false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);
        mf.validateMetafactoryArgs();
        return mf.buildCallSite();}

对于外部类,在运行时加上 vm 参数 -Djdk.internal.lambda.dumpProxyClasses,能够发现生成了!

`

public class LambdaTest {
    @FunctionalInterface
    public interface LambdaDemo{public void runLambda();
    }
    public static void doSomething(LambdaDemo demo){demo.runLambda();
    }

    public static void main(String[] args) {doSomething(()->System.out.println("hello world!"));
    }
  
    private static void lambda$main$0() {System.out.println("hello world!");
    }
  
    // 外部类
      final class Lambda$1 {
        @Override
        public void runLambda() {lambda$main$0();
        }
    }
}

由此能够晓得 Lambda 表达式,编译器在类中生成一个动态函数,运行时调以内部类模式调用该动态函数。

JVM 之动态方法调用:invokedynamic

Java8 之 Stream

Stream 是 Java 提供的一个接口,该接口容许以 申明式 的写法解决数据,能够把操作 链接 起来,造成数据处理流水线,还能将数据处理工作 并行化。(与 IO 流无关)

具体的内容且看下回。。

感想

Lambda 的行为参数化思维

Lambda 简化了代码,但并不是完满的,性能方面,在非并行计算中,很多计算未必有传统的 for 性能要高,也不易调试。所以具体的利用还应参考业务的需要。

本文只是对 Java8 局部个性做了介绍,还有很多内容须要本人去学习钻研的。就是想说,技术是很长的一条路,要被动并不排挤接管新的常识,才不会停滞不前。

正文完
 0