深刻解析java反射

本博文主要記錄Java 反射(reflect)的使用,在瞭解反射以前,你應該先了解 Java 中的 Class 類,若是你不是很瞭解,能夠先簡單瞭解下。java

1、什麼是反射?

反射 (Reflection) 是 Java 的特徵之一,它容許運行中的 Java 程序獲取自身的信息,而且能夠操做類或對象的內部屬性。程序員

Oracle 官方對反射的解釋是:面試

Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.數據庫

簡而言之,經過反射,咱們能夠在運行時得到程序或程序集中每個類型的成員和成員的信息。程序中通常的對象的類型都是在編譯期就肯定下來的,而 Java 反射機制能夠動態地建立對象並調用其屬性,這樣的對象的類型在編譯期是未知的。因此咱們能夠經過反射機制直接建立對象,即便這個對象的類型在編譯期是未知的。編程

2、反射的主要用途

不少人都認爲反射在實際的 Java 開發應用中並不普遍,其實否則。當咱們在使用 IDE(如 Eclipse,IDEA)時,當咱們輸入一個對象或類並想調用它的屬性或方法時,一按點號,編譯器就會自動列出它的屬性或方法,這裏就會用到反射。安全

反射最重要的用途就是開發各類通用框架。不少框架(好比 Spring)都是配置化的(好比經過 XML 文件配置 Bean),爲了保證框架的通用性,它們可能須要根據配置文件加載不一樣的對象或類,調用不一樣的方法,這個時候就必須用到反射,運行時動態加載須要加載的對象。性能優化

舉一個例子,在運用 Struts 2 框架的開發中咱們通常會在 struts.xml 裏去配置 Action,好比:架構

<action name="login"
       class="org.xxx.SimpleLoginAction"
       method="execute">
   <result>/shop/shop-index.jsp</result>
   <result name="error">login.jsp</result>
</action>

配置文件與Action創建了一種映射關係,當 View 層發出請求時,請求會被 StrutsPrepareAndExecuteFilter 攔截,而後 StrutsPrepareAndExecuteFilter 會去動態地建立 Action 實例。好比咱們請求 login.action,那麼 StrutsPrepareAndExecuteFilter就會去解析struts.xml文件,檢索actionnameloginAction,並根據class屬性建立SimpleLoginAction實例,並用invoke方法來調用execute方法,這個過程離不開反射。併發

對與框架開發人員來講,反射雖小但做用很是大,它是各類容器實現的核心。而對於通常的開發者來講,不深刻框架開發則用反射用的就會少一點,不過了解一下框架的底層機制有助於豐富本身的編程思想,也是頗有益的。app

你們能夠加個人程序員交流羣:790047143。羣內有阿里,京東等技術大牛講解的最新Java架構技術。做爲給廣大朋友的加羣福利——分佈式(Dubbo、Redis、RabbitMQ、Netty、RPC、Zookeeper、高併發、高可用架構)/微服務(Spring Boot、Spring Cloud)/源碼(Spring、Mybatis)/性能優化(JVM、TomCat、MySQL)【加羣備註好消息領取最新面試資料】

3、反射的基本運用

3.一、經過反射獲取class對象

經過反射獲取對象有三種方式

3.1.一、Class.forName()獲取

