反射機制是Java動態性之一,而說到動態性首先得了解動態語言。那么何為動態語言?
一、動態語言
動態語言,是指程序在運行時可以改變其結構:新的函數可以引進,已有的函數可以被刪除等結構上的變化。比如常見的JavaScript就是動態語言,除此之外Ruby,Python等也屬于動態語言,而C、C++則不屬于動態語言。
二、Java是動態語言嗎?
從動態語言能在運行時改變程序結構結構或則變量類型上看,Java和C、C++一樣都不屬于動態語言。
但是JAVA卻又一個非常突出的與動態相關的機制:反射機制。Java通過反射機制,可以在程序運行時加載,探知和使用編譯期間完全未知的類,并且可以生成相關類對象實例,從而可以調用其方法或則改變某個屬性值。所以JAVA也可以算得上是一個半動態的語言。
三、反射機制:
1.反射機制概念
在Java中的反射機制是指在運行狀態中,對于任意一個類都能夠知道這個類所有的屬性和方法;并且對于任意一個對象,都能夠調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成為Java語言的反射機制。
2.反射的應用場合
在Java程序中許多對象在運行是都會出現兩種類型:編譯時類型和運行時類型。
編譯時的類型由聲明對象時實用的類型來決定,運行時的類型由實際賦值給對象的類型決定
如:
Person p=new Student();
其中編譯時類型為Person,運行時類型為Student。
除此之外,程序在運行時還可能接收到外部傳入的對象,該對象的編譯時類型為Object,但是程序有需要調用該對象的運行時類型的方法。為了解決這些問題,程序需要在運行時發現對象和類的真實信息。然而,如果編譯時根本無法預知該對象和類屬于哪些類,程序只能依靠運行時信息來發現該對象和類的真實信息,此時就必須使用到反射了。
四、Java反射API
反射API用來生成JVM中的類、接口或則對象的信息。
- Class類:反射的核心類,可以獲取類的屬性,方法等信息。
- Field類:Java.lang.reflec包中的類,表示類的成員變量,可以用來獲取和設置類之中的屬性值。
- Method類: Java.lang.reflec包中的類,表示類的方法,它可以用來獲取類中的方法信息或者執行方法。
- Constructor類: Java.lang.reflec包中的類,表示類的構造方法。
五、使用反射的步驟
1.步驟
- 獲取想要操作的類的Class對象
- 調用Class類中的方法
- 使用反射API來操作這些信息
2.獲取Class對象的方法
- 調用某個對象的getClass()方法
Person p=new Person();
Class clazz=p.getClass();
- 調用某個類的class屬性來獲取該類對應的Class對象
Class clazz=Person.class;
- 使用Class類中的forName()靜態方法; (最安全/性能最好)
Class clazz=Class.forName("類的全路徑"); (最常用)
3.獲取方法和屬性信息
當我們獲得了想要操作的類的Class對象后,可以通過Class類中的方法獲取并查看該類中的方法和屬性。
示例代碼:
<<<<<<<<<<<<<<<<<<<<<<Person類<<<<<<<<<<<<<<<<<<<<<<<<<<
package reflection;
public class Person {
private String name;
private String gender;
private int age;
public Person() {
}
public Person(String name, String gender, int age) {
this.name = name;
this.gender = gender;
this.age = age;
}
//getter和setter方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return "姓名:"+name+" 性別:"+gender+" 年齡:"+age;
}
}
<<<<<<<<<<<<<<<<使用反射<<<<<<<<<<<<<<<<<<<
package reflection;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/*
* 通過用戶輸入類的全路徑,來獲取該類的成員方法和屬性
* Declared獲取全部不管是私有和公有
* 1.獲取訪問類的Class對象
* 2.調用Class對象的方法返回訪問類的方法和屬性信息
*/
public class Test {
public static void main(String[] args) {
try {
//獲取Person類的Class對象
Class clazz=Class.forName("reflection.Person");
//獲取Person類的所有方法信息
Method[] method=clazz.getDeclaredMethods();
for(Method m:method){
System.out.println(m.toString());
}
//獲取Person類的所有成員屬性信息
Field[] field=clazz.getDeclaredFields();
for(Field f:field){
System.out.println(f.toString());
}
//獲取Person類的所有構造方法信息
Constructor[] constructor=clazz.getDeclaredConstructors();
for(Constructor c:constructor){
System.out.println(c.toString());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出結果:
方法信息:
public java.lang.String reflection.Person.toString()
private java.lang.String reflection.Person.getName()
private void reflection.Person.setName(java.lang.String)
public void reflection.Person.setAge(int)
public int reflection.Person.getAge()
public java.lang.String reflection.Person.getGender()
public void reflection.Person.setGender(java.lang.String)
屬性信息:
private java.lang.String reflection.Person.name
private java.lang.String reflection.Person.gender
private int reflection.Person.age
構造方法信息
private reflection.Person()
public reflection.Person(java.lang.String,java.lang.String,int)
4.創建對象
當我們獲取到所需類的Class對象后,可以用它來創建對象,創建對象的方法有兩種:
- 使用Class對象的newInstance()方法來創建該Class對象對應類的實例,但是這種方法要求該Class對象對應的類有默認的空構造器。
- 先使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來創建 Class對象對應類的實例,通過這種方法可以選定構造方法創建實例。
示例代碼:
package reflection;
import java.lang.reflect.Constructor;
public class Demo01 {
public static void main(String[] args) {
try {
//獲取Person類的Class對象
Class clazz=Class.forName("reflection.Person");
/**
* 第一種方法創建對象
*/
//創建對象
Person p=(Person) clazz.newInstance();
//設置屬性
p.setName("張三");
p.setAge(16);
p.setGender("男");
System.out.println(p.toString());
/**
* 第二種方法創建
*/
//獲取構造方法
Constructor c=clazz.getDeclaredConstructor(String.class,String.class,int.class);
//創建對象并設置屬性
Person p1=(Person) c.newInstance("李四","男",20);
System.out.println(p1.toString());
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出結果:
姓名:張三 性別:男 年齡: 16
姓名:李四 性別:男 年齡: 20
應用——Annotation(注解)
如果沒有用來讀取注解的方法和工作,那么注解也就不會比注釋更有用處了。使用注解的過程中,很重要的一部分就是創建于使用注解處理器。Java SE5擴展了反射機制的API,以幫助程序員快速的構造自定義注解處理器。
注解處理器類庫(java.lang.reflect.AnnotatedElement):
Java使用Annotation接口來代表程序元素前面的注解,該接口是所有Annotation類型的父接口。除此之外,Java在java.lang.reflect 包下新增了AnnotatedElement接口,該接口代表程序中可以接受注解的程序元素,該接口主要有如下幾個實現類:
- Class:類定義 - Constructor:構造器定義
- Field:類的成員變量定義
- Method:類的方法定義
- Package:類的包定義
java.lang.reflect 包下主要包含一些實現反射功能的工具類,實際上,java.lang.reflect 包所有提供的反射API擴充了讀取運行時Annotation信息的能力。當一個Annotation類型被定義為運行時的Annotation后,該注解才能是運行時可見,當class文件被裝載時被保存在class文件中的Annotation才會被虛擬機讀取。
AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通過反射獲取了某個類的AnnotatedElement對象之后,程序就可以調用該對象的如下四個方法來訪問Annotation信息:
方法1: T getAnnotation(Class annotationClass): 返回該程序元素上存在的、指定類型的注解,如果該類型注解不存在,則返回null。
方法2:Annotation[] getAnnotations():返回該程序元素上存在的所有注解。
方法3:boolean is AnnotationPresent(Class< ?extends Annotation> annotationClass):判斷該程序元素上是否包含指定類型的注解,存在則返回true,否則返回false.
方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注釋。與此接口中的其他方法不同,該方法將忽略繼承的注釋。(如果沒有注釋直接存在于此元素上,則返回長度為零的一個數組。)該方法的調用者可以隨意修改返回的數組;這不會對其他調用者返回的數組產生任何影響。
一個簡單的注解處理器:
/***********注解聲明***************/
/**
* 水果名稱注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
String value() default "";
}
/**
* 水果顏色注解
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
/**
* 顏色枚舉
*/
public enum Color {
BULE, RED, GREEN
};
/**
* 顏色屬性
*/
Color fruitColor() default Color.GREEN;
}
/**
* 水果供應者注解
*
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
/**
* 供應商編號
*/
public int id() default -1;
/**
* 供應商名稱
*/
public String name() default "";
/**
* 供應商地址
*/
public String address() default "";
}
/*********** 注解使用 ***************/
public class Apple {
@FruitName("Apple")
private String appleName;
@FruitColor(fruitColor = Color.RED)
private String appleColor;
@FruitProvider(id = 1, name = "陜西紅富士集團", address = "陜西省西安市延安路89號紅富士大廈")
private String appleProvider;
public void setAppleColor(String appleColor) {
this.appleColor = appleColor;
}
public String getAppleColor() {
return appleColor;
}
public void setAppleName(String appleName) {
this.appleName = appleName;
}
public String getAppleName() {
return appleName;
}
public void setAppleProvider(String appleProvider) {
this.appleProvider = appleProvider;
}
public String getAppleProvider() {
return appleProvider;
}
public void displayName() {
System.out.println("水果的名字是:蘋果");
}
}
/*********** 注解處理器 ***************/
public class FruitInfoUtil {
public static void getFruitInfo(Class<?> clazz) {
String strFruitName = " 水果名稱:";
String strFruitColor = " 水果顏色:";
String strFruitProvicer = "供應商信息:";
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(FruitName.class)) {
FruitName fruitName = (FruitName) field
.getAnnotation(FruitName.class);
strFruitName = strFruitName + fruitName.value();
System.out.println(strFruitName);
} else if (field.isAnnotationPresent(FruitColor.class)) {
FruitColor fruitColor = (FruitColor) field
.getAnnotation(FruitColor.class);
strFruitColor = strFruitColor
+ fruitColor.fruitColor().toString();
System.out.println(strFruitColor);
} else if (field.isAnnotationPresent(FruitProvider.class)) {
FruitProvider fruitProvider = (FruitProvider) field
.getAnnotation(FruitProvider.class);
strFruitProvicer = " 供應商編號:" + fruitProvider.id() + " 供應商名稱:"
+ fruitProvider.name() + " 供應商地址:"
+ fruitProvider.address();
System.out.println(strFruitProvicer);
}
}
}
}
/***********main方法***************/
public class FruitRun {
public static void main(String[] args) {
FruitInfoUtil.getFruitInfo(Apple.class);
}
}
/***********輸出結果***************/
水果名稱:Apple
水果顏色:RED
供應商編號:1 供應商名稱:陜西紅富士集團 供應商地址:陜西省西安市延安路89號紅富士大廈
Java注解的基礎知識點(見下面導圖):[圖片上傳失敗...(image-c6dc6d-1531036897498)]
通過反射操作泛型,注解
上篇文章我介紹了Java反射的基本知識,如果沒看過的同學可以去看我的上一篇文章 反射概念與基礎,今天這篇文章主要介紹一下反射地具體應用實例,分別是通過Java反射操作泛型,和反射操作注解(不了解”注解”的同學可以看我的另一篇文章java注解)。
一、反射操作泛型(Generic)
Java采用泛型擦除機制來引入泛型。Java中的泛型僅僅是給編譯器Javac使用的,確保數據的安全性和免去強制類型轉換的麻煩。但是編譯一旦完成,所有和泛型有關的類型全部被擦除。
為了通過反射操作這些類型以迎合實際開發的需要,Java新增了ParameterizedType,GenericArrayType,TypeVariable和WildcardType幾種類型來代表不能被歸一到Class類中的類型但是又和原始類型齊名的類型。
- ParameterizedType:表示一種參數化的類型,比如Collection< String >
- GenericArrayType:表示一種元素類型是參數化類型或者類型變量的數組類型
- TypeVariable:是各種類型變量的公共父接口
- WildcardType:代表一種通配符類型表達式,比如?、? extends Number、? super Integer。(wildcard是一個單詞:就是”通配符“)
代碼示例:
package reflection;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
/**
* 通過反射獲取泛型信息
*
*/
public class Demo{
//定義兩個帶泛型的方法
public void test01(Map<String,Person> map,List<Person> list){
System.out.println("Demo.test01()");
}
public Map<Integer,Person> test02(){
System.out.println("Demo.test02()");
return null;
}
public static void main(String[] args) {
try {
//獲得指定方法參數泛型信息
Method m = Demo.class.getMethod("test01", Map.class,List.class);
Type[] t = m.getGenericParameterTypes();
for (Type paramType : t) {
System.out.println("#"+paramType);
if(paramType instanceof ParameterizedType){
//獲取泛型中的具體信息
Type[] genericTypes = ((ParameterizedType) paramType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("泛型類型:"+genericType);
}
}
}
//獲得指定方法返回值泛型信息
Method m2 = Demo.class.getMethod("test02", null);
Type returnType = m2.getGenericReturnType();
if(returnType instanceof ParameterizedType){
Type[] genericTypes = ((ParameterizedType) returnType).getActualTypeArguments();
for (Type genericType : genericTypes) {
System.out.println("返回值,泛型類型:"+genericType);
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
輸出結果:
java.util.Map< java.lang.String, reflection.Person >
泛型類型:class java.lang.String
泛型類型:class reflection.Person
java.util.List< reflection.Person >
泛型類型:class reflection.Person
返回值,泛型類型:class java.lang.Integer
返回值,泛型類型:class reflection.Person
二、反射操作注解(Annotation)
具體使用可以就看我的之前的文章 注解處理器
好了,介紹了兩個簡單的反射的應用,在順便講一下Java反射機制的性能問題。
三、反射性能測試
Method/Constructor/Field/Element 都繼承了 AccessibleObject , AccessibleObject 類中有一個 setAccessible 方法:
public void setAccessible(booleanflag)throws SecurityException
{
...
}
該方法有兩個作用:
- 啟用/禁用訪問安全檢查開關:值為true,則指示反射的對象在使用時取消Java語言訪問檢查; 值為false,則指示應該實施Java語言的訪問檢查;
- 可以禁止安全檢查, 提高反射的運行效率.
測試代碼:
package reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class TestReflect {
public static void testNoneReflect() {
Person user = new Person();
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
user.getName();
}
long count = System.currentTimeMillis() - start;
System.out.println("沒有反射, 共消耗 <" + count + "> 毫秒");
}
public static void testNotAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Person user = new Person();
Method method = Class.forName("reflection.Person").getMethod("getName");
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
method.invoke(user, null);
}
long count = System.currentTimeMillis() - start;
System.out.println("沒有訪問權限, 共消耗 <" + count + "> 毫秒");
}
public static void testUseAccess() throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Person user = new Person();
Method method = Class.forName("reflection.Person").getMethod("getName");
method.setAccessible(true);
long start = System.currentTimeMillis();
for (long i = 0; i < Integer.MAX_VALUE; ++i) {
method.invoke(user, null);
}
long count = System.currentTimeMillis() - start;
System.out.println("有訪問權限, 共消耗 <" + count + "> 毫秒");
}
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
testNoneReflect();
testNotAccess();
testUseAccess();
}
}
輸出結果:
沒有反射, 共消耗 <912> 毫秒
沒有訪問權限, 共消耗 <4366> 毫秒 有訪問權限, 共消耗 <2843> 毫秒
可以看到使用反射會比直接調用慢2000 毫秒 ,但是前提是該方法會執行20E+次(而且服務器的性能也肯定比我的機器要高),因此在我們的實際開發中,其實是不用擔心反射機制帶來的性能消耗的,而且禁用訪問權限檢查,也會有性能的提升。