一文講明白反射(必看)----反射概念、通過反射創建對象、使用反射指定方法、屬性

  • Java Reflection

 Reflection(反射)是被視爲動態語言的關鍵,反射機制允許程序在執行期藉助於Reflection API取得任何類的內部信息,並能直接操作任意對象的內部屬性及方法(包括私有的方法和屬性)。

Java反射機制提供的功能:

  1. 在運行時判斷任意一個對象所屬的類
  2. 在運行時構造任意一個類的對象
  3. 在運行時判斷任意一個類所具有的成員變量和方法 
  4. 在運行時調用任意一個對象的成員變量和方法
  5. 生成動態代理
  • 對反射內涵的理解

 其實,自己理解一下Java反射機制,再複述出來就是:它就是一面“鏡子”,能夠映照出所有,Java中,一切皆爲對象,無論是class,或者裏面的屬性、方法、接口等等,通通可以理解爲對象,如果程序處在運行當中,想要改變它的方法參數或者其他對象時,該怎麼處理呢?

對這個問題的解釋就要涉及程序的兩種編譯方式了,一種是靜態編譯,在編譯時就確定類型,綁定對象;另一種就是運用了反射機制的動態編譯,在程序運行時確定類型,綁定對象。

而要想利用Java反射機制去解剖一個類,其前提是,必須先要獲取到該類的字節碼文件(*.class)對象。爲什麼是這樣呢?從java程序的編譯過程來看,它最終要變成字節碼文件(*.class),才能被移植到各個裝有JVM的機器上運行。因此,反射機制利用的工具源頭就是這個字節碼文件。每一個類對應着一個字節碼文件也就對應着一個class類型的對象,即字節碼文件對象。

如圖是類的正常加載過程:Class對象的由來是將class文件讀入內存,併爲之創建一個Class對象

也就是說,正常創建並加載類的方式是:

而反射方式則是:

這裏的getClass()方法是什麼意思呢?其實,在Object類(我們知道它是所有類的鼻祖)中定義了以下的方法,此方法將被所有子類繼承:

● public final Class getClass() 

以上的方法返回值的類型是一個Class類,此類是Java反射的源頭,實際上所謂反射從程序的運行結果來看也很好理解, 即:可以通過對象反射求出類的名稱。

  • Class類

通過閱讀Class類的源碼,我們可以知道以下關於Class類的事:

對於每個類而言,JRE 都爲其保留一個不變的 Class 類型的對象。一個Class 對象包含了特定某個類的有關信息。

  1. Class本身也是一個類,只是名字與class關鍵字高度相似,而Java是區分大小寫的語言
  2. Class 對象只能由系統(JVM)建立對象,因爲這個類沒有public構造函數,構造函數爲private
  3. 一個類在 JVM 中只會有一個Class實例
  4. 一個Class對象對應的是一個加載到JVM中的一個.class文件 
  5.  每個類的實例都會記得自己是由哪個 Class 實例所生成
  6. 通過Class可以完整地得到一個類中的完整結構

Class類的常用方法(見表,後面會有代碼演示,其他方法請參考API手冊)

 實例化Class類對象(四種方法)

其實這小節講解的就是獲取字節碼文件對象的四種方法。

1.在源文件階段:

Class class1 = Class.forName("包名.類名");
//裝入類,並做類的靜態初始化,返回Class的對象

2.在字節碼階段

Class class2 = Person.class;
//JVM將使用類裝載器,將類裝入內存(前提是:類還沒有裝入內存),不做類的初始化工作,返回Class的對象

3.在創建對象階段

Person person = new Person();
Class c3=Person.getClass();
//對類進行靜態初始化、非靜態初始化;返回引用運行時真正所指的對象(子對象的引用會賦給父對象的引用變量中)所屬的類的Class的對象

4.在類加載器階段

//第四種(瞭解)利用類加載器,返回class類型
//得到類的加載器
ClassLoader classLoader = this.getClass().getClassLoader();
//加載類 返回class對象 傳入包類名稱
Class class4=classLoader.loadClass("com.my.Person");

由於篇幅所限,這四種實例化Class對象的方法的區別就說到這裏,關於他們之間的區別爲什麼是這樣,這位博主從源碼的角度去闡述,我將鏈接附在這裏:

https://blog.csdn.net/zhaominpro/article/details/89810941

Class創建對象的方式

