java 反射和註解
反射與 註解
1、反射(reflection)
1、反射概述
-
能夠分析類能力的程式稱為反射;
-
java 被視為準動態語言關鍵就是因為有了 反射機制;
-
java 可以通過Reflection API 取得任何類的內部資訊(包括 類名、欄位、方法、介面 ... ),並能直接操作任意物件的內部屬性及方法;
-
Class c = Class.forName("java.lang.String")
-
-
載入完類之後,會在堆記憶體的方法區中產生一個Class型別的物件(一個類只有一個Class物件),這個物件就包含了完整的類結構資訊。我們可以通過這個物件看到類的結構,這個物件就像一面鏡子,透過這個鏡子看到類的結構,所以我們稱之為:反射:
- 正常方式 : 引入需要的“包類”名稱 --> 通過new例項化 --> 取得例項物件
- 反射方式 : 例項化物件 --> getClass()方法 --> 得到完整的“包類” 名稱
2、反射的優缺
- 優點:可以實現動態建立物件和編譯,體現了很大的靈活性;
- 缺點:
- 對效能有影響:使用反射基本上是一種解釋操作,我們可以告訴jvm,我們希望做什麼並且它滿足我們的要求。這類操作總是慢於直接執行相同的操作;
- 安全性降低;
3、反射相關的主要API
- java.lang.Class : 代表一個類
- java.lang.reflect.Method : 代表類的方法
- java.lang.reflect.Field : 代表類的成員變數
- java.lang.reflect.Contructor : 代表類的構造器
- ...
public class Reflection01 { public static void main(String[] args) throws ClassNotFoundException { // 通過反射獲取類的class物件 Class c1 = Class.forName("com.object.User"); Class c2 = Class.forName("com.object.User"); Class c3 = Class.forName("com.object.User"); Class c4 = Class.forName("com.object.User"); // 列印的class 物件都是同一個物件,一個類在記憶體中只有一個class物件(儲存在方法區中) // 一個類被載入後,類的整個結構都會被封裝在class 物件中(反射物件) System.out.println(c1.hashCode()); System.out.println(c2.hashCode()); System.out.println(c3.hashCode()); System.out.println(c4.hashCode()); } } //實體類 (pojo 、 entity類) class User{ private String name; private int age; private int id; public User() { } public void setName(String name) { this.name = name; } public void setAge(int age) { this.age = age; } public void setId(int id) { this.id = id; } public String getName() { return name; } public int getAge() { return age; } public int getId() { return id; } public User(String name, int age, int id) { this.name = name; this.age = age; this.id = id; } }
3、反射機制功能
- 在執行時分析類的能力;
- 在執行時檢查物件,例如,編寫一個適用於所有類的toString 方法;
- 實現泛型陣列操作程式碼;
- 利用Method 物件,這個物件很像C++ 中的函式指標;
4、Class 類
-
Class 本身也是一個類;並且Class 物件只能由系統建立,我們只能通過方法去獲取這個物件;
-
一個類只有一個Class 物件;
-
一個Class 物件對應的是一個載入到JVM中的 .class 檔案;
-
每個例項(物件)都會 "記得" 自己是由哪個Class物件 所生成的;
-
通過Class 可以完整地得到 一個類(其他類)中所有被載入的結構;
-
Class 類是Reflection 的根源,針對任何你想動態載入、執行的類,唯有先獲得相對應的Class 物件;
-
獲取Class 物件的三種方法:
-
例項物件呼叫 .getClass() 方法可以得到一個例項物件的Class型別的物件;
Class a = person.getClass();
-
類名呼叫 .class 方法;(一般這種方式最長用,安全性最高,程式效能最高)
Class<Student> studentClass = Student.class;
-
Class.forName("類路徑") ;
Class<?> aClass1 = Class.forName("com.ze.controller.Student");
-
public class Reflection02 {
public static void main(String[] args) throws ClassNotFoundException {
Person02 p1 = new Student02();
System.out.println("這個人是:"+p1.name);
System.out.println(p1.toString());
// 一:通過物件獲取
Class p2 = p1.getClass();
// 二:通過forName 獲取
Class p3 = Class.forName("com.object.Student02");
// 三:通過類名獲取
Class p4 = Student02.class;
// 四:基本內建型別的包裝類都有一個Type 屬性
Class type = Integer.TYPE;
System.out.println(p1.hashCode());
System.out.println(p2.hashCode());
System.out.println(p3.hashCode());
System.out.println(p4.hashCode());
System.out.println(type);
// 獲得父類的型別
Class superP2 = p2.getSuperclass();
System.out.println(superP2);
}
}
// 建立一個父類
class Person02{
String name;
public Person02(String name) {
this.name = name;
}
public Person02() {
}
@Override
public String toString() {
return "Person02{" +
"name='" + name + '\'' +
'}';
}
}
// 學生子類繼承父類Person02
class Student02 extends Person02{
public Student02() {
this.name = "學生";
}
}
// 老師子類繼承父類Person02
class Teacher02 extends Person02{
public Teacher02() {
this.name = "老師";
}
}
-
Class物件 會描述一個特定類的屬性;
-
Class類方法:
- getName - 返回此Class物件所表示的實體(類,介面,陣列類或void)的名字(如果類在一個包裡,包的路徑名字也會作為類名一部分)
- getPackage - 得到這個型別的包的包名,如果這個型別是陣列型別,則返回元素型別的所屬包,如果這個型別是基本型別,則返回"java.lang";
- static ClassforName(String name) - 返回指定的類名name的Class 物件;
- Object newInstance() - 呼叫預設建構函式,返回Class物件的一個例項;
- Class getSuperClass() - 返回當前Class物件的父類的Class物件;
- Class[] getinterfaces() - 獲取當前Class物件的介面;
- ClassLoader getClassLoader() - 返回該類的類載入器;
- Constructor[] getConstructor() - 返回一個包含Constructor物件的陣列;
- Method getMothed(String name,Class.. T) - 返回一個Method物件,此物件的形參型別為param Type
- Field[] getDeclaredFields() - 返回Field 物件的一個數組;
-
哪些型別可以有Class 物件
- class:外部類,成員(成員內部類,靜態內部類),區域性內部類,匿名內部類;
- interface :介面
- [] :陣列
- enum : 列舉
- annotation : 註解@interface
- primitive type : 基本資料型別
- void
//基本所有的類都會有Class 物件
public class Reflection03 {
public static void main(String[] args) {
// Object的class類物件
Class c1 = Object.class;
// 介面的class物件
Class c2 = Comparable.class;
// 字串陣列的class物件
Class c3 = String[].class;
// 陣列的class物件
Class c4 = int[][].class;
// 註解的class 物件
Class c5 = Override.class;
// 列舉類的class 物件
Class c6 = ElementType.class;
// 基本資料包裝類的 class 物件
Class c7 = Integer.class;
// 基本資料型別的 class 物件
Class c8 = int.class;
// void的class 物件
Class c9 = void.class;
// Class類的 類物件
Class c10 = Class.class;
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
System.out.println(c9);
System.out.println(c10);
// 物件對應的類的型別一樣,那麼他們獲取的Class類物件就是同一個
int[] a = new int[10];
int[] b = new int[100];
System.out.println(a.getClass().hashCode());
System.out.println(b.getClass().hashCode());
System.out.println(a.getClass() == b.getClass());
}
}
5、類載入過程
- 類的載入(Load) : 將類的class檔案讀入記憶體,並建立一個java.lang.Class 物件,此過程由類載入器完成;
- 將class 檔案位元組碼內容載入到記憶體中,並將這些靜態資料轉換成方法區的執行時資料結構;
- 然後生成一個代表這個類的 java.lang.Class 物件;
- 類的連結 (Link): 將類的二進位制資料合併到JRE中
- 驗證:確保載入的類資訊符合JVM規範,沒有安全方面的問題;
- 準備:正式為類變數(static)分配記憶體空間,並設定類變數的初始值,這些記憶體都是在方法區進行分配;
- 解析:虛擬機器常量池內的符號引用(常量名)替換為直接引用(地址)的過程;
- 初始化 (Initialize):JVM 負責對類進行初始化
- 執行類構造器
() 方法的過程。 類構造器 () 方法是由編譯期自動收集類中所有類變數的賦值動作和靜態程式碼塊中的語句合併產生的(類構造器是構造類資訊的,不是構造該類物件的構造器); - 當初始化一個類的時候,如果發現其父類還沒有初始化,則需要先觸發父類的初始化;
- 虛擬機器會保證一個類的
() 方法在多執行緒環境中被正確加鎖和同步;
- 執行類構造器
public class Reflection04 {
public static void main(String[] args) {
AA aa = new AA();
System.out.println(AA.m); // 直接通過類呼叫類變數,是不會進行構造器的
/*
* 1、將類載入到記憶體中,會產生一個對應的Class物件;
* 2、連結,連結結束後 m = 0;
* 3、初始化:(合併靜態程式碼)
* <clinit>{
System.out.println("AA類靜態程式碼塊初始化");
m = 300;
* m = 100;
* }
* */
}
}
class AA{
public AA() {
System.out.println("AA的構造器");
}
/*
* m = 300;
* m = 100;
* */
static {
System.out.println("AA類靜態程式碼塊初始化");
m = 300;
}
static int m = 100;
}
記憶體分析圖:
6、類載入器
-
類載入器就是將class檔案位元組碼(.class 檔案)內容載入到記憶體中,並將這些靜態的資料轉換成方法區執行時資料結構,然後在堆中生成一個代表這個類的java.lang.Class 物件,作為方法區中類資料的訪問入口;
-
類載入器的分類
- 引導類載入器 - Bootstap Classloader : 是使用C++編寫的,是JVM自帶的類載入器,負責java平臺核心類庫,用來裝載核心類庫 - 該載入器無法直接獲取;
- 擴充套件類載入器 - Extension Classloader : 負責 jre/lib/ext 目錄下的jar包或 -D java.ext.dirs 指定目錄下的jar包裝入工作庫;
- 系統類載入器 - System Classloader : 負責java -classpath 或 -D java.class.path 所指定的目錄下的類與jar包裝入工作,是最常用的載入器;
- 自定義類載入器 - 使用者自定義,但是不能定義與系統類一樣的衝突類,因為java會自動檢查,使用者自定義類與java自有的類衝突,會無效,系統會自動呼叫java自有的類;
7、反射機制最重要的功能 - 檢查類結構
- Class類的 getFields 方法
- 返回這個類的公共欄位(屬性)的陣列 和父類的公員
- Class類的 getMethods 方法
- 返回這個類的公共方法的陣列 和父類的公員
- Class類的 getConstructors 方法
- 返回構造器的公共陣列 和父類的公員
- Class類的 getDeclareFields 方法
- 返回這個類或介面的所有(包括私有的成員、受保護成員、包成員)欄位(屬性)的陣列 但不包括父類的公員
- Class類的 getDeclareMethods 方法
- 返回這個類或介面的方法的所有(包括私有的成員、受保護成員、包成員)陣列 但不包括父類的公員
- Class類的 getDeclareConstructors 方法
- 返回類或介面構造器的所有(包括私有的成員、受保護成員、包成員)陣列 但不包括父類的公員
public class Reflection05 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException {
Class c1 = Class.forName("com.object.User");
// 獲得類名
System.out.println(c1.getName()); // 獲得包名+類名
System.out.println(c1.getSimpleName()); // 獲得類名
// 獲得類的屬性
System.out.println("````````````````````````````````````````````````");
Field[] fields = c1.getFields(); // 獲取public屬性,如果屬性是private的,那麼就不能獲取
for (Field field : fields) {
System.out.println(field);
}
Field[] declaredFields = c1.getDeclaredFields(); // 可以獲取全部的屬性
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
// Field name1 = c1.getField("name"); // 獲取指定 public屬性
// System.out.println(name1);
Field name2 = c1.getDeclaredField("name"); // 獲取指定屬性(private)也可以獲取
System.out.println(name2);
// 獲得類的方法
System.out.println("````````````````````````````````````````````````");
Method[] methods = c1.getMethods(); // 獲取本類和父類的所有public方法
for (Method method : methods) {
System.out.println(method);
}
Method[] declaredMethods = c1.getDeclaredMethods(); // 獲取本類的所有方法,包括private 方法
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//獲取指定的public方法
Method method1 = c1.getMethod("getName",null);
Method method2 = c1.getMethod("setName", String.class);
//獲取指定的private方法
Method getName = c1.getDeclaredMethod("getName", null);
// 獲取構造器
System.out.println("````````````````````````````````````````````````");
Constructor[] constructors = c1.getConstructors(); // 獲取全部public構造器
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
Constructor[] declaredConstructors = c1.getDeclaredConstructors(); // 獲取全部(包括private)的構造器方法
for (Constructor declaredConstructor : declaredConstructors) {
System.out.println("全部構造方法:"+declaredConstructor);
}
Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
System.out.println("獲取指定的構造器:"+constructor);
}
}
8、反射建立物件,操作物件
- 反射可以在執行時獲取物件的值
- 反射可以在執行時修改物件的值
- 如果物件的許可權是private 時候
- 反射機制預設行為受到java的訪問控制
- 但是,可以呼叫Field、Method或Constructor 物件的setAccessible 方法覆蓋 java的訪問控制;
- 如果物件的許可權是private 時候
// 通過反射獲取物件,操作構造器,屬性,方法
public class Reflection06 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException, NoSuchFieldException {
// 獲得class物件
Class c1 = Class.forName("com.object.User");
//構造一個物件
User user = (User) c1.newInstance(); // 通過newInstance方法構造物件,本質也是呼叫了user的無參構造器
System.out.println(user);
// 通過構造器建立物件
Constructor declaredConstructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
User user01 = (User) declaredConstructor.newInstance("有參構造器建立物件", 001, 18);
System.out.println(user01);
// 通過反射呼叫普通方法
User user02 = (User) c1.newInstance();
Method setName = c1.getDeclaredMethod("setName", String.class);
// 通過invoke方法啟用我們獲取的方法,引數是我們啟用的方法的物件 和 方法的引數
setName.invoke(user02,"測試方法設定");
System.out.println(user02.getName());
// 通過反射獲取物件的屬性
User user03 = (User)c1.newInstance();
Field name = c1.getDeclaredField("name");
name.setAccessible(true); // 這個方法表示開啟許可權設定(取消安全檢測);就可以操作物件的private屬性
name.set(user03,"測試欄位賦值");
System.out.println(user03.getName());
}
}
- setAccessible() 方法
- Mehtod 、Field 和 Constructor 物件都有setAccessible() 方法;
- setAccessible 作用是啟動和禁用訪問安全檢查的開關;
- 引數值為true 則表示反射的物件在使用是取消java語言訪問檢查;
- 提高了反射的效率,如果程式碼中必須用反射,該句程式碼需要頻繁的被呼叫,那麼請設定為true;
- 使原本無法訪問的私有成員也可以訪問;
- 引數值false則指示反射的物件應該實施java 語言訪問檢查;
9、使用反射操作泛型
-
java中採用泛型擦除的機制來引入泛型,java中的泛型僅僅是給編譯器javac使用的:確保資料的安全性和免去強制型別轉換的問題,但是,一旦編譯完成,所有和泛型有關的型別全部擦除;
-
為了通過反射操作這些泛型,java新增了 ParmmeterizedType,GenericArrayType,TypeVariable 和 WildcardType 幾種型別來代表不能被歸一到Class類中的型別,但是又和原始型別齊名的型別;
-
ParmmeterizedType : 表示一種引數化型別,比如Collection
-
GenericArrayType : 表示一種元素型別是引數化型別或者型別變數的陣列型別;
-
TypeVariable :是各種型別變數的公共父介面
-
WildcardType : 代表一種萬用字元型別表示式
-
// 通過反射獲取泛型
public class Reflection07 {
public static void main(String[] args) throws NoSuchMethodException {
// 通過反射獲取方法物件
Method test01 = Reflection07.class.getMethod("test01", Map.class, List.class);
// 通過方法物件獲取方法上的泛型引數型別(引數可能有多個,所以是個陣列列表)
Type[] genericParameterTypes = test01.getGenericParameterTypes();
for (Type genericParameterType : genericParameterTypes) {
System.out.println(genericParameterType);
if (genericParameterType instanceof ParameterizedType){ //判斷這個泛型的引數型別是否等於結構化引數型別
Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();// 獲取真實的引數資訊
for (Type actualTypeArgument : actualTypeArguments) { // 打印出這些引數資訊
System.out.println(actualTypeArgument);
}
}
}
Method test02 = Reflection07.class.getMethod("test02");
// 通過方法物件獲取 方法返回值的泛型引數型別
Type genericReturnType = test02.getGenericReturnType();
// 直接對返回值引數型別進行判斷(因為返回值是一個)
if (genericReturnType instanceof ParameterizedType){
// 獲取真實引數型別
Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
// 遍歷列印這些引數型別
for (Type actualTypeArgument : actualTypeArguments) {
System.out.println(actualTypeArgument);
}
}
}
// 設定泛型方法一
public void test01(Map<String,User> map, List<User> list){
System.out.println("test01");
}
// 設定泛型方法二
public Map<String,User> test02(){
System.out.println("test02");
return null;
}
}
10、反射操作註解
- getAnnotations
- getAnnotation
// 通過反射操作註解
public class Reflection08 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class c1 = Class.forName("com.object.Student03");
// 通過反射獲取類上所有的註解
Annotation[] annotations = c1.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation);
}
// 通過反射獲取類上指定的註解
Table01 annotation = (Table01) c1.getAnnotation(Table01.class);
// 通過.value()方法獲取註解上的值
String value = annotation.value();
System.out.println(value);
// 獲取屬性上的註解
System.out.println("-----------------------------");
Field f = c1.getDeclaredField("name");
// 獲取欄位上註解的物件
Field01 annotation1 = f.getAnnotation(Field01.class);
// 獲取註解的值
System.out.println(annotation1.columnName());
System.out.println(annotation1.type());
System.out.println(annotation1.length());
}
}
// 自定義類上的註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table01{
String value();
}
// 自定義屬性上的註解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Field01{
String columnName();
String type();
int length();
}
@Table01("tb_student")
class Student03{
@Field01(columnName = "db_id",type = "int",length = 10)
private int id;
@Field01(columnName = "db_age",type = "int",length = 10)
private int age;
@Field01(columnName = "db_name",type = "var char",length = 3)
private String name;
public void setId(int id) {
this.id = id;
}
public void setAge(int age) {
this.age = age;
}
public void setName(String name) {
this.name = name;
}
public int getId() {
return id;
}
public int getAge() {
return age;
}
public String getName() {
return name;
}
public Student03() {
}
public Student03(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student03{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
}
2、註解
註解 和 註釋:
-
註解 是可以給程式看;
-
註釋 只是為了給人看;
Annotation(註解)是從JDK5.0 開始引入的技術
Annotation 的作用:
- 不是程式本身,可以對程式作出解釋(這一點和註釋 comment 沒什麼區別)
- 可以被其他程式(比如:編譯器)等讀取
- 註解還可以有檢查(約束)的作用(對應註解下如果不按規則寫程式碼,就可能會報錯)
Annotation 的格式:
- 註解是以“@註釋名”在程式碼中存在的,還可以新增一些引數值,例如:
- @SuppressWarnings(value = "unchecked")
Annotation 可以在哪些地方使用?
- 可以附加在package , class , method , field 等上面 --- 相當於給他們添加了額外的輔助資訊;
- 我們可以通過反射機制程式設計實現對這些元資料的訪問;
內建註解
- @Override : 定義在java.lang.Override 中,此註解只適用於修辭方法,表示一個方法宣告打算重寫父類中的另外一個方法;
- @Deprecated : 定義在java.lang.Deprecated 中,此註解可以用於修辭方法,屬性,類,表示不鼓勵程式設計師使用這樣的元素,通常是因為 “它” 很危險,或者存在更好的選擇(比如一些已經廢棄的老方法被一些新的方式方法代替等)
- @SuppressWarning : 定義在java.lang.SuppressWarnings 中,用來抑制編譯時的警告資訊,與前兩個註解有所不同,這個註解需要新增一個引數才能正確的使用,這些引數都是已經定義好的,我們直接選擇使用:
- @SuppressWarnings("all")
- @SuppressWarnings("unchecked")
- @SuppressWarnings("value = {"unch,"deprecation"}" )
- 等等 ...
public class Annotation01 extends Object{
@Override //重寫的註解
public String toString() {
return "Annotation01{}";
}
@Deprecated // 這個註解的意思是:不推薦程式設計師使用的,但是可以使用(可能是這個方法已經被更好替代)
public void test1(){
System.out.println("Deprecated");
}
// 鎮壓警告註解 不建議平時使用
// 這個註解需要引數,不同引數鎮壓不同的警告
@SuppressWarnings("all")
public void test02(){
List list = new ArrayList();
}
}
元註解
-
元註解就是負責註解其他註解的註解(註解的註解)
-
Java 中定義了4個元註解(meta-annotation)型別,他們用於對其他註解作說明;
-
@Target : 用於描述註解的使用範圍(即被描述的註解可以使用在宣告地方)
-
@Retention : 表示需要在什麼級別儲存(有效)該註解資訊,用於描述註解的生命週期
- (SOURCE - 原始碼級別 < CLASS - 類級別 < RUNTIME - 執行時)
-
@Documented : 說明該註解將被包含在 javadoc 中
-
@Inherited :說明子類可以繼承父類中的該註解
-
public class TestAnnotation01 {
@MyAnnotation // 使用自定義的註解
public void test01(){
}
}
// 自定義註解
//(@Target 表示可以使用在類、方法、變數 等等範圍)
@Target( value = ElementType.METHOD) // 表示這個註解可以使用在方法上
// @Retention 表示我們的註解在什麼級別才有效 (SOURCE - 原始碼級別 < CLASS - 類級別 < RUNTIME - 執行時)
@Retention( value = RetentionPolicy.RUNTIME) // 表示這個自定義註解在執行時候有效,大部分自定義註解都是選擇 runtime 引數
// @Documented 表示是否將我們的註解生成在 javadoc 文件中
@Documented
// Inherited 表示子類可以繼承父類的註解
@Inherited
@interface MyAnnotation{
}
自定義註解
- @interface 用來宣告一個註解,格式:public@interface 註解名{定義的內容}
- 其中的每一個方法實際上都是宣告一個配置引數;
- 方法的名稱就是引數的名稱
- 返回值型別就是引數的型別(返回值只能是基本型別,Class , String , enum )
- 可以通過 default 來宣告引數的預設值
- 如果只有一個引數成員,一般引數名為 value
- 註解元素必須要有值,我們定義註解元素時,經常使用空字串,0作為預設值
// 自定義註解
public class Annotation02 {
// 1、如果自定義註解沒有引數,那麼註解後面不用加上引數;
// 2、如果自定義註解後面有引數,必須在註解後面加上引數;
// 3、如果自定義註解又預設引數,也可以不用加引數
@MyAnnotation01( name = "自定義註解", schools = "測試大學") // 括號裡面就是註解引數
public void test01(){
}
// 如果註解裡面只有一個value的值,那麼引數可以直接填寫,不用寫引數名;
// 注意:只用當註解中一個值,且是用value 命名的;
@MyAnnotation02("測試")
public void test02(){
}
}
@Target( {ElementType.METHOD,ElementType.TYPE})
@Retention( value = RetentionPolicy.RUNTIME)
@interface MyAnnotation01{
// 註解的引數:引數型別 + 引數名()
String name(); // 注意:這個不是方法,而是自定義註解的引數(屬性)格式就是要加小括號的;
// 設定註解引數為預設值
String test() default ""; //使用的default 而不是 = ;
int age() default 0;
int id() default -1; // 如果預設值為-1,代表不存在;
// 引數型別可以是字串陣列
String[] schools();
// 設定預設字串陣列
String[] grade() default {"一年級","二年級","三年級"};
}
@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation02{
String value();
}
- 註解的使用:
- 直接在方法、類、變數上 使用@+定義的註解名;
- 使用反射去讀取註解;