天天看點

【JAVA面向對象】認識反射機制(二)

三、反射機制操作

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);
    }
}