Java註解和反射是很基礎的Java知識了,爲什麼還要講它呢?由於我在面試應聘者的過程當中,發現很多面試者不多使用過註解和反射,甚至有人只能說出@Override
這一個註解。我建議你們仍是儘可能能在開發中使用註解和反射,有時候使用它們能讓你事半功倍,簡化代碼提升編碼的效率。不少優秀的框架都基本使用了註解和反射,在Spring AOP中,就把註解和反射用得淋漓盡致。java
Java註解(Annotation)亦叫Java標註,是JDK5.0開始引入的一種註釋機制。 註解能夠用在類、接口,方法、變量、參數以及包等之上。註解能夠設置存在於不一樣的生命週期中,例如SOURCE(源碼中),CLASS(Class文件中,默認是此保留級別),RUNTIME(運行期中)。程序員
註解以@註解名
的形式存在於代碼中,Java中內置了一些註解,例如@Override
,固然咱們也能夠自定義註解。註解也能夠有參數,例如@MyAnnotation(value = "陳皮")。面試
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }
那註解有什麼做用呢?其一是做爲一種輔助信息,能夠對程序作出一些解釋,例如@Override註解做用於方法上,表示此方法是重寫了父類的方法。其二,註解能夠被其餘程序讀取,例如編譯器,例如編譯器會對被@Override註解的方法檢測判斷方法名和參數等是否與父類相同,不然會編譯報錯;並且在運行期能夠經過反射機制訪問某些註解信息。數據庫
Java中有10個內置註解,其中6個註解是做用在代碼上的,4個註解是負責註解其餘註解的(即元註解),元註解提供對其餘註解的類型說明。編程
註解 | 做用 | 做用範圍 |
---|---|---|
@Override | 檢查該方法是不是重寫方法。若是其繼承的父類或者實現的接口中並無該方法時,會報編譯錯誤。 | 做用在代碼上 |
@Deprecated | 標記表示過期的,不推薦使用。能夠用於修飾方法,屬性,類。若是使用被此註解修飾的方法,屬性或類,會報編譯警告。 | 做用在代碼上 |
@SuppressWarnings | 告訴編譯器忽略註解中聲明的警告。 | 做用在代碼上 |
@SafeVarargs | Java 7開始支持,忽略任何使用參數爲泛型變量的方法或構造函數調用產生的警告。 | 做用在代碼上 |
@FunctionalInterface | Java 8開始支持,標識一個匿名函數或函數式接口。 | 做用在代碼上 |
@Repeatable | Java 8開始支持,標識某註解能夠在同一個聲明上使用屢次。 | 做用在代碼上 |
@Retention | 標識這個註解的保存級別,是隻在代碼中,仍是編入class文件中,或者是在運行時能夠經過反射訪問。包含關係runtime>class>source。 | 做用在其餘註解上,即元註解 |
@Documented | 標記這些註解是否包含在用戶文檔中javadoc。 | 做用在其餘註解上,即元註解 |
@Target | 標記某個註解的使用範圍,例如做用方法上,類上,屬性上等等。若是註解未使用@Target,則註解能夠用於任何元素上。 | 做用在其餘註解上,即元註解 |
@Inherited | 說明子類能夠繼承父類中的此註解,但這不是真的繼承,而是可讓子類Class對象使用getAnnotations()獲取父類被@Inherited修飾的註解 | 做用在其餘註解上,即元註解 |
使用@interface關鍵字自定義註解,其實底層就是定義了一個接口,並且自動繼承java.lang.annotation.Annotation
接口。數組
咱們自定義一個註解以下:安全
@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface MyAnnotation { String value(); }
咱們使用命令javap反編譯咱們定義的MyAnnotation註解的class文件,結果顯示以下。雖然註解隱式繼承了Annotation接口,可是Java不容許咱們顯示經過extends關鍵字繼承Annotation接口甚至其餘接口,不然編譯報錯。數據結構
D:\>javap MyAnnotation.class Compiled from "MyAnnotation.java" public interface com.nobody.MyAnnotation extends java.lang.annotation.Annotation { public abstract java.lang.String value(); }
註解的定義內容以下:框架
import java.lang.annotation.*; /** * @Description 自定義註解 * @Author Mr.nobody * @Date 2021/3/30 * @Version 1.0 */ @Target(ElementType.METHOD) // 此註解只能用在方法上。 @Retention(RetentionPolicy.RUNTIME) // 此註解保存在運行時期,能夠經過反射訪問。 @Inherited // 說明子類能夠繼承此類的此註解。 @Documented // 此註解包含在用戶文檔中。 public @interface CustomAnnotation { String value(); // 使用時須要顯示賦值 int id() default 0; // 有默認值,使用時能夠不賦值 }
/** * @Description 測試註解 * @Author Mr.nobody * @Date 2021/3/30 * @Version 1.0 */ public class TestAnnotation { // @CustomAnnotation(value = "test") 只能註解在方法上,這裏會報錯 private String str = "Hello World!"; @CustomAnnotation(value = "test") public static void main(String[] args) { System.out.println(str); } }
在這裏講解下Java8以後的幾個註解和新特性,其中一個註解是@FunctionalInterface,它做用在接口上,標識是一個函數式接口,即只有有一個抽象方法,可是能夠有默認方法。ide
@FunctionalInterface public interface Callback<P,R> { public R call(P param); }
還有一個註解是@Repeatable,它容許在同一個位置使用多個相同的註解,而在Java8以前是不容許的。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Repeatable(OperTypes.class) public @interface OperType { String[] value(); }
// 能夠理解@OperTypes註解做爲接收同一個類型上重複@OperType註解的容器 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface OperTypes { OperType[] value(); }
@OperType("add") @OperType("update") public class MyClass { }
注意,對於重複註解,不能再經過clz.getAnnotation(Class<A> annotationClass)方法來獲取重複註解,Java8以後,提供了新的方法來獲取重複註解,即clz.getAnnotationsByType(Class<A> annotationClass)方法。
package com.nobody; import java.lang.annotation.Annotation; /** * @Description * @Author Mr.nobody * @Date 2021/3/31 * @Version 1.0 */ @OperType("add") @OperType("update") public class MyClass { public static void main(String[] args) { Class<MyClass> clz = MyClass.class; Annotation[] annotations = clz.getAnnotations(); for (Annotation annotation : annotations) { System.out.println(annotation.toString()); } OperType operType = clz.getAnnotation(OperType.class); System.out.println(operType); OperType[] operTypes = clz.getAnnotationsByType(OperType.class); for (OperType type : operTypes) { System.out.println(type.toString()); } } } // 輸出結果爲 @com.nobody.OperTypes(value=[@com.nobody.OperType(value=[add]), @com.nobody.OperType(value=[update])]) null @com.nobody.OperType(value=[add]) @com.nobody.OperType(value=[update])
在Java8中,ElementType枚舉新增了兩個枚舉成員,分別爲TYPE_PARAMETER和TYPE_USE,TYPE_PARAMETER標識註解能夠做用於類型參數,TYPE_USE標識註解能夠做用於標註任意類型(除了Class)。
咱們先了解下什麼是靜態語言和動態語言。動態語言是指在運行時能夠改變其自身結構的語言。例如新的函數,對象,甚至代碼能夠被引進,已有的函數能夠被刪除或者結構上的一些變化。簡單說便是在運行時代碼能夠根據某些條件改變自身結構。動態語言主要有C#,Object-C,JavaScript,PHP,Python等。靜態語言是指運行時結構不可改變的語言,例如Java,C,C++等。
Java不是動態語言,可是它能夠稱爲準動態語言,由於Java能夠利用反射機制得到相似動態語言的特性,Java的動態性讓它在編程時更加靈活。
反射機制容許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操做任意對象的內部屬性以及方法等。類在被加載完以後,會在堆內存的方法區中生成一個Class類型的對象,一個類只有一個Class對象,這個對象包含了類的結構信息。咱們能夠經過這個對象看到類的結構。
好比咱們能夠經過Class clz = Class.forName("java.lang.String");
得到String類的Class對象。咱們知道每一個類都隱式繼承Object類,Object類有個getClass()
方法也能獲取Class對象。
Java反射機制提供的功能
Java反射機制的優缺點
Java反射相關的主要API
咱們知道在運行時經過反射能夠準確獲取到註解信息,其實以上類(Class,Method,Field,Constructor等)都直接或間接實現了AnnotatedElement接口,並實現了它定義的方法,AnnotatedElement接口的做用主要用於表示正在JVM中運行的程序中已使用註解的元素,經過該接口提供的方法能夠獲取到註解信息。
在Java反射中,最重要的是Class這個類了。Class自己也是一個類。當程序想要使用某個類時,若是此類還未被加載到內存中,首先會將類的class文件字節碼加載到內存中,並將這些靜態數據轉換爲方法區的運行時數據結構,而後生成一個Class類型的對象(Class對象只能由系統建立),一個類只有一個Class對象,這個對象包含了類的結構信息。咱們能夠經過這個對象看到類的結構。每一個類的實例都會記得本身是由哪一個Class實例所生成的。
經過Class對象能夠知道某個類的屬性,方法,構造器,註解,以及實現了哪些接口等信息。注意,只有class,interface,enum,annotation,primitive type,void,[] 等纔有Class對象。
package com.nobody; import java.lang.annotation.ElementType; import java.util.Map; public class TestClass { public static void main(String[] args) { // 類 Class<MyClass> myClassClass = MyClass.class; // 接口 Class<Map> mapClass = Map.class; // 枚舉 Class<ElementType> elementTypeClass = ElementType.class; // 註解 Class<Override> overrideClass = Override.class; // 原生類型 Class<Integer> integerClass = Integer.class; // 空類型 Class<Void> voidClass = void.class; // 一維數組 Class<String[]> aClass = String[].class; // 二維數組 Class<String[][]> aClass1 = String[][].class; // Class類也有Class對象 Class<Class> classClass = Class.class; System.out.println(myClassClass); System.out.println(mapClass); System.out.println(elementTypeClass); System.out.println(overrideClass); System.out.println(integerClass); System.out.println(voidClass); System.out.println(aClass); System.out.println(aClass1); System.out.println(classClass); } } // 輸出結果 class com.nobody.MyClass interface java.util.Map class java.lang.annotation.ElementType interface java.lang.Override class java.lang.Integer void class [Ljava.lang.String; class [[Ljava.lang.String; class java.lang.Class
獲取Class對象的方法
Class clz = User.class;
Class clz = user.getClass();
Class clz = Class.forName("com.nobody.User");
Class<Integer> clz = Integer.TYPE;
Class類的經常使用方法
在反射中常常會使用到Method的invoke方法,即public Object invoke(Object obj, Object... args)
,咱們簡單說明下:
泛型是JDK 1.5的一項新特性,它的本質是參數化類型(Parameterized Type)的應用,也就是說所操做的數據類型被指定爲一個參數,在用到的時候再指定具體的類型。這種參數類型能夠用在類、接口和方法的建立中,分別稱爲泛型類、泛型接口和泛型方法。
在Java中,採用泛型擦除的機制來引入泛型,泛型能編譯器使用javac時確保數據的安全性和免去強制類型轉換問題,泛型提供了編譯時類型安全檢測機制,該機制容許程序員在編譯時檢測到非法的類型。而且一旦編譯完成,全部和泛型有關的類型會被所有擦除。
Java新增了ParameterizedType
,GenericArrayType
,TypeVariable
和WildcardType
等幾種類型,能讓咱們經過反射操做這些類型。
package com.nobody; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.Map; public class TestReflectGenerics { public Map<String, Person> test(Map<String, Integer> map, Person person) { return null; } public static void main(String[] args) throws NoSuchMethodException { // 獲取test方法對象 Method test = TestReflectGenerics.class.getDeclaredMethod("test", Map.class, Person.class); // 獲取方法test的參數類型 Type[] genericParameterTypes = test.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); } } } // 獲取方法test的返回值類型 Type genericReturnType = test.getGenericReturnType(); System.out.println("返回值類型:" + genericReturnType); // 若是參數類型等於參數化類型 if (genericReturnType instanceof ParameterizedType) { // 得到真實參數類型 Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments(); for (Type actualTypeArgument : actualTypeArguments) { System.out.println(" " + actualTypeArgument); } } } } class Person {} // 輸出結果 方法參數類型:java.util.Map<java.lang.String, java.lang.Integer> class java.lang.String class java.lang.Integer 方法參數類型:class com.nobody.Person 返回值類型:java.util.Map<java.lang.String, com.nobody.Person> class java.lang.String class com.nobody.Person
在Java運行時,經過反射獲取代碼中的註解是比較經常使用的手段了,獲取到了註解以後,就能知道註解的全部信息了,而後根據信息進行相應的操做。下面經過一個例子,獲取類和屬性的註解,解析映射爲數據庫中的表信息。
package com.nobody; import java.lang.annotation.*; public class AnalysisAnnotation { public static void main(String[] args) throws Exception { Class<?> aClass = Class.forName("com.nobody.Book"); // 獲取類的指定註解,而且獲取註解的值 Table annotation = aClass.getAnnotation(Table.class); String value = annotation.value(); System.out.println("Book類映射的數據庫表名:" + value); java.lang.reflect.Field bookName = aClass.getDeclaredField("bookName"); TableField annotation1 = bookName.getAnnotation(TableField.class); System.out.println("bookName屬性映射的數據庫字段屬性 - 列名:" + annotation1.colName() + ",類型:" + annotation1.type() + ",長度:" + annotation1.length()); java.lang.reflect.Field price = aClass.getDeclaredField("price"); TableField annotation2 = price.getAnnotation(TableField.class); System.out.println("price屬性映射的數據庫字段屬性 - 列名:" + annotation2.colName() + ",類型:" + annotation2.type() + ",長度:" + annotation2.length()); } } // 做用於類的註解,用於解析表數據 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Table { // 表名 String value(); } // 做用於字段,用於解析表列 @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @interface TableField { // 列名 String colName(); // 列類型 String type(); // 長度 int length(); } @Table("t_book") class Book { @TableField(colName = "name", type = "varchar", length = 15) String bookName; @TableField(colName = "price", type = "int", length = 10) int price; } // 輸出結果 Book類映射的數據庫表名:t_book bookName屬性映射的數據庫字段屬性 - 列名:name,類型:varchar,長度:15 price屬性映射的數據庫字段屬性 - 列名:price,類型:int,長度:10
前面咱們說過,反射對性能有必定影響。由於反射是一種解釋操做,它老是慢於直接執行相同的操做。並且Method,Field,Constructor都有setAccessible()方法,它的做用是開啓或禁用訪問安全檢查。若是咱們程序代碼中用到了反射,並且此代碼被頻繁調用,爲了提升反射效率,則最好禁用訪問安全檢查,即設置爲true。
package com.nobody; import java.lang.reflect.Method; public class TestReflectSpeed { // 10億次 private static int times = 1000000000; public static void main(String[] args) throws Exception { test01(); test02(); test03(); } public static void test01() { Teacher t = new Teacher(); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { t.getName(); } long end = System.currentTimeMillis(); System.out.println("普通方式執行10億次消耗:" + (end - start) + "ms"); } public static void test02() throws Exception { Teacher teacher = new Teacher(); Class<?> aClass = Class.forName("com.nobody.Teacher"); Method getName = aClass.getDeclaredMethod("getName"); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { getName.invoke(teacher); } long end = System.currentTimeMillis(); System.out.println("反射方式執行10億次消耗:" + (end - start) + "ms"); } public static void test03() throws Exception { Teacher teacher = new Teacher(); Class<?> aClass = Class.forName("com.nobody.Teacher"); Method getName = aClass.getDeclaredMethod("getName"); getName.setAccessible(true); long start = System.currentTimeMillis(); for (int i = 0; i < times; i++) { getName.invoke(teacher); } long end = System.currentTimeMillis(); System.out.println("關閉安全檢查反射方式執行10億次消耗:" + (end - start) + "ms"); } } class Teacher { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } //輸出結果 普通方式執行10億次消耗:13ms 反射方式執行10億次消耗:20141ms 關閉安全檢查反射方式執行10億次消耗:8233ms
經過實驗可知,反射比直接執行相同的方法慢了不少,特別是當反射的操做被頻繁調用時效果更明顯,固然經過關閉安全檢查能夠提升一些速度。因此,放射也不該該氾濫成災的,而是適度使用才能發揮最大做用。