java-反射

反射:在運行時期,通過反射可以動態地去獲取類中的信息(類的信息,方法信息,構造器信息,字段等信息);

類的加載過程(加載機制):

  1. 編碼

  2. 類的加載器去加載(將字節碼文件加載到JVM中,給每個類創建字節碼對象)

  3. 初始化

  4. 運行期

1. Class實例

其實就是一些類型(類 接口 數組 基本數據類型  void)的字節碼對象

Class 類的實例表示正在運行的 Java 應用程序中的類和接口(字節碼對象);

枚舉是一種類,註釋(指的是註解Annotation)是一種接口;

每個數組屬於被映射爲 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class 對象;

基本的 Java 類型(booleanbytecharshortintlongfloatdouble)和關鍵字 void 也表示爲 Class 對象;

  注意

  1 Class和它的實例的產生: Class的實例是已經存在的類型,所以不能夠直接new一個Class對象出來,而通過已知的類型和Class來獲得

  2、同一種類型不管通過什麼方式得到Class的實例都是相等的;一個類型的字節碼對象只有一份

    線程同步:同步監聽對象字節碼對象來充當同步監聽  始終保證都共享的是同一個同步監聽對象

  3Class的實例就看成是Java中我們學過的所有的數據類型在JVM中存在的一種狀態(字節碼對象)

    String.class   int.class  List.class  int[].class  int[][].class

在 Java API 中,獲取 Class 類對象有三種方法:

第一種,使用 Class.forName 靜態方法。當你知道該類的全路徑名時,你可以使用該方法獲取 Class 類對象。

Class clz = Class.forName("java.lang.String");

第二種,使用 .class 方法。

這種方法只適合在編譯前就知道操作的 Class。

Class clz = String.class;

第三種,使用類對象的 getClass() 方法。

String str = new String("Hello");
Class clz = str.getClass();

 Class實例 的其他獲得方式的一些注意點

public static void main(String[] args) throws Exception {
	//1. 獲取接口的字節碼對象的兩種方式
		Class list1 = Class.forName("java.util.List");
		Class list2 = List.class;
		System.out.println(list1 == list2);  //true
		
		
	//2. 獲取數組的字節碼對象的兩種方式
		int[] a = {1,5,8};
		Class a1 = int[].class;
		Class a2 = a.getClass();
		System.out.println(a1 == a2);        //true
		
		int[] b = {};
		Class b1 = b.getClass();
		System.out.println(a1 == b1);		//true
		
		String[] s = {};
		Class s1 = s.getClass();
		String[][] ss = {};
		Class s2 = ss.getClass();
		System.out.println(s1 == s2);		//false
//		綜上:具有相同元素類型  和  維數的數組都共享該 Class 對象;
		
		
	//3. 獲取基本數據類型、包裝類型 的字節碼對象的幾種方式
		Class int1 = int.class;
		Class int2 = Integer.TYPE;
		//獲取 包裝類型  的字節碼對象的三種方式
		Class intc1 = Class.forName("java.lang.Integer");
		Class intc2 = Integer.class;
		Class intc3 = new Integer(10).getClass();
		
/*	注意點:	Integer 是 Object Type 對象類型, int 是 Primitive Type 原始類型
			Integer 有成員變量,有成員方法,int 無成員變量,無成員方法
			Integer.class 與 int.class 不同
			Integer.TYPE 與 int.class 相同
*/
		System.out.println(int1 == int2);   //true
		System.out.println(intc1 == int2); 	//false
		System.out.println(intc1 == intc3);  //true
		
	 
/*	4. void 和基本數據類型只能通過類型名稱獲取該字節碼對象
		 還可以通過其對應的包裝類的TYPE屬性獲取其字節碼對象。
		 * eg 獲取int的字節碼對象
		 * 		int.class或者 Integer.TYPE
		 * 	  獲取void的字節碼對象
		 * 		void .class 或者 Void.TYPE
		 * 
	其中: Void類是一個不可實例化的佔位符類,它持有對標識Java關鍵字void的Class對象的引用。
		 並且本身的構造函數爲private,即該類是不可以實例化的
	底層代碼:	
		public final class Void {
		    public static final Class<Void> TYPE = (Class<Void>) Class.getPrimitiveClass("void");
		    private Void() {}
		}
*/		
		 Class v1 = void.class;
		 Class v2 = Void.TYPE;
		 
		 Class vc1 = Void.class;
		 Class vc2 = Class.forName("java.lang.Void");
		 
		 System.out.println(v1 == v2);    //true
		 System.out.println(v1 == vc2);   //false
	}

  

 

2. Constructor類是什麼?
  Constructor是一個類,位於java.lang.reflect包下。
  在Java反射中 Constructor類描述的是 類的構造方法信息

