Java基础: 反射常见知识整理

反射是一种功能强大且复杂的机制 。 使用它的主要人员是工具构造者, 而不是应用程序员。
在实际开发中,我们更多的是在阅读系统或第三方源码看到他们的身影。

本文,将对反射中常见的知识进行总结学习。

demo中的代码分别为:实体类父类Fruit.class , 实体类子类Apple.class ,测试类为MainActivity.class

Fruit 代码如下:

public class Fruit {

    public String taste;

    public Fruit() {
    }

    public String getTaste() {
        return taste;
    }

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

}

Apple代码如下:

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

测试类为MainActivity.classonCreate。使用的编译工具为Android Studio。如果是Eclipse,则是在main方法中。

一.获取类类型

获取类类型的方法有四种:

  • 通过类对象获取:对象.getClass(),这个方法是属于 Object 类的;
  • 通过类的 class 对象获取:类名.class;
  • 通过全类名获取:Class.forName(“全类名”),这是Class 类的静态方法。当类不能定位到时,会抛出 ClassNotFoundException;
  • 通过 ClassLoader.loadClass()加载:loadClass() 也需要传入全类名。当类不能定位到时,会抛出 ClassNotFoundException;
    //获取类类型
    private void oneMethod() {

        // 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.glh.reflectdemo.Apple");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("appleClass3 = " + appleClass3);
        // 4, 通过 ClassLoader.loadClass()加载
        Class appleClass4 = null;
        try {
            appleClass4 = MainActivity.class.getClassLoader().loadClass("com.glh.reflectdemo.Apple");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println("appleClass4 = " + appleClass4);

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

打印出来的日志如下:

I/System.out: appleClass1 = class com.glh.reflectdemo.Apple
I/System.out: appleClass2 = class com.glh.reflectdemo.Apple
I/System.out: appleClass3 = class com.glh.reflectdemo.Apple
I/System.out: appleClass4 = class com.glh.reflectdemo.Apple
I/System.out: result = true

上面的代码演示了获取类类型的四种方法,并且我们还可以看到 appleClass1,appleClass2,appleClass3,appleClass4 是相等的,它们都指向同一个对象。这就说明:类只会被加载一次。

二.获取类内部信息

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 获取构造函数

在 Class 类中提供了 4 种相应的 API 可以用来获取构造函数的 Constructor 对象:

  • public Constructor getConstructor(Class<?>… parameterTypes)
    throws NoSuchMethodException, SecurityException:
    返回与指定的 parameterTypes相匹配的公共构造方法的 Constructor 对象;

  • public Constructor<?>[] getConstructors()
    throws SecurityException:
    返回此类公共构造方法的 Constructor 对象数组;

  • public Constructor getDeclaredConstructor(Class<?>… parameterTypes)
    throws NoSuchMethodException,SecurityException:
    返回带有指定参数列表的构造方法的 Constructor 对象;

  • public Constructor<?>[] getDeclaredConstructors()
    throws SecurityException:
    返回此类所有已声明的构造方法的 Constructor 对象的数组。

在进行代码演示之前,我们先观察一下上面的 4 个 API:
1 和 2 都是只能获取公共的构造方法对象;
3 和 4 是可以获取所有(public,proctected,private 等)符合的构造方法对象,它们的函数名都包含了 Declared;
1 和 3 都是可以传入参数列表,返回单个的构造方法对象,还有可以抛出NoSuchMethodException;
2 和 4 都不需要传参数,返回一个构造方法对象的数组

下面是演示代码:

private void twoMethod() {

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

打印出来日志如下:

I/System.out: 1, 演示 public Constructor<?>[] getConstructors()
I/System.out: 获取所有的公共构造方法对象数组:
I/System.out: public com.glh.reflectdemo.Apple()
I/System.out: public com.glh.reflectdemo.Apple(java.lang.String,int)


I/System.out: 2, 演示 public Constructor<T> getConstructor(Class<?>... parameterTypes)
I/System.out: 获取指定参数列表的公共构造方法对象
I/System.out: 获取 public Apple():
I/System.out: constructor = public com.glh.reflectdemo.Apple()

I/System.out: 获取 public Apple(String color, int size):
I/System.out: constructor = public com.glh.reflectdemo.Apple(java.lang.String,int)

I/System.out: 获取 private Apple(String color, Float price):
I/System.out: java.lang.NoSuchMethodException: <init> [class java.lang.String, class java.lang.Float]


I/System.out: 3, 演示 public Constructor<?>[] getDeclaredConstructors()
I/System.out: 获取所有的构造方法对象:
I/System.out: public com.glh.reflectdemo.Apple()
I/System.out: com.glh.reflectdemo.Apple(java.lang.String)
I/System.out: public com.glh.reflectdemo.Apple(java.lang.String,int)
I/System.out: private com.glh.reflectdemo.Apple(java.lang.String,java.lang.Float)


I/System.out: 4, 演示 public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)
I/System.out: 获取指定参数列表的构造方法对象
I/System.out: 获取 private Apple(String color, Float price):
I/System.out: constructor = private com.glh.reflectdemo.Apple(java.lang.String,java.lang.Float)
I/System.out: 获取 public Apple():
I/System.out: constructor = public com.glh.reflectdemo.Apple()

Apple 类有 2 个 public 的构造方法,1 个 private 的构造方法,1 个 default 的构造方法,看一下上面的打印结果,是符合预期的。

需要特别注意的地方是:
需要指定参数列表的两个 API,如果构造方法的参数列表里有基本数据类型(如 int,float,boolean 等)的形参,就应当传入对应的基本数据类型的 Class 对象(如 int.class,float.class,boolean.class 等),而不应该传入(如Integer.class,Float.class,Boolean.class 等),同样地,如果构造方法的参数列表里有基本数据类型的包装器类型(如Integer,Float,Boolean 等)的形参,就应当传入对应的包装器类型的 Class 对象(如Integer.class,Float.class,Boolean.class 等),而不应该传入基本数据类型的 Class 对象(如 int.class,float.class,boolean.class 等)。否则,就会抛出 NoSuchMethodException。
还有一点就是,parameterTypes参数列表的元素顺序一定要与打算获取的构造方法的形参列表中中的参数类型,个数与顺序完全一致。

有同学可能会问,getConstructors() 方法为什么没有获取 Apple 的父类 Fruit 的 public 构造方法呢?这是因为父类的构造函数不能被子类继承。

2.2 使用构造函数构造实例

使用在 Constructor 类中下的 :

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

参数列表 initargs 是一个Object类型的可变参数列表,我们可以把原本传递给构造函数的形参列表的实参,传递给这个可变参数列表,这里要保证参数的个数,顺序,类型都是一致的。但是,如果实参是基本数据类型,可以直接传入,因为这里基本类型的值会被包装在对应的包装器对象之中。
返回的 T 是一个泛型参数,代表创建出来的新对象。

测试代码如下:

private void threeMethod() {

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

打印出来日志如下:

I/System.out: com.glh.reflectdemo.Apple@2790e5e
I/System.out: color = red, price = 1.8
I/System.out: com.glh.reflectdemo.Apple@a9ff23f
I/System.out: color = red, size = 100

需要注意的一点是,public void setAccessible(boolean flag) 是 AccessibleObject 类中的一个方法,而 AccessibleObject 是 Constructor,Field 和 Method 的基类。这个方法的作用是修改对象的 accessible 标志(其实是 AccessibleObject 里的 override 字段),默认为 false,表示任何函数,字段,构造方法是否可以访问。如果设置为 true,不管函数,字段,构造方法是什么类型的权限修饰符,都可以访问;如果为 false,那么只有 public 修饰的才可以访问

在上面的例子中,若不设置declaredConstructor.setAccessible(boolean flag) 为 true,那么会抛出如下异常:
在这里插入图片描述如果参数列表 initargs 里包含基本数据类型,那么传基本数据类型或对应的包装器类型都是正确的;如果参数列表 initargs 里包含基本数据类型的包装器类型,那么传基本数据类型还是对应的包装器类型也是正确的。

三.Field 对象

3.1 获取 Field 对象

在 Class 类有 4 个相应的 API :

  • public Field getDeclaredField(String name)
    throws NoSuchFieldException,SecurityException:
    返回本类声明的指定字段名的 Field 对象;

  • public Field[] getDeclaredFields()
    throws SecurityException:
    返回本类声明的所有字段名的 Field 对象的数组;

  • public Field getField(String name)
    throws NoSuchFieldException, SecurityException:
    返回这个 Class 对象指定字段名的 公共Field 对象,也包括从父类继承来的 公共 Field对象;

  • public Field[] getFields()
    throws SecurityException:
    返回这个 Class对象的所有公共Field 对象,,也包括从父类继承来的 公共 Field 对象。

在进行代码演示之前,我们先耐心观察一下上面的 4 个 API:
1 和 2 都是可以获取所有(public,proctected,private 等)符合的 Field 对象,但不包括继承的字段,它们的函数名都包含了 Declared(Declared 的含义是本类声明的,不包含从父类继承来的字段);
3 和 4 都是只能获取公共的 Field 对象,但是它的范围是本类和父类中的公共 Field 对象;
1 和 3 都是可以传入一个参数,即变量名,返回单个的 Field 对象,并且都可能抛出 NoSuchFieldException;
2 和 4 都不需要传入参数,它们返回的是 Field 对象的数组。

下面是演示代码:

    private void fourMethod() {

        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");
            System.out.println(sizeField);
        } 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();
        }
    }

测试结果如下:

I/System.out: 1, 演示 public Field[] getDeclaredFields()
I/System.out: 获取本类所有已声明字段的 Field 对象数组
I/System.out: java.lang.String com.glh.reflectdemo.Apple.color
I/System.out: private final int com.glh.reflectdemo.Apple.id
I/System.out: public float com.glh.reflectdemo.Apple.price
I/System.out: private int com.glh.reflectdemo.Apple.size
I/System.out: private static int com.glh.reflectdemo.Apple.count
         
I/System.out: 2, 演示 public Field getDeclaredField(String name)
I/System.out: 获取本类指定字段名的 Field 对象
I/System.out: 获取 private int size;
I/System.out: sizeField = private int com.glh.reflectdemo.Apple.size
I/System.out: 获取 public float price;
I/System.out: priceField = public float com.glh.reflectdemo.Apple.price
I/System.out: 获取父类 Fruit 的 public String taste;
I/System.out: java.lang.NoSuchFieldException: No field taste in class Lcom/glh/reflectdemo/Apple; (declaration of 'com.glh.reflectdemo.Apple' appears in /data/app/com.glh.reflectdemo--_tuUlNoTxyByJax-KF9Xg==/base.apk!classes2.dex)
         
I/System.out: 3, 演示 public Field[] getFields()
I/System.out: 获取所有已声明字段的公共Field 对象数组,包括继承自父类的
I/System.out: public float com.glh.reflectdemo.Apple.price
I/System.out: public java.lang.String com.glh.reflectdemo.Fruit.taste
         
I/System.out: 4, 演示 public Field getField(String name)
I/System.out: 获取指定字段名的公共的 Field 对象
I/System.out: 获取 private String size
I/System.out: java.lang.NoSuchFieldException: size
I/System.out: 获取 public float price;
I/System.out: public float com.glh.reflectdemo.Apple.price        

3.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 对象表示的字段的值。

为了进一步掌握它们的用法,我们去看下面的代码:

private void fiveMethod() 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);
        }
    }

