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();
@CallerSensitive
public 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 attribute
return this;
}
// if null then nest membership validation failed, so we
// act as-if we have no nest-host attribute
if (host == null || host == this) {
return this;
}
// returning a different class requires a security check
SecurityManager 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();
@CallerSensitive
public 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 VM
if (members.length > 1) {
// If we return anything other than the current class we need
// a security check
SecurityManager 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