什么是反射?

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

反射的作用

在运行时,通过反射可以判断任意一个对象所属的类、构造任意一个类的对象、判断任意一个类所具有的成员变量和方法、调用任意一个对象的方法以及给任意一个类的属性设值等等。

Class类

在Java中,除了int等基本数据类型以外,其他的类型全部都是class(包括interface)。
class是由JVM在运行状态中动态加载的,JVM第一次读取到class类型时,就将它加入到内存。每加载一种class,JVM就为它创建一个Class类型的实例,并关联起来。

例如String类型,JVM加载String类型时,先读取String.class字节码文件到内存,并创建一个与String类关联的Class实例。

通过查看源码可以知道,Class类型的构造方法是用了private修饰,因此,只有JVM才能够创建实例,自己的程序是无法创建的。

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

获取Class实例的三种方法

  • 通过一个类的静态变量class获取:
Class cls = String.class;
  • 通过实例变量提供的getClass()方法获取:
String s = "Hello";
Class cls = s.getClass();
  • 知道完整类名,通过静态方法Class.forName()获取
Class cls = Class.forName("java.lang.String");

当获取到Class实例,就可以通过该实例获取对应类型的实例:

// 获取String的Class实例
Class cls = String.class;
// 创建一个String实例
//相当于 new String()
String s = (String) cls.newInstance();

注意:通过Class.newInstance()可以创建类实例,它的局限是:只能调用public的无参数构造方法。带参数的构造方法,或者非public的构造方法都无法通过Class.newInstance()被调用。

动态加载

JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。利用JVM动态加载class的特性,我们可以在运行期间根据条件加载不同的实现类。

访问字段

Class类提供以下几个方法来获取相应的字段:

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
package edu.gzc;
public class Main {
    public static void main(String[] args) throws NoSuchFieldException {
	// write your code here
        Class stdClass = Student.class;
        // 获取public字段"score":
        System.out.println(stdClass.getField("score"));
        // 获取继承的public字段"name":
        System.out.println(stdClass.getField("name"));
        // 获取private字段"grade":
        System.out.println(stdClass.getDeclaredField("grade"));
    }
}
class Student extends Persion{
    public int score;
    private int grade;
}
class Persion{
    public String name;
}

输出:
在这里插入图片描述
一个Field对象包含了一个字段的所有信息:

  • getName():返回字段名称,例如,“name”;
  • getType():返回字段类型,也是一个Class实例,例如,String.class;
  • getModifiers():返回字段的修饰符,它是一个int,不同的bit表示不同的含义。

通过Field对象获取字段信息

        Field score = stdClass.getDeclaredField("score");
        System.out.println(score.getName());  //输出 score
        System.out.println(score.getType());  ///输出 int
        //获取字段修饰符
        int m =score.getModifiers();
        System.out.println(Modifier.isPublic(m));   // 输出 true

获取字段值和设置字段值
先获取Class实例,再获取Field实例:

  • Field.get(Object)获取指定实例的指定字段的值。
  • Field.set(Object, Object)实现设置字段值,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。
public class Main {
    public static void main(String[] args) throws Exception {
        // write your code here
        //获取字段值
        Persion p = new Persion("xiaoming");
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        //不管字段是public,还是private,都允许访问
        f.setAccessible(true);
        Object value = f.get(p);
        System.out.println(value);   // 输出 xiaoming 
		//设置字段值
        f.set(p, "zhangsan");
        System.out.println(p.getName());   //输出  zhangsan
    }
}
class Persion {
    public String name;
    public Persion(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

如果获取非public字段,会抛出IllegalAccessException异常,可以将private修改为public ,或者在得到Field对象后,设置访问权限:f.setAccessible(true);
但该方法也可能会失败,如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)

调用方法

通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有public的Method(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
package edu.gzc;
public class Main {
    public static void main(String[] args) throws Exception {
	 	Class stuClass=Student.class;
        //获取public 方法setScore,参数为int
        System.out.println(stuClass.getDeclaredMethod("setScore", int.class));;
        //获取继承的public方法getName,无参数
        System.out.println(stuClass.getMethod("getName"));
	}
}
class Student extends Persion {
    private int score;
    private int grade;
    public int getScore() {
        return score;
    }
    public void setScore(int score) {
        this.score = score;
    }
}
class Persion {
    private String name;
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
}

输出:
在这里插入图片描述
一个Method对象包含一个方法的所有信息:

  • getName():返回方法名称,例如:”getScore“;
  • getReturnType():返回方法返回值类型,也是一个Class实例,例如:String.class;
  • getParameterTypes():返回方法的参数类型,是一个Class数组,例如:{String.class, int.class}
  • getModifiers():返回方法的修饰符,它是一个int,不同的bit表示不同的含义。

调用方法
Method实例调用invoke就相当于调用该方法,invoke的第一个参数是对象实例,即在哪个实例上调用该方法,后面的可变参数要与方法参数一致,否则将报错。
方法:Object invoke(Object instance, Object... parameters)

import java.lang.reflect.Method;
public class Test {
    public static void main(String[] args) throws Exception {
        String str = "hello world";
        // 获取String substring(int)方法,参数为int:
        Method m =String.class.getDeclaredMethod("substring", int.class);
        // 在str对象上调用该方法并获取结果:
        String result= (String) m.invoke(str, 6);
        // 打印调用结果:
        System.out.println(result);   //输出 world
    }
}

调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke方法传入的第一个参数永远为null
例如 :

Integer n = (Integer) m.invoke(null, "12345");

调用非public方法
通过设置setAccessible(true)来访问非public方法。setAccessible(true)可能会失败。

注意:使用反射调用方法时,仍然遵循多态原则。

调用构造方法

使用反射来创建实例,则需要调用 Class提供的newInstance()方法。

Person p = Person.class.newInstance();

要注意的是,newInstance()只能调用该类无参的public的无参构造方法。

那么如何调用任意构造方法呢???

为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class...):获取某个publicConstructor
  • getDeclaredConstructor(Class...):获取某个Constructor
  • getConstructors():获取所有publicConstructor
  • getDeclaredConstructors():获取所有Constructor

调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

import java.lang.reflect.Constructor;
public class Test {
    public static void main(String[] args) throws Exception {
        //获取构造方法Integer(int)
        Constructor cons1 = Integer.class.getConstructor(int.class);
        //调用构造方法
        Integer n1 = (Integer) cons1.newInstance(123);
        System.out.println(n1);  // 输出 123

        // 获取构造方法Integer(String)
        Constructor cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);  //  输出 456
    }

获取继承关系

获取父类的Class:
通过Class提供的Class getSuperclass()方法可以获取父类的Class

获取interface:

  • 通过Class提供的Class[] getInterfaces()方法可以获取该类实现的接口的Class
  • getInterfaces()只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型。
  • 对所有interfaceClass调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces()
  • 如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
  • 两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()

参考文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1264799402020448


版权声明:本文为qq_41193133原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/qq_41193133/article/details/104831736