Java反射學習筆記

1. 獲取類類型

獲取類類型的方法有四種:

  • 通過類對象獲取:對象.getClass(),這個方法是屬於 Object 類的;
  • 通過類的 class 對象獲取:類名.class
  • 通過全類名獲取:Class.forName(“全類名”),這是Class 類的靜態方法。當類不能定位到時,會拋出 ClassNotFoundException
  • 通過 ClassLoader.loadClass()加載:loadClass() 也需要傳入全類名。當類不能定位到時,會拋出 ClassNotFoundException

下面是代碼演示:

package com.java.advanced.features.reflect.clazz;

import com.java.advanced.features.reflect.Apple;

public class GetClassTest {
    public static void main(String[] args) {
        // 1, 通過類對象獲取
        Apple apple = new Apple();
        Class appleClass1 = apple.getClass();
        System.out.println("appleClass1 = " + appleClass1);
        // 2, 通過類的 class 對象獲取
        Class appleClass2 = Apple.class;
        System.out.println("appleClass2 = " + appleClass2);
        // 3, 通過全類名獲取
        Class appleClass3 = null;
        try {
            appleClass3 = Class.forName("com.java.advanced.features.reflect.Apple");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("appleClass3 = " + appleClass3);
        // 4, 通過 ClassLoader.loadClass()加載
        Class appleClass4 = null;
        try {
            appleClass4 = GetClassTest.class.getClassLoader().loadClass("com.java.advanced.features.reflect.Apple");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("appleClass4 = " + appleClass4);

        System.out.println("result = " + (appleClass1 == appleClass2
                && appleClass2 == appleClass3
                && appleClass3 == appleClass4));

    }
}
/*
打印結果:
appleClass1 = class com.java.advanced.features.reflect.Apple
appleClass2 = class com.java.advanced.features.reflect.Apple
appleClass3 = class com.java.advanced.features.reflect.Apple
appleClass4 = class com.java.advanced.features.reflect.Apple
result = true
 */

上面的代碼演示了獲取類類型的四種方法,並且我們還可以看到 appleClass1appleClass2appleClass3appleClass4 是相等的,它們都指向同一個對象。這就說明:類只會被加載一次

2. 獲取類內部信息

這是 Apple.java,下邊我們會多次用到它:

package com.java.advanced.features.reflect;

public class Apple extends Fruit {
    private static int count;
    private final int id = count++;
    String color;
    private int size;
    public float price;

    public Apple() {
    }

    public Apple(String color, int size) {
        this.color = color;
        this.size = size;
    }

    private Apple(String color, Float price) {
        this.color = color;
        this.price = price;
    }

    Apple(String color) {
        this.color = color;
    }

    public static int getCount() {
        return count;
    }

    public String getColor() {
        return color;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        checkSize(size);
        this.size = size;
    }

    public float getPrice() {
        return price;
    }

    private void checkPrice(float price) {
        if (price < 0) {
            throw new IllegalArgumentException("price is not valid, price = " + price);
        }
    }

    void checkSize(Integer size) {
        if (size > 1000) {
            throw new IllegalArgumentException("size is not valid, size = " + size);
        }
    }

    public boolean initColorAndPrice(String color, float price) {
        this.color = color;
        this.price = price;
        return true;
    }
}

package com.java.advanced.features.reflect;

public class Fruit {
    public String taste;

    public Fruit() {
    }

    public String getTaste() {
        return taste;
    }

    public void setTaste(String taste) {
        this.taste = taste;
    }
}

Apple 類包含 :

  • 5 個屬性:
    private static int count;
    private final int id = count++;
    String color;
    private int size;
    public float price;
    
  • 4 個構造方法:
    public Apple()
    public Apple(String color, int size)
    private Apple(String color, Float price)
    Apple(String color)
    
  • 1個靜態方法:
    public static int getCount()
    
  • 7個成員方法:
    public String getColor()
    public int getSize()
    public void setSize(int size)
    public float getPrice()
    private void checkPrice(float price)
    void checkSize(Integer size)
    public boolean initColorAndPrice(String color, float price)
    

Fruit 類包括:

  • 1個屬性:
    public String taste;
    
  • 1個構造方法:
    public Fruit()
    
  • 2個成員方法:
    public String getTaste()
    public void setTaste(String taste)
    

2.1 構造函數

2.1.1 獲取構造函數

Class 類中提供了 4 種相應的 API 可以用來獲取構造函數的 Constructor 對象:

  1. public Constructor<T> getConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回與指定的 parameterTypes 相匹配的公共構造方法的 Constructor 對象;
  2. public Constructor<?>[] getConstructors() throws SecurityException:返回此類公共構造方法的 Constructor 對象數組;
  3. public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:帶有指定參數列表的構造方法的 Constructor 對象;
  4. public Constructor<?>[] getDeclaredConstructors() throws SecurityException:返回此類所有已聲明的構造方法的 Constructor 對象的數組。

在進行代碼演示之前,我們先觀察一下上面的 4 個 API:
1 和 2 都是隻能獲取公共的構造方法對象;
3 和 4 是可以獲取所有(publicproctectedprivate 等)符合的構造方法對象,它們的函數名都包含了 Declared;
1 和 3 都是可以傳入參數列表,返回單個的構造方法對象,還有可以拋出NoSuchMethodException
2 和 4 都不需要傳參數,返回一個構造方法對象的數組。

下面是演示代碼:

package com.java.advanced.features.reflect.classinternalinfo.constructor;

import com.java.advanced.features.reflect.Apple;

import java.lang.reflect.Constructor;

public class ConstructorGetTest {
    public static void main(String[] args) {
        Class<Apple> appleClass = Apple.class;

        System.out.println("1, 演示 public Constructor<?>[] getConstructors()");
        System.out.println("獲取所有的公共構造方法對象數組:");
        Constructor<?>[] constructors = appleClass.getConstructors();
        for (Constructor<?> constructor : constructors) {
            System.out.println(constructor);
        }

        System.out.println("2, 演示 public Constructor<T> getConstructor(Class<?>... parameterTypes)");
        System.out.println("獲取指定參數列表的公共構造方法對象");
        try {
            System.out.println("獲取 public Apple():");
            Constructor<Apple> constructor = appleClass.getConstructor();
            System.out.println("constructor = " + constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("獲取 public Apple(String color, int size):");
            Constructor<Apple> constructor = appleClass.getConstructor(String.class, int.class);
            // 需要注意的是,下面這種寫法會拋出異常:
            // java.lang.NoSuchMethodException: com.java.advanced.features.reflect.Apple.<init>(java.lang.String, java.lang.Integer)
            // 這是因爲沒有找到參數列表爲(String, Integer) 的構造方法
            // Constructor<Apple> constructor1 = appleClass.getConstructor(String.class, Integer.class);
            System.out.println("constructor = " + constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
            System.out.println("獲取 private Apple(String color, Float price):");
            appleClass.getConstructor(String.class, Float.class);
        } catch (NoSuchMethodException e) {
            System.out.println(e);
        }

        System.out.println("3, 演示 public Constructor<?>[] getDeclaredConstructors()");
        Constructor<?>[] declaredConstructors = appleClass.getDeclaredConstructors();
        System.out.println("獲取所有的構造方法對象:");
        for (Constructor<?> declaredConstructor : declaredConstructors) {
            System.out.println(declaredConstructor);
        }

        System.out.println("4, 演示 public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)");
        System.out.println("獲取指定參數列表的構造方法對象");
        try {
            System.out.println("獲取 private Apple(String color, Float price):");
            Constructor<Apple> constructor = appleClass.getDeclaredConstructor(String.class, Float.class);
            System.out.println("constructor = " + constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("獲取 public Apple():");
            Constructor<Apple> constructor = appleClass.getDeclaredConstructor();
            System.out.println("constructor = " + constructor);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }
}
/*
打印結果:
1, 演示 public Constructor<?>[] getConstructors()
獲取所有的公共構造方法對象數組:
public com.java.advanced.features.reflect.Apple(java.lang.String,int)
public com.java.advanced.features.reflect.Apple()
2, 演示 public Constructor<T> getConstructor(Class<?>... parameterTypes)
獲取指定參數列表的公共構造方法對象
獲取 public Apple():
constructor = public com.java.advanced.features.reflect.Apple()
獲取 public Apple(String color, int size):
constructor = public com.java.advanced.features.reflect.Apple(java.lang.String,int)
獲取 private Apple(String color, Float price):
java.lang.NoSuchMethodException: com.java.advanced.features.reflect.Apple.<init>(java.lang.String, java.lang.Float)
3, 演示 public Constructor<?>[] getDeclaredConstructors()
獲取所有的構造方法對象:
com.java.advanced.features.reflect.Apple(java.lang.String)
private com.java.advanced.features.reflect.Apple(java.lang.String,java.lang.Float)
public com.java.advanced.features.reflect.Apple(java.lang.String,int)
public com.java.advanced.features.reflect.Apple()
4, 演示 public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
獲取指定參數列表的構造方法對象
獲取 private Apple(String color, Float price):
constructor = private com.java.advanced.features.reflect.Apple(java.lang.String,java.lang.Float)
獲取 public Apple():
constructor = public com.java.advanced.features.reflect.Apple()
 */

Apple 類有 2 個 public 的構造方法,1 個 private 的構造方法,1 個 default 的構造方法,看一下上面的打印結果,是符合預期的。

需要特別注意的地方是:
需要指定參數列表的兩個 API,如果構造方法的參數列表裏有基本數據類型(如 intfloatboolean 等)的形參,就應當傳入對應的基本數據類型的 Class 對象(如 int.classfloat.classboolean.class 等),而不應該傳入(如Integer.classFloat.classBoolean.class 等),同樣地,如果構造方法的參數列表裏有基本數據類型的包裝器類型(如IntegerFloatBoolean 等)的形參,就應當傳入對應的包裝器類型的 Class 對象(如Integer.classFloat.classBoolean.class 等),而不應該傳入基本數據類型的 Class 對象(如 int.classfloat.classboolean.class 等)。否則,就會拋出 NoSuchMethodException
還有一點就是,parameterTypes參數列表的元素順序一定要與打算獲取的構造方法的形參列表中中的參數類型,個數與順序完全一致。

有同學可能會問,getConstructors() 方法爲什麼沒有獲取 Apple 的父類 Fruitpublic 構造方法呢?這是因爲父類的構造函數不能被子類繼承。

2.1.2 使用構造函數構造實例

使用在 Constructor 類中下的 :

public T newInstance(Object... initargs)
              throws InstantiationException,
                     IllegalAccessException,
                     IllegalArgumentException,
                     InvocationTargetException

參數列表 initargs 是一個Object類型的可變參數列表,我們可以把原本傳遞給構造函數的形參列表的實參,傳遞給這個可變參數列表,這裏要保證參數的個數,順序,類型都是一致的。但是,如果實參是基本數據類型,可以直接傳入,因爲這裏基本類型的值會被包裝在對應的包裝器對象之中。
返回的 T 是一個泛型參數,代表創建出來的新對象。

下面是演示代碼:

package com.java.advanced.features.reflect.classinternalinfo.constructor;

import com.java.advanced.features.reflect.Apple;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class ConstructorNewInstanceTest {
    public static void main(String[] args) {
        Class<Apple> appleClass = Apple.class;
        try {
            // 1,獲取到 private Apple(String color, Float price) 對應的 Constructor 對象
            Constructor<Apple> declaredConstructor = appleClass.getDeclaredConstructor(String.class, Float.class);
            // 因爲目標構造方法是 private 的,所以需要設置下邊的代碼爲 true。
            declaredConstructor.setAccessible(true);
//            Apple apple = declaredConstructor.newInstance("red", Float.valueOf(1.8f));
            // 上面一行等價於下面一行
            Apple apple = declaredConstructor.newInstance("red", 1.8f);
            // 錯誤寫法演示1: 參數順序寫錯,拋出:java.lang.IllegalArgumentException: argument type mismatch
            // declaredConstructor.newInstance(Float.valueOf(1.8f), "red");
            // 錯誤寫法演示2:參數個數寫錯,拋出: java.lang.IllegalArgumentException: wrong number of arguments
            // declaredConstructor.newInstance("red");
            System.out.println(apple);
            System.out.println("color = " + apple.getColor() + ", price = " + apple.getPrice());
        } catch (NoSuchMethodException | IllegalAccessException
                | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
        try {
            // 2, 獲取 public Apple(String color, int size) 對應的 Constructor 對象
            Constructor<Apple> constructor = appleClass.getConstructor(String.class, int.class);
            Apple apple = constructor.newInstance("red", 100);
            // 上面一行等價於下面一行
            //  Apple apple = constructor.newInstance("red", Integer.valueOf(100));
            System.out.println(apple);
            System.out.println("color = " + apple.getColor() + ", size = " + apple.getSize());
        } catch (NoSuchMethodException | IllegalAccessException
                | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}
/*
打印結果:
com.java.advanced.features.reflect.Apple@4554617c
color = red, price = 1.8
com.java.advanced.features.reflect.Apple@74a14482
color = red, size = 100
 */

需要注意的一點是,public void setAccessible(boolean flag)AccessibleObject 類中的一個方法,而 AccessibleObjectConstructorFieldMethod 的基類。這個方法的作用是修改對象的 accessible 標誌(其實是 AccessibleObject 裏的 override 字段),默認爲 false,表示任何函數,字段,構造方法是否可以訪問。如果設置爲 true,不管函數,字段,構造方法是什麼類型的權限修飾符,都可以訪問;如果爲 false,那麼只有 public 修飾的纔可以訪問。

在上面的例子中,若不設置declaredConstructor.setAccessible(boolean flag)true,那麼會拋出如下異常:

java.lang.IllegalAccessException: Class com.java.advanced.features.reflect.classinternalinfo.constructor.ConstructorNewInstanceTest 
can not access a member of class com.java.advanced.features.reflect.Apple with modifiers "private"
	at sun.reflect.Reflection.ensureMemberAccess(Reflection.java:102)

如果參數列表 initargs 裏包含基本數據類型,那麼傳基本數據類型或對應的包裝器類型都是正確的;如果參數列表 initargs 裏包含基本數據類型的包裝器類型,那麼傳基本數據類型還是對應的包裝器類型也是正確的。

2.2 Field 對象

2.2.1 獲取 Field 對象

Class 類有 4 個相應的 API :

  1. public Field getDeclaredField(String name) throws NoSuchFieldException,SecurityException:返回本類聲明的指定字段名的 Field 對象;
  2. public Field[] getDeclaredFields() throws SecurityException:返回本類聲明的所有字段名的 Field 對象的數組;
  3. public Field getField(String name) throws NoSuchFieldException, SecurityException:返回這個 Class 對象指定字段名的 公共Field 對象,也包括從父類繼承來的 公共 Field 對象;
  4. public Field[] getFields() throws SecurityException:返回這個 Class 對象的所有公共Field 對象,,也包括從父類繼承來的 公共 Field 對象。

在進行代碼演示之前,我們先耐心觀察一下上面的 4 個 API:
1 和 2 都是可以獲取所有(publicproctectedprivate 等)符合的 Field 對象,但不包括繼承的字段,它們的函數名都包含了 Declared(Declared 的含義是本類聲明的,不包含從父類繼承來的字段);
3 和 4 都是隻能獲取公共的 Field 對象,但是它的範圍是本類和父類中的公共 Field 對象;
1 和 3 都是可以傳入一個參數,即變量名,返回單個的 Field 對象,並且都可能拋出 NoSuchFieldException
2 和 4 都不需要傳入參數,它們返回的是 Field 對象的數組。

下面是演示代碼:

package com.java.advanced.features.reflect.classinternalinfo.field;

import com.java.advanced.features.reflect.Apple;

import java.lang.reflect.Field;

public class FieldGetTest {
    public static void main(String[] args) {
        Class<Apple> appleClass = Apple.class;

        System.out.println("1, 演示 public Field[] getDeclaredFields()");
        System.out.println("獲取本類所有已聲明字段的 Field 對象數組");
        Field[] declaredFields = appleClass.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            System.out.println(declaredField);
        }
        
        System.out.println("2, 演示 public Field getDeclaredField(String name)");
        System.out.println("獲取本類指定字段名的 Field 對象");
        try {
            System.out.println("獲取 private int size;");
            Field sizeField = appleClass.getDeclaredField("size");
            System.out.println("sizeField = " + sizeField);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("獲取 public float price;");
            Field priceField = appleClass.getDeclaredField("price");
            System.out.println("priceField = " + priceField);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("獲取父類 Fruit 的 public String taste;");
            appleClass.getDeclaredField("taste");
        } catch (NoSuchFieldException e) {
            // 此處拋出異常:java.lang.NoSuchFieldException: taste
            System.out.println(e);
        }

        System.out.println("3, 演示 public Field[] getFields()");
        System.out.println("獲取所有已聲明字段的公共Field 對象數組,包括繼承自父類的");
        Field[] fields = appleClass.getFields();
        for (Field field : fields) {
            System.out.println(field);
        }

        System.out.println("4, 演示 public Field getField(String name)");
        System.out.println("獲取指定字段名的公共的 Field 對象");
        try {
            System.out.println("獲取 private String size");;
            Field sizeField = appleClass.getField("size");
        } catch (NoSuchFieldException e) {
            System.out.println(e);
        }

        try {
            System.out.println("獲取 public float price;");
            Field priceField = appleClass.getField("price");
            System.out.println(priceField);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        }
    }
}

/*
打印結果:
1, 演示 public Field[] getDeclaredFields()
獲取本類所有已聲明字段的 Field 對象數組
private static int com.java.advanced.features.reflect.Apple.count
private final int com.java.advanced.features.reflect.Apple.id
java.lang.String com.java.advanced.features.reflect.Apple.color
private int com.java.advanced.features.reflect.Apple.size
public float com.java.advanced.features.reflect.Apple.price
2, 演示 public Field getDeclaredField(String name)
獲取本類指定字段名的 Field 對象
獲取 private int size;
sizeField = private int com.java.advanced.features.reflect.Apple.size
獲取 public float price;
priceField = public float com.java.advanced.features.reflect.Apple.price
獲取父類 Fruit 的 public String taste;
java.lang.NoSuchFieldException: taste
3, 演示 public Field[] getFields()
獲取所有已聲明字段的公共Field 對象數組,包括繼承自父類的
public float com.java.advanced.features.reflect.Apple.price
public java.lang.String com.java.advanced.features.reflect.Fruit.taste
4, 演示 public Field getField(String name)
獲取指定字段名的公共的 Field 對象
獲取 private String size
java.lang.NoSuchFieldException: size
獲取 public float price;
public float com.java.advanced.features.reflect.Apple.price
 */

回頭看一下 Apple 類以及其父類 Fruit 類:
Apple 類本身聲明瞭 5 個屬性:

    private static int count;
    private final int id = count++;
    String color;
    private int size;
    public float price;

Fruit 類是 Apple 類的父類,只聲明瞭 1 個屬性:

	public String taste;

對照一下上面的代碼,可以知道打印結果是符合預期的。

2.2.2 Field 的 set,get 操作

Field 類中,有一系列的 set,get 相關的 API:
我們先來看這一對:

public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException
public Object get(Object obj) throws IllegalArgumentException, IllegalAccessException

簡單看一下上面的兩個 API:
set 函數:在指定對象(obj)上把 Field 對象表示的字段設置爲指定的新值(value);
get 函數:返回指定對象(obj)上這個 Field 對象表示的字段的值。

爲了進一步掌握它們的用法,我們去看下面的代碼:

package com.java.advanced.features.reflect.classinternalinfo.field;


import com.java.advanced.features.reflect.Apple;
import com.java.advanced.features.reflect.Fruit;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class FieldSetGetTest1 {
    public static void main(String[] args) throws NoSuchMethodException,
            IllegalAccessException,
            InvocationTargetException,
            InstantiationException, NoSuchFieldException {
        Class<Apple> appleClass = Apple.class;
        Constructor<Apple> constructor = appleClass.getConstructor();
        Apple apple = constructor.newInstance();
        System.out.println("1, 獲取 String color; 字段,修改它的值並獲取修改後的值:");
        Field colorField = appleClass.getDeclaredField("color");
        // 解除此 Field 對象的 Java 語言訪問控制
        colorField.setAccessible(true);
        colorField.set(apple, "red");
        String color = (String) colorField.get(apple);
        System.out.println("color = " + color + ", getColor() = " + apple.getColor());

        System.out.println("2, 獲取 private int size; 字段,修改它的值並獲取修改後的值:");
        Field sizeField = appleClass.getDeclaredField("size");
        // 下面這行不寫,會報異常:
        // java.lang.IllegalAccessException: Class com.java.advanced.features.reflect.
        // classinternalinfo.field.FieldApiTest can not access a member of class
        // com.java.advanced.features.reflect.Apple with modifiers "private"
        sizeField.setAccessible(true);
        sizeField.set(apple, 10);
        int size = (int) sizeField.get(apple);
        System.out.println("size = " + size + ", getSize() = " + apple.getSize());

        System.out.println("3, 獲取 private static int count; 字段,修改它的值並獲取修改後的值:");
        Field countField = appleClass.getDeclaredField("count");
        countField.setAccessible(true);
        // 對於 set 來說, 如果底層字段是一個靜態字段,則忽略 obj 變量;它可能爲 null。
        // 所以,這裏第一個參數 obj,可以爲 null。當然,也可以填入對象值。
        countField.set(null, 33);
        // 對於 get 來說, 如果底層字段是一個靜態字段,則忽略 obj 變量;它可能爲 null。
        // 所以,這裏第一個參數 obj,可以爲 null。當然,也可以填入對象值。
        int count = (int) countField.get(null);
        System.out.println("count = " + count + ", getCount() = " + Apple.getCount());

        System.out.println("下面演示幾種異常:");
        System.out.println("1: 實例字段下,指定對象變量爲 null,拋出 NullPointerException 異常");
        try {
            Field priceField = appleClass.getField("price");
            priceField.set(null, 12f);
        } catch (Exception e) {
            System.out.println(e);
        }
        System.out.println("2: 實例字段下,指定對象變量不是類的實例,拋出 IllegalArgumentException 異常");
        try {
            Field priceField = appleClass.getField("price");
            priceField.set(new Fruit(), 12f);
        } catch (Exception e) {
            System.out.println(e);
        }
        System.out.println("3: 底層字段的類型是基本類型,但是設置給 obj 的字段的新值無法轉換爲基本類型,拋出 IllegalArgumentException");
        try {
            Field priceField = appleClass.getField("price");
            priceField.set(apple, "price");
        } catch (Exception e) {
            System.out.println(e);
        }
    }

}

/*
打印結果:
1, 獲取 String color; 字段,修改它的值並獲取修改後的值:
color = red, getColor() = red
2, 獲取 private int size; 字段,修改它的值並獲取修改後的值:
size = 10, getSize() = 10
3, 獲取 private static int count; 字段,修改它的值並獲取修改後的值:
count = 33, getCount() = 33
下面演示幾種異常:
1: 實例字段下,指定對象變量爲 null,拋出 NullPointerException 異常
java.lang.NullPointerException
2: 實例字段下,指定對象變量不是類的實例,拋出 IllegalArgumentException 異常
java.lang.IllegalArgumentException: Can not set float field com.java.advanced.features.reflect.Apple.price to com.java.advanced.features.reflect.Fruit
3: 底層字段的類型是基本類型,但是設置給 obj 的字段的新值無法轉換爲基本類型,拋出 IllegalArgumentException
java.lang.IllegalArgumentException: Can not set float field com.java.advanced.features.reflect.Apple.price to java.lang.String
*/

雖然上面的 setget 函數確實可以用,但是卻有一些問題存在:
在取出數據時,我們不得不進行強轉。
那麼,有沒有什麼更好的辦法呢?有的,有的。在 Field 類中,針對基本數據類型的值,有一系列的 setget 方法:

public boolean getBoolean(Object obj) throwsIllegalArgumentException, IllegalAccessException
public void setBoolean(Object obj, boolean z) throws IllegalArgumentException, IllegalAccessException

public byte getByte(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setByte(Object obj, byte b) throws IllegalArgumentException, IllegalAccessException

public char getChar(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setChar(Object obj, char c) throws IllegalArgumentException, IllegalAccessException

public double getDouble(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setDouble(Object obj, double d) throws IllegalArgumentException, IllegalAccessException

public float getFloat(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setFloat(Object obj, float f) throws IllegalArgumentException, IllegalAccessException

public int getInt(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setInt(Object obj, int i) throws IllegalArgumentException, IllegalAccessException

public long getLong(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setLong(Object obj, long l) throws IllegalArgumentException, IllegalAccessException

public short getShort(Object obj) throws IllegalArgumentException, IllegalAccessException
public void setShort(Object obj, short s) throws IllegalArgumentException, IllegalAccessException

關於這些 API 的使用,下面是一個例子:

package com.java.advanced.features.reflect.classinternalinfo.field;


import com.java.advanced.features.reflect.Apple;
import com.java.advanced.features.reflect.Fruit;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;

public class FieldSetGetTest2 {
    public static void main(String[] args) throws NoSuchMethodException,
            IllegalAccessException,
            InvocationTargetException,
            InstantiationException, NoSuchFieldException {
        Class<Apple> appleClass = Apple.class;
        Constructor<Apple> constructor = appleClass.getConstructor();
        Apple apple = constructor.newInstance();

        System.out.println("1, 獲取 private int size; 字段,修改它的值並獲取修改後的值:");
        Field sizeField = appleClass.getDeclaredField("size");
        sizeField.setAccessible(true);
        sizeField.setInt(apple, 10);
        int size = sizeField.getInt(apple);
        System.out.println("size = " + size + ", getSize() = " + apple.getSize());

        System.out.println("2, 獲取 private static int count; 字段,修改它的值並獲取修改後的值:");
        Field countField = appleClass.getDeclaredField("count");
        countField.setAccessible(true);
        // 對於 set 來說, 如果底層字段是一個靜態字段,則忽略 obj 變量;它可能爲 null。
        // 所以,這裏第一個參數 obj,可以爲 null。當然,也可以填入對象值。
        countField.setInt(null, 33);
        // 對於 get 來說, 如果底層字段是一個靜態字段,則忽略 obj 變量;它可能爲 null。
        // 所以,這裏第一個參數 obj,可以爲 null。當然,也可以填入對象值。
        int count = countField.getInt(null);
        System.out.println("count = " + count + ", getCount() = " + Apple.getCount());

        System.out.println("3, 獲取 public float price; 字段,修改它的值並獲取修改後的值:");
        Field priceField = appleClass.getField("price");
        priceField.setFloat(apple, 12f);
        float price = priceField.getFloat(apple);
        System.out.println("price = " + price + ", getPrice() = " + apple.getPrice());
    }
}
/*
打印結果:
1, 獲取 private int size; 字段,修改它的值並獲取修改後的值:
size = 10, getSize() = 10
2, 獲取 private static int count; 字段,修改它的值並獲取修改後的值:
count = 33, getCount() = 33
3, 獲取 public float price; 字段,修改它的值並獲取修改後的值:
price = 12.0, getPrice() = 12.0
*/

2.3 Method 對象

2.2.1 獲取 Method 對象

Class 類有 4 個相應的 API :

  1. public Method getDeclaredMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回本類聲明的指定方法名的 Method 對象;
  2. public Method[] getDeclaredMethods() throws SecurityException:返回本類聲明的所有方法名的 Method 對象的數組;
  3. public Method getMethod(String name, Class<?>... parameterTypes) throws NoSuchMethodException, SecurityException:返回這個 Class 對象指定方法名的 公共Method 對象,也包括從父類繼承來的 公共 Method 對象;
  4. public Method[] getMethods() throws SecurityException:返回這個 Class 對象的所有公共Method 對象,,也包括從父類繼承來的 公共 Method 對象。

在進行代碼演示之前,我們先觀察一下上面的 4 個 API:
1 和 2 都是可以獲取所有(publicproctectedprivate 等)符合的 Method 對象,但不包括繼承的字段,它們的函數名都包含了 Declared(Declared 的含義是本類聲明的,不包含從父類繼承來的字段);
3 和 4 都是隻能獲取公共的 Method 對象,但是它的範圍是本類和父類中的公共 Method 對象;
1 和 3 都是可以傳入參數,第一個參數name是函數名,第二參數 parameterTypes是可變參數,返回單個的 Method 對象,並且都可能拋出 NoSuchMethodException
2 和 4 都不需要傳入參數,它們返回的是 Method 對象的數組。

package com.java.advanced.features.reflect.classinternalinfo.method;

import com.java.advanced.features.reflect.Apple;

import java.lang.reflect.Method;

public class MethodGetTest {
    public static void main(String[] args) {
        Class<Apple> appleClass = Apple.class;
        System.out.println("1, 演示 public Method[] getDeclaredMethods()");
        System.out.println("獲取本類聲明的所有方法對象,但不包括繼承的方法");
        Method[] declaredMethods = appleClass.getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            System.out.println(declaredMethod);
        }

        System.out.println("2, 演示 public Method getDeclaredMethod(String name, Class<?>... parameterTypes)");
        try {
            System.out.println("獲取 void checkSize(Integer size)");
            Method checkSizeMethod = appleClass.getDeclaredMethod("checkSize", Integer.class);
            // 下面的寫法會拋出異常:java.lang.NoSuchMethodException: com.java.advanced.features.reflect.Apple.checkSize(int)
            // Method checkSizeMethod = appleClass.getDeclaredMethod("checkSize", int.class);
            System.out.println(checkSizeMethod);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        try {
            System.out.println("獲取 private void checkPrice(float price)");
             Method checkPriceField = appleClass.getDeclaredMethod("checkPrice", float.class);
            // 下面的寫法會拋出異常:java.lang.NoSuchMethodException: com.java.advanced.features.reflect.Apple.checkPrice(java.lang.Float)
            // Method checkPriceField = appleClass.getDeclaredMethod("checkPrice", Float.class);
            System.out.println(checkPriceField);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

        System.out.println("3, 演示 public Method[] getMethods()");
        Method[] methods = appleClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        System.out.println("4, 演示 public Method getMethod(String name, Class<?>... parameterTypes)");
        try {
            System.out.println("獲取 private void checkPrice(float price)");
            Method checkPriceField = appleClass.getMethod("checkPrice", float.class);
        } catch (NoSuchMethodException e) {
            System.out.println(e);
        }

        try {
            System.out.println("獲取  public String getColor()");
            Method colorMethod = appleClass.getMethod("getColor");
            System.out.println(colorMethod);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }

    }
}
/*
1, 演示 public Method[] getDeclaredMethods()
獲取本類聲明的所有方法對象,但不包括繼承的方法
void com.java.advanced.features.reflect.Apple.checkSize(java.lang.Integer)
private void com.java.advanced.features.reflect.Apple.checkPrice(float)
public java.lang.String com.java.advanced.features.reflect.Apple.getColor()
public float com.java.advanced.features.reflect.Apple.getPrice()
public static int com.java.advanced.features.reflect.Apple.getCount()
public boolean com.java.advanced.features.reflect.Apple.initColorAndPrice(java.lang.String,float)
public int com.java.advanced.features.reflect.Apple.getSize()
public void com.java.advanced.features.reflect.Apple.setSize(int)
2, 演示 public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
獲取 void checkSize(Integer size)
void com.java.advanced.features.reflect.Apple.checkSize(java.lang.Integer)
獲取 private void checkPrice(float price)
private void com.java.advanced.features.reflect.Apple.checkPrice(float)
3, 演示 public Method[] getMethods()
public java.lang.String com.java.advanced.features.reflect.Apple.getColor()
public float com.java.advanced.features.reflect.Apple.getPrice()
public static int com.java.advanced.features.reflect.Apple.getCount()
public boolean com.java.advanced.features.reflect.Apple.initColorAndPrice(java.lang.String,float)
public int com.java.advanced.features.reflect.Apple.getSize()
public void com.java.advanced.features.reflect.Apple.setSize(int)
public java.lang.String com.java.advanced.features.reflect.Fruit.getTaste()
public void com.java.advanced.features.reflect.Fruit.setTaste(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
4, 演示 public Method getMethod(String name, Class<?>... parameterTypes)
獲取 private void checkPrice(float price)
java.lang.NoSuchMethodException: com.java.advanced.features.reflect.Apple.checkPrice(float)
獲取  public String getColor()
public java.lang.String com.java.advanced.features.reflect.Apple.getColor()
 */

可以看到:
getDeclaredMethods() 是獲取本類聲明的所有方法對象,但不包括繼承的方法,所以獲取的是 Apple 自己聲明的 8 個方法,而不能獲取到從父類繼承而來的 2 個方法;
getMethods() 是獲取所有的公共方法對象,且包括繼承的方法,所以獲取的是 Apple自己聲明的 6 個公共的方法,從父類 Fruit 繼承而來的 2 個公共方法,從 Object 繼承而來的 8 個公共方法;
對於getDeclaredMethodgetMethod,需要特別注意參數列表中的可變參數傳值問題:這一點和構造方法部分的可變參數列表是一致的。

2.2.2 Method 的 invoke 操作

Method 類中,

public Object invoke(Object obj, Object... args) throws IllegalAccessException,   
IllegalArgumentException,   InvocationTargetException

這個方法的作用是對帶有指定參數(args)的指定對象(obj)調用由此 Method 對象表示的底層方法。
下面是演示代碼:

package com.java.advanced.features.reflect.classinternalinfo.method;

import com.java.advanced.features.reflect.Apple;

import java.lang.reflect.Method;

public class MethodInvokeTest {
    public static void main(String[] args) throws Exception {
        // 獲取 Apple 對象
        Class<Apple> appleClass = Apple.class;
        Apple apple = appleClass.newInstance();
        // 演示 public Object invoke(Object obj, Object... args)
        // 獲取 public void initColorAndPrice(String color, float price) 方法,並調用
        Method initColorAndPriceMethod = appleClass.getDeclaredMethod("initColorAndPrice", String.class, float.class);
        boolean result = (boolean) initColorAndPriceMethod.invoke(apple, "red", 18f);
        System.out.println("getColor() = " + apple.getColor() + ", getPrice() = " + apple.getPrice() + ", result = " + result);

        // 獲取 void setSize(int size) 方法,並調用
        Method checkSizeMethod = appleClass.getDeclaredMethod("setSize", int.class);
        // 解除此 Method 對象的 Java 語言訪問控制
        checkSizeMethod.setAccessible(true);
        // 沒有返回值時,返回 null
        Object invoke = checkSizeMethod.invoke(apple, 100);
        System.out.println("getSize() = " + apple.getSize() + ", result = " + invoke);


        // 獲取 public static int getCount() 方法,並調用
        Method getCountMethod = appleClass.getMethod("getCount");
        // 底層方法是靜態的,那麼可以忽略指定的 obj 參數。該參數可以爲 null。
        int count = (int) getCountMethod.invoke(null);
        System.out.println("count = " + count);
    }
}
/*
打印結果:
getColor() = red, getPrice() = 18.0, result = true
getSize() = 100, result = null
count = 1
 */

3. 最後

本篇文章,對反射知識使用頻率比較高的部分,主要是類內部信息獲取做了講解,以及代碼演示。
這裏是代碼地址的github地址:https://github.com/jhwsx/Java_01_AdvancedFeatures/tree/master/src/com/java/advanced/features/reflect

參考

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