三、反射機制操作
3.1 通過反射擷取構造方法并使用
先來建立一個使用者類用來測試:
package com.bean;
public class User {
public Integer id;
private String username;
private String password;
public User() {
}
/**
* 使用private修飾有參構造,目前構造隻能在本類中使用
* @param username
* @param password
*/
private User(String username,String password){
this.username = username;
this.password = password;
}
public User(Integer id, String username, String password) {
this.id = id;
this.username = username;
this.password = password;
}
public Integer getId() {return id;}
public void setId(Integer id) {this.id = id;}
public String getUsername() {return username;}
public void setUsername(String username) {this.username = username;}
public String getPassword() {return password;}
public void setPassword(String password) {this.password = password;}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
private String show(){
System.out.println("id : "+getId()+" , username : "+getUsername()+" , password : "+ getPassword());
return "helloworld";
}
}
下面看一下怎樣通過反射擷取構造器:
package com.demo;
import java.lang.reflect.Constructor;
/**
* 反射操作構造方法
*/
public class Demo06 {
public static void main(String[] args) throws Exception {
//擷取User類對應的Class對象
Class<?> clazz = Class.forName("com.bean.User");
//擷取無參構造方法對象,c1存儲的是一個構造方法對象,
Constructor<?> c1 = clazz.getConstructor();
//使用無參建立User類對象
Object obj1 = c1.newInstance();
System.out.println(obj1);
System.out.println("---------------");
//擷取User類對應的有參構造方法對象 分别填入參數對應的.class對象
Constructor<?> c2 = clazz.getConstructor(Integer.class, String.class, String.class);
//使用有參建立User對象
Object obj2 = c2.newInstance(1, "張三", "root");
System.out.println(obj2);
System.out.println("---------------");
//擷取User(String username,String password)構造方法對象
//Constructor<?> c3 = clazz.getConstructor(String.class, String.class);
//使用私有有參建立User對象
//Object obj3 = c3.newInstance("張國靜", "20000101");
//System.out.println(obj3);
//此時運作會報錯,原因是此處要擷取的構造方法是私有的,無法擷取,
//可以改變setAccessible設定實作暴力反射
Constructor<?> c3 = clazz.getDeclaredConstructor(String.class, String.class);
//暴力反射,讓私有構造器對象可以被外部通路(一般不推薦使用)
c3.setAccessible(true);
Object obj3 = c3.newInstance("張國靜", "20000101");
System.out.println(obj3);
}
}
3.2 通過反射擷取成員變量并使用
package com.demo;
import com.bean.User;
import java.lang.reflect.Field;
/**
* 反射操作成員變量
*/
public class Demo07 {
public static void main(String[] args) throws Exception {
//擷取User類的Class對象
Class<User> clazz = User.class;
User user = clazz.newInstance();
//操作public修飾的成員變量
Field idField = clazz.getField("id");
//設定該成員變量值
//set方法有兩個參數obj、value
//obj:需要設定的對象
//value:需要設定的值
//給user對象的id屬性設定值為250
idField.set(user,250);
System.out.println(user);
//擷取該成員變量值
Object idValue = idField.get(user);
System.out.println(idValue);
//操作非public修飾的成員變量
Field usernameField = clazz.getDeclaredField("username");
usernameField.setAccessible(true);//暴力反射
usernameField.set(user,"小明");
System.out.println(user);
Object usernameValue = usernameField.get(user);
System.out.println(usernameValue);
}
}
3.3 通過反射擷取成員方法并使用
package com.demo;
import com.bean.User;
import java.lang.reflect.Method;
/**
* 反射操作成員方法
*/
public class Demo08 {
public static void main(String[] args) throws Exception {
//擷取User類的Class對象
Class<User> clazz = User.class;
User user = clazz.newInstance();
//擷取public修飾的成員方法 有兩個參數,方法名、該方法參數的class對象
Method method01 = clazz.getMethod("setPassword", String.class);
//使用方法對象,invoke方法有兩個參數 obj、args
//obj:哪個對象在執行該方法
//args:方法執行時所需的參數值
method01.invoke(user,"123456");
System.out.println(user);
//操作非public修飾的成員方法
Method method02 = clazz.getDeclaredMethod("show");
method02.setAccessible(true);
Object result = method02.invoke(user);
System.out.println(result);
}
}
四、反射機制應用
4.1 反射結合工廠模式
我們舉個摘水果的例子來解釋:
首先建立水果父類:
package com.bean;
/**
* 水果類
*/
public class Fruit {
private String fruitName;
public Fruit() {
}
public Fruit(String fruitName) {
this.fruitName = fruitName;
}
public String getFruitName() {
return fruitName;
}
public void setFruitName(String fruitName) {
this.fruitName = fruitName;
}
@Override
public String toString() {
return "Fruit{" +
"fruitName='" + fruitName + '\'' +
'}';
}
}
再建立兩個水果子類:蘋果和香蕉
package com.bean;
/**
* 蘋果
*/
public class Apple extends Fruit{
private int weight;
public Apple() {
}
public Apple(String fruitName , int weight) {
super(fruitName);
this.weight = weight;
}
}
package com.bean;
/**
* 香蕉
*/
public class Banana extends Fruit{
private int weight;
public Banana() {
}
public Banana(String fruitName,int weight) {
super(fruitName);
this.weight = weight;
}
}
來看看下邊的測試:
package com.demo;
import com.bean.Apple;
import com.bean.Banana;
public class Demo02 {
public static void main(String[] args) {
//擷取一個蘋果
Apple apple = new Apple("紅富士",10);
//擷取一個香蕉
Banana banana = new Banana("黃金大香蕉",10);
}
}
這種方式的缺點是什麼?
我們發現這種操作方式的類之間耦合性太高,這僅僅是兩個水果,如果是很多水果那麼就需要依次new對象!!
那麼有什麼解決方法呢?
下面來看一種改進的方法:添加水果工廠
package com.factory;
import com.bean.Apple;
import com.bean.Banana;
/**
* 水果工廠
* 工廠模式:降低耦合
*/
public class FruitFactory1 {
public static Apple getApple(){
return new Apple("紅富士",10);
}
public static Banana getBanana(){
return new Banana("東亞小香蕉",10);
}
}
再來看看測試:
package com.demo;
import com.bean.Apple;
import com.bean.Banana;
import com.factory.FruitFactory1;
public class Demo03 {
public static void main(String[] args) {
//擷取一個蘋果
Apple apple = FruitFactory1.getApple();
//擷取一個香蕉
Banana banana = FruitFactory1.getBanana();
}
}
這樣做發現和之前的demo02例子的性質差不多,仍然存在問題!需要在工廠類中添加很多方法(一種水果對應一個)!
再看一種改進的方式:結合多态
package com.factory;
import com.bean.Apple;
import com.bean.Banana;
import com.bean.Fruit;
/**
* 水果工廠
* 工廠模式:降低耦合
* 結合使用多态:可擴充性強
*/
public class FruitFactory2 {
public static Fruit getFruit(String fruitName){
if ("apple".equals(fruitName)) {
return new Apple("紅富士",10);
} else if ("banana".equals(fruitName)) {
return new Banana("南亞小香蕉",10);
}
return null;
}
}
測試類:
package com.demo;
import com.bean.Apple;
import com.bean.Banana;
import com.factory.FruitFactory2;
public class Demo04 {
public static void main(String[] args) {
//擷取一個蘋果
Apple apple = (Apple) FruitFactory2.getFruit("apple");
//擷取一個香蕉
Banana banana = (Banana) FruitFactory2.getFruit("banana");
}
}
此時我們發現如果有很多水果,在工廠二中必須要有許多的if-else判斷,是以還可以進行改進,用反射機制結合工廠模式:
package com.factory;
import com.bean.Fruit;
/**
* 水果工廠
* 工廠模式:降低耦合
* 結合使用多态:可擴充性強
* 反射機制:解決if..else過多的問題
*/
public class FruitFactory {
public static Fruit getFruit(String fruitName) throws Exception {
//Class.forName(全類名).newInstance,可以根據全類名,獲得類,再new對象(newInstance方法)
return (Fruit) Class.forName(fruitName).newInstance();
}
}
測試類:
package com.demo;
import com.bean.Fruit;
import com.factory.FruitFactory;
public class Demo05 {
public static void main(String[] args) throws Exception {
//擷取蘋果
Fruit fruit1 = FruitFactory.getFruit("com.bean.Apple");
//擷取香蕉
Fruit fruit2 = FruitFactory.getFruit("com.bean.Banana");
}
}
存在的問題:
com.bean.Apple對應的對象
将以上"Apple"字元串寫到java代碼中,合适嗎?
顯然不合适!!!
如果全類名發生改變了,那你就必須修改java源代碼,必須要重新部署項目!
"Apple"字元串和java程式的耦合性非常高!!
"Apple"字元串不能放到java代碼中,而應該放置到配置檔案中!!
這樣秩序修改配置檔案即可,不需操作源碼!!
解耦!降低耦合!!!
4.2 反射結合配置檔案
建立一個配置檔案如bean.properties,在檔案中填寫:
bean01=com.bean.Apple
bean02=com.bean.Banana
package com.factory;
import com.bean.Fruit;
import java.io.InputStream;
import java.util.Properties;
/**
* 水果工廠
* 結合配置檔案
*/
public class FruitFactoryPro {
private static final Properties properties = new Properties();
public static Fruit getFruit() throws Exception {
try {
//适用類自身自帶的流
InputStream is = FruitFactoryPro.class.getClassLoader().getResourceAsStream("com/factory/bean.properties");
properties.load(is);//通過流,将配置資訊的内容分割成鍵值對
return (Fruit)Class.forName(properties.getProperty("bean01")).newInstance();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
測試類:
package com.demo;
import com.bean.Fruit;
import com.factory.FruitFactory;
import com.factory.FruitFactoryPro;
public class Demo05 {
public static void main(String[] args) throws Exception {
///擷取蘋果
Fruit fruit1 = FruitFactoryPro.getFruit();
}
}
//此處是簡易版,可以通過外部操作(如網頁web控制等)來确定調用配置檔案的哪個内容
4.3反射越過泛型檢查
- 案例示範:
- 建立ArrayList的一個對象,在這個集合中添加一個字元串資料,如何實作呢?
package com.demo;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
public class Demo09 {
public static void main(String[] args) throws Exception {
//建立List集合對象
List<Integer> list = new ArrayList<Integer>();
list.add(1);
list.add(2);
list.add(3);
System.out.println(list);
//泛型隻在編譯期有效!!!
//反射越過泛型檢查
//反射可以在程式運作時,動态地調用List中的add方法去添加元素
Class<? extends List> clazz = list.getClass();
Method add = clazz.getDeclaredMethod("add", Object.class);
add.setAccessible(true);
Object result = add.invoke(list, "hello generic type !");
System.out.println(list);
System.out.println(result);
}
}
4.4 反射通用方法
package com.demo;
import com.bean.User;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* 反射案例
* 給指定對象的指定屬性設定指定值
*/
public class Demo10 {
public static void main(String[] args) throws Exception {
User user = new User();
setValue(user,"id" , 123);
System.out.println(user);
}
/**
* 給指定對象的指定屬性設定指定值
* @param obj : 指定對象
* @param fieldName : 指定屬性
* @param value : 指定值
*/
public static void setValue(Object obj , String fieldName , Object value) throws Exception {
Class<?> clazz = obj.getClass();
//方法名規範:如果隻有一個單詞,所有的字母全都小寫。如果有多個單詞,從第二個單詞開始,首字母大寫!!!
//變量名規範: 如果隻有一個單詞,所有的字母全都小寫。如果有多個單詞,從第二個單詞開始,首字母大寫!!!
//username setUsername
//根據屬性名稱擷取對應的set方法名稱
//String methodName = "set" + "U" + "sername";
String methodName = "set" + fieldName.substring(0,1).toUpperCase() + fieldName.substring(1);
//擷取變量
Field field = clazz.getDeclaredField(fieldName);
//擷取變量的資料類型
Class<?> fieldType = field.getType();
//擷取到該變量的set方法對象
Method method = clazz.getMethod(methodName, fieldType);
//執行set方法
method.invoke(obj , value);
}
}