好了,現在我們有了Class對象,能做什麼?我們可以創建類的對象:調用Class對象的newInstance()方法。要求:1)類必須有一個無參數的構造器。2)類的構造器的訪問權限需要足夠。

難道沒有無參的構造器就不能創建對象了嗎?

不是!只要在操作的時候明確的調用類中的構造方法,並將參數傳遞進去之後,纔可以實例化操作。步驟如下: 1)通過Class類的getDeclaredConstructor(Class ... parameterTypes) 取得本類的指定形參類型的構造器 2)向構造器的形參中傳遞一個對象數組進去,裏面包含了構造器中所需的各個參數。3)通過Constructor實例化對象。

下面我們來演示一下。

首先是新建一個Dog類。

package com.my;
public class Dog {
	String name;
	private String color;
	public String getColor() {
		return color;
	}
	public void setColor(String color) {
		this.color = color;
	}
	
	
	
	//無參構造
	public Dog() {
		System.out.println("dog無參構造");
	}
	//有參構造
	public Dog(String name, String color) {
		System.out.println("dog有參構造");
		this.name = name;
		this.color = color;
	}
	
	//play
	public void play() {
		System.out.println(name+"在玩耍");
	}
	
	@Override
	public String toString() {
		return "Dog [name=" + name + ", color=" + color + "]";
	}
	
	
	

}
package com.my;


import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

//Class創建對象的方式
public class DogTest {

