上次講了注解的定義和自定義注解,
但是留了個問題沒有進(jìn)一步說明,就是注解所設(shè)定的數(shù)據(jù)是存在什么地方的曙搬?
這里需要引入一個新東西,類的常量池。
class的結(jié)構(gòu)
對于Java新手來說這部分可能不是很友好纵装,
class文件是java文件編譯后的字節(jié)碼征讲,對于一個class文件來說規(guī)定的結(jié)構(gòu)可以理解為一張表,
下面是class文件結(jié)構(gòu)的規(guī)定搂擦,
如果第一次接觸的話可以先忽略具體的各個項目稳诚,
總的說就是Java編譯后的字節(jié)碼按照表的規(guī)定非常嚴(yán)格的以表的結(jié)構(gòu)構(gòu)成。
對于我們要關(guān)注的問題"注解的數(shù)據(jù)存儲在哪里"來說瀑踢,
只需要關(guān)注表里面的 constant_pool 這個部分扳还,
這個稱作常量池的東西,保存了一系列的數(shù)據(jù)橱夭,分為四種
- Literal氨距,字面量
- Symbolic References,符號引用
- Others棘劣,其他
- constant pool俏让,常量
注解的數(shù)據(jù)就存在 constant pool這里。
常量池
用比較直觀的方式來理解常量池的話茬暇,最簡單便捷的方式就是看字節(jié)碼首昔,
javap 是一個查看字節(jié)碼的命令,之前多次用過它來理解Java的字節(jié)碼糙俗,
這里我們用 javap來看常量池的話可以執(zhí)行
javap -p Student.Class
輸出
Classfile /Users/zhenghui/StudioProjects/MyProject/AnnotationDemo/Student.class
Last modified 2018-7-28; size 925 bytes
MD5 checksum 6ec1e13999388ff134142418179a88d8
Compiled from "Student.java"
public class Student
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #15.#34 // java/lang/Object."<init>":()V
#2 = Fieldref #4.#35 // Student.name:Ljava/lang/String;
#3 = Fieldref #4.#36 // Student.age:I
#4 = Class #37 // Student
#5 = Methodref #4.#38 // Student."<init>":(Ljava/lang/String;I)V
#6 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream;
#7 = Class #41 // java/lang/StringBuilder
#8 = Methodref #7.#34 // java/lang/StringBuilder."<init>":()V
#9 = String #42 // name:
#10 = Methodref #7.#43 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
#11 = String #44 // age:
#12 = Methodref #7.#45 // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
#13 = Methodref #7.#46 // java/lang/StringBuilder.toString:()Ljava/lang/String;
#14 = Methodref #47.#48 // java/io/PrintStream.println:(Ljava/lang/String;)V
#15 = Class #49 // java/lang/Object
#16 = Utf8 name
#17 = Utf8 Ljava/lang/String;
#18 = Utf8 age
#19 = Utf8 I
#20 = Utf8 <init>
#21 = Utf8 (Ljava/lang/String;I)V
#22 = Utf8 Code
#23 = Utf8 LineNumberTable
#24 = Utf8 createStudent
#25 = Utf8 (Ljava/lang/String;I)LStudent;
#26 = Utf8 RuntimeVisibleAnnotations
#27 = Utf8 LSexual;
#28 = Utf8 value
#29 = Utf8 male
#30 = Utf8 displayInfo
.... //省略部分內(nèi)容
{
java.lang.String name;
descriptor: Ljava/lang/String;
flags:
int age;
descriptor: I
flags:
.... //省略部分內(nèi)容
public void displayInfo();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: new #7 // class java/lang/StringBuilder
6: dup
7: invokespecial #8 // Method java/lang/StringBuilder."<init>":()V
10: ldc #9 // String name:
12: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
15: aload_0
16: getfield #2 // Field name:Ljava/lang/String;
19: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: ldc #11 // String age:
24: invokevirtual #10 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
27: aload_0
28: getfield #3 // Field age:I
31: invokevirtual #12 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
34: invokevirtual #13 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
37: invokevirtual #14 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
40: return
LineNumberTable:
line 16: 0
line 17: 40
}
內(nèi)容比較長勒奇,就只截取其中一部分。
感興趣的話可以自己寫個簡單的類編譯一下巧骚,然后查看完整的字節(jié)碼赊颠,跟上面的大同小異。
上面的字節(jié)碼是從上一個文章中的例子里編譯來的劈彪,
在 Constant pool 這部分保存了我們注解的內(nèi)容竣蹦,關(guān)注
#24 - #29 的內(nèi)容,
這里就是注解所攜帶的信息存放的地方了沧奴。
這里用了一個RuntimeVisibleAnnotations作為標(biāo)注痘括,對應(yīng)注解中的RUNTIME標(biāo)記。
可能跟你一開始理解的不同滔吠,現(xiàn)在應(yīng)該明白远寸,注解的信息并不保存在方法的執(zhí)行棧中,而是在一個叫常量池的地方獨立保存起來屠凶。
關(guān)于class的文件結(jié)構(gòu)可以說很長的篇幅驰后,
比如魔數(shù),比如最大最小版本矗愧,
可能做過gradle插件的同學(xué)會遇到"major.minor version 52.0"這么個問題灶芝,
原因是在低版本的java上使用了高版本的插件導(dǎo)致的郑原,這個 version 52就定義在class文件的 major version 字段。
如果打算進(jìn)階資深Java開發(fā)的話可以仔細(xì)弄清楚這一塊的知識哦夜涕。