打印出来的日志如下:

I/System.out: 1, 获取 String color; 字段,修改它的值并获取修改后的值:
I/System.out: color = red, getColor() = red
        
I/System.out: 2, 获取 private int size; 字段,修改它的值并获取修改后的值:
I/System.out: size = 10, getSize() = 10
        
I/System.out: 3, 获取 private static int count; 字段,修改它的值并获取修改后的值:
I/System.out: count = 33, getCount() = 33
        
I/System.out: 下面演示几种异常:
I/System.out: 1: 实例字段下,指定对象变量为 null,抛出 NullPointerException 异常
I/System.out: java.lang.NullPointerException: null receiver
I/System.out: 2: 实例字段下,指定对象变量不是类的实例,抛出 IllegalArgumentException 异常
I/System.out: java.lang.IllegalArgumentException: Expected receiver of type com.glh.reflectdemo.Apple, but got com.glh.reflectdemo.Fruit
I/System.out: 3: 底层字段的类型是基本类型,但是设置给 obj 的字段的新值无法转换为基本类型,抛出 IllegalArgumentException
I/System.out: java.lang.IllegalArgumentException: field com.glh.reflectdemo.Apple.price has type float, got java.lang.String

虽然上面的 set,get 函数确实可以用,但是却有一些问题存在:
在取出数据时,我们不得不进行强转。
那么,有没有什么更好的办法呢?有的。在 Field 类中,针对基本数据类型的值,有一系列的 set,get 方法:

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 的使用,下面是一个例子:

