本文从Class类、访问Field变量、调用方法、调用构造方法与获取继承关系来阐述Java反射原理与实现。

Class类

1.除了int等基本类型外,Java的其他类型全部都是class(包括interface)。

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。

2.每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。注意:这里的Class类型是一个名叫Classclass。它长这样:

  1. public final class Class {
  2. private Class() {}
  3. }


String类为例,当JVM加载String类时,它首先读取String.class文件到内存,然后,为String类创建一个Class实例并关联起来:

Class cls = new Class(String);


3.反射

通过Class实例获取class信息的方法称为反射(Reflection)。 反射的目的是为了获得某个实例的信息。

4.获取Class实例的class信息的方法

方法一: 直接通过一个class的静态变量class获取:

Class cls = String.class;


方法二:如果我们有一个实例变量,可以通过该实例变量提供的getClass()方法获取:

String s = "Hello";
Class cls = s.getClass();


方法三:如果知道一个class的完整类名,可以通过静态方法Class.forName()获取:

Class cls = Class.forName("java.lang.String");


5.JVM在执行Java程序的时候,并不是一次性把所有用到的class全部加载到内存,而是第一次需要用到class时才加载。

// Main.java
public class Main {
    public static void main(String[] args) {
        if (args.length > 0) {
            create(args[0]);
        }
    }

    static void create(String name) {
        Person p = new Person(name);
    }
}


当执行Main.java时,由于用到了Main,因此,JVM首先会把Main.class加载到内存。然而,并不会加载Person.class,除非程序执行到create()方法,JVM发现需要加载Person类时,才会首次加载Person.class。如果没有执行create()方法,那么Person.class根本就不会被加载。

这就是JVM动态加载class的特性。

访问Field成员变量

1.获取field成员变量实例

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
public class ReflectionFindField {
    public static void main(String[] args) throws NoSuchFieldException {        
        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 Person {
    public int score;    
    private int grade;
}
class Person {    
    public String name;
}

输出:
public int Java_basic.reflection.Student.score
public java.lang.String Java_basic.reflection.Person.name
private int Java_basic.reflection.Student.grade


2.Field实例对象

一个Field对象包含了一个字段的所有信息:

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


String类的value字段为例,它的定义是:

public final class String {
    private final byte[] value;
}


我们用反射获取该字段的信息,代码如下:

Field f = String.class.getDeclaredField("value");
f.getName(); // "value"
f.getType(); // class [B 表示byte[]类型
int m = f.getModifiers();
Modifier.isFinal(m); // true
Modifier.isPublic(m); // false
Modifier.isProtected(m); // false
Modifier.isPrivate(m); // true
Modifier.isStatic(m); // false


3.获取字段值

利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。通过Field.get(Object)实现。

4.设置字段值

设置字段值是通过Field.set(Object, Object)实现的,其中第一个Object参数是指定的实例,第二个Object参数是待修改的值。

public class Main {

    public static void main(String[] args) throws Exception {
        Person p = new Person("Xiao Ming");
        System.out.println(p.getName()); // "Xiao Ming"
        Class c = p.getClass();
        Field f = c.getDeclaredField("name");
        //修改非public字段,需要首先调用setAccessible(true)。
        f.setAccessible(true);
        f.set(p, "Xiao Hong");
        System.out.println(p.getName()); // "Xiao Hong"
    }

}

class Person {
    private String name;


    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return this.name;
    }

}

调用方法

1.Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)
public class ReflectMethod {
    public static void main(String[] args) throws NoSuchMethodException {
        Class stdClass = Student_Method.class;
        // 获取public方法getScore,参数为String:
        System.out.println(stdClass.getMethod("getScore", String.class));
        // 获取继承的public方法getName,无参数:
        System.out.println(stdClass.getMethod("getName"));
        // 获取private方法getGrade,参数为int:
        System.out.println(stdClass.getDeclaredMethod("getGrade", int.class));
    }
}

class Student_Method extends Person_Method {
    public int getScore(String type) {
        return 99;
    }

    private int getGrade(int year) {
        return 1;
    }

}

class Person_Method {
    public String getName() {
        return "Person";
    }
}

输出:
public int Student_Method.getScore(java.lang.String)
public java.lang.String Person_Method.getName()
private int Student_Method.getGrade(int)


2.Method对象

一个Method对象包含一个方法的所有信息:

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


3.调用方法

(1)当我们获取到一个Method对象时,就可以对它进行调用。我们以下面的代码为例:

String s = "Hello world";
String r = s.substring(6); // "world"


如果用反射来调用substring方法,需要以下代码:

public class Main {
    public static void main(String[] args) throws Exception {
        // String对象:
        String s = "Hello world";
        // 获取String substring(int)方法,参数为int:
        Method m = String.class.getMethod("substring", int.class);
        // 在s对象上调用该方法并获取结果:
        String r = (String) m.invoke(s, 6);
        // 打印调用结果:
        System.out.println(r);
    }
}


(2)当调用的方法是静态方法时,invoke的第一个参数永远为null

调用构造方法

1.如果通过反射来创建新的实例,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();


注:调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。

2.调用任意构造方法

Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例。

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

        // 获取构造方法Integer(String)
        java.lang.reflect.Constructor<Integer> cons2 = Integer.class.getConstructor(String.class);
        Integer n2 = (Integer) cons2.newInstance("456");
        System.out.println(n2);
    }

}


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

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


注:Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

获取继承关系

1.获取父类的Class

Class i = Integer.class;
Class n = i.getSuperclass(); //n= class java.lang.Number

2.获取interface

由于一个类可能实现一个或多个接口,通过Class我们就可以查询到实现的接口类型。

注:getInterfaces()`只返回当前类直接实现的接口类型,并不包括其父类实现的接口类型!

public class Main {
    public static void main(String[] args) throws Exception {
        Class s = Integer.class;
        Class[] is = s.getInterfaces();
        for (Class i : is) {
            System.out.println(i);
        }
    }
}

输出:

interface java.lang.Comparable
interface java.lang.constant.Constable
interface java.lang.constant.ConstantDesc


3.继承关系

(1) 当我们判断一个实例是否是某个类型时,正常情况下,使用instanceof操作符

Object n = Integer.valueOf(123);
boolean isDouble = n instanceof Integer; // true


(2) 如果是两个Class实例,要判断一个向上转型是否成立,可以调用isAssignableFrom()

// Number n = ?
Number.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Number
// Object o = ?
Object.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Object
// Integer i = ?
Integer.class.isAssignableFrom(Number.class); // false,因为Number不能赋值给Integer


4.动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。