注解與反射
- 1.反射的概述
- 2.反射基本使用
- 1.獲取Class的三種方法
- 2.通過反射獲取構造方法
- 3.通過反射獲取到屬性欄位
- 4.通過反射獲取方法
- 3.反射main方法
- 4.使用反射讀取組態檔,呼叫方法
- 5.使用反射跳過泛型機制檢測
- 6.注解
- 1.注解概念
- 2.jdk自帶注解常用
- 3.自定義開發注解
- 4.元注解
- 5.注解+反射案例
- 1.模仿Spring的自動裝配功能
- 2.獲取欄位上注解的屬性值
- 7.列舉
- 用法一:常量
- 用法二:列舉
- 用法三:列舉類中添加方法
- 用法四:使用介面組織列舉
1.反射的概述
JAVA反射機制是在運行狀態中,對于任意一個類,都能夠知道這個類的所有屬性和方法;對于任意一個物件,都能夠呼叫它的任意一個方法和屬性;這種動態獲取的資訊以及動態呼叫物件的方法的功能稱為java語言的反射機制,
要想解剖一個類,必須先要獲取到該類的位元組碼檔案物件,而解剖使用的就是Class類中的方法.所以先要獲取到每一個位元組碼檔案對應的Class型別的物件.
反射是比較重要的一個知識,現在的大多數框架都使用得到了反射+注解的形式,對我們的程式進行封裝,讓我們開箱即用,大大的減少了我們的開發時間,提升了我們的開發效率,使用反射+注解使得我們的程式變得更加的靈活多變
反射就是把java類中的屬性和方法映射成與之對應的Java物件,一個類有:成員變數、方法、構造方法、包等等資訊,利用反射技術可以對一個類進行解剖,把這些組成一個完整類的成員,拆分為一個一個的java物件,


