反射:在運行時期,通過反射可以動態地去獲取類中的信息(類的信息,方法信息,構造器信息,字段等信息);
類的加載過程(加載機制):
1. 編碼
2. 類的加載器去加載(將字節碼文件加載到JVM中,給每個類創建字節碼對象)
3. 初始化
4. 運行期
1. Class實例
其實就是一些類型(類 接口 數組 基本數據類型 void)的字節碼對象
Class
類的實例表示正在運行的 Java 應用程序中的類和接口(字節碼對象);
枚舉是一種類,註釋(指的是註解Annotation)是一種接口;
每個數組屬於被映射爲 Class 對象的一個類,所有具有相同元素類型和維數的數組都共享該 Class
對象;
基本的 Java 類型(boolean
、byte
、char
、short
、int
、long
、float
和 double
)和關鍵字 void
也表示爲 Class
對象;
注意:
1、 Class類和它的實例的產生: Class的實例是已經存在的類型,所以不能夠直接new一個Class對象出來,而通過已知的類型和Class來獲得
2、同一種類型不管通過什麼方式得到Class的實例都是相等的;一個類型的字節碼對象只有一份!
線程同步:同步監聽對象字節碼對象來充當同步監聽 始終保證都共享的是同一個同步監聽對象
3、Class的實例就看成是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=李四] } }