java当中,除了基本类型外,其他都是class(interface),没有继承关系的数据类型是无法赋值的。本身class是一种数据类型(type)。
而class本身是在jvm执行过程中动态加载的,每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。
这里的Class
类型是一个名叫Class
的class
1 | public final class 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实例:
通过class静态变量获取:
1
Class cls = String.class
通过实例变量的getClass()方法获取
1
2String s ="abc";
Class cls = s.getClass();知道一个class的完整类名,可以通过静态方法
Class.forName()
获取1
Class cls = Class.forName("java.lang.String");
因为Class
实例在JVM当中是唯一的,因此上述三种方法获取的Class
实例是同一个实例,可以用==
比较。
1 | Class cls1 = String.class; |
注意Class实例比较和instanceof的区别:
instanceof
是判断是否a是b的子类1
2
3
4Integer n = new Integer(123);
boolean b1 = n instanceof Integer; // true,因为n是Integer类型
boolean b2 = n instanceof Number; // true,因为n是Number类型的子类==
判断的是是否是同一个class1
2boolean b3 = n.getClass() == Integer.class; // true,因为n.getClass()返回Integer.class
boolean b4 = n.getClass() == Number.class; // false,因为Integer.class!=Number.class
拿到某个对象的class后,就可以通过反射获取该Object
的class
信息
1 | package com.drawon.reflection; |
注意:数组也是一种class(比如String[]),并且它不同于String.class
,它的类名是[Ljava.lang.String
。
如果获取到了一个Class实例,我们就可以通过该class实例来创建对应类型的实例。
1 | // 获取String的Class实例: |
上述代码等同于new String()
。通过newInstance()
方法创建实例的局限是只能调用public
的无参构造方法,带参数的活非public
的构造方法都无法被class.newInstance()
方法调用
动态加载
JVM在执行java程序的时候,在需要用某个class时才对其进行加载。例如,Commons Logging总是优先使用Log4j,只有当Log4j不存在时,才使用JDK的logging。利用JVM动态加载特性,大致的实现代码如下:
1 | // Commons Logging优先使用Log4j: |
这就是为什么我们只需要把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 | public class Main { |
获取字段值
利用反射拿到字段的一个Field
实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
例如,对于一个Person
实例,我们可以先拿到name
字段对应的Field
,再获取这个实例的name
字段的值:
1 | import java.lang.reflect.Field; |
获取字段值的方法是:
- 获取class
- 通过
getFiled()
函数获取field实例f - 如果要获取的字段值是private的,需要先执行field.setAccessible(true);
- 通过field.get(object)获取字段值,返回类型为Object
设置字段值
除了获取字段值,还可以设置字段值
1 | package com.drawon.reflection; |
调用方法
除了Field
对象,还可以通过class对象获取类的Method
Method getMethod(name, Class...)
:获取某个public
的Method
(包括父类)Method getDeclaredMethod(name, Class...)
:获取当前类的某个Method
(不包括父类)Method[] getMethods()
:获取所有public
的Method
(包括父类)Method[] getDeclaredMethods()
:获取当前类的所有Method
(不包括父类)
ps:所有public的属性和方法用getxxx(),所有private的方法用getDeclaredxxx()
1 | public class GetMethod { |
打印出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 | public class GetMethod { |
调用静态方法
如果获取到的Method表示一个静态方法,调用静态方法时,由于无需指定实例对象,所以invoke
方法传入的第一个参数永远为null
。我们以Integer.parseInt(String)
为例:
1 | // reflection import java.lang.reflect.Method; |
调用非public方法
和Field类似,对于非public方法,我们虽然可以通过Class.getDeclaredMethod()
获取该方法实例,但直接对其调用将得到一个IllegalAccessException
。为了调用非public方法,我们通过Method.setAccessible(true)
允许其调用:
1 | public class Main { |
多态
我们来考察这样一种情况:一个Person
类定义了hello()
方法,并且它的子类Student
也覆写了hello()
方法,那么,从Person.class
获取的Method
,作用于Student
实例时,调用的方法到底是哪个?
1 | public class Main { |
打印结果为:Student:hello
这表明在反射时,依旧遵守多态原则
调用构造方法
通常对象用new
来创建
1 | Person p = new Person(); |
如果通过反射创建,可以调用newInstance()
方法
1 | Person p = Person.class.newInstance(); |
通过newInstance()
新建对象的局限是只能调用该类的public无参构造方法
为了调用任意构造函数,反射提供了一个Constructor
对象,包含构造方法的所有信息,Constructor
和Method
很像,只是Constructor调用的是构造方法,method使用invoke,constructor使用newinstance
1 | public class GetConstructor { |
通过class获取constructor的方法如下:
getConstructor(Class...)
:获取某个public
的Constructor
;getDeclaredConstructor(Class...)
:获取某个Constructor
;getConstructors()
:获取所有public
的Constructor
;getDeclaredConstructors()
:获取所有Constructor
。
调用非public
的Constructor
时,必须首先通过setAccessible(true)
设置允许访问。
获取继承关系
通过class实例的getSuperClass方法获取父类的class
1 | public class Main { |
打印结果为:
1 | class java.lang.Number |
表示integer的父类是Number,Number的父类是Object,Object的父类为null
获取interface
获取类实现的一个或多个接口,方法如下:
1 | package com.drawon.reflection; |
发现Integer类实现了Comparable接口,打印结果如下:
1 | interface java.lang.Comparable |
注意:getInterfaces()
只返回当前类实现的接口,并不包含父类实现的接口。要找到父类实现的接口,需要用getSuperclass()方法。
但对interface使用getSuperClass得到的是null,获取接口的父接口需要用getInterfaces()
,如果一个类没有实现任何接口,会返回null
继承关系
判断是否属于某个类型,用instanceof
来进行判断
如果两个class实例,判断是否能够向上转型,用isAssignableFrom()
1 | // Integer i = ? |