java反射

java当中,除了基本类型外,其他都是class(interface),没有继承关系的数据类型是无法赋值的。本身class是一种数据类型(type)。

而class本身是在jvm执行过程中动态加载的,每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。

这里的Class类型是一个名叫Classclass

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

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

1
Class cls = new Class(String)

查看Class源码时,可以发现Class的构造方法是private的,因此只有JVM能够创建Class实例。

所以,JVM持有的每个Class实例都指向一个数据类型。

一个Class实例包含了该calss的所有完整信息。

通过Class实例获取class信息的方法称为反射(Reflection)

有三个办法可以通过一个class获取对应的Class实例:

  1. 通过class静态变量获取:

    1
    Class cls = String.class
  2. 通过实例变量的getClass()方法获取

    1
    2
    String s ="abc";
    Class cls = s.getClass();
  3. 知道一个class的完整类名,可以通过静态方法Class.forName()获取

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

因为Class实例在JVM当中是唯一的,因此上述三种方法获取的Class实例是同一个实例,可以用==比较。

1
2
3
4
5
6
Class cls1 = String.class;

String s = "abc";
Class cls2 = s.getClass();

boolean sameClass = cls1 == cls2; // true

注意Class实例比较和instanceof的区别:

  • instanceof是判断是否a是b的子类

    1
    2
    3
    4
    Integer n = new Integer(123);

    boolean b1 = n instanceof Integer; // true,因为n是Integer类型
    boolean b2 = n instanceof Number; // true,因为n是Number类型的子类
  • ==判断的是是否是同一个class

    1
    2
    boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
    boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class

拿到某个对象的class后,就可以通过反射获取该Objectclass信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package com.drawon.reflection;

public class Reflection {
public static void main(String[] args) {
printClassInfo("".getClass());
printClassInfo(Runnable.class);
printClassInfo(java.time.Month.class);
printClassInfo(String[].class);
printClassInfo(int.class);
}

static void printClassInfo(Class cls) {
System.out.println("Class name:" + cls.getName());
System.out.println("Simple name: " + cls.getSimpleName());
if (cls.getPackage() != null) {
System.out.println("Package name: " + cls.getPackage().getName());
}
System.out.println("is interface: " + cls.isInterface());
System.out.println("is enum: " + cls.isEnum());
System.out.println("is array: " + cls.isArray());
System.out.println("is primitive: " + cls.isPrimitive());
System.out.println("--------");
}
}

注意:数组也是一种class(比如String[]),并且它不同于String.class,它的类名是[Ljava.lang.String

如果获取到了一个Class实例,我们就可以通过该class实例来创建对应类型的实例。

1
2
3
4
// 获取String的Class实例:
Class cls = String.class;
// 创建一个String实例:
String s = (String) cls.newInstance();

上述代码等同于new String()。通过newInstance()方法创建实例的局限是只能调用public的无参构造方法,带参数的活非public的构造方法都无法被class.newInstance()方法调用

动态加载

JVM在执行java程序的时候,在需要用某个class时才对其进行加载。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging。利用JVM动态加载特性,大致的实现代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Commons Logging优先使用Log4j:
LogFactory factory = null;
if (isClassPresent("org.apache.logging.log4j.Logger")) {
factory = createLog4j();
} else {
factory = createJdkLog();
}

boolean isClassPresent(String name) {
try {
Class.forName(name);
return true;
} catch (Exception e) {
return false;
}
}

这就是为什么我们只需要把Log4j的jar包放到classpath中,Commons Logging就会自动使用Log4j的原因。

访问字段

通过获取某个Object的Class对象,就可以获取关于这个类的一切信息,

  • Field getField(name):根据字段名获取某个public的field(包括父类)
  • Field getDeclaredField(name):根据字段名获取当前类的某个field(不包括父类)
  • Field[] getFields():获取所有public的field(包括父类)
  • Field[] getDeclaredFields():获取当前类的所有field(不包括父类)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Main {
public static void main(String[] args) throws Exception {
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;
}

获取字段值

利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。

例如,对于一个Person实例,我们可以先拿到name字段对应的Field,再获取这个实例的name字段的值:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.Field;

public class GetFiled {
public static void main(String[] args) throws Exception {
Object p = new Person("xiaoming");
Class cls = p.getClass();
Field f = cls.getDeclaredField("name");
f.setAccessible(true);
Object o = f.get(p);
System.out.println(o);

}


}
class Person{
private String name;

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

获取字段值的方法是:

  1. 获取class
  2. 通过getFiled()函数获取field实例f
  3. 如果要获取的字段值是private的,需要先执行field.setAccessible(true);
  4. 通过field.get(object)获取字段值,返回类型为Object

设置字段值

除了获取字段值,还可以设置字段值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.drawon.reflection;

import java.lang.reflect.Field;

public class GetFiled {
public static void main(String[] args) throws Exception {
Person p = new Person("xiaoming");
Class cls = p.getClass();
Field f = cls.getDeclaredField("name");
f.setAccessible(true);
f.set(p,"gugu");
System.out.println(p.getName());

}


}
class Person{
private String name;

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

public String getName() {
return name;
}
}

调用方法

除了Field对象,还可以通过class对象获取类的Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类)
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类)
  • Method[] getMethods():获取所有publicMethod(包括父类)
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)

