2024年最全Android开发太难了:Java Lambda ≠ Android Lambda (上),Java面试宝典最新版

总结

互联网大厂比较喜欢的人才特点:对技术有热情,强硬的技术基础实力;主动,善于团队协作,善于总结思考。无论是哪家公司,都很重视高并发高可用技术,重视基础,所以千万别小看任何知识。面试是一个双向选择的过程,不要抱着畏惧的心态去面试,不利于自己的发挥。同时看中的应该不止薪资,还要看你是不是真的喜欢这家公司,是不是能真的得到锻炼。其实我写了这么多,只是我自己的总结,并不一定适用于所有人,相信经过一些面试,大家都会有这些感触。

**另外本人还整理收藏了2021年多家公司面试知识点以及各种技术点整理 **

下面有部分截图希望能对大家有所帮助。

在这里插入图片描述

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

};

runnable.run();

}

public static void main(String[] args) {

new TestJavaLambda().test();

}

}

运行下,看下出错的堆栈:

hello java lambda

Exception in thread “main” java.lang.ArithmeticException: / by zero

at com.example.zhanghongyang.blog02.TestJavaLambda.lambda$test$0(TestJavaLambda.java:8)

at com.example.zhanghongyang.blog02.TestJavaLambda.test(TestJavaLambda.java:10)

at com.example.zhanghongyang.blog02.TestJavaLambda.main(TestJavaLambda.java:14)

看下到底和何方神圣调用的我们的run方法:

嗯…最后的堆栈是:

TestJavaLambda.lambda$test$0(TestJavaLambda.java:8)

是我们TestJavaLambda中的lambda$test$0方法调用的?

是我们刚才发编译看漏了,还有这个方法?我们再反编译看下:

javap /Users/zhanghongyang/repo/KotlinLearn/app/src/main/java/com/example/zhanghongyang/blog02/TestJavaLambda.class

Compiled from “TestJavaLambda.java”

public class com.example.zhanghongyang.blog02.TestJavaLambda {

public com.example.zhanghongyang.blog02.TestJavaLambda();

public void test();

public static void main(java.lang.String[]);

private void lambda$test$0();

}

这次javap -p 查看,-p代表private方法也输出出来。

还真有这个方法,看下这个方法的字节码:

private static void lambda$test$0();

descriptor: ()V

flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC

Code:

stack=2, locals=0, args_size=0

0: getstatic #7 // Field java/lang/System.out:Ljava/io/PrintStream;

3: ldc #8 // String hello java lambda

5: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V

8: return

LineNumberTable:

line 7: 0

line 8: 8

很简单,就是我们上面lambda表达式{}里面的内容,打印一行日志。

那这个方法是test调用的?不对呀,这个堆栈好像有问题,我们在回头看下刚才堆栈:

Exception in thread “main” java.lang.ArithmeticException: / by zero

at com.example.zhanghongyang.blog02.TestJavaLambda.lambda$test$0(TestJavaLambda.java:8)

at com.example.zhanghongyang.blog02.TestJavaLambda.test(TestJavaLambda.java:10)

at com.example.zhanghongyang.blog02.TestJavaLambda.main(TestJavaLambda.java:14)

有没有发现这个堆栈太过于简单了,我们的Runnable.run的调用栈呢?

这个堆栈应该是被简化了,那我们再加一行日志,看下run()方法执行时,自己身处于哪个类?

我们在run方法里面加了一行

System.out.println(this.getClass().getCanonicalName());

看下输出:

com.example.zhanghongyang.blog02.TestJavaLambda

嗯…其实我们执行了一个废操作,当前这个方法里面的代码都被放到lambda$test$0()了,当然输出是TestJavaLambda。

不行了,我要放大招了。

我们修改下方法,让这个进程活的久一点:

public void test() {

Runnable runnable = () -> {

System.out.println(“hello java lambda”);

System.out.println(this.getClass().getCanonicalName());

// 新增

try {

Thread.sleep(1000000);

} catch (InterruptedException e) {

e.printStackTrace();

}

int a = 1 / 0;

};

runnable.run();

}

运行后…

切到命令行,执行jps命令,查看当前程序进程的pid:

java zhanghongyang$ jps

99315 GradleDaemon

3682 TestJavaLambda

21298 Main

3685 Jps

3258 GradleDaemon

1275

3261 KotlinCompileDaemon

看到了3682,然后执行

jstack 3682

在这里插入图片描述