Class 類的實體表示正在運行的 Java 應用程式中的類和介面,也就是說jvm中每個實體都應該屬于某個Class物件與之對應,(包括基本資料型別)
Class 沒有公共構造方法,Class 物件是在加載類時由 Java 虛擬機以及通過呼叫類加載器中的defineClass 方法自動構造的,也就是這不需要我們自己去處理創建,JVM已經幫我們創建好了,
**我們來看下Class類的一些方法:**當然太多,等下挑常用的講
| Modifier and Type | Method and Description |
|---|---|
<U> 類<? extends U> | asSubclass(類<U> clazz) 類這個 類物件來表示由指定的類物件表示的類的子類, |
T | cast(Object obj) 施放一個目的是通過本表示的類或介面 類物件, |
boolean | desiredAssertionStatus() 如果要在呼叫此方法時初始化該類,則回傳將分配給此類的斷言狀態, |
static 類<?> | forName(String className) 回傳與給定字串名稱的類或介面相關聯的 類物件, |
static 類<?> | forName(String name, boolean initialize, ClassLoader loader) 使用給定的類加載器回傳與給定字串名稱的類或介面相關聯的 類物件, |
AnnotatedType[] | getAnnotatedInterfaces() 回傳一個 AnnotatedType物件的陣列, AnnotatedType使用型別指定由此 AnnotatedType物件表示的物體的超級 類 , |
AnnotatedType | getAnnotatedSuperclass() 回傳一個 AnnotatedType物件,該物件表示使用型別來指定由此 類物件表示的物體的 類類, |
<A extends Annotation>A | getAnnotation(類<A> annotationClass) 回傳該元素的,如果這樣的注釋 *,*否則回傳null指定型別的注釋, |
Annotation[] | getAnnotations() 回傳此元素上 存在的注釋, |
<A extends Annotation>A[] | getAnnotationsByType(類<A> annotationClass) 回傳與此元素相關 聯的注釋 , |
String | getCanonicalName() 回傳由Java語言規范定義的基礎類的規范名稱, |
類<?>[] | getClasses() 回傳包含一個陣列 類表示所有的公共類和由此表示的類的成員介面的物件 類物件, |
ClassLoader | getClassLoader() 回傳類的類加載器, |
類<?> | getComponentType() 回傳 類陣列的組件型別的Class, |
Constructor<T> | getConstructor(類<?>... parameterTypes) 回傳一個 Constructor物件,該物件反映 Constructor物件表示的類的指定的公共 類函式, |
Constructor<?>[] | getConstructors() 回傳包含一個陣列 Constructor物件反射由此表示的類的所有公共構造 類物件, |
<A extends Annotation>A | getDeclaredAnnotation(類<A> annotationClass) 如果這樣的注釋 直接存在 ,則回傳指定型別的元素注釋,否則回傳null, |
Annotation[] | getDeclaredAnnotations() 回傳 直接存在于此元素上的注釋, |
<A extends Annotation>A[] | getDeclaredAnnotationsByType(類<A> annotationClass) 如果此類注釋 直接存在或 *間接存在,*則回傳該元素的注釋(指定型別), |
類<?>[] | getDeclaredClasses() 回傳一個反映所有被這個 類物件表示的類的成員宣告的類和 類物件的陣列, |
Constructor<T> | getDeclaredConstructor(類<?>... parameterTypes) 回傳一個 Constructor物件,該物件反映 Constructor物件表示的類或介面的指定 類函式, |
Constructor<?>[] | getDeclaredConstructors() 回傳一個反映 Constructor物件表示的類宣告的所有 Constructor物件的陣列 類 , |
Field | getDeclaredField(String name) 回傳一個 Field物件,它反映此表示的類或介面的指定已宣告欄位 類物件, |
Field[] | getDeclaredFields() 回傳的陣列 Field物件反映此表示的類或介面宣告的所有欄位 類物件, |
方法 | getDeclaredMethod(String name, 類<?>... parameterTypes) 回傳一個 方法物件,它反映此表示的類或介面的指定宣告的方法 類物件, |
方法[] | getDeclaredMethods() 回傳包含一個陣列 方法物件反射的類或介面的所有宣告的方法,通過此表示 類物件,包括公共,保護,默認(包)訪問和私有方法,但不包括繼承的方法, |
類<?> | getDeclaringClass() 如果由此 類物件表示的類或介面是另一個類的成員,則回傳表示其宣告的類的 類物件, |
類<?> | getEnclosingClass() 回傳呼層類的即時封閉類, |
Constructor<?> | getEnclosingConstructor() 如果此類物件表示建構式中的本地或匿名類,則回傳表示底層類的立即封閉建構式的Constructor物件, |
方法 | getEnclosingMethod() 如果此類物件表示方法中的本地或匿名類,則回傳表示基礎類的即時封閉方法的方法物件, |
T[] | getEnumConstants() 回傳此列舉類的元素,如果此Class物件不表示列舉型別,則回傳null, |
Field | getField(String name) 回傳一個 Field物件,它反映此表示的類或介面的指定公共成員欄位 類物件, |
Field[] | getFields() 回傳包含一個陣列 Field物件反射由此表示的類或介面的所有可訪問的公共欄位 類物件, |
Type[] | getGenericInterfaces() 回傳 Type表示通過由該物件所表示的類或介面直接實作的介面秒, |
Type | getGenericSuperclass() 回傳 Type表示此所表示的物體(類,介面,基本型別或void)的直接超類 類 , |
類<?>[] | getInterfaces() 確定由該物件表示的類或介面實作的介面, |
方法 | getMethod(String name, 類<?>... parameterTypes) 回傳一個 方法物件,它反映此表示的類或介面的指定公共成員方法 類物件, |
方法[] | getMethods() 回傳包含一個陣列 方法物件反射由此表示的類或介面的所有公共方法 類物件,包括那些由類或介面和那些從超類和超介面繼承的宣告, |
int | getModifiers() 回傳此類或介面的Java語言修飾符,以整數編碼, |
String | getName() 回傳由 類物件表示的物體(類,介面,陣列類,原始型別或空白)的名稱,作為 String , |
軟體包 | getPackage() 獲取此類的包, |
ProtectionDomain | getProtectionDomain() 回傳 ProtectionDomain , |
URL | getResource(String name) 查找具有給定名稱的資源, |
InputStream | getResourceAsStream(String name) 查找具有給定名稱的資源, |
Object[] | getSigners() 獲得這個類的簽名者, |
String | getSimpleName() 回傳源代碼中給出的基礎類的簡單名稱, |
類<? super T> | getSuperclass() 回傳 類表示此所表示的物體(類,介面,基本型別或void)的超類 類 , |
String | getTypeName() 為此型別的名稱回傳一個內容豐富的字串, |
TypeVariable<類<T>>[] | getTypeParameters() 回傳一個 TypeVariable物件的陣列,它們以宣告順序表示由此 GenericDeclaration物件表示的通用宣告宣告的型別變數, |
boolean | isAnnotation() 如果此 類物件表示注釋型別,則回傳true, |
boolean | isAnnotationPresent(類<? extends Annotation> annotationClass) 如果此元素上 存在指定型別的注釋,則回傳true,否則回傳false, |
boolean | isAnonymousClass() 回傳 true當且僅當基礎類是匿名類時, |
boolean | isArray() 確定此 類物件是否表示陣列類, |
boolean | isAssignableFrom(類<?> cls) 確定由此 類物件表示的類或介面是否與由指定的Class 類表示的類或介面相同或是超類或 類介面, |
boolean | isEnum() 當且僅當該類在源代碼中被宣告為列舉時才回傳true, |
boolean | isInstance(Object obj) 確定指定的Object是否與此 Object表示的物件分配 類 , |
boolean | isInterface() 確定指定 類物件表示介面型別, |
boolean | isLocalClass() 回傳 true當且僅當基礎類是本地類時, |
boolean | isMemberClass() 回傳 true當且僅當基礎類是成員類時, |
boolean | isPrimitive() 確定指定 類物件表示一個基本型別, |
boolean | isSynthetic() 如果這個類是一個合成類,回傳true ; 回傳false其他, |
T | newInstance() 創建由此 類物件表示的類的新實體, |
String | toGenericString() 回傳描述此 類的字串,包括有關修飾符和型別引數的資訊, |
String | toString() 將物件轉換為字串, |
2.反射基本使用
User:案例類
package test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Author: compass
* @Date: 2021-10-23-0:00
* @Version:1.0
* @Description:
*/
public class User {
private int id;
private int age;
private String name;
private String sex;
public static List<User> getUserList(){
List<User> userList = new ArrayList<>();
userList.add(new User(1,29,"小明","男"));
userList.add(new User(2,21,"小王","男"));
userList.add(new User(3,18,"小李","女"));
userList.add(new User(4,23,"小華","女"));
userList.add(new User(7,50,"小花","女"));
userList.add(new User(5,50,"小k","男"));
userList.add(new User(6,60,"小浪","女"));
return userList;
}
public User(int id, int age, String name, String sex) {
this.id = id;
this.age = age;
this.name = name;
this.sex=sex;
}
public User(int id, int age, String name) {
this.id = id;
this.age = age;
this.name = name;
}
public User( String name) {
this.name = name;
}
public User(int id, String name) {
this.id=id;
this.name = name;
}
public User() {
}
public String getSex() {
return sex;
}
public void setSex(int id) {
this.sex = sex;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", age=" + age +
", name='" + name + '\'' +
'}';
}
/**
* 獲取一個User物件
* @return
*/
public User getUser(){
return new User(1,29,"小明","男");
}
}
1.獲取Class的三種方法
// 1.使用Object的方法getClass(),Object是所有類的父類,那么任何一個物件,都應該有getClass()方法
User u = new User();
Class<? extends User> uClass = u.getClass();
//2.任何資料型別都包含了一個class屬性,型別.class的方式
Class<User> aClass = User.class;
//3.常用的 Class.classFrom(String arg),傳遞一個String型別的從類路徑到該類的具體路徑
// Class.forName("test.User") : 這個代碼會導致我們的靜態代碼塊加載,如果只是想執行某個類的靜態代碼塊,那么可以使用該方法
Class<?> bClass = Class.forName("test.User");
獲取到某個類的Class物件有什么用?
我們可以通過該類的class物件,通過newInstance() 方法對該類進行實體化,不用我們手動去new,注意該方法,默認呼叫的是無參構造方法,如果一個類沒有任何構造方法,默認有一個無參構造,如果你重寫了有參構造,記得無參構造一定要帶上,否則newInstance() 方法將創建物件失敗,
User user = (User)bClass.newInstance();
2.通過反射獲取構造方法
@Test
void test1() throws Exception{
// 獲取共有的構造方法(public進行修飾的方法),并且回傳一個 Constructor陣列
Constructor<?>[] constructors = User.class.getConstructors();
for (Constructor constructor: constructors){
System.out.println(constructor);
}
System.out.println("----------------------------------------------");
// 獲取指定某個構造方法,不傳遞任何引數,默認獲取無參構造,如果要獲取有參的需要跟上引數型別,必須和構造方法中引數順序一致
Constructor<User> constructor1 = User.class.getConstructor(); // 獲取無參構造
System.out.println("無參構造="+constructor1);
System.out.println("----------------------------------------------");
Constructor<User> constructor2 = User.class.getConstructor(String.class); // 獲取有參構造 User(String name)
System.out.println("有參構造="+constructor2);
System.out.println("----------------------------------------------");
// 獲取私有、受保護、默認、公有的構造方法,并且回傳一個 Constructor陣列
Constructor<?>[] dc1 = User.class.getDeclaredConstructors();
for (int i = 0; i < dc1.length; i++) {
System.out.println("private="+dc1[i]);
}
System.out.println("----------------------------------------------");
// 獲取私有、受保護、默認、公有的構造方法,獲取單個的(自定義)
Constructor dc2 = User.class.getDeclaredConstructor(int.class,String.class);
System.out.println(dc2);
// 如果該構造方法是私有的,不能直接進行訪問,需要先設定為可訪問的
if (!dc2.isAccessible()){ // 先判斷該方法是否可以進行訪問,如果不可以,進行修改,設定為可訪問的
dc2.setAccessible(true); // 將private修飾的方法修飾為可訪問的
}
// 呼叫 獲取到的構造方法,可以跟引數(不過要和獲取到的構造方法中的引數對應,否則拋出:IllegalArgumentException)
User user1 = (User)dc2.newInstance(1, "張三");
System.out.println("name="+user1.getName());
}
3.通過反射獲取到屬性欄位
@Test // 通過class物件獲取到屬性
void test2() throws Exception{
// 獲取到User類中的所有(公共)屬性 ,回傳一個 Field[]陣列
Field[] fields = User.class.getFields();
for (Field field : fields) {
System.out.println("filed = "+field);
}
System.out.println("----------------------------------------");
// 獲取到User類中單個屬性,回傳一個Field物件
Field emailFiled = User.class.getField("email");
System.out.println("emailFiled = "+emailFiled);
System.out.println("----------------------------------------");
// 獲取到User類中的所有屬性(私有、受保護、默認)
Field[] df1 = User.class.getDeclaredFields();
for (Field field : df1) {
System.out.println("field = "+field);
}
System.out.println("----------------------------------------");
// 獲取到User類中的單個的屬性(私有、受保護、默認)
Field emailAttributes = User.class.getDeclaredField("email");
System.out.println("emailAttributes ="+emailAttributes);
System.out.println("----------------------------------------");
// 通過反射實體化一個物件,并且獲取到該類中的欄位(email),并且給其賦值
Class<User> userClass = User.class; // 獲得一個User.class物件
User user = userClass.newInstance();
Field[] declaredFields = userClass.getDeclaredFields(); // 獲取到user類中所有的屬性
for (int i = 0; i < declaredFields.length; i++) { // 遍歷User類中所有的屬性,找到email屬性,為其賦值
if (declaredFields[i].toString().contains("email")){
declaredFields[i].setAccessible(true);//設定為可訪問的,以免程式出錯
declaredFields[i].set(user,"admin@qq.com");
}
}
System.out.println(user.email);
}
4.通過反射獲取方法
@Test // 同過class物件獲取到類中的所有方法,以及呼叫方法
void test3() throws Exception{
// 獲取到User類中所有的public方法,包括父類的
Method[] methods = User.class.getMethods();
for (Method method : methods) {
System.out.println(" method = "+method);
}
System.out.println("----------------------------------------");
// 獲取到User類中單個的public方法,包括父類的
Method equals = User.class.getMethod("equals", Object.class);
System.out.println("equals ="+equals);
System.out.println("----------------------------------------");
// 獲取到User類中所有的(私有、受保護、默認)方法
Method[] methods2 = User.class.getDeclaredMethods();
for (Method method : methods2) {
System.out.println(" method = "+method);
}
System.out.println("----------------------------------------");
// 獲取到User類中單個的(私有、受保護、默認)方法
Method select = User.class.getDeclaredMethod("select", int.class);
System.out.println("select ="+select);
// 獲取到某個類中的方法,并且查詢到select方法,然后執行
// invoke(呼叫物件,引數) :注意引數一定要匹配
Class<User> userClass = User.class;
User user = userClass.newInstance();
Method[] dm = userClass.getDeclaredMethods();
for (int i = 0; i < dm.length; i++) { // 這里怎么多條件只是練習下api而已
if (dm[i].getName().equals("select")&&dm[i].getParameterCount()==1&& dm[i].getReturnType().getName().equals("java.lang.String")){
//if (dm[i].toString().contains("select(int)")) // 這樣寫也是ok的
dm[i].setAccessible(true); //設定為可訪問的
// executeResult:就是我們方法呼叫完后的執行結果
Object executeResult = dm[i].invoke(user, 123456);
System.out.println("執行結果 = "+executeResult);
}
}
}
3.反射main方法
public class MyTest {
public static void main(String[] args) {
System.out.println("hello world");
}
}
@Test // 反射main方法
void test4() throws Exception{
Class<MyTest> testClass = MyTest.class;
Method main = testClass.getDeclaredMethod("main",String[].class);
main.setAccessible(true);
Object args = new String[]{"1"};
main.invoke(testClass.newInstance(),args);
}
4.使用反射讀取組態檔,呼叫方法
info.properties:內容 注意idea專案中的當前位置是,當前程序路徑下,
user=test.User
method=select
@Test // 讀取組態檔,創建物件,并且呼叫方法
void test5() throws Exception{
Properties properties = new Properties();
// 獲取到組態檔流
FileReader reader = new FileReader("src/main/resources/info.properties");
// 將組態檔流加載到 Properties 中
properties.load(reader);
// 從info.properties中獲取到user的資訊,并且得到User的class物件
Class<?> aClass = Class.forName(properties.get("user").toString());
// 利用反射實體化User物件
Object object = aClass.newInstance();
// 獲取到User類中的select方法
Method select = aClass.getDeclaredMethod(properties.get("method").toString(), int.class);
// 設定為可訪問的
select.setAccessible(true);
// 呼叫select方法,并且獲取回傳結果
Object executeResult = select.invoke(object,1);
System.out.println("executeResult = "+executeResult);
}
5.使用反射跳過泛型機制檢測
@Test // 使用反射跳過泛型機制檢測
void test6() throws Exception{
ArrayList<String> list = new ArrayList<>();
list.add("admin");
list.add("root");
//list.add(new User(10,20,"admin")); //因為泛型機制的原因,此處不能添加User型別的資料,只能添加String型別的資料
//獲取ArrayList的Class物件,反向的呼叫add()方法,添加資料
Class listClass = list.getClass();
//獲取add()方法
Method method = listClass.getMethod("add", Object.class);
//呼叫add()方法
method.invoke(list, new User(10,20,"admin"));
for(Object value : list){
System.out.println("value = "+value);
}
}
6.注解
1.注解概念
注解(注釋,標注,Annotation)的作用 ?
如果要對于注解的作用進行分類,我們可以根據它所起的作用,大致可分為三類:
撰寫檔案:通過代碼里標識的元資料生成檔案,
代碼分析:通過代碼里標識的元資料對代碼進行分析,
編譯檢查:通過代碼里標識的元資料讓編譯器能實作基本的編譯檢查,
注解出現的位置
Annotation(注解)是JDK5.0及以后版本引入的,它可以用于創建檔案,跟蹤代碼中的依賴性,甚至執行基本編譯時檢查,從某些方面看,Annotation 就像修飾符一樣被使用,并應用于包、型別、構造方法、方法、成員變數、引數、本地變數的宣告中,這些資訊被存盤在Annotation的“name=value”結構對中,
注解成員屬性
Annotation的成員在Annotation型別中以無引數的方法的形式被宣告,其方法名和回傳值定義了該成員的名字和型別,在此有一個特定的默認 語法:允許宣告任何Annotation成員的默認值,一個Annotation可以將name=value對作為沒有定義默認值的Annotation 成員的值,當然也可以使用name=value對來覆寫其它成員默認值,這一點有些近似類的繼承特性,父類的建構式可以作為子類的默認建構式,但是也 可以被子類覆寫,
注解生命周期分類
原始碼注解:注解只在原始碼中存在,編譯成.class檔案就不存在了,
編譯時注解:注解在原始碼和.class檔案中都存在,(例如:JDK的三個注解)
運行時注解:在運行階段還起作用,甚至會影響運行邏輯的注解,
注解不會影響程式代碼的執行
Annotation 能被用來為某個程式元素(類、方法、成員變數等)關聯任何的資訊,需要注意的是,這里存在著一個基本的規則:Annotation不能影響程式代碼的執行,無論增加、洗掉 Annotation,代碼都始終如一的執行,另外,盡管一些Annotation通過java的反射api方法在運行時被訪問,而java語言解釋器在作業時忽略了這些Annotation,正是由于java虛擬機忽略了Annotation,導致了Annotation型別在代碼中是“不起作用”的; 只有通過某種配套的工具才會對Annotation型別中的資訊進行訪問和處理
2.jdk自帶注解常用
@Override:表示子類覆寫了父類的方法
@Deprecation:表示方法已經過時,方法上有橫線,使用時會有警告,
@author: 標明開發該類模塊的作者
@version: 標明該類模塊的版本
@see: 參考轉向,也就是相關主題
@param: 對方法中某引數的說明
@return: 對方法回傳值的說明
@exception: 對方法可能拋出的例外進行說明
@throws: 拋出的例外
@since:描述文本
@FunctionalInterface: 表示該介面是一個函式式介面,并且可以作為Lambda運算式引數傳入
@SuppviseWarnings: 表示關閉一些警告資訊(通知java編譯器忽略特定的編譯警告)
SuppviseWarnings:詳細引數如下:
| 屬性值 | 描述 |
|---|---|
| all | 抑制所有警告 |
| boxing | 抑制裝箱、拆箱操作時候的警告 |
| cast | 抑制映射相關的警告 |
| dep-ann | 抑制啟用注釋的警告 |
| deprecation | 抑制過期方法警告 |
| fallthrough | 抑制確在switch中缺失breaks的警告 |
| finally | 抑制finally模塊沒有回傳的警告 |
| hiding | 抑制相對于隱藏變數的區域變數的警告的步驟 |
| incomplete-switch | 忽略沒有完整的switch陳述句 |
| nls | 忽略非nls格式的字符 |
| null | 忽略對null的操作 |
| rawtypes | 使用generics時忽略沒有指定相應的型別 |
| restriction | 抑制與不鼓勵或禁止參考的使用相關的警告 |
| serial | 忽略在serializable類中沒有宣告serialVersionUID變數 |
| static-access | 抑制不正確的靜態訪問方式警告 |
| ic-access | 抑制子類沒有按最優方法訪問內部類的警告 |
| unchecked | 抑制沒有進行型別檢查操作的警告 |
| unqualified-field-access | 抑制沒有權限訪問的域的警告 |
| unused | 抑制沒被使用過的代碼的警告 |
3.自定義開發注解
自定義注解的語法規則
- 使用@interface關鍵字定義注解,注意關鍵字的位置
使用@interface自定義注解時,自動繼承了java.lang.annotation.Annotation介面,由編譯程式自動完成其他細節,在定義注解時,不能繼承其他的注解或介面, - 成員以無引數無例外的方式宣告,注意區別一般類成員變數的宣告, 其中的每一個方法實際上是宣告了一個配置引數,方法的名稱就是引數的名稱
- 可以使用default為成員指定一個默認值,如上所示
- 成員屬性型別是受限的,合法的型別包括原始型別以及String、Class、Annotation、Enumeration (JAVA的基本資料型別有8種:
byte(位元組)、short(短整型)、int(整數型)、long(長整型)、float(單精度浮點數型別)、double(雙精度浮點數型別)、char(字符型別)、boolean(布爾型別) - 注解類可以沒有成員,沒有成員的注解稱為標識注解,例如JDK注解中的@Override、@Deprecation
- 如果注解只有一個成員,并且把成員取名為value(),則在使用時可以忽略成員名和賦值號“=” 如果成員名 不為value,則使用時需指明成員名和賦值號"=",
自定義注解初識
// 自定義一個MyAnnotation注解,該注解中有一個className屬性成員
public @interface MyAnnotation {
String className();
}
在其他類中使用該注解(后面會說到如何去獲取注解,以及注解中的屬性)
@MyAnnotation(className = "java.lang.reflect.Field")
public class MyTest2 {
注解中的 default關鍵字:
// 自定義一個MyAnnotation注解
public @interface MyAnnotation {
String className();
int userId() default 1004; // 增加一個 userId屬性,并且設定默認值為:1024
}
@MyAnnotation(className = "java.util.UUID") // 不寫userId也可以,使用我們設定的默認值:1024
class MyAnnotationTest{
}
成員屬性至有一個,且名稱為 value的特性
// 自定義一個MyAnnotation注解
public @interface MyAnnotation {
String value();
}
//注解中只有一個成員屬性,且名稱為value,可以不用謝屬性名,當然也可以寫為:value = "simpleType"
@MyAnnotation(value = "simpleType")
class MyAnnotationTest{
}
當注解的成員屬性是陣列的時候
// 自定義一個MyAnnotation注解
public @interface MyAnnotation {
String[] dataType(); //成員屬性使用陣列的形式
}
// 當注解成員屬性是陣列時:dataType = {屬性值1,屬性值2,屬性值3,......}
@MyAnnotation(dataType = {"java","php","javaScript"})
class MyAnnotationTest{
}
注解中使用列舉類
// 自定義一個MyAnnotation注解
public @interface MyAnnotation {
Color[] colorType();
}
// 當注解成員屬性是陣列時:dataType = {屬性值1,屬性值2,屬性值3,......}
@MyAnnotation(colorType={Color.BLACK,Color.GREEN})
class MyAnnotationTest{
}
// 定義顏色列舉型別
enum Color{
READ,GREEN,BLACK,WHITE;
}
4.元注解
什么是元注解?簡單的來說,就是注解的注解,也就是用來描述我們自定義的注解的注解,聽起來有點像套娃的感覺,其實注解,就是一個標簽,就像是商品一樣,貼上了標簽,對這個商品進行描述,比如說:價格呀,生產日期啊等等,我們的注解也是如此,可以對一個類,一個方法,一個屬性等等進行描述,我們可以獲取到他們上面是否包含有該注解,或者是獲取到他們上面的注解來指向某些邏輯,就像是我們的spring框架一樣,使用 @Autowired注解一樣,讓我們的物件進行自動裝配,使用注解+反射的開發形式,大大的提高了開發效率,代碼的靈活性,可重復性,
1.@Target
@Target說明了Annotation所修飾的物件范圍:即注解的作用域,用于說明注解的使用范圍(即注解可以用在什么地方,比如類的注解,方法注解,成員變數注解等等)簡單的來說,就是你自己定義的注解可以出現在什么位置都是由@Target注解來決定的如果沒有使用@Target進行描述:那么自定義的的注解可以應用于程式的任何位置
java.lang.annotation.ElementType這個列舉中規定@Target的取值范圍以及作用
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
// 類,介面,注解,列舉
TYPE,
/** Field declaration (includes enum constants) */
// 成員屬性,列舉型常量
FIELD,
/** Method declaration */
// 只能出現在方法上
METHOD,
/** Formal parameter declaration */
// 只能用于形參宣告
PARAMETER,
/** Constructor declaration */
// 只能用于構造方法上
CONSTRUCTOR,
/** Local variable declaration */
// 區域變數宣告
LOCAL_VARIABLE,
/** Annotation type declaration */
// 注解型別宣告
ANNOTATION_TYPE,
/** Package declaration */
// 用于包描述
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
// 引數型別宣告
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
// 型別使用
TYPE_USE
}
2.@Retention
@Retention定義了該Annotation被保留的時間長短:
- 某些Annotation僅出現在源代碼中,編譯器在編譯時丟棄;
- 某些Annotation被編譯在class檔案中;編譯在class檔案中的Annotation可能會被虛擬機忽略,
- 某些Annotation在class被裝載時將被讀取(請注意并不影響class的執行,因為Annotation與class在使用上是被分離的),
使用這個meta-Annotation可以對 Annotation的“生命周期”限制,
@Retention的取值是在RetentionPoicy這個列舉中規定的
- SOURCE:在源檔案中有效(即源檔案保留)
- CLASS :在class檔案中有效(即class保留)
- RUNTIME:在運行時有效(即運行時保留)
3.@Inherited
@Inherited 元注解是一個標記注解,@Inherited闡述了某個被標注的型別是被繼承的,如果一個使用了@Inherited修飾的Annotation型別被用于一個class,則這個Annotation將被用于該class的子類,
注意:@Inherited Annotation型別是被標注過的class的子類所繼承,類并不從它所實作的介面繼承Annotation,方法并不從它所多載的方法繼承Annotation,當@Inherited Annotation型別標注的Annotation的Retention是RetentionPolicy.RUNTIME,則反射API增強了這種繼 承性,如果我們使用java.lang.reflect去查詢一個@Inherited annotation型別的annotation時,反射代碼檢查將展開作業:檢查class和其父類,直到發現指定的Annotation型別被發現, 或者到達類繼承結構的頂層,
@Inherited:也就是說,子類可以繼承父類的類上的注解
// 自定義注解
// 自定義一個MyAnnotation注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited // 表示 MyAnnotation 這個注解可以被子類鎖繼承
public @interface MyAnnotation {
String name();
}
//測驗代碼
@MyAnnotation(name = "admin")
public class MyTest2 {
@Test
void test1() throws Exception{
// 通過反射獲取到 MyTest3的class物件
Class<MyTest3> aClass = MyTest3.class;
Annotation[] annotations = aClass.getAnnotations();// 獲取自身和父親的注解
// 得到注解的型別
System.out.println(annotations[0].annotationType()); // interface test.MyAnnotation
}
}
// 此處默認繼承父類的 @MyAnnotation(name = "admin")
class MyTest3 extends MyTest2{
}
- AnnotatedElement 介面
此類非常重要,我們需要通過反射獲取注解,那么久離不開這個介面,我們的Class類是實作了該介面的,那就說明,我們可以通過class物件來獲取到class成員上的注解,
來我們看下API:
| Modifier and Type | Method and Description |
|---|---|
<T extends Annotation>T | getAnnotation(類<T> annotationClass) 回傳該元素的,如果這樣的注釋 *,*否則回傳null指定型別的注釋, |
Annotation[] | getAnnotations() 回傳此元素上 存在的注釋, |
default <T extends Annotation>T[] | getAnnotationsByType(類<T> annotationClass) 回傳與此元素相關 聯的注釋 , |
default <T extends Annotation>T | getDeclaredAnnotation(類<T> annotationClass) 如果這樣的注釋 直接存在 ,則回傳指定型別的元素注釋,否則回傳null, |
Annotation[] | getDeclaredAnnotations() 回傳 直接存在于此元素上的注釋, |
default <T extends Annotation>T[] | getDeclaredAnnotationsByType(類<T> annotationClass) 如果此類注釋 直接存在或 *間接存在,*則回傳該元素的注釋(指定型別), |
default boolean | isAnnotationPresent(類<? extends Annotation> annotationClass) 如果此元素上 存在指定型別的注釋,則回傳true,否則回傳false, |
5.注解+反射案例
1.模仿Spring的自動裝配功能
? MyAnnotation :自定義注解
import java.lang.annotation.*;
/**
* Created with IntelliJ IDEA.
*
* @Author: compass
* @Date: 2021-10-24-19:53
* @Version:1.0
* @Description: 自定義注解
*/
// 自定義一個MyAnnotation注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE_USE,ElementType.FIELD})
public @interface MyAnnotation {
String className()default "";
Color[] coloType()default Color.RED; //默認紅色
String tableName()default "";
String attribute()default "";
String Autowired()default "yes"; // yes表示自動裝配,no表示不自動裝備
}
enum Color{
RED,GREEN,BLACK,BLUE;
}
2.Student:物體類
/**
* Created with IntelliJ IDEA.
*
* @Author: compass
* @Date: 2021-10-24-21:48
* @Version:1.0
* @Description:
*/
public class Student {
private int id;
private String name;
private String email;
private int status;
private static Student student;
public String show(String message){
System.out.println("Student類的 show(String message) 方法被呼叫");
System.out.println("message = "+message);
return " call success";
}
public Student(int id, String name, String email, int status) {
this.id = id;
this.name = name;
this.email = email;
this.status = status;
} public Student() {
}
/**
* 單例模式,雙重檢測機制(多執行緒下安全)回傳一個student物件
* @return 回傳一個單例的student物件
*/
public static Student getInstance(){
if (student==null){
synchronized (Student.class){
if (student==null){
student= new Student();
}
}
}
return student;
}
public int getId() {
return id;
}
public void setStudent(Student student) {
this.student = student;
}
public Student getStudent() {
return student;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", email='" + email + '\'' +
", status=" + status +
'}';
}
}
MyTest2:核心方法init()
import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.util.Properties;
/**
* Created with IntelliJ IDEA.
*
* @Author: compass
* @Date: 2021-10-24-20:02
* @Version:1.0
* @Description:
*/
@MyAnnotation(Autowired = "yes")
public class MyTest2 {
@MyAnnotation(Autowired = "yes") // 如果把屬性值改為 no,將呼叫失敗
private static Student student;
public static void init(){
// 雖然這種方式比較復雜,也許我沒有設計的那么好,但是可以聯合注解和反射做一個小練習,
// 對注解和反射有個理解(當然可以把這段代碼寫在靜態代碼塊中)
try {
Class<MyTest2> aClass = MyTest2.class;
// 獲取到 MyTest2 中的student屬性
Field studentFiled = aClass.getDeclaredField("student");
studentFiled.setAccessible(true); // 將該欄位先設定為可訪問,以免后面使用到出錯
// 判斷 MyTest2 中的student屬性中是否有MyAnnotation注解
if (studentFiled.isAnnotationPresent(MyAnnotation.class)){
// 獲取到注解中的屬性值
String flag = studentFiled.getAnnotation(MyAnnotation.class).Autowired();
// 如果注解中的屬性值==yes,那么久創建一個Student的實體物件,賦值給student
if (flag.equals("yes")){
// 讀取config.properties組態檔中的studentClassName屬性值
Properties p = new Properties();
FileReader configFile = new FileReader("src/main/resources/config.properties");
p.load(configFile);
String className = p.get("studentClassName").toString();
// 使用反射機制創建一個物件 賦值給student
Class<?> forName = Class.forName(className.trim());
student = (Student)forName.newInstance();
}
}
} catch (Exception e) {
e.printStackTrace();
System.out.println("初始化例外");
}
}
@Test
void test1() throws Exception{
init(); // 只要是呼叫成功我們可以直接執行以下代碼的
// 如果斷言失敗,請在idea中的 jvm option中設定:-ea
assert student!=null; // 斷言判斷student是否為空,如果為空拋出 java.lang.AssertionError 例外
String result = student.show("hello world!");
System.out.println("call result = "+result);
}
}
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-fbRIdAaR-1635165117962)(C:\Users\14823\AppData\Roaming\Typora\typora-user-images\image-20211024230433211.png)]
當然還有別的更好的辦法,話幾個小時看了下反射和注解,如果有總結不太到位的地方請多多諒解,
2.獲取欄位上注解的屬性值
import java.lang.annotation.*;
/**
* Created with IntelliJ IDEA.
*
* @Author: compass
* @Date: 2021-10-24-19:53
* @Version:1.0
* @Description: 自定義注解
*/
// 自定義一個MyAnnotation注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
public @interface MyAnnotation {
String[] name()default "";
String tableName()default "";
String attribute()default "";
String Autowired()default "yes"; // yes表示自動裝配,no表示不自動裝備
}
// 列舉類
enum Color{
RED,GREEN,BLACK,BLUE;
}
// 自定義一個MyAnnotation2注解
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Target({ElementType.TYPE, ElementType.FIELD,ElementType.METHOD})
@interface MyAnnotation2 {
Color[] coloType() default Color.RED; //默認紅色
}
package test;
import org.junit.jupiter.api.Test;
import java.io.FileReader;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Properties;
public class MyTest2 {
@MyAnnotation(Autowired = "yes") // 單個屬性
private static Student student;
// 使用自定義注解中帶有列舉型別陣列的屬性
@MyAnnotation2(coloType = {Color.RED,Color.BLACK,Color.GREEN,Color.BLUE}) //列舉型別陣列
String colorType;
@MyAnnotation(name = {"admin","root","jack"}) //String型別陣列
String name;
}
class MyTest3{
// 獲取欄位屬性上列舉型別陣列的屬性值
@Test
void test2() throws Exception {
Class<MyTest2> aClass = MyTest2.class;
Field field = aClass.getDeclaredField("colorType");
MyAnnotation2 annotation = field.getAnnotation(MyAnnotation2.class);
Color[] colors = annotation.coloType();
for (Color color : colors) {
System.out.println(color);
}
}
// 獲取欄位上String型別陣列的屬性值
@Test
void test3() throws Exception {
Class<MyTest2> aClass = MyTest2.class;
Field field = aClass.getDeclaredField("name");
boolean b = field.isAnnotationPresent(MyAnnotation.class);
System.out.println(Arrays.toString(field.getDeclaredAnnotation(MyAnnotation.class).name()));
}
// 獲取欄位上單個屬性值
@Test
void test4() throws Exception{
Class<MyTest2> aClass = MyTest2.class;
Field student = aClass.getDeclaredField("student");
if ( student.isAnnotationPresent(MyAnnotation.class)){
String value = student.getDeclaredAnnotation(MyAnnotation.class).Autowired();
System.out.println("value="+value);
}
}
}
7.列舉
列舉是一種特殊的常量類,構造方法默認是強制私有化的,(感覺列舉也有點忘了,大概的記錄一下,不需要的可以直接跳過)
列舉型別的每個自定義必須采用常量的命名方式,每個常量型別的欄位屬性寫好注釋,明確資料的用途【列舉代碼撰寫規范】
用法一:常量
public enum Color {
RED, GREEN, BLANK, YELLOW
}
用法二:列舉
enum Color{
RED,GREEN,BLACK,BLUE;
}
@Test
void test4(){
Color color = Color.RED;
switch (color){
case RED:
System.out.println("read");break;
case BLACK:
System.out.println("black");break;
case BLUE:
System.out.println("blue");break;
default:
System.out.println("green");
}
}
用法三:列舉類中添加方法
public enum Color {
READ(1,"紅色"),GREEN(2,"綠色"),BLACK(3,"黑色"),BLUE(4,"藍色");
private String name;
private int index;
// 構造方法
Color(int index, String name) {
this.index=index;
this.name=name;
}
/**
* 根據索引獲取名稱
* @param index
* @return
*/
public static String getName(int index){
for (Color value : Color.values()) {
if (value.index==index){
return value.name;
}
}
return null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getIndex() {
return index;
}
public void setIndex(int index) {
this.index = index;
}
@Override
public String toString() {
return "Color{" +
"name='" + name + '\'' +
", index=" + index +
'}';
}
}
class MyTest{
public static void main(String[] args) {
// 根據列舉型別欄位獲取一個列舉型別物件
Color color = Color.valueOf("READ");
System.out.println("index="+color.getIndex());
System.out.println("name="+color.getName());
// 根據獲取到列舉類中的所有屬性
Color[] values = Color.values();
for (Color value : values) {
System.out.println("item="+value);
}
Color black1 = Color.BLACK;
Color black2 = Color.BLACK;
// 是常量,所有無論獲取多少次都是相同的,比較可以使用 == ,不必使用 equals
System.out.println(black1==black2);
System.out.println(black1.equals(black2));
}
}
用法四:使用介面組織列舉
interface Food {
// 水果
enum FRUITS implements Food{
APPLE,BANANA,GRAPE,PEAR
}
//喝的
enum DRINK implements Food{
WATER, COKE, COFFEE
}
}
//實作列舉型別介面
class FoodImpl implements Food{
}
class MyTest2{
public static void main(String[] args) {
// 可以獲取到介面中定義的列舉型別組,以及組中定義的列舉屬性值
Food[] drinks = FoodImpl.DRINK.values();
System.out.println(Arrays.toString(drinks));
// 可以獲取到介面中定義的列舉型別組,以及組中定義的列舉屬性值
Food[] fruits = FoodImpl.FRUITS.values();
System.out.println(Arrays.toString(fruits));
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/337686.html
標籤:java