ps:所有public的属性和方法用getxxx(),所有private的方法用getDeclaredxxx()

1
2
3
4
5
6
7
8
public class GetMethod {
public static void main(String[] args) throws NoSuchMethodException {
Person p = new Person("xiaoming");
Class cls = p.getClass();
Method method = cls.getMethod("getName");
System.out.println(method);
}
}

打印出public java.lang.String com.drawon.reflection.Person.getName()

method包含信息如下:

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

调用方法

获取method之后,可以通过invoke来调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class GetMethod {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Person p = new Person("xiaoming");
Class cls = p.getClass();
Method method1 = cls.getMethod("getName");
Method method2 = cls.getMethod("getAge", int.class);

System.out.println(method1.invoke(p));
int age = (int) method2.invoke(p,10);
}
}

class Person{
private String name;

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

public String getName() {
return name;
}

public int getAge(int age){
return age;
}
}

调用静态方法

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

1
2
3
4
5
6
7
8
9
10
11
12
// reflection import java.lang.reflect.Method; 
public class Main {
public static void main(String[] args) throws Exception {
// 获取Integer.parseInt(String)方法,参数为String:
Method m = Integer.class.getMethod("parseInt", String.class);
// 调用该静态方法并获取结果:
Integer n = (Integer) m.invoke(null, "12345");
// 打印调用结果:
System.out.println(n);
}
}

调用非public方法

和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()获取该方法实例,但直接对其调用将得到一个IllegalAccessException。为了调用非public方法,我们通过Method.setAccessible(true)允许其调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) throws Exception {
Person p = new Person();
Method m = p.getClass().getDeclaredMethod("setName", String.class);
m.setAccessible(true);
m.invoke(p, "Bob");
System.out.println(p.name);
}
}

class Person {
String name;
private void setName(String name) {
this.name = name;
}
}

多态

我们来考察这样一种情况:一个Person类定义了hello()方法,并且它的子类Student也覆写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class Main {
public static void main(String[] args) throws Exception {
// 获取Person的hello方法:
Method h = Person.class.getMethod("hello");
// 对Student实例调用hello方法:
h.invoke(new Student());
}
}

class Person {
public void hello() {
System.out.println("Person:hello");
}
}

class Student extends Person {
public void hello() {
System.out.println("Student:hello");
}
}

打印结果为:Student:hello

这表明在反射时,依旧遵守多态原则

调用构造方法

通常对象用new来创建

1
Person p = new Person();

如果通过反射创建,可以调用newInstance()方法

1
Person p = Person.class.newInstance();

通过newInstance()新建对象的局限是只能调用该类的public无参构造方法

为了调用任意构造函数,反射提供了一个Constructor对象,包含构造方法的所有信息,ConstructorMethod很像,只是Constructor调用的是构造方法,method使用invoke,constructor使用newinstance

1
2
3
4
5
6
7
8
public class GetConstructor {
public static void main(String[] args) throws Exception{
Class cls = Person.class;
Constructor cons = cls.getConstructor(String.class);
Person p = (Person) cons.newInstance("kuku");
System.out.println(p);
}
}

通过class获取constructor的方法如下:

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

调用非publicConstructor时,必须首先通过setAccessible(true)设置允许访问。

获取继承关系

通过class实例的getSuperClass方法获取父类的class

1
2
3
4
5
6
7
8
9
10
11
public class Main {
public static void main(String[] args) throws Exception {
Class i = Integer.class;
Class n = i.getSuperclass();
System.out.println(n);
Class o = n.getSuperclass();
System.out.println(o);
System.out.println(o.getSuperclass());
}
}

打印结果为:

1
2
3
class java.lang.Number
class java.lang.Object
null

表示integer的父类是Number,Number的父类是Object,Object的父类为null

获取interface

获取类实现的一个或多个接口,方法如下:

1
2
3
4
5
6
7
8
9
10
11
package com.drawon.reflection;

public class GetInterface {
public static void main(String[] args) {
Class[] interfaces = Integer.class.getInterfaces();
for (Class i:interfaces){
System.out.println(i);
}
}
}

发现Integer类实现了Comparable接口,打印结果如下:

1
interface java.lang.Comparable

注意:getInterfaces()只返回当前类实现的接口,并不包含父类实现的接口。要找到父类实现的接口,需要用getSuperclass()方法。

但对interface使用getSuperClass得到的是null,获取接口的父接口需要用getInterfaces(),如果一个类没有实现任何接口,会返回null

继承关系

判断是否属于某个类型,用instanceof来进行判断

如果两个class实例,判断是否能够向上转型,用isAssignableFrom()

1
2
3
4
5
6
7
8
// Integer i = ?
Integer.class.isAssignableFrom(Integer.class); // true,因为Integer可以赋值给Integer
// 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

动态代理

参考Spring AOP的实现机制