如何獲取Constructor類對象?
    - getConstructors(): 獲取一個類中所有非私有化的構造方法

     - getConstructor(): 獲取非私有 無參的構造方法
    - getConstructor(Class[] params): 獲取類的特定構造方法,params參數指定構造方法的參數類型


    - getDeclaredConstructors(): 獲取類中所有的構造方法(public、protected、default、private)

    - getDeclaredConstructor():   獲取私有化的無參構造方法
    - getDeclaredConstructor(Class[] params): 獲取類的特定構造方法,params參數指定構造方法的參數類型

 

package com.ganshao.Test2;
import java.lang.reflect.Constructor;

//Person 類
class Person {
	private String name;
	public Person(String name) {
		this.name = name;
	}
	private Person() {}
}
//Student 類
class Student {
	public Student(String name) {}
}
public class Test1 {
	public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
		Class c1 = Class.forName("com.ganshao.Test2.Person");//獲取Person類的字節碼對象
		/*
		 * public Constructor<T> getConstructor(Class<?>... parameterTypes)
		 * 返回一個 Constructor 對象,它反映此 Class 對象所表示的類的指定公共構造方法。
		 * */
		Constructor pc1= c1.getConstructor(String.class);		//不可以去獲取私有化的構造方法
		Constructor con1 = c1.getDeclaredConstructor();			//可以去獲取私有化的無參構造方法
		System.out.println(pc1);    //public com.ganshao.Test2.Person(java.lang.String)
		System.out.println(con1);   //private com.ganshao.Test2.Person()
		
		System.out.println("---");
	//獲取一個類中所有非私有化的構造方法
		Constructor[] cons = c1.getConstructors();
		for (Constructor constructor : cons) {
			System.out.println(constructor);
		}
		System.out.println("---");
		
	//獲取一個類中所有的構造方法(跟權限無關)
		Constructor[] cons1 = c1.getDeclaredConstructors();   //注意末尾加了   "s"
		for (Constructor constructor : cons1) {
			System.out.println(constructor);
		}
		System.out.println("---");
		
		Constructor s1 = Student.class.getDeclaredConstructor();
		System.out.println(s1);     //報錯:NoSuchMethodException,因爲沒有私有的構造方法
	}
}

  

3. 創建對象五種的方式
  * 1.  new 構造器 ,可以創建有參數對象、無參數對象,不能調用priavte的構造方法
  * 2.  字節碼對象.newInstance()   只能夠調用 無參數的 非私有的 構造方法, 故當類的 無參的構造方法 被私有化就會報錯。
  * 3.  通過Constructor的對象.newInstance(Object...init)獲得    //可以創建有參數對象、無參數對象,通過setAccessible(true)的方式去調用priavte的構造方法

  * 4. 使用clone方法,我們需要先實現Cloneable接口            例如:  Employee emp4 = (Employee) emp3.clone();

  * 5.  使用反序列化  (這個在io 流中說過)

ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();

  

 注意:方法2.3是通過反射創建類對象的

 

AccessibleObject 類   是Method、Field、Constructor類的基類,

它提供了標記反射對象的能力,以抑制在使用時使用默認Java語言訪問控制檢查,從而能夠任意調用被私有化保護的方法、域和構造函數;

  提供了:void setAccessible(boolean flag) 方法

    值爲 true 則指示反射的對象在使用時應該取消 Java 語言訪問檢查。值爲 false 則指示反射*的對象應該實施 Java 語言訪問檢查。

//Student 類
class Student {
	private String name;
	
	public Student(String name) {this.name = name;}
	private Student() {}
	
	@Override
	public String toString() {
		return "Student [name=" + name + "]";
	}
}
public class Test1 {
	public static void main(String[] args) throws Exception {	
	//2. 字節碼對象.newInstance()
//		Class c = Student.class;
//		Student s = (Student)(c.newInstance());
//		System.out.println(s);   // 此時 如果 Student()構造方法 訪問權限設置爲 private 會報錯。
		
	//3. 通過Constructor方法獲得
		Class c = Student.class;
		Constructor constructor = c.getDeclaredConstructor();
		constructor.setAccessible(true);     //讓方法的private修飾符在這一次創建對象時失效
		Student s2 =(Student)(constructor.newInstance());
		System.out.println(s2);            //Student [name=null]
		
		Constructor constructor1 = c.getConstructor(String.class);
		Student s3 =(Student)(constructor1.newInstance("張三"));
		System.out.println(s3);          //Student [name=張三]
	}
}

  

4. 通過反射獲取類屬性、方法、構造器

我們通過 Class 對象的 getFields() 方法可以獲取 Class 類的屬性,但無法獲取私有屬性。

而如果使用 Class 對象的 getDeclaredFields() 方法則可以獲取包括私有屬性在內的所有屬性:

與獲取類屬性一樣,當我們去獲取類方法、類構造器時,如果要獲取私有方法或私有構造器,則必須使用有 declared 關鍵字的方法。

其中 :注意末尾加不加  “s” ,加s 獲取指定的,    不加 s 獲取所有的。     getMethod(可以有參數)         getMethods()   。

