我们在阅读JDK反射包源码时,会遇到isSynthetic()方法,其实现之一是Modifier.isSynthetic(getModifiers()),其他方式的原理一样。Modifier是专门定义修饰符的类,其中static final int SYNTHETIC = 0x00001000 表名synthetic是修饰符的一种。
我尝试用synthetic来修饰自定义的Class/Field/Method/Constructer。但是都会编译失败,理由是不认识synthetic修饰符。那么synthetic到底是怎么使用的呢?
我从Java语言规范中找到了定义:
Any constructs introduced by the compiler that do not have a corresponding construct in the source code must be marked as synthetic ,except for default constructs and the class initialization method.
意思大概是这样:任何被编译器引入的构造器在源代码中没有一个相应的构造器,那么该段代码就必须要被标记为synthetic的(复合的),除了默认的构造器和类初始化方法。
基于上面的定义,我们知道synthetic是由编译器生成的,而且synthetic有复合的、合成的意思。所以猜测是否是用在内部类中的。我们先构造一个内部类:
public class SyntheticDemo { public static void main(String[] args) { InnerClass innerObject = new InnerClass(); System.out.println("inner: " + innerObject.inner); } private static class InnerClass{ private String inner = "我在内部"; } }
编译后生成了三个文件:
我们依次反编译一下这三个class文件
javap SyntheticDemo$1.class:
Compiled from "SyntheticDemo.java" class compiler.SyntheticDemo$1 { }
javap -v SyntheticDemo$1.class
Last modified 2019-12-12; size 205 bytes MD5 checksum 942560dec8fdbc3d9b65e31e4b41295a Compiled from "SyntheticDemo.java" class compiler.SyntheticDemo$1 minor version: 0 major version: 52 flags: ACC_SUPER, ACC_SYNTHETIC Constant pool: #1 = Class #7 // compiler/SyntheticDemo$1 #2 = Class #9 // java/lang/Object #3 = Utf8 SourceFile #4 = Utf8 SyntheticDemo.java #5 = Utf8 EnclosingMethod #6 = Class #10 // compiler/SyntheticDemo #7 = Utf8 compiler/SyntheticDemo$1 #8 = Utf8 InnerClasses #9 = Utf8 java/lang/Object #10 = Utf8 compiler/SyntheticDemo { } SourceFile: "SyntheticDemo.java" EnclosingMethod: #6.#0 // compiler.SyntheticDemo InnerClasses: static #1; //class compiler/SyntheticDemo$1
这个类里面什么都没有,并且此类的flags包含ACC_SYNTHETIC。
javap SyntheticDemo$InnerClass.class:
Compiled from "SyntheticDemo.java" class compiler.SyntheticDemo$InnerClass { compiler.SyntheticDemo$InnerClass(compiler.SyntheticDemo$1); static java.lang.String access$100(compiler.SyntheticDemo$InnerClass); }
javap SyntheticDemo$InnerClass.class:
Classfile /D:/idea_workspace/mycode/target/classes/compiler/SyntheticDemo$InnerClass.class Last modified 2019-12-12; size 763 bytes MD5 checksum f13a4310236918575d26a2909aa8507e Compiled from "SyntheticDemo.java" class compiler.SyntheticDemo$InnerClass minor version: 0 major version: 52 flags: ACC_SUPER Constant pool: #1 = Fieldref #5.#26 // compiler/SyntheticDemo$InnerClass.inner:Ljava/lang/String; #2 = Methodref #5.#27 // compiler/SyntheticDemo$InnerClass."<init>":()V #3 = Methodref #6.#27 // java/lang/Object."<init>":()V #4 = String #28 // 我在内部 #5 = Class #30 // compiler/SyntheticDemo$InnerClass #6 = Class #31 // java/lang/Object #7 = Utf8 inner #8 = Utf8 Ljava/lang/String; #9 = Utf8 <init> #10 = Utf8 ()V #11 = Utf8 Code #12 = Utf8 LineNumberTable #13 = Utf8 LocalVariableTable #14 = Utf8 this #15 = Utf8 InnerClass #16 = Utf8 InnerClasses #17 = Utf8 Lcompiler/SyntheticDemo$InnerClass; #18 = Class #32 // compiler/SyntheticDemo$1 #19 = Utf8 (Lcompiler/SyntheticDemo$1;)V #20 = Utf8 x0 #21 = Utf8 Lcompiler/SyntheticDemo$1; #22 = Utf8 access$100 #23 = Utf8 (Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String; #24 = Utf8 SourceFile #25 = Utf8 SyntheticDemo.java #26 = NameAndType #7:#8 // inner:Ljava/lang/String; #27 = NameAndType #9:#10 // "<init>":()V #28 = Utf8 我在内部 #29 = Class #33 // compiler/SyntheticDemo #30 = Utf8 compiler/SyntheticDemo$InnerClass #31 = Utf8 java/lang/Object #32 = Utf8 compiler/SyntheticDemo$1 #33 = Utf8 compiler/SyntheticDemo { compiler.SyntheticDemo$InnerClass(compiler.SyntheticDemo$1); descriptor: (Lcompiler/SyntheticDemo$1;)V flags: ACC_SYNTHETIC Code: stack=1, locals=2, args_size=2 0: aload_0 1: invokespecial #2 // Method "<init>":()V 4: return LineNumberTable: line 18: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcompiler/SyntheticDemo$InnerClass; 0 5 1 x0 Lcompiler/SyntheticDemo$1; static java.lang.String access$100(compiler.SyntheticDemo$InnerClass); descriptor: (Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String; flags: ACC_STATIC, ACC_SYNTHETIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: getfield #1 // Field inner:Ljava/lang/String; 4: areturn LineNumberTable: line 18: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 x0 Lcompiler/SyntheticDemo$InnerClass; } SourceFile: "SyntheticDemo.java" InnerClasses: static #18; //class compiler/SyntheticDemo$1
SyntheticDemo$InnerClass里含有默认访问权限的构造器和access$100方法。并且这个构造器和方法都是synthetic的。构造器的参数是SyntheticDemo$1。access$100的逻辑是获取内部类的私有属性。
在看看最后一个class,javap SyntheticDemo.class:
Compiled from "SyntheticDemo.java" public class compiler.SyntheticDemo { public compiler.SyntheticDemo(); public static void main(java.lang.String[]); }
javap -v SyntheticDemo.class
Classfile /D:/idea_workspace/mycode/target/classes/compiler/SyntheticDemo.class Last modified 2019-12-12; size 1040 bytes MD5 checksum 7519ad7d2df356217e19642ed0a06ee4 Compiled from "SyntheticDemo.java" public class compiler.SyntheticDemo minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #13.#32 // java/lang/Object."<init>":()V #2 = Class #33 // compiler/SyntheticDemo$InnerClass #3 = Methodref #2.#34 // compiler/SyntheticDemo$InnerClass."<init>":(Lcompiler/SyntheticDemo$1;)V #4 = Fieldref #35.#36 // java/lang/System.out:Ljava/io/PrintStream; #5 = Class #37 // java/lang/StringBuilder #6 = Methodref #5.#32 // java/lang/StringBuilder."<init>":()V #7 = String #38 // inner: #8 = Methodref #5.#39 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #9 = Methodref #2.#40 // compiler/SyntheticDemo$InnerClass.access$100:(Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String; #10 = Methodref #5.#41 // java/lang/StringBuilder.toString:()Ljava/lang/String; #11 = Methodref #42.#43 // java/io/PrintStream.println:(Ljava/lang/String;)V #12 = Class #44 // compiler/SyntheticDemo #13 = Class #45 // java/lang/Object #14 = Class #46 // compiler/SyntheticDemo$1 #15 = Utf8 InnerClasses #16 = Utf8 InnerClass #17 = Utf8 <init> #18 = Utf8 ()V #19 = Utf8 Code #20 = Utf8 LineNumberTable #21 = Utf8 LocalVariableTable #22 = Utf8 this #23 = Utf8 Lcompiler/SyntheticDemo; #24 = Utf8 main #25 = Utf8 ([Ljava/lang/String;)V #26 = Utf8 args #27 = Utf8 [Ljava/lang/String; #28 = Utf8 innerObject #29 = Utf8 Lcompiler/SyntheticDemo$InnerClass; #30 = Utf8 SourceFile #31 = Utf8 SyntheticDemo.java #32 = NameAndType #17:#18 // "<init>":()V #33 = Utf8 compiler/SyntheticDemo$InnerClass #34 = NameAndType #17:#47 // "<init>":(Lcompiler/SyntheticDemo$1;)V #35 = Class #48 // java/lang/System #36 = NameAndType #49:#50 // out:Ljava/io/PrintStream; #37 = Utf8 java/lang/StringBuilder #38 = Utf8 inner: #39 = NameAndType #51:#52 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #40 = NameAndType #53:#54 // access$100:(Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String; #41 = NameAndType #55:#56 // toString:()Ljava/lang/String; #42 = Class #57 // java/io/PrintStream #43 = NameAndType #58:#59 // println:(Ljava/lang/String;)V #44 = Utf8 compiler/SyntheticDemo #45 = Utf8 java/lang/Object #46 = Utf8 compiler/SyntheticDemo$1 #47 = Utf8 (Lcompiler/SyntheticDemo$1;)V #48 = Utf8 java/lang/System #49 = Utf8 out #50 = Utf8 Ljava/io/PrintStream; #51 = Utf8 append #52 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #53 = Utf8 access$100 #54 = Utf8 (Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String; #55 = Utf8 toString #56 = Utf8 ()Ljava/lang/String; #57 = Utf8 java/io/PrintStream #58 = Utf8 println #59 = Utf8 (Ljava/lang/String;)V { public compiler.SyntheticDemo(); descriptor: ()V flags: ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 10: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcompiler/SyntheticDemo; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=2, args_size=1 0: new #2 // class compiler/SyntheticDemo$InnerClass 3: dup 4: aconst_null 5: invokespecial #3 // Method compiler/SyntheticDemo$InnerClass."<init>":(Lcompiler/SyntheticDemo$1;)V 8: astore_1 9: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream; 12: new #5 // class java/lang/StringBuilder 15: dup 16: invokespecial #6 // Method java/lang/StringBuilder."<init>":()V 19: ldc #7 // String inner: 21: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 24: aload_1 25: invokestatic #9 // Method compiler/SyntheticDemo$InnerClass.access$100:(Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String; 28: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 31: invokevirtual #10 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 34: invokevirtual #11 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 37: return LineNumberTable: line 14: 0 line 15: 9 line 16: 37 LocalVariableTable: Start Length Slot Name Signature 0 38 0 args [Ljava/lang/String; 9 29 1 innerObject Lcompiler/SyntheticDemo$InnerClass; } SourceFile: "SyntheticDemo.java" InnerClasses: static #14; //class compiler/SyntheticDemo$1
到这里,所有谜题都解开了。在new内部类时,调用了编译器生成的那个构造器,并传递了一个null作为参数:
5: invokespecial #3 // Method compiler/SyntheticDemo$InnerClass."<init>":(Lcompiler/SyntheticDemo$1;)V 8: astore_1
获取内部类的私有属性时,调用的编译生成的access$100方法:
invokestatic #9 // Method compiler/SyntheticDemo$InnerClass.access$100:(Lcompiler/SyntheticDemo$InnerClass;)Ljava/lang/String;
因为内部类是一个私有的,所以默认构造器也会是私有的构造器,那么外部类就无法调用此默认构造器。而我们知道Java语言中外部类是可以构造和访问内部类的,其实现就是编译器耍的花招,编译生成了一些构造器、类、方法,来帮我们把外部类和内部类私有部分连接起来,而这类编译器生成的构造器、方法、类就是synthetic标记的。也就是说这个特性是Java编译器赋予的,而其他运行在JVM上的语言则不一定有此特性,要看其对应的编译器是否也用了一些花招来做连接了。
如果我们给把InnerClass的inner域的private关键字去掉,再查字节码,发现内部类的access$100没有了,因为现在外部类可以直接访问到内部类的包访问权限的域,当然就不需要生成access$100来做桥梁了。读者可以自己试试。
如果我们把InnerClass的内部类private去掉,或者不去掉而是添加一个非private的内部类构造器,那么查看字节码发现,SyntheticDemo$1.class消失了,而且编译器也不生成带SyntheticDemo$1.class参数的包权限构造器了,因为不需要了。读者可以自行试验。
匿名内部类用到了synthetic了吗?
public class SyntheticDemo2 { public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { System.out.println("11"); } }).start(); } }
以上代码也会生成一个SyntheticDemo2$1.class。我们查看其字节码:
Classfile /D:/idea_workspace/mycode/target/classes/compiler/SyntheticDemo2$1.class Last modified 2019-12-12; size 503 bytes MD5 checksum 207c9ba26f66c2fbfd5d092c7f9f844e Compiled from "SyntheticDemo2.java" final class compiler.SyntheticDemo2$1 implements java.lang.Runnable minor version: 0 major version: 52 flags: ACC_FINAL, ACC_SUPER Constant pool: #1 = Methodref #3.#19 // java/lang/Object."<init>":()V #2 = Class #20 // compiler/SyntheticDemo2$1 #3 = Class #21 // java/lang/Object #4 = Class #22 // java/lang/Runnable #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Utf8 LineNumberTable #9 = Utf8 LocalVariableTable #10 = Utf8 this #11 = Utf8 InnerClasses #12 = Utf8 Lcompiler/SyntheticDemo2$1; #13 = Utf8 run #14 = Utf8 SourceFile #15 = Utf8 SyntheticDemo2.java #16 = Utf8 EnclosingMethod #17 = Class #23 // compiler/SyntheticDemo2 #18 = NameAndType #24:#25 // main:([Ljava/lang/String;)V #19 = NameAndType #5:#6 // "<init>":()V #20 = Utf8 compiler/SyntheticDemo2$1 #21 = Utf8 java/lang/Object #22 = Utf8 java/lang/Runnable #23 = Utf8 compiler/SyntheticDemo2 #24 = Utf8 main #25 = Utf8 ([Ljava/lang/String;)V { compiler.SyntheticDemo2$1(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 13: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcompiler/SyntheticDemo2$1; public void run(); descriptor: ()V flags: ACC_PUBLIC Code: stack=0, locals=1, args_size=1 0: return LineNumberTable: line 17: 0 LocalVariableTable: Start Length Slot Name Signature 0 1 0 this Lcompiler/SyntheticDemo2$1; } SourceFile: "SyntheticDemo2.java" EnclosingMethod: #17.#18 // compiler.SyntheticDemo2.main InnerClasses: static #2; //class compiler/SyntheticDemo2$1
字节码中没有ACC_SYNTHETIC标记,说明其与 synthetic 无关。
class SyntheticDemo3 { public static void main(String[] args) { new Thread(()->{}).start(); } }
只有一个SyntheticDemo3.class
其字节码:
Classfile /D:/idea_workspace/mycode/target/classes/compiler/SyntheticDemo3.class Last modified 2019-12-12; size 1042 bytes MD5 checksum 50722aaf911c908da83df897f5141c05 Compiled from "SyntheticDemo3.java" class compiler.SyntheticDemo3 minor version: 0 major version: 52 flags: ACC_SUPER Constant pool: #1 = Methodref #7.#22 // java/lang/Object."<init>":()V #2 = Class #23 // java/lang/Thread #3 = InvokeDynamic #0:#28 // #0:run:()Ljava/lang/Runnable; #4 = Methodref #2.#29 // java/lang/Thread."<init>":(Ljava/lang/Runnable;)V #5 = Methodref #2.#30 // java/lang/Thread.start:()V #6 = Class #31 // compiler/SyntheticDemo3 #7 = Class #32 // java/lang/Object #8 = Utf8 <init> #9 = Utf8 ()V #10 = Utf8 Code #11 = Utf8 LineNumberTable #12 = Utf8 LocalVariableTable #13 = Utf8 this #14 = Utf8 Lcompiler/SyntheticDemo3; #15 = Utf8 main #16 = Utf8 ([Ljava/lang/String;)V #17 = Utf8 args #18 = Utf8 [Ljava/lang/String; #19 = Utf8 lambda$main$0 #20 = Utf8 SourceFile #21 = Utf8 SyntheticDemo3.java #22 = NameAndType #8:#9 // "<init>":()V #23 = Utf8 java/lang/Thread #24 = Utf8 BootstrapMethods #25 = MethodHandle #6:#33 // 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; #26 = MethodType #9 // ()V #27 = MethodHandle #6:#34 // invokestatic compiler/SyntheticDemo3.lambda$main$0:()V #28 = NameAndType #35:#36 // run:()Ljava/lang/Runnable; #29 = NameAndType #8:#37 // "<init>":(Ljava/lang/Runnable;)V #30 = NameAndType #38:#9 // start:()V #31 = Utf8 compiler/SyntheticDemo3 #32 = Utf8 java/lang/Object #33 = Methodref #39.#40 // java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;L java/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #34 = Methodref #6.#41 // compiler/SyntheticDemo3.lambda$main$0:()V #35 = Utf8 run #36 = Utf8 ()Ljava/lang/Runnable; #37 = Utf8 (Ljava/lang/Runnable;)V #38 = Utf8 start #39 = Class #42 // java/lang/invoke/LambdaMetafactory #40 = NameAndType #43:#47 // metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/l ang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; #41 = NameAndType #19:#9 // lambda$main$0:()V #42 = Utf8 java/lang/invoke/LambdaMetafactory #43 = Utf8 metafactory #44 = Class #49 // java/lang/invoke/MethodHandles$Lookup #45 = Utf8 Lookup #46 = Utf8 InnerClasses #47 = Utf8 (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; #48 = Class #50 // java/lang/invoke/MethodHandles #49 = Utf8 java/lang/invoke/MethodHandles$Lookup #50 = Utf8 java/lang/invoke/MethodHandles { compiler.SyntheticDemo3(); descriptor: ()V flags: Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0 LocalVariableTable: Start Length Slot Name Signature 0 5 0 this Lcompiler/SyntheticDemo3; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=3, locals=1, args_size=1 0: new #2 // class java/lang/Thread 3: dup 4: invokedynamic #3, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable; 9: invokespecial #4 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V 12: invokevirtual #5 // Method java/lang/Thread.start:()V 15: return LineNumberTable: line 11: 0 line 12: 15 LocalVariableTable: Start Length Slot Name Signature 0 16 0 args [Ljava/lang/String; } SourceFile: "SyntheticDemo3.java" InnerClasses: public static final #45= #44 of #48; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles BootstrapMethods: 0: #25 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/Method Type;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; Method arguments: #26 ()V #27 invokestatic compiler/SyntheticDemo3.lambda$main$0:()V #26 ()V
lambda表达式没有通过内部类实现,而是通过invokedynamic这个指令解决的。有机会写一遍invokedynamic的文章。
更多Java文章,请关注公众号: