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 = ? |