1. 程式人生 > >Java能夠分析類能力的程式-反射

Java能夠分析類能力的程式-反射

一、為什麼要學習反射

我們為什麼要用反射,這主要是反射的動態性決定的,由於反射可以實現動態建立物件,這就很大程度發揮了java的靈活性,降低了程式呼叫的耦合性,使系統可以更加的靈活,可以更好的應對變化。而且反射式學習Java框架的基礎,在Java框架的底層我們可以看到很多實現都是基於反射的。

二、我們能用反射干哪些事情

在執行時判斷任意一個物件所屬的類

在執行時構造任意一個類的物件

在執行時判斷任意一個類所具有的成員變數和方法

在執行時呼叫任意一個物件的成員變數和方法

生成動態代理

跟反射相關的常用類主要有:
(1) java.lang.Class:代表一個類
(2)java.lang.reflect.Method:代表類的方法
(3) java.lang.reflect.Field:代表類的成員變數
(4)java.lang.reflect.Constructor:代表類的構造方法
下面一個個講有什麼用,怎麼用。

三、怎麼用

1、what‘s Class?
Class是一個類,是反射機制的源頭。一般我們在建立一個物件的時候都是先通過構造一個類,在建立一個這個類的物件,而反射剛好就是這個的反過程。我們可以通過指導物件來找到它屬於哪個類的,並且獲取到這個類的資訊。
在這裡插入圖片描述通過反射我們可以得到的資訊:某個類的屬性、方法、構造器、類實現了哪些介面、父類是什麼。對於每個類而言,JRE都為其保留了一個不變的Class型別的物件,一個Class物件包含了特定某個類的有關資訊。
Class本身也是一個類,每個類在JVM中只有有一個Class例項,每個Class物件對應的是一個載入到JVM中的一個.class檔案,每個類的例項都會記得自己是由哪個Class例項所生成的。
2、類載入器


當我們程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過如下三個步驟來對該類進行初始化。
在這裡插入圖片描述ClassLoader(類載入器)是用來把類(.class)裝在進入記憶體的,JVM在執行時會產生3個類載入器組成的初始化載入器層次結構 ,如下圖所示:
在這裡插入圖片描述

在這裡插入圖片描述下面我們通過程式來看一下這三個載入器的執行順序和功能

package java_1;

import java.util.Scanner;
import java.util.*;

public class ss {

	public static void main(String[] args) throws Exception
	{
		ClassLoader loader1 = ClassLoader.getSystemClassLoader();
		System.out.println("系統類載入器為:"+loader1);
		//2.獲取系統類載入器的父類載入器,即擴充套件類載入器
		ClassLoader loader2 = loader1.getParent();
		System.out.println("擴充套件類載入器:"+loader2);
		 
		//3.獲取擴充套件類載入器的父類載入器,即引導類載入器
		ClassLoader loader3 = loader2.getParent();
		System.out.println("引導類載入器:"+loader3);
		 
		//4.測試當前類由哪個類載入器進行載入
		Class class1 = Person.class;
		ClassLoader loader4= class1.getClassLoader();
		System.out.println("當前類的類載入器:"+loader4);
		 
		//5.測試JDK提供的String類由哪個類載入器載入
		String className= "java.lang.String";
		Class class2 = Class.forName(className);
		ClassLoader loader5= class2.getClassLoader();
		System.out.println("JDK提供的String類載入器:"+loader5);

}
}
class Person{
	public Person(String name,int age) {
		this.name = name;
		this.age = age;
	}
	private String name;
	private int age;
}

執行結果:
系統類載入器為:
[email protected]
擴充套件類載入器:[email protected] 引導類載入器:null 當前類的類載入器:[email protected] JDK提供的String類載入器:null

通過輸出的結果我們可以看到我們自己定義的類是通過系統類載入器載入的。通過上圖我們可以看到系統類載入器的上一層是拓展類載入器,再上一層是引導類載入器,引導類載入器是不允許我們訪問的,從輸出結果為空就可以看出。關於載入器的內容可以參考 https://blog.csdn.net/javazejian/article/details/73413292,
通過上圖我們可以知道類載入器之間的關係為:
啟動類載入器,由C++實現,沒有父類。