private void sixMethod() 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());
        
    }

打印出来的日志如下:

I/System.out: 1, 获取 private int size; 字段,修改它的值并获取修改后的值:
I/System.out: size = 10, getSize() = 10
        
I/System.out: 2, 获取 private static int count; 字段,修改它的值并获取修改后的值:
I/System.out: count = 33, getCount() = 33
        
I/System.out: 3, 获取 public float price; 字段,修改它的值并获取修改后的值:
I/System.out: price = 12.0, getPrice() = 12.0

四.Method 对象

4.1 获取 Method 对象

在 Class 类有 4 个相应的 API :

  • public Method getDeclaredMethod(String name, Class<?>…parameterTypes)
    throws NoSuchMethodException, SecurityException:

  • 返回本类声明的指定方法名的 Method 对象;

  • public Method[] getDeclaredMethods()
    throws SecurityException:
    返回本类声明的所有方法名的 Method 对象的数组;

  • public Method getMethod(String name, Class<?>… parameterTypes)
    throws NoSuchMethodException, SecurityException:
    返回这个 Class 对象指定方法名的公共Method 对象,也包括从父类继承来的 公共 Method 对象;

  • public Method[] getMethods()
    throws SecurityException:
    返回这个 Class对象的所有公共Method 对象,,也包括从父类继承来的公共 Method 对象。

