java for底层原理是啥_Java8新特性 Lambda底层实现原理

前言

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

筛选出条件为XX的实体类ID ListList 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注解中有这样一段描述:/*

*

Note that instances of functional interfaces can be created with

* lambda expressions, method references, or constructor references.

*/

说明通过 @FunctionalInterface 标记的接口可以通过 Lambda 表达式创建实例。

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

加不加@FunctionalInterface对于接口是不是函数式接口没有影响,该注解知识提醒编译器去检查该接口是否仅包含一个抽象方法

a9ee366fd2aaaa6d8b581fe78a25b5c2.png

例如:public interface Comparator {

int compare(T o1, T o2);

}

public interface Runnable {

void run();

}

public interface Callable {

V call() throws Exception;

}

上面三个接口都只有一个抽象方法,但是三个方法的签名都不一样,这要求Lambda表达式与实现接口的方法签名要一致。下面用函数描述符来表示上述三个方法的签名,箭头前面是方法的入参类型,后面是返回类型。compare:(T, T) -> int,两个泛型T类型的入参,返回int类型

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

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

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

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

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

语法

Lambda表达式由三部分组成:参数列表

箭头

主体

00457e9db3ef0b81b2f9bd261aaf74be.png

有两种风格,分别是:表达式-风格

(parameters) -> expression

块-风格

(parameters) -> { statements; }

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

例:() -> {}

() -> "hello"

() -> { return "hello"; }Java 8中内部类或者Lambda表达式对外部类变量的引用条件放松了,不要求强制的加上final关键字了,但是Java 8中要求这个变量是effectively finalpublic class LambdaTest {

public static void main(String[] args) {

String str = "hello";

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

boolean test(T t);

}

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

适用场景:表示一个涉及类型T的布尔表达式。// 判断空白字符串

Predicate isBlankPredicate = s -> s != null && s.trim().length() == 0;

isBlankPredicate.test(" "); // true

// 判断用户年龄是否大于20

Predicate agePredicate = u -> u.getAge() > 20;

agePredicate.test(new User(18)); // falseJava是一个强类型的语言,因此参数必须要有类型,如果编译器能够推测出Lambda表达式的参数类型,则不需要我们显示的进行指定。

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

复合Lambda表达式---以比较器复合(Comparator)为例

使用 Comparator 对用户年龄按从大到小进行排序:List 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 {

public static > Comparator comparing(

Function super T, ? extends U> keyExtractor)

{

Objects.requireNonNull(keyExtractor);

return (Comparator & Serializable)

(c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));

}

default Comparator reversed() {

return Collections.reverseOrder(this);

}

}Java 8中在接口中增加了默认实现这种函数,其实在很大程序上违背了接口具有抽象这种特征的,增加default实现主要原因是因为考虑兼容及代码的更改成本,例如,在Java 8中向iterator这种接口增加一个方法,那么实现这个接口的所有类都要需实现一遍这个方法,那么Java 8需要更改的类就太多的,因此在Iterator接口里增加一个default实现,那么实现这个接口的所有类就都具有了这种实现

方法引用

当在Lambda表达式中直接调用了一个方法时可以使用,其写法为目标引用::方法名称指向静态方法的方法引用Function fun = s -> Integer.parseInt(s);

Function fun = Integer::parseInt;

指向任意类型实例方法的方法引用Function fun = s -> s.length();

Function fun = String::length;

指向现存外部对象实例方法的方法引用String s = "hello";

Supplier len = () -> s.length();

Supplier 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文件

aba973320a14a8b52daf0887c54a6dae.png

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

ac0d165162f843735c187b276a7d8a11.png

由此可以看出Lambda 表达式在 Java 8 中首先会生成一个私有的静态函数,这个私有的静态函数干的就是 Lambda 表达式里面的内容。

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

其中main方法执行了一条invokedynamic指令。invokedynamic出现的位置代表一个动态调用点,invokedynamic指令后面会跟一个指向常量池的调用点限定符,这个限定符会被解析为一个动态调用点。依据这个可以找到对应的动态调用引导方法java.lang.invoke.CallSite。

0efc31abe21e475d7dd0d4d4b73c2d45.png

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

1190000023747150

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

1190000023747150

根据BootstrapMethods对应的#27可以找到此处Lambda InvokeDynamic指令对应的引导方法是LambdaMetafactory.metafactory,返还一个CallSite

LambdaMetafactory.javapublic 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,可以发现生成了!

c62051f2fc1365f7b36bc033f19ece09.png

`b170f9126f2f305edea37b94b855671f.pngpublic 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表达式,编译器在类中生成一个静态函数,运行时调以内部类形式调用该静态函数。

Java8之Stream

Stream是Java提供的一个接口,该接口允许以声明式的写法处理数据,可以把操作链接起来,形成数据处理流水线,还能将数据处理任务并行化。(与IO流无关)

具体的内容且看下回。。

感想

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

本文只是对Java8部分特性做了介绍,还有很多内容需要自己去学习研究的。就是想说,技术是很长的一条路,要主动并不排斥接收新的知识,才不会停滞不前。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值