	public static void main(String[] args) {
		
        //這裏trycatch拋出一下異常
		try {
			createDog();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		try {
			createDog2();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	//第一種  無參構造,拋異常  
	public static void  createDog() throws InstantiationException, IllegalAccessException {
		Class class1=Dog.class;
		//必須有無參構造  注意權限修飾符 建議使用public
		Dog d1=(Dog) class1.newInstance();
		System.out.println(d1);
	}
	//第二種 使用其他構造方法,拋異常
	public static void createDog2() throws NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		Class class1=Dog.class;
		//當我們使用其他構造方法  得到類的實例  注意   得到構造器指定構造方法 對應參數類型
		Constructor cc1= class1.getDeclaredConstructor(String.class,String.class);
		//注意事項 此newInstance方法是 構造器對象 調用出來的 不是class對象
		Dog d1=(Dog) cc1.newInstance("小花","黑白相間");
		System.out.println(d1);
	}
	

}

 通過反射調用類的完整結構

使用反射可以取得:

1.實現的全部接口
public Class<?>[] getInterfaces() 確定此對象所表示的類或接口實現的接口。

2.所繼承的父類
public Class<? Super T> getSuperclass() 返回表示此 Class 所表示的實體(類、接口、基本 類型)的父類的 Class。

3.全部的構造器
public Constructor<T>[] getConstructors()返回此 Class 對象所表示的類的所有public構造方法。

public Constructor<T>[] getDeclaredConstructors()返回此 Class 對象表示的類聲明的所有構造方法。

  • Constructor類中:
  • 取得修飾符: public int getModifiers();
  • 取得方法名稱: public String getName();
  • 取得參數的類型:public Class<?>[] getParameterTypes();

4.全部的方法
public Method[] getDeclaredMethods() 返回此Class對象所表示的類或接口的全部方法

public Method[] getMethods() 返回此Class對象所表示的類或接口的public的方法

  • Method類中:
  • public Class<?> getReturnType()取得全部的返回值
  • public Class<?>[] getParameterTypes()取得全部的參數
  • public int getModifiers()取得修飾符
  • public Class<?>[] getExceptionTypes()取得異常信息

5.全部的Field
public Field[] getFields() 返回此Class對象所表示的類或接口的public的Field。 

public Field[] getDeclaredFields() 返回此Class對象所表示的類或接口的全部Field。

  • Field方法中:
  • publicintgetModifiers() 以整數形式返回此Field的修飾符
  • publicClass<?>getType() 得到Field的屬性類型
  • publicStringgetName() 返回Field的名稱。

6.泛型相關
獲取父類泛型類型:Type getGenericSuperclass() 

泛型類型:ParameterizedType 

獲取實際的泛型類型參數數組:getActualTypeArguments()

7.類所在的包 Package getPackage()

 

下面我們來演示一下:

首先新建一個Pet類:

package com.my;

//他是cat的父類   <>就是泛型  注意泛型不僅可以在集合中使用 還可以在類中 方法中應用 T 佔位符 (字母隨意都可以 一般使用T E B等)
public class Pet<T> {

	String color;
    
    @Override
	public String toString() {
		return "Pet [color=" + color + "]";
	}	
}

然後實現一個接口:

package com.my;
//接口
public interface MyInter {
}

然後新建一個Cat類,讓它繼承Pet父類並實現接口:

package com.my;
//cat繼承pet類
public class Cat extends Pet<String> implements MyInter {

	private String name;
	public int age;
	
	
	//有參構造
	 Cat(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}


	 //無參構造
	public Cat() {
		super();
	}



	@Override
	public String toString() {
		return "Cat [name=" + name + ", age=" + age + "]";
	}
	
	//成員方法
	public void eat() {
		System.out.println(name+"在吃");
	}
	void play() {
		System.out.println(name+"在玩");
	}
}

下面進入我們的測試類,可以看到,Cat類中繼承了父類,實現了接口,有public和private兩種類型的成員變量,不僅有public修飾的無參構造,還有默認default修飾的有參構造,我們重寫了toString方法,併爲它寫了兩個權限不同的成員方法,這些應該足夠我們測試了。

package com.my;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;

// 通過反射 ==》父類   父類泛型    接口  屬性  方法  構造方法  包名
public class OtherTest {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//這裏我們trycatch拋出一下異常
        try {
			fun1();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		fun2();
		fun3();
		fun4();
		fun5();
		System.out.println("------------------fun6----------");
		fun6();
		System.out.println("------------------fun7----------");
		fun7();
	}
	//1.獲取運行時父類
	public static void fun1() throws InstantiationException, IllegalAccessException {
		Class<Cat> c1=Cat.class;
        Class p1=c1.getSuperclass();
		Pet pp1=(Pet) p1.newInstance();
		System.out.println(p1);
		System.out.println(pp1);
	}

	//2.獲取父類帶泛型
	public static void fun2() {
		Class<Cat> c1=Cat.class;
		Type t1 = c1.getGenericSuperclass();
		System.out.println(t1);
	}
	//3.獲取接口
	public static void fun3() {
		Class<Cat> c1=Cat.class;
		Class [] cc1=c1.getInterfaces();
		for (Class class1 : cc1) {
			System.out.println(class1);
		}
	}
	//4.獲取包
	public static void fun4() {
		Class<Cat> c1=Cat.class;
		Package package1=c1.getPackage();
		System.out.println(package1);
	}
	//5.獲取構造方法
	public static void fun5() {
		Class<Cat> c1=Cat.class;
		//返回public修飾的所有構造方法
		Constructor[] constructors = c1.getConstructors();
		for (Constructor constructor : constructors) {
			System.out.println(constructor);
		}
		//返回所有構造方法
		Constructor[] declaredConstructors = c1.getDeclaredConstructors();
		for (Constructor constructor : declaredConstructors) {
			System.out.println("----"+constructor);
		}
		
	}
	//6.獲取所有成員方法
	public static void fun6() {
		Class<Cat> c1=Cat.class;
		//返回帶public修飾的所有成員方法
		Method[] methods = c1.getMethods();
		for (Method method : methods) {
			System.out.println(method);
		}
		//返回所有成員方法
		//返回包含一個數組 方法對象反射的類或接口的所有聲明的方法,通過此表示 類對象,包括公共,保護,默認(包)訪問和私有方法,但不包括繼承的方法。 
		Method[] declaredMethods = c1.getDeclaredMethods();
		for (Method method : declaredMethods) {
			System.out.println("Declared======"+method);
		}
	}
	//7.獲取所有屬性
	public static void fun7() {
		Class<Cat> c1=Cat.class;
		//返回public修飾的所有屬性
		Field[] fields = c1.getFields();
		for (Field field : fields) {
			System.out.println(field);
		}
		//返回所有屬性 忽略修飾符
		Field[] declaredFields = c1.getDeclaredFields();
		for (Field field : declaredFields) {
			System.out.println(field);
		}
	}

}

關於反射的基本知識就說到這裏,相信足夠日常開發使用,關於其他反射有關的知識或者細節,我將不定期更新,如有不足之處歡迎您在評論區指出。

原創不易,如果覺得這篇文章還算不錯的話,請您評論、點贊、分享、收藏,您的支持是我創作的最大動力!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章