太感人了,终于把这行隐藏的run方法的堆栈找出来了。

这里大家不要太在意jps,jstack这些指令,都是jdk自带的,你就知道能查堆栈就行了,别出去搜这两个命令去啦,文章看完再说。

另外获取堆栈其实也能通过方法调用,小缘是通过Reflection.getCallerClass看的。

到现在我们具体真相又进了一步:

我们lambda$test$0()方法是这个对象:com.example.zhanghongyang.blog02.TestJavaLambda$$Lambda$1/1313922862的run方法调用的。

我们又能下个结论了:

文中lambda表达式的写法,在运行时,会帮我们生成中间类,类名格式为 原类名$$Lambda$数字,然后通过这个中间类最终完成调用。

那么你可能表示不服:

你说运行时生成就生成呀?你拿出来给我看看?

嗯…等会我拿出来给你看。

不过我们先思考另一个问题。

四、编译产物中遗漏的信息


上文我们一直在说:

  1. 对于文中例子中的Lambda表达式编译时没有生成中间类;

  2. 运行时帮我们生成了中间类;

那有个很明显的问题,编译时你没给我生成,运行时生成了;运行时它怎么知道要不要生成,生成什么样的类,你编译产物就那一个class文件,里面肯定要包含这类信息的呀?

是这么个道理。

我们再次发编译javap -v查看,在输出信息的最后:

SourceFile: “TestJavaLambda.java”

InnerClasses:

public static final #78= #77 of #81; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles

BootstrapMethods:

0: #35 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;

Method arguments:

#36 ()V

#37 invokespecial com/example/zhanghongyang/blog02/TestJavaLambda.lambda$test$0:()V

#36 ()V

果然包含一段信息,而且包含TestJavaLambda.lambda$test$0关键词。

大家不用管那么多,你就知道,文中lambda的例子,会在编译的class文件中新增一个方法lambda$test$0(),并且会携带一段信息告知JVM在运行时创建一个中间class。

其实LambdaMetafactory.metafactory正是用来生成中间class的,jdk中也有相关类可以查看,后续我们再详细说这个。

五、把中间类拿出来看看?


我们一直说运行时帮我们生成了一个中间类,类名大概为:TestJavaLambda$$Lambda$1,但是口说无凭,得拿出来大伙才信,对吧。

还好不是说我吃了两碗凉粉…

我们刚才说了JVM帮我们生成了中间类,其实java在运行的时候可以带很多参数,其中有个系统属性很神奇,我用给你们看:

java -Djdk.internal.lambda.dumpProxyClasses com.example.zhanghongyang.blog02.TestJavaLambda

懂了吧,加上这个系统属性运行,可以dump出生成的类:

在这里插入图片描述

是不是有点意思。

其实动态代理中间也会生成代理类,也可以通过类似方式导出。

然后我们看看这个类呗,这个类我们就不太在乎细节了,直接AS里面看反编译之后的:

在这里插入图片描述

真简单…

所以,本文中的例子,Lambda表达式和匿名内部类的区别还是挺大的,大家只要了解:

  1. invokedynamic可以用于lambda;

  2. Java lambda表达式的中间类并不是没有,而是在首次运行时生成的。

至于性能问题,影响应该是微乎其微的,几乎没有的。

下面有个灵魂一问:

你看这些有啥用?

毕竟我是搞Android的,其实我更在乎Android中lambda的实现,所以就先以Java Lambda为开始了,至于你问我为啥要看Android Lambda实现,毕竟现在经常要字节码插抓桩,自定义Transform,对于一些类背后的行为还是要搞清楚的。

但是,大家一定要注意,本文讲的是 Java lambda 的原理。

技术学习总结

学习技术一定要制定一个明确的学习路线,这样才能高效的学习,不必要做无效功,既浪费时间又得不到什么效率,大家不妨按照我这份路线来学习。

最后面试分享

大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

[外链图片转存中…(img-KHSCksbC-1715069216383)]

[外链图片转存中…(img-JeqisLZ5-1715069216383)]

[外链图片转存中…(img-99xEuEl6-1715069216384)]

最后面试分享

大家不妨直接在牛客和力扣上多刷题,同时,我也拿了一些面试题跟大家分享,也是从一些大佬那里获得的,大家不妨多刷刷题,为金九银十冲一波!

[外链图片转存中…(img-jQ2IDJUB-1715069216384)]

[外链图片转存中…(img-1M639JG8-1715069216384)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值