Java基礎知識之註解、元註解
註解
Java註解也稱Java標註,是jdk1.5(5.0)後的新特征。Java語言中的類、方法、變量、參數和包等都可以被標註。和Javadoc不同,Java註解可以通過反射獲取標註內容,在編譯器生成類文件時,標註可以被嵌入到字節碼中,Java虛擬機可以保留標註內容,在運行時可以獲取到標註內容,當然它也支持自定義Java標註
功能:用於說明程序
用途:一般用在框架中使用
格式:@AnnotationName
文檔註釋:
@param @return @Exception從根本上是一個註釋,不存在代碼編譯,不會生成對應的文檔
註解:
@Override並不是沒有編譯就有效果,是因為不管是Eclipse還是IDEA都可以預編譯Java代碼生成對應的.class文件的
註解作用
生成文檔
代碼中生成對應的JavaDoc API文檔
@param @return
【IDEA JavaDoc工具使用參數】
Other Command Line Arguments:-encoding utf-8 -charset utf-8
解決中文亂碼,因為IDEA默認編碼集為UTF-8 windows默認編碼集為 GBK
代碼檢查
繼承重寫,或者說接口遵從之後的實現中,存在@Override
代碼數據獲取:【小型框架】
通過反射獲取指定註解中的一些內容,例如:配置,數據,操作,驗證等
Java預定義的註解
@Override
重寫/實現方法的情況下,檢查方法聲明是否和父類或者接口中的方法聲明一致,強制格式檢查
@Deprecated
標記當前方法已過時
@SuppressWarnings(“all”)
壓制警告,可以用於一些代碼中存在明確無異常的情況下,壓制一些警告
Annotation註解屬性【難點】
屬性
開發實際使用註解的方式中,數據使用方式更加偏向於屬性概念
使用
- 書寫代碼中使用
@MyAnnotation(id=1, name=“ocean”, age=16) - 使用反射時,會涉及到getXXX方法
通過屬性名獲取對應值的概念來完成的
實際上是利用abstract方法來完成屬性概念的
屬性使用的格式【實際按照方法格式操作】
- 屬性的值數據類型和對應的具體數據 => 返回值類型和返回的數據屬性類型支持:
- 基本數據類型
- String類型
- 其他數據類型
- enmu枚舉類型,一個帶有名字的常量,為瞭更好的閱讀性和操作
- 以上類型對應的數組
- 屬性值要求
- 定義屬性時可以使用default關鍵字,加上默認值,該屬性在使用的過程中是沒有強制要求屬性值,如果沒有賦予屬性值,采用對應的默認值操作,如果賦值,使用對應值
- 如果註解中有且隻有一個value屬性,或者說註解中除value屬性之外,都有默認值,不管是類,方法,成員變量,包使用當前註解是可以直接在括號內加入
對應數據類型數值- 如果屬性是數組類型, {}大括號保存,並且不同的內容,使用,隔開屬性的鍵名字 ==> 方法的名字
自定義註解
格式: public @interface AnnotationName { 屬性列表; } Annotation註解是可以編譯得到對應的.class字節碼文件,驗證瞭註解是可以參與編譯過程的 通過反編譯工具可以得到一下內容 【Annotation本質】 public interface MyAnnotation1 extends java.lang.annotation.Annotation { } MyAnnotation1 本質是一個interface,同時java.lang.annotation.Annotation 子接口
package cn.ocean888.a_annotation.MyAnnotation; /** * 自定義註解!!! * public interface MyAnnotation1 extends java.lang.annotation.Annotation { * } * * @author Anonymous * @date 2020/3/10 11:01 */ public @interface MyAnnotation1 { // 屬性 ==> 方法形式 }
元註解
基於註解的解釋,用來約束註解的的一些操作問題
@Retention
表示這個註解的保存方式,是隻在代碼中,還是編入class文件中,或者是運行時可以通過反射訪問
RetentionPolicy.RUNTIME:當前註解會編譯生成對應的.class字節碼文件,並且可以加載到JVM中,參與代碼執行
RetentionPolicy.CLASS
RetentionPolicy.SOURCE:註解將被編譯器丟棄(該類型的註解信息隻會保留在源碼裡,源碼經過編譯後,註解信息會被丟棄,不會保留在編譯好的class文件裡)
@Document
標記這些註解是否包含在用戶文檔中
是否可以通過JavaDoc工具,生成對應的API文檔
@Target
標記這個註解應該是那種Java成員
屬性:ElementType
TYPE:當前註解可以用於類聲明
METHOD:當前註解可以用於方法聲明位置
FIELD:當前註解可以用於成員變量聲明位置
@Inherited
標記這個註解是繼承於那個註解類(默認 註解不繼承於任何子類)
獲取類上的註解
Java獲取類上的註解有下面3個方法:
- Class.getAnnotations() 獲取所有的註解,包括自己聲明的以及繼承的
- Class.getAnnotation(Class< A > annotationClass) 獲取指定的註解,該註解可以是自己聲明的,也可以是繼承的
- Class.getDeclaredAnnotations() 獲取自己聲明的註解、
java.lang.Class類的isAnnotation()方法用於檢查此Class是否為Annotation類型
java.lang.Class類的isAnnotationPresent()方法用於檢查此類中是否存在指定註釋類型的註釋
實例:
實例1:兩種屬性文件的加載的方式
1.properties
className = cn.ocean888.a_annotation.Person id = 1 name = "ocean"
Person類
package cn.ocean888.a_annotation; public class Person { Integer id; String name; public Person() { } public Person(Integer id, String name) { this.id = id; this.name = name; } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "id=" + id + ", name='" + name + '\'' + '}'; } }
第一種使用反射來完成
package cn.ocean888.a_annotation; import java.io.FileInputStream; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Properties; public class Demo1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, NoSuchFieldException { Properties properties = new Properties(); properties.load(new FileInputStream("./src/1.properties")); String className = properties.getProperty("className"); String id = properties.getProperty("id"); String name = properties.getProperty("name"); System.out.println(className); /* 使用反射的方法 */ Class<?> aClass = Class.forName(className); Person person = (Person) aClass.getConstructor().newInstance(); System.out.println(person); Field declaredField = aClass.getDeclaredField("id"); declaredField.setAccessible(true); declaredField.set(person, Integer.parseInt(id)); Field declaredField2 = aClass.getDeclaredField("name"); declaredField2.setAccessible(true); declaredField2.set(person, name); System.out.println(person); } }
第二種使用反射加註解的方式來完成
自定義註解
package cn.ocean888.a_annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; // 聲明當前註解有且隻能用於類名之上 @Target(ElementType.TYPE) // 當前註解參與代碼運行 @Retention(RetentionPolicy.RUNTIME) @interface MyAnnotaion1 { // 屬性 String className(); int id(); String name(); }
ReflectAnnotation.java
package cn.ocean888.a_annotation; import java.lang.annotation.Annotation; @MyAnnotaion1(className = "cn.ocean888.a_annotation.Person", id = 2, name = "ocean") public class ReflectAnnotation { public static void main(String[] args) { // 加載ReflectAnnotation Class<ReflectAnnotation> cls = ReflectAnnotation.class; // 因為註解再類名之上,通過Class獲取對應的Annotation MyAnnotaion1 annotation = cls.getAnnotation(MyAnnotaion1.class); String s = annotation.className(); int id = annotation.id(); String name = annotation.name(); System.out.println(s); System.out.println(id); System.out.println(name); } }
實例2使用註解測試代碼運行
對Tools方法中帶有@Check註解標記的方法進行檢查
Tools.java
package cn.ocean888.a_annotation_checkMethod; import java.util.ArrayList; /** * 需要測試的方法 */ public class Tools { @Check public void test1() { String str = null; System.out.println(str.toString()); } @Check public void test2() { int[] arr = null; System.out.println(arr[5]); } @Check public void test3() { int[] arr = {1,2,3,4,5}; System.out.println(arr[3]); } @Check public void test4() { ArrayList<Integer> integers = new ArrayList<>(); System.out.println(integers.get(20).toString()); } @Check public void test5() { throw new NullPointerException("NullPointException"); } }
Utils.java
package cn.ocean888.a_annotation_checkMethod; public class Utils { @Check public void test() { throw new IndexOutOfBoundsException("下標越界"); } }
Check.java
package cn.ocean888.a_annotation_checkMethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 該註解沒有任何屬性,隻是作為是否需要測試的標記 */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface Check { }
ClassAnnotation.java
package cn.ocean888.a_annotation_checkMethod; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 自定義註解 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface ClassAnnotation { String className(); }
TestProject.java
package cn.ocean888.a_annotation_checkMethod; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; /** * 測試Tools類內的方法,如果方法帶有Check註解,執行測試並記錄異常 */ @ClassAnnotation(className = "cn.ocean888.a_annotation_checkMethod.Tools") public class TestProject { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException { // 從註解中獲取對應的屬性 Class<TestProject> testProjectClass = TestProject.class; // 獲取所有指定的註解 ClassAnnotation annotation = testProjectClass.getAnnotation(ClassAnnotation.class); String s = annotation.className(); // s = cn.ocean888.a_annotation_checkMethod.Utils Class<?> aClass = Class.forName(s); Object tools = aClass.getConstructor().newInstance(); // 獲取所有Tools類內的方法,不包括父類方法 Method[] declaredMethods = aClass.getDeclaredMethods(); // 記錄錯誤出現次數 int count = 0; long l = System.currentTimeMillis(); BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter("./src/log.txt")); // 遍歷方法數組 for (Method declaredMethod : declaredMethods) { declaredMethod.setAccessible(true); // 判斷當前方法是否帶有註解@Check標記 if (declaredMethod.isAnnotationPresent(Check.class)) { try { declaredMethod.invoke(tools); } catch (Exception e) { count += 1; // 1.哪一個方法出現異常 bufferedWriter.write("方法:" + declaredMethod.getName()); bufferedWriter.newLine(); // 2.發生異常原因,獲取對應的類型 bufferedWriter.write("異常類型:" + e.getCause().getClass().getSimpleName()); bufferedWriter.newLine(); // 3.異常信息 bufferedWriter.write("異常信息:" + e.getCause().getMessage()); bufferedWriter.newLine(); } } } long l1 = System.currentTimeMillis(); bufferedWriter.write("出現錯誤的次數" + count); bufferedWriter.newLine(); bufferedWriter.write("總耗時" + (l1 - l)); bufferedWriter.close(); } }
代碼的靈活性在於可以對className直接進行替換
註解使用總結
- 註解在大多數情況下,都是使用過程,而不是自定義,會使用到框架中預處理好的註解
- 註解使用對象
- 編譯器
- 解析代碼
- JVM運行代碼使用
- 註解是一個標簽,有時候是做標記,有時候標記有屬性
註解和python裝飾器的區別
先說java的註解(Annotation),實際上是給語法元素打一個標記。比如你可以給一個函數打一個標記,給一個類打一個標記等等。Java隻保證記錄這個標記,但是不會主動根據這給標記做任何事。
比如,你在Spring裡,給一個私有成員打 @Autowired 這個標記。
public class XXXService { @Autowired private XXXXRepository xxxxRepository; // ... }
如果你不用Spring框架的話,不會有任何事情發生,直接訪問這個字段就是空。當如果你配置瞭合適的處理流程,而這個流程就會根據有沒有這個標記幹活。比如你要求Spring “Auto Scan” 並且註入依賴,這個處理過程會用反射去讀哪些元素被做瞭某個特定標記。沒有標記就不理,有標記就註入。
python裡的decorator是一個語法糖,是希望把“decorator”這個形式寫得更漂亮。比如,你想記錄一個函數開始執行之前和之後的log:
def foo(): print("Hello") def logit(fn): def inner(): print("before execute") fn() printf("after execute") return inner
這時,你可以魔改以下foo的實現,用logit這個“裝飾器”來部分修改foo的行為,然後執行:
foo = logit(foo) foo()
但python裡的語法可以讓這個東西寫成:
@logit def foo(): print("Hello") foo()
也就是說,python這裡的裝飾器是一個有邏輯的,可以執行的函數,隻不過其寫法有些特殊要求;而Java裡面的Annotation隻是個標記,需要其他代碼來“根據標記執行“。
當然,裝飾器模式是個很通用的東西,無論是python,java還是其他語言都可以寫。隻是python提供瞭特殊的語法糖而已。但java世界裡做類似decorator的事情,希望動態魔改一個函數的行為,可以用動態代理或者AOP。
Java的Annotation因為相當於多加瞭一層(標記 + 處理邏輯),是一把雙刃劍。好處是,在不動代碼的情況下你可以通過外部配置來修改程序的行為。比如給一個函數打上@Test標。如果通過UT框架運行,這些打標的函數會被當作是測試用例;但如果外部直接用普通的main啟動,這些@Test就會沒有一樣,不會影響代碼本身的邏輯。但反過來,也容易引來一些問題。比如有的時候,你很難知道那個根據標記執行的邏輯是不是真的跑瞭。也許你哪裡配置拼錯一個字,或者classpath少依賴一個包,就造成那個邏輯並沒有真的執行。這時從表面上也許很難看出來出錯瞭。
總結
到此這篇關於Java基礎知識之註解、元註解的文章就介紹到這瞭,更多相關Java註解、元註解內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!