getMethods方式返回的自己以及父類的所有的公共的方法

getDeclaredMethod 返回自己的所有方法  不包含基類中的方法

 getMethod("方法的對象",形參列表的字節碼對象)

5. 通過反射執行方法

獲得字節碼對象-->獲取方法對象  -->通過方法的對象.invoke() 去執行方法

Object invoke(Object obj, Object... args)
返回值類型Object 參數:該方法所屬的對象, [...]多個實際參數

 

package 反射的三種用法;
interface A{}
@SuppressWarnings(value = { "123" })/*壓縮警告*/
public class Person implements A {
	private String name;
	private int age;
	
	@Deprecated/*表示定義的方法或者字段或者類過時了*/
	public int getAge() {
		return age;
	}
	public Person(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	public Person() {
	}
	@Override
	public String toString() {
		return "Person [name=" + name + ", age=" + age + "]";
	}
//私有的方法
	private void fun(String name){
		System.out.println("你好:"+name);
	}
//靜態的方法	
	public static void fun1(int a){
		System.out.println("你好:"+a);
	}
	
	
}

 

  

 

 

public static void main(String[] args) throws Exception{
//1. 反射調用方法
//	 1. 通過反射調用 普通方法        比如:toString()方法
		Class c1 = Person.class;             //獲得字節碼對象
		Method m1 = c1.getMethod("toString");// 獲取方法對象
		Object obj = m1.invoke(new Person());//通過方法的對象.invoke() 去執行方法
		System.out.println(obj);          //Person [name=null, age=0]

//	 2. 通過反射調用 私有的方法
		Method m2 = c1.getDeclaredMethod("fun", String.class);
		m2.setAccessible(true);         //  若沒有setAccessible設置這一次的訪問權限。   會報錯:IllegalAccessException
		m2.invoke(new Person(),"李四");  //你好:李四
//	 3. 反射調用  靜態的方法      只需要將invoke方法的第一個參數設爲null即可.
		Method m3 = c1.getMethod("fun1",int.class);
		m3.invoke(null,11);   //你好:11
		
//2. 反射獲取字段
		Field f1 = c1.getDeclaredField("name");
		System.out.println(f1);   //private java.lang.String 反射的三種用法.Person.name
		
		Field[] f2 = c1.getDeclaredFields();
		for (Field i : f2) {
			System.out.println(i);
		}
		//private java.lang.String 反射的三種用法.Person.name
		//private int 反射的三種用法.Person.age
		
//3. 反射獲得字段的類型  (  字段對象.getType()  )
		System.out.println(f1.getType());   //class java.lang.String
//4. 獲得修飾符
		int modify = f1.getModifiers();  //獲取字段的訪問修飾符
		System.out.println(modify);      //2
//5. 獲得包  c1.getPackage()  和實現的接口  c1.getInterfaces()
//6. 獲得註解(註釋Annotation) 
		Method m4 = c1.getDeclaredMethod("getAge");//獲取方法
		System.out.println(m4.getAnnotation(Deprecated.class));  //@java.lang.Deprecated()
//7. 獲得類型的簡稱 getName()
		System.out.println(m4.getName());  //getAge
	} 
}

 

  

 

 

6. 反射可以越過集合類的泛型檢查 

public static void main(String[] args) throws Exception {
	//反射可以越過集合類的泛型檢查
		ArrayList<Integer> list = new ArrayList<>();
		list.add(123);
		list.add(new Integer(45));
		
		//通過反射添加泛型以外的類型的數據
		Class c = list.getClass();
		Method m1 = c.getMethod("add",Object.class);
		m1.invoke(list,"nihao");  //調用list集合的add方法,添加數據
		
		System.out.println(list);  //[123, 45, nihao]
	}

 

7. 通過反射運行配置文件內容 

思想:  (其實在實際開發中經常遇到需求變更)那可不可以不改源程序就能應對大量的需求變更呢?

答案是可以的,通過Java給我們提供的反射機制,不改源程序,只對配置文件做修改即可,   spring框架就是基於反射機制,通過修改配置文件來實現需求

 

class Person{
	private int id;
	private String name;
	public Person(int id,String name){
		this.id = id;
		this.name = name;
	}
	@Override
	public String toString() {
		return "Person [id=" + id + ", name=" + name + "]";
	}
}
public class Test2 {
	public static void main(String[] args) throws Exception{
		//反射機制,不改源程序,只對配置文件做修改
		Constructor<Person> cons = Person.class.getConstructor(int.class,String.class);
		//Properties類用於讀取配置文件的鍵值對
		Properties prop = new Properties();
		prop.load(new FileInputStream("person.properties"));  //讀取
		Person p  = cons.newInstance(Integer.parseInt(prop.getProperty("id")),prop.getProperty("name"));
		System.out.println(p);    //Person [id=3242, name=李四]
	}
}

 


 

  

 

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