在进行代码演示之前,我们先观察一下上面的 4 个 API:
1 和 2 都是可以获取所有(public,proctected,private 等)符合的 Method 对象,但不包括继承的字段,它们的函数名都包含了 Declared(Declared 的含义是本类声明的,不包含从父类继承来的字段);
3 和 4 都是只能获取公共的 Method 对象,但是它的范围是本类和父类中的公共 Method 对象;
1 和 3 都是可以传入参数,第一个参数name是函数名,第二参数 parameterTypes是可变参数,返回单个的 Method 对象,并且都可能抛出 NoSuchMethodException;
2 和 4 都不需要传入参数,它们返回的是 Method 对象的数组。

private void seventMethod() {

        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);
            System.out.println(checkPriceField);
        } 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();
        }  
    }

打印出来的日志如下:

I/System.out: 1, 演示 public Method[] getDeclaredMethods()
I/System.out: 获取本类声明的所有方法对象,但不包括继承的方法
I/System.out: private void com.glh.reflectdemo.Apple.checkPrice(float)
I/System.out: public static int com.glh.reflectdemo.Apple.getCount()
I/System.out: void com.glh.reflectdemo.Apple.checkSize(java.lang.Integer)
I/System.out: public java.lang.String com.glh.reflectdemo.Apple.getColor()
I/System.out: public float com.glh.reflectdemo.Apple.getPrice()
I/System.out: public int com.glh.reflectdemo.Apple.getSize()
I/System.out: public boolean com.glh.reflectdemo.Apple.initColorAndPrice(java.lang.String,float)
I/System.out: public void com.glh.reflectdemo.Apple.setSize(int)
        