public static Class<?> forName(String className)
            throws ClassNotFoundException {
    Class<?> caller = Reflection.getCallerClass();
    return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

好比,在 JDBC 開發中經常使用此方法加載數據庫驅動

Class.forName("包名.類名");

3.1.二、類名.class獲取

例如:

Class<?> intClass = int.class;
Class<?> integerClass = Integer.TYPE;

#RelfectEntity類爲本文的例子
Class relfectEntity2 = RelfectEntity.class;

3.1.三、對象getClass()獲取

StringBuilder str = new StringBuilder("hello world");
Class<?> strClass = str.getClass();

三種方法均可以實現獲取class對象,框架開發中,通常第一種用的比較多。

3.二、獲取類的構造器信息

獲取類構造器的用法,主要是經過Class類的getConstructor方法獲得Constructor類的一個實例,而Constructor類有一個newInstance方法能夠建立一個對象實例:

public T newInstance(Object ... initargs)

3.三、獲取類的實例

經過反射來生成對象主要有兩種方式。

  • 使用Class對象的newInstance()方法來建立Class對象對應類的實例。
Class<?> c = String.class;
Object str = c.newInstance();
  • 先經過Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來建立實例。
//獲取String所對應的Class對象
Class<?> c = String.class;
//獲取String類帶一個String參數的構造器
Constructor constructor = c.getConstructor(String.class);
//根據構造器建立實例
Object obj = constructor.newInstance("23333");
System.out.println(obj);

這種方法能夠用指定的構造器來建立實例。

3.四、獲取類的變量

實體類:

/**
 * 基類
 */
public class BaseClass {

	public String publicBaseVar1;

	public String publicBaseVar2;
}

/**
 * 子類
 */
public class ChildClass extends BaseClass{

	public String publicOneVar1;

	public String publicOneVar2;

	private String privateOneVar1;

	private String privateOneVar2;
}

測試:

public class VarTest {
	
	public static void main(String[] args) {
		//1.獲取並輸出類的名稱
		Class mClass = ChildClass.class;
		System.out.println("類的名稱:" + mClass.getName());
		System.out.println("----獲取全部 public 訪問權限的變量(包括本類聲明的和從父類繼承的)----");
		
		//2.獲取全部 public 訪問權限的變量(包括本類聲明的和從父類繼承的)
		Field[] fields = mClass.getFields();
		
		//遍歷變量並輸出變量信息
		for (Field field : fields) {
			//獲取訪問權限並輸出
			int modifiers = field.getModifiers();
			System.out.print(Modifier.toString(modifiers) + " ");
			
			//輸出變量的類型及變量名
	        System.out.println(field.getType().getName() + " " + field.getName());
		}
		System.out.println("----獲取全部本類聲明的變量----");
		
		//3.獲取全部本類聲明的變量
		Field[] allFields = mClass.getDeclaredFields();
		for (Field field : allFields) {
			//獲取訪問權限並輸出
			int modifiers = field.getModifiers();
			System.out.print(Modifier.toString(modifiers) + " ");
			
			//輸出變量的類型及變量名
	        System.out.println(field.getType().getName() + " " + field.getName());
		}
	}
}

輸出結果:

類的名稱:com.example.java.reflect.ChildClass
----獲取全部 public 訪問權限的變量(包括本類聲明的和從父類繼承的)----
public java.lang.String publicOneVar1
public java.lang.String publicOneVar2
public java.lang.String publicBaseVar1
public java.lang.String publicBaseVar2
----獲取全部本類聲明的變量----
public java.lang.String publicOneVar1
public java.lang.String publicOneVar2
private java.lang.String privateOneVar1
private java.lang.String privateOneVar2

3.五、修改類的變量

修改子類

/**
 * 子類
 */
public class ChildClass extends BaseClass{

	public String publicOneVar1;

	public String publicOneVar2;

	private String privateOneVar1;

	private String privateOneVar2;

	public String printOneMsg() {
		return privateOneVar1;
	}
}

測試:

public class VarModfiyTest {
	
	public static void main(String[] args) throws Exception {
		//1.獲取並輸出類的名稱
		Class mClass = ChildClass.class;
		System.out.println("類的名稱:" + mClass.getName());
		System.out.println("----獲取ChildClass類中的privateOneVar1私有變量----");
		
		//2.獲取ChildClass類中的privateOneVar1私有變量
		Field privateField = mClass.getDeclaredField("privateOneVar1");
		
		//3. 操做私有變量
	    if (privateField != null) {
	        //獲取私有變量的訪問權
	        privateField.setAccessible(true);
	        
	        //實例化對象
	        ChildClass obj = (ChildClass) mClass.newInstance();
	        
	        //修改私有變量,並輸出以測試
	        System.out.println("privateOneVar1變量,修改前值: " + obj.printOneMsg());

	        //調用 set(object , value) 修改變量的值
	        //privateField 是獲取到的私有變量
	        //obj 要操做的對象
	        //"hello world" 爲要修改爲的值
	        privateField.set(obj, "hello world");
	        System.out.println("privateOneVar1變量,修改後值: " + obj.printOneMsg());
	    }
	}
}

輸出結果:

類的名稱:com.example.java.reflect.ChildClass
----獲取ChildClass類中的privateOneVar1私有變量----
privateOneVar1變量,修改前值: null
privateOneVar1變量,修改後值: hello world

3.六、獲取類的全部方法

修改實體類

/**
 * 基類
 */
public class BaseClass {

	public String publicBaseVar1;

	public String publicBaseVar2;

	private void privatePrintBaseMsg(String var) {
		System.out.println("基類-私有方法,變量:" + var);
	}

	public void publicPrintBaseMsg(String var) {
		System.out.println("基類-公共方法,變量:" + var);
	}
}

/**
 * 子類
 */
public class ChildClass extends BaseClass{

	public String publicOneVar1;

	public String publicOneVar2;

	private String privateOneVar1;

	private String privateOneVar2;

	public String printOneMsg() {
		return privateOneVar1;
	}

	private void privatePrintOneMsg(String var) {
		System.out.println("子類-私有方法,變量:" + var);
	}

	public void publicPrintOneMsg(String var) {
		System.out.println("子類-公共方法,變量:" + var);
	}
}

測試:

public class MethodTest {

	public static void main(String[] args) {
		//1.獲取並輸出類的名稱
		Class mClass = ChildClass.class;
		System.out.println("類的名稱:" + mClass.getName());
		System.out.println("----獲取全部 public 訪問權限的方法,包括本身聲明和從父類繼承的---");

		//2 獲取全部 public 訪問權限的方法,包括本身聲明和從父類繼承的
		Method[] mMethods = mClass.getMethods();
		for (Method method : mMethods) {
			//獲取並輸出方法的訪問權限(Modifiers:修飾符)
			int modifiers = method.getModifiers();
	        System.out.print(Modifier.toString(modifiers) + " ");

	        //獲取並輸出方法的返回值類型
	        Class returnType = method.getReturnType();
	        System.out.print(returnType.getName() + " " + method.getName() + "( ");

	        //獲取並輸出方法的全部參數
	        Parameter[] parameters = method.getParameters();
	        for (Parameter parameter : parameters) {
	            System.out.print(parameter.getType().getName() + " " + parameter.getName() + ",");
	        }

	        //獲取並輸出方法拋出的異常
	        Class[] exceptionTypes = method.getExceptionTypes();
	        if (exceptionTypes.length == 0){
	            System.out.println(" )");
	        } else {
	            for (Class c : exceptionTypes) {
	                System.out.println(" ) throws " + c.getName());
	            }
	        }
		}
		System.out.println("----獲取全部本類的的方法---");
		//3. 獲取全部本類的的方法
	    Method[] allMethods = mClass.getDeclaredMethods();
	    for (Method method : allMethods) {
			//獲取並輸出方法的訪問權限(Modifiers:修飾符)
			int modifiers = method.getModifiers();
			System.out.print(Modifier.toString(modifiers) + " ");

	        //獲取並輸出方法的返回值類型
	        Class returnType = method.getReturnType();
	        System.out.print(returnType.getName() + " " + method.getName() + "( ");

	        //獲取並輸出方法的全部參數
	        Parameter[] parameters = method.getParameters();
	        for (Parameter parameter : parameters) {
	            System.out.print(parameter.getType().getName() + " " + parameter.getName() + ",");
	        }

	        //獲取並輸出方法拋出的異常
	        Class[] exceptionTypes = method.getExceptionTypes();
	        if (exceptionTypes.length == 0){
	            System.out.println(" )");
	        } else {
	            for (Class c : exceptionTypes) {
	                System.out.println(" ) throws " + c.getName());
	            }
	        }
		}
	}
}

輸出:

類的名稱:com.example.java.reflect.ChildClass
----獲取全部 public 訪問權限的方法,包括本身聲明和從父類繼承的---
public java.lang.String printOneMsg(  )
public void publicPrintOneMsg( java.lang.String arg0, )
public void publicPrintBaseMsg( java.lang.String arg0, )
public final void wait( long arg0,int arg1, ) throws java.lang.InterruptedException
public final native void wait( long arg0, ) throws java.lang.InterruptedException
public final void wait(  ) throws java.lang.InterruptedException
public boolean equals( java.lang.Object arg0, )
public java.lang.String toString(  )
public native int hashCode(  )
public final native java.lang.Class getClass(  )
public final native void notify(  )
public final native void notifyAll(  )
----獲取全部本類的的方法---
public java.lang.String printOneMsg(  )
private void privatePrintOneMsg( java.lang.String arg0, )
public void publicPrintOneMsg( java.lang.String arg0, )

爲啥會輸出這麼多呢?

由於全部的類默認繼承object類,打開object類會發現有些公共的方法,因此一併打印出來了!

3.七、調用方法

public class MethodInvokeTest {

	public static void main(String[] args) throws Exception {
		// 1.獲取並輸出類的名稱
		Class mClass = ChildClass.class;
		System.out.println("類的名稱:" + mClass.getName());
		System.out.println("----獲取ChildClass類的私有方法privatePrintOneMsg---");

		// 2. 獲取對應的私有方法
		// 第一個參數爲要獲取的私有方法的名稱
		// 第二個爲要獲取方法的參數的類型,參數爲 Class...,沒有參數就是null
		// 方法參數也可這麼寫 :new Class[]{String.class}
		Method privateMethod = mClass.getDeclaredMethod("privatePrintOneMsg", String.class);

		// 3. 開始操做方法
		if (privateMethod != null) {
			// 獲取私有方法的訪問權
			// 只是獲取訪問權,並非修改實際權限
			privateMethod.setAccessible(true);

			// 實例化對象
			ChildClass obj = (ChildClass) mClass.newInstance();

			// 使用 invoke 反射調用私有方法
			// obj 要操做的對象
			// 後面參數傳實參
			privateMethod.invoke(obj, "hello world");
		}
	}
}

輸出結果:

類的名稱:com.example.java.reflect.ChildClass
----獲取ChildClass類的私有方法privatePrintOneMsg---
子類-私有方法,變量:hello world

4、總結

因爲反射會額外消耗必定的系統資源,所以若是不須要動態地建立一個對象,那麼就不須要用反射。另外,反射調用方法時能夠忽略權限檢查,所以可能會破壞封裝性而致使安全問題。

原文:http://www.justdojava.com/2019/07/05/java-reflect/

相關文章
相關標籤/搜索