類加載機制與反射

類加載機制與反射

當程序主動使用某個類時,若是該類還未被加載到內存中,則系統會經過加載、鏈接、初始化三個步驟來對該類初始化。java

類的加載

類加載指的是將類的class文件讀入內存,併爲之建立一個java.lang.Class對象。類的加載由類加載器完成,加載器由JVM提供。數組

類的鏈接

當類被加載以後,系統會生成一個對應的Class對象,接着就會進入鏈接階段,鏈接階段負責把類的二進制數據合併到JRE中。類的鏈接步驟:學習

  • 驗證:驗證階段用於檢驗被加載的類是否有正確的內部結構,並和其餘類協調一致。
  • 準備:類的準備階段負責爲類的類變量分配內存。並設置默認初始值。
  • 解析:將類的二進制數據中的符號引用替換成直接引用。

類的初始化

JVM負責對類進行初始化,主要就是對類變量進行初始化。在類中對類變量指定初始化值有兩種方式:測試

1,聲明類變量時指定初始化值;code

2,使用靜態初始化塊爲類變量指定初始值。orm

JVM初始化一個類包含如下幾個步驟:對象

1,假如這個類尚未被加載和鏈接,則程序先加載並鏈接該類。繼承

2,假如該類的直接父類尚未被初始化,則先初始化其直接父類。內存

3,假如類中有初始化語句,則系統依次執行這些初始化語句。文檔

經過反射查看類信息

java程序中許多對象在運行時都會出現兩種類型:A a=new B();這種代碼將會生成一個a變量,其中A爲編譯時類型,B爲運行時類型。必須時B繼承A

爲了解決這些問題,程序須要在運行時發現對象和和類的真實信息,有以下兩種作法:

1,假設在編譯時和運行時發現對象和類的信息,咱們可使用instanceof運算符賴判斷,再利用強制類型轉化成其運行時類型。

2,沒法知道該對象和類的信息,這裏就要使用反射了。

得到Class對象

每一個類被加載都會生成一個Class對象,經過該Class對象就能夠訪問到JVM中的這個類。能夠經過一下三種方式得到程序中的Class對象。

1,使用Class類的forName(String clazzName)靜態方法,clazzName是某個類的全限定名(完整包名)。

2,調用某個類的class屬性來獲取該類對應的Class對象,好比A.class將會返回A類的Class對象。

3,調用某個對象的getClass方法,該方法屬於Object對象,因此全部的類均可以調用,返回當前對象所屬類對應的Class對象。

public class InstanceofTest {
	public static void main(String[] args) throws Exception {
		InstanceofA p = new InstanceofB();
		System.out.println(p instanceof InstanceofA);
		if (p instanceof InstanceofB) {
			System.out.println("1:" + Class.forName("www.com.lining.test1.InstanceofA"));
			System.out.println("2:" + "InstanceofA.class");
			System.out.println("3:" + p.getClass());
		} else {
			System.out.println(p.getClass());
		}
	}
}

輸出

1:class www.com.lining.test1.InstanceofA
2:InstanceofA.class
3:class www.com.lining.test1.InstanceofB

運行時和編譯時類型不同可是有繼承關係,使用getClass方法獲取此時的Class對象都是運行時對象

獲取class對象以後就能夠獲取class對象中的信息了,好比獲取此類的全部public構造器、全部的public的方法等,能夠參照幫助文檔中Class類的方法來學習。

使用反射生成並操做

Class對象能夠得到類的方法,構造器,成員變量(實例變量),這三個類都位於java.lang.reflect包下。

建立對象

1,使用Class對象的newInstance()方法來建立該Class對象對應類的實例,這種方式要求該Class對象的對應類有默認構造器,使用newInstance()來建立實例其實就是利用了默認的構造器。

2,先使用Class對象獲取指定的Constructor對象,再調用Constructor對象的newInstance()方法來建立該Class對象對應的類的實例,這種方式能夠選擇使用指定的構造器來建立實例。(java9以後就廢棄了直接使用newInstance()來建立實例,而是使用getDeclaredConstructor().newInstance()建立)

package www.com.lining.test1;

