synthetic关键字和NBAC机制
1、synthetic关键字
根据Java语言规范,所有存在于字节码文件中,但是不存在于源代码文件中的构造,都应该被synthetic关键字标注
这里的构造,原文是Constructs,实际上指的是字段、方法和构造器([构造] => Constructs => Field、Method、Constructor
)
由Java编译器在编译阶段自动生成的构造都要被synthetic关键字标注
1)、isSynthetic()
java.lang.reflect
中的Field、Method、Constructor都实现java.lang.reflect.Member
接口,该接口中有一个isSynthetic()
方法,用来判断这个构造是否被synthetic关键字标注
来看下该方法的注释:
public
interface Member {/*** Returns {@code true} if this member was introduced by* the compiler; returns {@code false} otherwise.** @return true if and only if this member was introduced by* the compiler.* @jls 13.1 The Form of a Binary* @since 1.5*/public boolean isSynthetic();
如果该成员(构造)是由编译器生成的,则返回true
下面通过几个案例来看下编辑器什么情况下会在编译阶段自动生成构造并标注synthetic关键字
注意:下面的案例要在JDK 11之前的版本下执行
2)、Field
public class FiledDemo {class FiledDemoInner {}}
public class Main {public static void main(String[] args) {fieldDemo();}public static void fieldDemo() {Field[] fields = FiledDemo.FiledDemoInner.class.getDeclaredFields();for (Field field : fields) {System.out.println(field.getName() + " " + field.isSynthetic());}}}
运行结果:
this$0 true
也就是说编译器在FiledDemoInner中生成了一个this$0的字段并标记为synthetic
public class FiledDemo {public String hello() {return "hello";}class FiledDemoInner {// 根据Java语法要求,要调用某一个类的实例方法,那就一定要持有一个方法所在的类的实例public void sayHello() {System.out.println(hello());}}}
要在FiledDemoInner中调用FiledDemo的方法就一定要持有FiledDemo的实例
一个内部类经过编译之后也是一个独立的类,相当于有A、B两个类,B类想要调用A类的方法但又没有A类的实例,这肯定调用不了
为了解决内部类调用外部类资源的问题,Java编译器生成了this$0这个属性,实际就代表FiledDemo的实例
那我们再看看类FiledDemoInner通过javac
编译生成的class文件
class FiledDemo$FiledDemoInner {FiledDemo$FiledDemoInner(FiledDemo var1) {this.this$0 = var1;}public void sayHello() {System.out.println(this.this$0.hello());}
}
3)、Method
public class MethodDemo {class MethodDemoInner {private String innerName;}public void setInnerName(String name) {new MethodDemoInner().innerName = name;}public String getInnerName() {return new MethodDemoInner().innerName;}}
public class Main {public static void main(String[] args) {methodDemo();}public static void methodDemo() {Method[] methods = MethodDemo.MethodDemoInner.class.getDeclaredMethods();for (Method method : methods) {System.out.println(method.getName() + " " + method.isSynthetic());}}}
运行结果:
access$000 true
access$002 true
MethodDemoInner中有两个synthetic方法:access$000和access$002
外部类通过内部类的实例直接访问private的字段
public class MethodDemo {class MethodDemoInner {private MethodDemo this$0;private String innerName;public void access$002(String name) {this.innerName = name;}public String access$000() {return this.innerName;}}public void setInnerName(String name) {new MethodDemoInner().access$002(name);//new MethodDemoInner().innerName = name;}public String getInnerName() {return new MethodDemoInner().access$000();//return new MethodDemoInner().innerName;}}
编译器生成了access$000和access$002两个方法,相当于innerName的get和set方法,会把赋值操作new MethodDemoInner().innerName = name
转换为new MethodDemoInner().access$002(name)
,把取值操作new MethodDemoInner().innerName
转换为new MethodDemoInner().access$000()
给内部类生成这两个synthetic方法实际上是为了满足编译器对Java语法的规范,虽然说在开发源代码的时候可以破坏语法要求来编写代码(外部类访问内部类的private属性),但是编译器是不认可的(总不能在编译器里if else去判断,如果是内部类走一套规则,不是内部类走另一套规则吧,这样就会有很多分支判断)。编译器在内部类帮我们生成好这两个方法,在把外部类的调用转换为标准的调用,这样执行编译的时候,编译器看到的语法就是正确的,编译器只需要一套逻辑就可以解决问题
4)、Constructor
public class ConstructorDemo {public ConstructorDemoInner inner = new ConstructorDemoInner();class ConstructorDemoInner {private ConstructorDemoInner() {}}}
public class Main {public static void main(String[] args) {constructorDemo();}public static void constructorDemo() {Constructor<?>[] constructors = ConstructorDemo.ConstructorDemoInner.class.getDeclaredConstructors();for (Constructor<?> constructor : constructors) {System.out.print(constructor.getName() + " " + constructor.isSynthetic() + " ");System.out.print(constructor.getModifiers() + " ");System.out.println(Modifier.toString(constructor.getModifiers()));}}}
运行结果:
com.ppdai.synthetic.ConstructorDemo$ConstructorDemoInner false 2 private
com.ppdai.synthetic.ConstructorDemo$ConstructorDemoInner true 4096
编译器为内部类生成了一个default的synthetic的构造器(4096表示synthetic),解决了外部类要new内部类的实例,但是内部类的构造器是private的问题
2、NBAC机制
1)、synthetic关键字产生的问题
synchetic其实就是通过编译器来帮我们解决了内部类中对其外部类的字段或方法访问控制的问题,但是在某些情况下会出现问题
注意:下面的案例要在JDK 11之前的版本下执行
public class Outer {public void outerPublic() {new Inner().innerPublic();}public void outerPublic2() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {new Inner().reflectOuter(new Outer());}private void outerPrivate() {System.out.println("outerPrivate");}class Inner {public void innerPublic() {outerPrivate();}public void reflectOuter(Outer ob) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {Method method = ob.getClass().getDeclaredMethod("outerPrivate");method.invoke(ob);}}}
内部类Inner中定义了两个方法:innerPublic()
方法是正常调用外部类Outer
私有的outerPrivate()
方法,reflectOuter()
方法则是通过反射调用相同方法
public class Main {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {new Outer().outerPublic();new Outer().outerPublic2();}}
运行结果:
outerPrivate
Exception in thread "main" java.lang.IllegalAccessException: Class com.ppdai.nbnc.Outer$Inner can not access a member of class com.ppdai.nbnc.Outer with modifiers "private"at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)at java.lang.reflect.AccessibleObject.slowCheckMemberAccess(AccessibleObject.java:296)at java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:288)at java.lang.reflect.Method.invoke(Method.java:491)at com.ppdai.nbnc.Outer$Inner.reflectOuter(Outer.java:37)at com.ppdai.nbnc.Outer.outerPublic2(Outer.java:17)at com.ppdai.nbnc.Main.main(Main.java:20)
发现outerPublic()
方法没有问题,但是outerPublic2()
却执行报错了,可能你会想到这里的反射调用并没有添加method.setAccessible(true)
来允许访问,加了之后肯定运行没问题。确实如此,但是仔细想想,outerPublic()
方法是可以直接调用外部类的私有方法,那反射是不是也应该可以直接访问
在内部类里面存在同一个方法的不同调用方式呈现不同结果的情况,如果调用外部类的一个private方法
- 直接调用:不报错
- 反射调用:报错
2)、什么是NBAC
NBAC全称是Nested Based Access Controll,翻译过来就是基于嵌套的访问控制,这种机制是JDK 11版本才出现的,而它其实在某些方面来说是对JDK中synthetic的一种完善补充,在编译时也会对之前synthetic的代码合成产生一些影响
同样是上面问题的Demo,如果你将JDK的编译版本切换到1.11的话,再运行测试的main方法,outerPublic()
和outerPublic2()
方法都是不会报错
通过引入NBAC机制解决了在内部类中通过反射和直接调用外部类private方法二义性的问题
NBAC相应的一些Java API是由Class类定义实现的,源码如下:
public final class Class<T> implements java.io.Serializable,GenericDeclaration,Type,AnnotatedElement {private native Class<?> getNestHost0();@CallerSensitivepublic Class<?> getNestHost() {if (isPrimitive() || isArray()) {return this;}Class<?> host;try {host = getNestHost0();} catch (LinkageError e) {// if we couldn't load our nest-host then we// act as-if we have no nest-host attributereturn this;}// if null then nest membership validation failed, so we// act as-if we have no nest-host attributeif (host == null || host == this) {return this;}// returning a different class requires a security checkSecurityManager sm = System.getSecurityManager();if (sm != null) {checkPackageAccess(sm,ClassLoader.getClassLoader(Reflection.getCallerClass()), true);}return host;}public boolean isNestmateOf(Class<?> c) {if (this == c) {return true;}if (isPrimitive() || isArray() ||c.isPrimitive() || c.isArray()) {return false;}try {return getNestHost0() == c.getNestHost0();} catch (LinkageError e) {return false;}}private native Class<?>[] getNestMembers0();@CallerSensitivepublic Class<?>[] getNestMembers() {if (isPrimitive() || isArray()) {return new Class<?>[] { this };}Class<?>[] members = getNestMembers0();// Can't actually enable this due to bootstrapping issues// assert(members.length != 1 || members[0] == this); // expected invariant from VMif (members.length > 1) {// If we return anything other than the current class we need// a security checkSecurityManager sm = System.getSecurityManager();if (sm != null) {checkPackageAccess(sm,ClassLoader.getClassLoader(Reflection.getCallerClass()), true);}}return members;}
Outer和内部类Inner,Inner的nestHost是Outer.class,Outer的nestMembers是Inner.class+自身
Inner => nestHost = Outer.class
Outer => nestMembers = {Inner.class,Outer.class}
public class Main {public static void main(String[] args) {System.out.println("Inner的嵌套宿主:" + Outer.Inner.class.getNestHost().getName());System.out.println("Outer的嵌套成员:");for (Class<?> nestMember : Outer.class.getNestMembers()) {System.out.println(nestMember.getName());}System.out.println("Inner的嵌套成员:");for (Class<?> nestMember : Outer.Inner.class.getNestMembers()) {System.out.println(nestMember.getName());}System.out.println("Inner和Outer是NestMate吗? " + Outer.Inner.class.isNestmateOf(Outer.class));}}
运行结果:
Inner的嵌套宿主:com.ppdai.nbnc.Outer
Outer的嵌套成员:
com.ppdai.nbnc.Outer
com.ppdai.nbnc.Outer$Inner
Inner的嵌套成员:
com.ppdai.nbnc.Outer
com.ppdai.nbnc.Outer$Inner
Inner和Outer是NestMate吗? true
通过保存类的嵌套宿主类以及嵌套成员类的引用,这样去访问成员类的属性和方法时,就可以直接通过保存的对象引用去访问,某些情况下可以替代synthetic合成代码的方式来进行访问
在JDK11下执行讲解synthetic中的案例MethodDemo,发现并不会生成synthetic方法了
参考:
https://www.bilibili.com/video/BV1dy4y1V7ck?p=8
https://www.bilibili.com/video/BV1dy4y1V7ck?p=9
synthetic关键字和NBAC机制相关推荐
- java synthetic_浅谈Java编程中的synthetic关键字
导读 正文 java synthetic关键字.有synthetic标记的field和method是class内部使用的,正常的源代码里不会出现synthetic field.小颖编译工具用的就是ja ...
- Java的synchronized关键字:同步机制总结
不久前用到了同步,现在回过头来对JAVA中的同步做个总结,以对前段时间工作的总结和自我技术的条理话.JAVA的synchronized关键字能够作为函数的修饰符,也可作为函数内的语句,也就是平时说的同 ...
- java synthetic_Java 中冷门的 synthetic 关键字原理解读
看JAVA的反射时,看到有个synthetic ,还有一个方法isSynthetic() 很好奇,就了解了一下: 1.定义 Any constructs introduced by a Java co ...
- java的synthetic_java synthetic关键字
有synthetic标记的field和method是class内部使用的,正常的源代码里不会出现synthetic field.小颖编译工具用的就是jad.所有反编译工具都不能保证完全正确地反编译cl ...
- java synthetic_Java synthetic 关键字原理解读
看Java的反射时,看到有个synthetic ,还有一个方法isSynthetic() 很好奇,就了解了一下: 1.定义 Any constructs introduced by a Java co ...
- synthetic Java合成类型
Synthetic 看Class源码的时候,看到有个关键字Synthetic以及isSynthetic()方法,遂有兴趣查阅了一番. 一开始以为,就是复合类型(引用类型),也就是非基本类型,可后来看到 ...
- java线程锁机制_多线程之锁机制
前言 在Java并发编程实战,会经常遇到多个线程访问同一个资源的情况,这个时候就需要维护数据的一致性,否则会出现各种数据错误,其中一种同步方式就是利用Synchronized关键字执行锁机制,锁机制是 ...
- Java并发编程-volatile关键字介绍
前言 要学习好Java的多线程,就一定得对volatile关键字的作用机制了熟于胸.最近博主看了大量关于volatile的相关博客,对其有了一点初步的理解和认识,下面通过自己的话叙述整理一遍. 有什么 ...
- Android Handler机制(一) 为什么设计Handler
一. 前言 做安卓开发也有好几年了, 一直在用Hanlder, 都是为了做项目功能实现就完了,核心原理研究的不深,平时有什么问题都是看下博客也能够解决,积累的都是零散的,过段时间忘了又得重新去翻, A ...
最新文章
- web静态资源访问规则||webjars的访问配置——webjars是maven库里面对css js image打的一个jar包
- 4.类型设计规范《.NET设计规范》
- 你必须知道的session与cookie
- parseInt 的使用方式,基数表达
- Uniswap V3的流通性突破5亿美元,24小时交易量仅次于V2和Sushiswap
- @扎克伯格:一句对不起,能挽回我们泄漏的数据吗?
- javascript简单性能问题及学习笔记
- 数据清洗(根据元素匹配选取数据)
- VisualDiffer for Mac(文件对比利器)支持m1
- 26 伪造ICMP数据包
- YYF根据学生编号查询学生签到状态
- Splunk 模式的中国践行者——日志易让日志分析更容易
- Python制作登陆界面(1)(超简单)
- Interview Tips with Consulting Firms
- 北京专精特新企业申报攻略
- 如何使用CentOS linux来搭建u-email邮件服务器(AIIP全国技能大赛企业联合教程)
- 【预测师】的时间管理方法论(泰山版)
- ssm报错:Invalid bound statement (not found): mapper.UserMapper.findAllUser
- 后台传给前台的进度条传百分比数据
- 牛客_求将一个数组分割为两个差值最小的部分
热门文章
- Java StringTokenizer 类与示例
- 跟老齐学python轻松入门pdf_跟老齐学Python:轻松入门pdf
- Markdown语法写博客
- 沈志云院士做客“妙语茶香”
- 【JZOJ5222】【GDOI2018模拟7.12】A
- php 读取 js json格式数据,js读取和解析JSON数据的方法
- window cmd 创建文件以及文件夹
- python 从同花顺获取数据导出,通达信PYTHON读取本地数据,如何使用python在文件中读取数据?...
- 行动瑜伽:何谓不执的行动
- 【python】欧姆龙智能物料排布之二维装箱算法实现