java synthetic_Java冷知识(三)编译器的花招之synthetic

我们在阅读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 = "我在内部";

}

}

编译后生成了三个文件:

SyntheticDemo$1.class

SyntheticDemo$InnerClass.class

SyntheticDemo.class

我们依次反编译一下这三个class文件

SyntheticDemo$1.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。

SyntheticDemo$InnerClass.class

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."":()V

#3 = Methodref #6.#27 // java/lang/Object."":()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

#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 // "":()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 "":()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的逻辑是获取内部类的私有属性。

SyntheticDemo.class

在看看最后一个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."":()V

#2 = Class #33 // compiler/SyntheticDemo$InnerClass

#3 = Methodref #2.#34 // compiler/SyntheticDemo$InnerClass."":(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."":()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

#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 // "":()V

#33 = Utf8 compiler/SyntheticDemo$InnerClass

#34 = NameAndType #17:#47 // "":(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."":()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."":(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."":()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."":(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."":()V

#2 = Class #20 // compiler/SyntheticDemo2$1

#3 = Class #21 // java/lang/Object

#4 = Class #22 // java/lang/Runnable

#5 = Utf8

#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 // "":()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."":()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 无关。

lambda 表达式

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."":()V

#2 = Class #23 // java/lang/Thread

#3 = InvokeDynamic #0:#28 // #0:run:()Ljava/lang/Runnable;

#4 = Methodref #2.#29 // java/lang/Thread."":(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

#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 // "":()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 // "":(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."":()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."":(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文章,请关注公众号:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值