import java.io.FileInputStream;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.swing.JFrame;
/*
 * 經過反射來建立未知的類的對象,利用流來讀取配置文件而後解析爲k-v模式,而後獲取該value,利用value建立對象
 */
public class ObjectPoolFactory {
	// 定義一個對象池,前面是對象名,後面是實際對象
	private Map<String, Object> pool = new HashMap<>();

	// 定義一個建立對象的方法
	// 該方法只要傳入一個字符串類名(權限定名)程序玖能夠根據該類名生成java對象
	@SuppressWarnings("unused")
	private Object createObject(String clazzName) throws Exception {

		Class<?> clazz = Class.forName(clazzName);// 獲取class對象
		return clazz.getDeclaredConstructor().newInstance();// 建立該class對應的對象的實例
	}
	/*
	@SuppressWarnings("unused")
	private Object createObject(String clazzName) throws Exception {
//使用帶參數的構造器建立對象
		Class<?> clazz = Class.forName(clazzName);// 獲取class對象
		Constructor ctor = clazz.getConstructor(String.class);
		return ctor.newInstance("20190607");// 建立該class對應的對象的實例
	
	}
*/

//該方法根據指定文件來初始化對象池,根據配置文件來建立對象
	public void initPool(String filename) {

		try (FileInputStream input = new FileInputStream(filename);) {
			Properties props = new Properties();
			props.load(input);// 從流中讀取屬性列表,解析爲k-v的屬性列表
			for (String name : props.stringPropertyNames()) {// 返回此屬性列表的鍵集k-v
				pool.put(name, createObject(props.getProperty(name)));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public Object getObject(String name) {

		return pool.get(name);// 獲取map集合中name對應的值
	}
	public static void main(String[] args) {
		ObjectPoolFactory fact = new ObjectPoolFactory();
		fact.initPool("index");//這裏的路徑是放在工程下的文件,直接在工程下建立。
		Date date = new Date();
		System.out.println(date);
		System.out.println(fact.getObject("a"));
		JFrame jf = new JFrame();
		System.out.println(jf);
		System.out.println(fact.getObject("b"));
	}
}

輸出

Fri Sep 27 15:51:34 CST 2019
Fri Sep 27 15:51:33 CST 2019
javax.swing.JFrame[frame0,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]
javax.swing.JFrame[frame1,0,0,0x0,invalid,hidden,layout=java.awt.BorderLayout,title=,resizable,normal,defaultCloseOperation=HIDE_ON_CLOSE,rootPane=javax.swing.JRootPane[,0,0,0x0,invalid,layout=javax.swing.JRootPane$RootLayout,alignmentX=0.0,alignmentY=0.0,border=,flags=16777673,maximumSize=,minimumSize=,preferredSize=],rootPaneCheckingEnabled=true]

index文件:

a=java.util.Date
b=javax.swing.JFrame

注意:這裏用帶參數的構造器建立實例時,.newInstance(xx)x表明一個或多個參數,這裏的參數個數類型順序要和建立的類的構造方法參數相對應,否則會報異常java.lang.reflect.InvocationTargetException

調用方法

當得到某個類對應的Class對象後,就能夠經過getMethods()方法返回數組或者getMethod()方法返回對象來獲取所有或者指定的方法。java.lang.reflect.Method裏包含一個invoke方法。

  • Object invoke(Object obj,args....):該方法調用指定的對象obj上的方法,args表明該方法的參數,個數的多少用於重載機制
  • void setAccessible(boolean flg):值爲 true 則指示反射的對象在使用時應該取消 Java 語言訪問檢查。值爲 false 則指示反射的對象應該實施 Java 語言訪問檢查。
package www.com.lining.test1;

import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

public class MethodPoolFactory {

	private Map<String, Object> pool = new HashMap<>();// 對象池,放置建立後的對象k-v
	private Properties pro = new Properties();// 建立一個持久的屬性集對象

	// 從指定文件中初始化Properties對象
	public void init(String filename) {
		try (FileInputStream input = new FileInputStream(filename))// 建立讀取配置文件的流
		{
			pro.load(input);// 讀取流裏的數據解析爲k-v
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

//定義一個建立對象的方法
	public Object createObject(String name) throws Exception {
		Class<?> clazz = Class.forName(name);
		return clazz.getDeclaredConstructor().newInstance();
	}

	// 根據讀取的文件來初始化對象池,建立對象
	public void initPool() throws Exception {
		for (String name : pro.stringPropertyNames()) {// 返回此屬性列表的鍵值集
			if (!name.contains("%")) {
				pool.put(name, createObject(pro.getProperty(name)));
			}
		}
	}

	// 根據屬性文件來調用指定對象的xx方法,xx能夠是建立對象中的任何一個實例方法,好比setter
	public void initProperty() throws Exception {

		for (String name : pro.stringPropertyNames()) {
			if (name.contains("%")) {
				String[] objAndProp = name.split("%");// 將配置文件按照「%」來分割
				Object target = getObject(objAndProp[0]);// 獲取建立的對象
				String mtdName = "set" + objAndProp[1].substring(0, 1).toUpperCase() + objAndProp[1].substring(1);
				Class<?> targetClazz = target.getClass();// 獲取該對象的Class實例
				Method mtd = targetClazz.getMethod(mtdName, String.class);// 經過該實例獲取該方法
				mtd.setAccessible(true);
				mtd.invoke(target, pro.getProperty(name));// 調用該方法

			}
		}
	}

	public Object getObject(String name) {
		return pool.get(name);
	}

	public static void main(String[] args) throws Exception {

		MethodPoolFactory method = new MethodPoolFactory();
		method.init("index1");
		method.initPool();
		method.initProperty();
		System.out.println(method.getObject("b"));

	}
}

輸出

123
%12345%

8
9ddddddd
kjfjf
www.com.lining.test1.Test@1f32e575

index1文件

b=www.com.lining.test1.Test
b%title=123,%12345%,,8,9ddddddd,kjfjf

反射調用的類(能夠本身建立也能夠運用jdk中的)

package www.com.lining.test1;
public class Test {

	public void setTitle(String sb) {
		for (String s : sb.split(",")) {
			System.out.println(s);
		}
	}
}

注意:

1,調用的方法有可能在類中有重載,因此要注意參數的個數類型。

2,setAccessible(boolean flg)方法不屬於Method,而是屬於AccessibleObject類,因此根據繼承關係能夠知道File,Method,Constructor均可以調用這個方法。該方法能夠實現對private等權限的調用。

訪問成員變量

1,經過Class對象的getFields()或getField()方法能夠獲取該類所包括的所有成員變量或指定成員變量。File提供了以下兩組方法來讀取或設置成員變量值。

  • getXXX(Object obj):獲取obj對象的該成員變量的值。當這裏的obj是引用數據類型,則要取消get後面的XXX.
  • setXXX(Object obj,XXX val):將obj對象的該成員變量設置成val值。當這裏的XXX是引用數據類型,則要取消get後面的XXX.
package www.com.lining.test1;

public class FilePoolFactory {

	private String name;
	private int age;

	public String toString() {

		return "FilePoolFactory[name:" + name + ",age:" + age + "]";
	}

}

測試類:

package www.com.lining.test1;

import java.lang.reflect.Field;

public class FilePoolTest {

	public static void main(String[] args)
			throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {

		FilePoolFactory f = new FilePoolFactory();// 建立一個FilePoolFactory的對象
		Class<FilePoolFactory> clazz = FilePoolFactory.class;// 獲取對應的class對象
		Field nameFiled = clazz.getDeclaredField("name");// 經過class對象獲取成員變量
		nameFiled.setAccessible(true);// 設置獲取的權限
		nameFiled.set(f, "jessica");// 設置對象f的成員變量name的值。
		Field ageFiled = clazz.getDeclaredField("age");
		ageFiled.setAccessible(true);
		ageFiled.set(f, 19);
		System.out.println(f.toString());
	}

}

輸出:

FilePoolFactory[name:jessica,age:19]

注意:這裏必定要設置訪問權限setAccessible(boolean flg),若是權限是字段是private權限,那麼就須要使用該方法了,否則會報 java.lang.IllegalAccessException。

相關文章
相關標籤/搜索