1. Class對象
類是程序的一部分,每個類都有一個Class對象,而反射所依賴的就是這個Class對象。
在JVM中,類都是動態加載的,當程序創建第一個對類的靜態成員的引用時,就會加載這個類,這個可以證明構造函數也是類的靜態方法,即使構造方法沒有使用static關鍵字。
類加載器首先檢查在這個類的Class對象是否已經加載,如果尚未加載,默認的類加載器就會根據類名查找 .class 文件,這個類的所有對象都是依據這個類的 .class對象 來創建的。下面舉個例子:
package com.ga;
class Candy{
static{
System.out.println("Loading Candy");
}
}
class Gum{
static{
System.out.println("Loading Gum");
}
}
class Cookie{
static{
System.out.println("Loading Cookie");
}
}
public class SweetShop {
public static void main(String[] args) {
System.out.println("inside main");
new Candy();
System.out.println("After creating Candy");
try{
Class.forName("Gum"); //獲取Class對象的引用的一種方法
}catch (ClassNotFoundException e){
System.out.println("找不到Gum");;
}
System.out.println("After Class.forName(\"Gum\")");
new Cookie();
System.out.println("After creating Cookie");
}
}
輸出結果:
inside main
Loading Candy
After creating Candy
Loading Gum
After Class.forName("Gum")
Loading Cookie
After creating Cookie
上面就用了兩種方式來加載類,第一種直接 new 某個類,第二種就是調用靜態函數 forName() ,根據類名進行加載。
常用的獲取Class對象的方法有:
Class.forName(); //這裏調用的是Class類的靜態函數
class.getClass(); //這裏是調用對象的 getClass() 方法
Class.getInterfaces(); //查詢接口,第一個 Class 是某個類的類名
Class.getSuperclass(); //查詢基類,第一個 Class 是某個類的類名
Class.class; //類字面常量,第一個 Class 是某個類的類名
注:推薦使用類字面常量。如果使用 forName() 或者 getClass() ,有可能會因爲沒找到類(類名打錯)而在運行時報錯,使用類字面常量的話,他在編寫代碼時就受到檢查,如果沒有導入這個類,就會提示錯誤。
2. 反射
反射就是在程序運行時,獲取某個類的Class對象,並根據這個Class對象新建一個實例對象,或者獲得這個類所擁有的方法,屬性,來實現一些通用的方法。大部分框架的實現就是使用了反射。
舉個例子,獲取User的屬性和方法:
class User{
private String name;
private String password;
public User(String name, String password) {
this.name = name;
this.password = password;
}
public User(String name){
this.name = name;
}
public User() {
}
//get/set省略
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
public static void main(String[] args) {
Field[] fields = User.class.getDeclaredFields(); //獲取所有字段
Method[] methods = User.class.getDeclaredMethods(); //獲取所有方法
for (Field field: fields) {
System.out.println(field.toString());
}
for (Method method: methods) {
System.out.println(method.toString());
}
}
}
輸出結果:
private java.lang.String com.qianfeng.User.name
private java.lang.String com.qianfeng.User.password
public java.lang.String com.qianfeng.User.getName()
public void com.qianfeng.User.setName(java.lang.String)
public java.lang.String com.qianfeng.User.getPassword()
public void com.qianfeng.User.setPassword(java.lang.String)
注:getConstructors(),getDeclaredFields(),getDeclaredMethods()等返回數組的方法,數組內容的順序是隨機的。(如要保證順序,可參照Java反射,順序輸出類中的方法)
接下來列舉一些常用方法(有 Declared 這個詞的方法,獲得到任意訪問權限的屬性或方法,沒有的話只能訪問公有即 public 的屬性或方法):
獲取構造器
public static void main(String[] args) {
try {
//獲取User類中的一個公有構造方法,爲空就是無參構造器
Constructor constructor1 = User.class.getConstructor();
//根據參數類型的類對象獲取對應的一個構造方法,這個方法就會獲取只有一個參數,且參數類型爲String類型的構造器
Constructor constructor2 = User.class.getConstructor(String.class);
//獲取User類中所有的公有構造器
Constructor[] constructor3 = User.class.getConstructors();
//獲取User類中的任意權限的一個構造器,用法同上面的獲取公有構造器的方法
Constructor constructor4 = User.class.getDeclaredConstructor();
//獲取User類中的所有構造器
Constructor[] constructor5 = User.class.getDeclaredConstructors();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
獲取成員變量
public static void main(String[] args) {
try {
//根據屬性名獲取對應的公有屬性
Field field = User.class.getField("name");
//獲取所有公有屬性
Field[] fields = User.class.getFields();
//根據屬性名獲取對應的任意權限的屬性
Field field1 = User.class.getDeclaredField("password");
//獲取所有權限的屬性
Field[] fields1 = User.class.getDeclaredFields();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
獲取成員方法
先給User添加兩個方法
public User Create(String name, String password){
return new User(name, password);
}
public User Create(String name){
return new User(name);
}
public static void main(String[] args) {
try {
//根據函數名和參數類型獲取對應的公有成員方法
Method method1 = User.class.getMethod("Create", String.class);
//獲取所有的公有成員方法
Method[] method2 = User.class.getMethods();
//根據函數名和參數類型獲取對應的所有權限的成員方法
Method method3 = User.class.getDeclaredMethod("Create", String.class, String.class);
//獲取所有權限的成員方法
Method[] method4 = User.class.getDeclaredMethods();
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
}
使用技巧
獲取了類的屬性和方法後,就可以根據這些來修改該類的實例對象的屬性的值,或者調用方法,比如:
public static void main(String[] args) {
User user = null;
try {
//通過反射創建實例,
user = User.class.newInstance();
//通過反射獲取 name 字段,並將對象 user 中的 name 字段的值設爲 “user”
User.class.getField("name").setAccessible(true); ////解除私有限定
User.class.getField("name").set(user, "user");
//通過反射獲取 password 字段,並將對象 user 中的 password 字段的值設爲 “password”
User.class.getField("password").setAccessible(true);
User.class.getField("password").set(user, "password");
System.out.println(user);
//通過反射獲取只有一個參數,且參數類型爲 String 類型的 setName 方法,通過invoke調用,invoke()中,第一個參數爲要調用該方法的對象實例,第二個爲參數的值
Object object = user;
User.class.getDeclaredMethod("setName", String.class).invoke(object, "user1");
User.class.getDeclaredMethod("setPassword", String.class).invoke(object, "password1");
System.out.println(user);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
輸出結果
User{name='user', password='password'}
User{name='user1', password='password1'}
由於反射類中的 Field , Method 和 Constructor 繼承自 AccessibleObject ,因此,通過在這些類上調用 setAccessible() 方法,可以修改訪問權限,從而實現對這些字段的操作。
也可以用下面的方式解除操作權限,第一個參數爲字段的對象,第二個爲權限:
AccessibleObject.setAccessible(fields, true);