拓展類載入器(ExtClassLoader),由Java語言實現,父類載入器為null。

系統類載入器(AppClassLoader),由Java語言實現,父類載入器為ExtClassLoader。

自定義類載入器,父類載入器肯定為AppClassLoader。

3、如何得到Class物件?
獲取Class物件主要有4中方法
(1)用類的靜態屬性class,用在已知類

Class class1 = Person.class;
System.out.println(class1);

(2)用getClass()方法,用在已知類的物件

Person c = new Person("tang",12);
Class class1 = c.getClass();
		

(3)用forName()方法,已知Person類的全類名,且該類在類路徑下,可通過Class類的靜態方法forName()獲取。

String s = " java_1.Person";
Class class1 = Class.forName(s);

(4)通過類載入器

Person c = new Person("tang",12);
String s = "java_1.Person";
ClassLoader className =c.getClass().getClassLoader();
Class class1 = className.loadClass(s);

以上四個的執行結果都是
class java_1.Person
4、java.lang.reflect包中的三個類,Method,Field,Constructor
前面說到這三個分別用於描述類的方法,域和構造器。下面用一個例子來說明他們的作用

package reflect;

import java.util.*;
import java.lang.reflect.*;


public class ReflectionTest {
	public static void main(String[] args) {
		String name;
//		System.out.println(args.length);
//		for(int i=0;i<args.length;i++)
//		{
//			System.out.println(args[i]+" ");
//		}
		if(args.length>0)name=args[0];
		else
		{
			Scanner in = new Scanner(System.in);
			System.out.println("Enter class name(java.util.Date):");
			name = in.next();
		}
		try
		{
			Class c1 = Class.forName(name);//將字元轉成類
			Class superc1 = c1.getSuperclass();//返回c1的父類
			//返回一個整型數值,用不同的位開關描述public和static這樣的修飾符使用情況
			String modifiers = Modifier.toString(c1.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println("class "+name);
			//判斷它有父類
			if(superc1!=null&&superc1!=Object.class)System.out.print("extends"+superc1.getName());//getName.返回類的名稱
			System.out.print("\n{\n");
			System.out.print("獲取類的所有建構函式:\n");
			printConstructors(c1);//獲取到所有的建構函式和建構函式裡面的引數型別
			System.out.println();
			System.out.print("獲取類的所有方法:\n");
			printMethods(c1);//獲取到類中所有的方法
			System.out.println();
			System.out.print("獲取類的所有域:\n");
			printFields(c1);//獲取類中的域
			System.out.println("}");
		}
		catch(ClassNotFoundException e)
		{
			e.printStackTrace();
		}
		System.exit(0);
	}
	//描述類的域
	public static void printFields(Class c1) {
		// TODO Auto-generated method stub
		Field[] fields = c1.getDeclaredFields();
		for(Field f:fields)
		{
			Class type = f.getType();//這是獲取域的型別
			String name = f.getName();
			System.out.println(" ");
			String modifiers = Modifier.toString(f.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println(type.getName()+" "+name+";");
			
		}
		
	}
	//描述類的方法
	private static void printMethods(Class c1) {
		// TODO Auto-generated method stub
		Method[] methods = c1.getDeclaredMethods();
		for(Method m:methods)
		{
			Class retType = m.getReturnType();//獲取返回的引數型別
			String name = m.getName();
			System.out.println("  ");
			String modifiers = Modifier.toString(m.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println(retType.getName()+" "+name+"(");
			Class[] paramTypes = m.getParameterTypes();
			for(int j=0;j<paramTypes.length;j++)
			{
				if(j>0)System.out.println(", ");
				System.out.println(paramTypes[j].getName());
				
			}
			System.out.println(");");
		}
	}
	//描述類的建構函式
	private static void printConstructors(Class c1) {
		// TODO Auto-generated method stub
		//返回它所有的建構函式放在陣列中
		Constructor[] constructors = c1.getConstructors();
		for(Constructor c:constructors)
		{
			//獲得方法名
			String name = c.getName();
			System.out.println(" ");
			String modifiers = Modifier.toString(c.getModifiers());
			if(modifiers.length()>0)System.out.println(modifiers+" ");
			System.out.println(name+"(");
			Class[] paramTypes = c.getParameterTypes();//獲取引數型別
			for(int j=0;j<paramTypes.length;j++)
			{
				if(j>0)System.out.println(", ");
				System.out.println(paramTypes[j].getName());
				
			}
			System.out.println("),");
		}
	}

}

方法:
<1>
(1)

Field[] getFields(),
Method[] getMethods(),
Constructor[] getConstructors()

他們分別能夠返回的是一個包含Field物件、Method物件和Constructor物件的陣列,包含所有的公有域,方法和構造器和從父類繼承來的公有域、方法和構造器。
(2)

Field[] getDeclaredFields(),//返回包含Field物件的陣列,如果這個類沒有域或者是基本型別或陣列型別,則返回Null
Method[] getDeclaredMethods(),//返回這個類或介面的所有方法
Constructor[] getDeclaredConstructors()//返回這個類的所有構造器

這兩個的區別在於有沒有返回從父類那裡繼承下來的

<2> String getName()
返回一個用於描述構造器、方法或域名的字串。
比如

Constructor[] constructors = c1.getConstructors();
		for(Constructor c:constructors)
			//獲得方法名
			String name = c.getName();

在這一句中就會返回構造器的名稱
<3> int getModifiers()
返回一個用於描述構造器、方法或域的修飾符的整型數值,使用Modefier類中的這個方法可以分析這個返回值。

Constructor[] constructors = c1.getConstructors();
		for(Constructor c:constructors)
			String modifiers = Modifier.toString(c.getModifiers());
比如我們獲取到的是一個
public Double(){
}的構造器,這個返回的就是public

<4> Class[] getParameterTypes()(只存在Method和Constructor中)
返回一個用於描述引數型別的Class物件陣列

Constructor[] constructors = c1.getConstructors();
for(Constructor c:constructors)
	Class[] paramTypes = c.getParameterTypes();//獲取引數型別

比如我們獲得一個Public Double(int a,String b){}的構造器
則返回的是 int和String

<5>getReturnType()(只存在Method中)

Method[] methods = c1.getDeclaredMethods();
for(Method m:methods)
	Class retType = m.getReturnType();//獲取返回的引數型別
	
如果在Double類中有一個 public int getDate(Sting a){}這樣的方法,則返回int

<6>getType()(fields中的)

Field[] fields = c1.getDeclaredFields();
		for(Field f:fields)
			Class type = f.getType();//這是獲取域的型別
如果在類中有一個private int name;
返回int

<7>setAccessible()

Person harry = new Person("Harry Hacker",18);
Class c1 = harry.getClass();
//f.setAccessible(true);
Field f = c1.getDeclaredField("name");
Object v = f.get(harry);

在這個程式碼中我們想要用域的欄位來獲取物件的值,但是在類中,域是一個private,直接用get呼叫時無法獲取到的,上面的程式碼會丟擲一個錯誤

IllegalAccessException

所以我們就可以用setAccessible()來修改訪問控制
在上面新增

f.setAccessible(true);

<8>Object newInstance(Object[] args)和newInstance(),一個有參一個無參

建立 Class 物件所對應的那個類的物件: 呼叫 Class 物件的 newInstance() 方法

Class c = Person.class;
Object object = c.newInstance();

<9> public Object invoke(Object,implicitParameter,Object[] explicitParamenters)
呼叫這個來執行方法,但是invoke的引數和返回值都是Object型別的,這就意味著必須進行多次的型別轉換。這樣會使編譯器錯過檢查程式碼的機會,所以最好不要用這種方式來執行方法,應該用介面和內部類。

四、動態代理與AOP的結合—反射典例

五、Java反射和CGLIB原理及其異同

後面學習到再補充。。。。。