I/System.out: 2, 演示 public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
I/System.out: 获取 void checkSize(Integer size)
I/System.out: void com.glh.reflectdemo.Apple.checkSize(java.lang.Integer)
I/System.out: 获取 private void checkPrice(float price)
I/System.out: private void com.glh.reflectdemo.Apple.checkPrice(float)
        
I/System.out: 3, 演示 public Method[] getMethods()
I/System.out: public boolean java.lang.Object.equals(java.lang.Object)
I/System.out: public final java.lang.Class java.lang.Object.getClass()
I/System.out: public java.lang.String com.glh.reflectdemo.Apple.getColor()
I/System.out: public static int com.glh.reflectdemo.Apple.getCount()
I/System.out: public float com.glh.reflectdemo.Apple.getPrice()
I/System.out: public int com.glh.reflectdemo.Apple.getSize()
I/System.out: public java.lang.String com.glh.reflectdemo.Fruit.getTaste()
I/System.out: public int java.lang.Object.hashCode()
I/System.out: public boolean com.glh.reflectdemo.Apple.initColorAndPrice(java.lang.String,float)
I/System.out: public final native void java.lang.Object.notify()
I/System.out: public final native void java.lang.Object.notifyAll()
I/System.out: public void com.glh.reflectdemo.Apple.setSize(int)
I/System.out: public void com.glh.reflectdemo.Fruit.setTaste(java.lang.String)
I/System.out: public java.lang.String java.lang.Object.toString()
I/System.out: public final native void java.lang.Object.wait() throws java.lang.InterruptedException
I/System.out: public final void java.lang.Object.wait(long) throws java.lang.InterruptedException
I/System.out: public final native void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
        
I/System.out: 4, 演示 public Method getMethod(String name, Class<?>... parameterTypes)
I/System.out: 获取 private void checkPrice(float price)
I/System.out: java.lang.NoSuchMethodException: checkPrice [float]
I/System.out: 获取  public String getColor()
I/System.out: public java.lang.String com.glh.reflectdemo.Apple.getColor()

可以看到:
getDeclaredMethods() 是获取本类声明的所有方法对象,但不包括继承的方法,所以获取的是 Apple 自己声明的 8 个方法,而不能获取到从父类继承而来的 2 个方法;

getMethods() 是获取所有的公共方法对象,且包括继承的方法,所以获取的是 Apple自己声明的 6 个公共的方法,从父类 Fruit 继承而来的 2 个公共方法,从 Object 继承而来的 8 个公共方法;

对于getDeclaredMethod 和 getMethod,需要特别注意参数列表中的可变参数传值问题:这一点和构造方法部分的可变参数列表是一致的。

4.2 Method 的 invoke 操作

在 Method 类中,

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

这个方法的作用是对带有指定参数(args)的指定对象(obj)调用由此 Method 对象表示的底层方法。通过传递给定参数,并返回方法的返回值。对于静态方法,把null作为隐式参数传递。在使用包装器传递基本类型的值时,基本类型的返回值必须是未包装的

下面是演示代码:

private void eightMethod() throws Exception{

        // 获取 Apple 对象
        Class<Apple> appleClass = Apple.class;
        Apple apple = appleClass.newInstance();

        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);
        // 没有返回值时(void),返回 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);

       // 获取 void getSize方法,并调用
        Method checkSizeMethod1 = appleClass.getDeclaredMethod("getSize");
        // 解除此 Method 对象的 Java 语言访问控制
        checkSizeMethod1.setAccessible(true);
        // 返回值为int类型
        int invoke1 = (int)checkSizeMethod1.invoke(apple);
        //打印结果为初始值
        System.out.println("getSize() = " + apple.getSize() + ", result = " + invoke1);
    }

打印日志如下:

I/System.out: getColor() = red, getPrice() = 18.0, result = true
I/System.out: getSize() = 100, result = null
I/System.out: count = 1
I/System.out: getSize() = 0, result = 0

其中第1条日志 result返回true是因为initColorAndPrice 方法返回的true

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

至此,反射的常见知识,分享完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值