注:本文代码基于JDK 11

一、Class

每个已加载的类在内存都有一份类信息,每个对象都有指向它所属类信息的引用,Java中类信息对应的类就是java.lang.Class,所有类的根父类Object有一个方法,可以获取对象的Class对象,请看如下代码:

1
public final native Class<?> getClass();

Class是一个泛型类,有一个类型参数,getClass()并不知道具体的类型,所以返回Class

获取Class对象不一定需要实例对象,如果在写程序时就知道类名,可以使用<类名>.class获取Class对象,请看如下代码:

1
Class<Date> cls = Date.class;

接口也有Class对象,且这种方式对于接口也是适用的,请看如下代码:

1
Class<Comparable> cls = Comparable.class;

基本类型没有getClass方法,但也都有对应的Class对象,类型参数为对应的包装类型,请看如下代码:

1
2
3
4
Class<Integer> intCls = int.class;
Class<Byte> byteCls = byte.class;
Class<Character> charCls = char.class; 
Class<Double> doubleCls = double.class;

void作为特殊的返回类型,也有对应的Class,请看如下代码:

1
Class<Void> voidCls = void.class;

对于数组,每种类型都有对应数组类型的Class对象,每个维度都有一个,即一维数组有一个,二维数组有一个不同的类型,请看如下代码:

1
2
3
4
5
6
String[] strArr = new String[10];
int[][] twoDimArr = new int[3][2];
int[] oneDimArr = new int[10];
Class<? extends String[]> strArrCls = strArr.getClass();
Class<? extends int[][]> twoDimArrCls = twoDimArr.getClass(); 
Class<? extends int[]> oneDimArrCls = oneDimArr.getClass();

枚举类型也有对应的Class,请看如下代码:

1
2
3
4
5
enum Size {
    SMALL, MEDIUM, BIG 
}

Class<Size> cls = Size.class;

Class有一个静态方法forName,可以根据类名直接加载Class,获取Class对象,请看如下代码:

1
2
3
4
5
6
try {
    Class<?> cls = Class.forName("java.util.HashMap");
    System.out.println(cls.getName());
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

有了Class对象后,我们就可以获取到关于类型的很多信息,并基于这些信息采取一些行动。

1.1、名称信息

Class有如下方法,可以获取与名称有关的信息,请看如下代码:

1
2
3
4
5
6
7
8
// 返回Java内部使用的真正名称
public String getName()
// 返回的名称不带包信息
public String getSimpleName()
// 返回的名称更为友好
public String getCanonicalName()
// 返回的包信息
public Package getPackage()

数组类型的getName返回值,它使用前缀[表示数组,有几个[表示是几维数组;数组的类型用一个字符表示,I表示int, L表示类或接口,其它类型与字符的对应关系为:boolean(Z)byte(B)char(C)double(D)float(F)long(J)short(S)。对于引用类型的数组,注意最后有一个分号;。不同Class对象的各种名称方法的返回值如下图所示。

1.2、字段信息

字段是指类中定义的静态和实例变量,用类Field表示,位于包java.lang.reflect下,Class有4个获取字段信息的方法,请看如下代码:

1
2
3
4
5
6
7
8
// 返回所有的public字段,包括其父类的,如果没有字段,返回空数组
public Field[] getFields()
// 返回本类声明的所有字段,包括非public的,但不包括父类的
public Field[] getDeclaredFields()
// 返回本类或父类中指定名称的public字段,找不到抛出异常NoSuchFieldException
public Field getField(String name)
// 返回本类中声明的指定名称的字段,找不到抛出异常NoSuchFieldException
public Field getDeclaredField(String name)

Field有很多方法,可以获取字段的信息,也可以访问和 操作指定对象中该字段的值,请看如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 获取字段的名称
public String getName()
// 判断当前程序是否有该字段的访问权限
public boolean isAccessible()
// flag设为true表示忽略Java的访问检查机制,以允许读写非public的字段
public void setAccessible(boolean flag)
// 获取指定对象obj中该字段的值
public Object get(Object obj)
// 将指定对象obj中该字段的值设为value
public void set(Object obj, Object value)

get/set方法中,对于静态变量,obj被忽略,可以为null,如果字段值为基本类型,get/set会自动在基本类型与对应的包装类型间进行转换;对于private字段,直接调用get/set会抛出非法访问异常IllegalAccessException,应该先调用setAccessible(true)以关闭Java的检查机制。

举个例子,反射修改childDoublechildInt的值,请看如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Child {
    private double childDouble = 0d;
    private static int childInt = 0;
}

try{
    Child child = new Child();
    Class<? extends Child> cls = child.getClass();
    Field doubleField = cls.getDeclaredField("childDouble");
    if (!doubleField.canAccess(child)) {
        doubleField.setAccessible(true);
    }
    doubleField.set(child, 88);
    System.out.println(doubleField.get(child));

    Field intField = Child.class.getDeclaredField("childInt");
    if (!intField.canAccess(null)) {
        intField.setAccessible(true);
    }
    intField.set(null, 99);
    System.out.println(intField.get(null));
}catch (NoSuchFieldException | IllegalAccessException e) {
    e.printStackTrace();
}

除了以上方法,Field还有很多其它方法,请看如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 返回字段的修饰符
public int getModifiers()
// 返回字段的类型
public Class<?> getType()
// 以基本类型操作字段
public void setBoolean(Object obj, boolean z)
public boolean getBoolean(Object obj)
public void setDouble(Object obj, double d)
public double getDouble(Object obj)
// 查询字段的注解信息
public <T extends Annotation> T getAnnotation(Class<T> annotationClass)
public Annotation[] getDeclaredAnnotations()

1.3、方法信息

方法是指类中定义的静态和实例方法,用类Method表示,Class有提供相关方法,请看如下代码:

1
2
3
4
5
6
7
8
9
// 返回所有的public方法,包括其父类的,如果没有方法,返回空数组
public Method[] getMethods()
// 返回本类声明的所有方法,包括非public的,但不包括父类的
public Method[] getDeclaredMethods()
// 返回本类或父类中指定名称和参数类型的public方法,
// 找不到抛出异常NoSuchMethodException
public Method getMethod(String name, Class<?>... parameterTypes)
// 返回本类中声明的指定名称和参数类型的方法,找不到抛出异常NoSuchMethodException
public Method getDeclaredMethod(String name, Class<?>... parameterTypes)

通过Method可以获取方法的信息,也可以调用对象的方法,请看如下代码:

1
2
3
4
5
6
// 获取方法的名称
public String getName()
// flag设为true表示忽略Java的访问检查机制,以允许调用非public的方法
public void setAccessible(boolean flag)
// 在指定对象obj上调用Method代表的方法,传递的参数列表为args
public Object invoke(Object obj, Object... args) throws IllegalAccessException, Illegal-ArgumentException, InvocationTargetException

invoke方法,如果Method为静态方法,obj被忽略,可以为null, args可以为null,也可以为一个空的数组,方法调用的返回值被包装为Object返回,如果实际方法调用抛出异常,异常被包装为InvocationTargetException重新抛出,可以通过getCause方法得到原异常。

举个例子,反射调用IntegerparseInt方法,请看如下代码:

1
2
3
4
5
6
7
8
9
Class<?> cls = Integer.class;
try {
    Method method = cls.getMethod("parseInt", new Class[]{String.class});
    System.out.println(method.invoke(null, "123"));
} catch (NoSuchMethodException e) {
    e.printStackTrace();
} catch (InvocationTargetException e) {
    e.printStackTrace();
}

1.4、创建对象和构造方法

Class还有一些方法,可以获取所有的构造方法,请看如下代码:

1
2
3
4
5
6
7
8
// 获取所有的public构造方法,返回值可能为长度为0的空数组
public Constructor<?>[] getConstructors()
// 获取所有的构造方法,包括非public的
public Constructor<?>[] getDeclaredConstructors()
// 获取指定参数类型的public构造方法,没找到抛出异常NoSuchMethodException
public Constructor<T> getConstructor(Class<?>... parameterTypes)
// 获取指定参数类型的构造方法,包括非public的,没找到抛出异常NoSuchMethodException
public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes)

Constructor表示构造方法,通过它可以创建对象,请看如下代码:

1
public T newInstance(Object ... initargs) throws InstantiationException,IllegalAccessException, IllegalArgumentException, InvocationTargetException

举个例子,反射创建StringBuilder对象,请看如下代码:

1
2
Constructor<StringBuilder> contructor= StringBuilder.class.getConstructor(new Class[]{int.class});
StringBuilder sb = contructor.newInstance(100);

1.5、类型检查和转换

Class类提供了相关方法来判断变量指向的实际对象类型,请看如下代码:

1
public native boolean isInstance(Object obj)

举个例子,请看如下代码:

1
System.out.println(ArrayList.class.isInstance(list));

除了判断类型,在程序中也往往需要进行强制类型转换,Class类也提供了相关方法,请看如下代码:

1
public T cast(Object obj)

isInstance/cast描述的都是对象和类之间的关系,Class还有一个方法,可以判断Class之间的关系,请看如下代码:

1
2
// 检查参数类型cls能否赋给当前Class类型的变量
public native boolean isAssignableFrom(Class<?> cls);

举个例子,如下表达式的结果都为true,请看如下代码:

1
2
3
Object.class.isAssignableFrom(String.class)
String.class.isAssignableFrom(String.class)
List.class.isAssignableFrom(ArrayList.class)

1.6、Class的类型信息

Class类提供了相关方法来判断给定的Class对象是什么类型,请看如下代码:

1
2
3
4
5
6
7
8
public native boolean isArray()  //是否是数组
public native boolean isPrimitive()  //是否是基本类型
public native boolean isInterface()  //是否是接口
public boolean isEnum()  //是否是枚举
public boolean isAnnotation()  //是否是注解
public boolean isAnonymousClass()  //是否是匿名内部类
public boolean isMemberClass()  //是否是成员类,成员类定义在方法外,不是匿名类
public boolean isLocalClass()  //是否是本地类,本地类定义在方法内,不是匿名类

1.7、类的声明信息

Class类提供了相关方法来获取类的声明信息,如修饰符、父类、接口、注解等等,请看如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 获取修饰符,返回值可通过Modifier类进行解读
public native int getModifiers()
// 获取父类,如果为Object,父类为null
public native Class<? super T> getSuperclass()
// 对于类,为自己声明实现的所有接口,对于接口,为直接扩展的接口,不包括通过父类继承的
public native Class<?>[] getInterfaces();
// 自己声明的注解
public Annotation[] getDeclaredAnnotations()
// 所有的注解,包括继承得到的
public Annotation[] getAnnotations()
// 获取或检查指定类型的注解,包括继承得到的
public <A extends Annotation> A getAnnotation(Class<A> annotationClass)
public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)

1.8、类的加载

Class有两个静态方法,可以根据类名加载类,请看如下代码:

1
2
public static Class<?> forName(String className)
public static Class<?> forName(String name, boolean initialize, ClassLoader loader)

ClassLoader表示类加载器,initialize表示加载后,是否执行类的初始化代码(如static语句块)。

需要注意的是,基本类型不支持forName方法,会抛出异常ClassNotFoundException,请看如下代码:

1
Class.forName("int");

可以对Class.forName进行一下包装,请看如下代码:

1
2
3
4
5
6
7
8
public static Class<?> forName(String className)
        throws ClassNotFoundException{
    if("int".equals(className)){
        return int.class;
    }
    // 其它基本类型略
    return Class.forName(className);
}

1.9、反射与数组

对于数组类型,有一个专门的方法,可以获取它的元素类型,请看如下代码:

1
public native Class<?> getComponentType()

举个例子,获取 String[] 的元素类型,请看如下代码:

1
2
String[] arr = new String[]{};
System.out.println(arr.getClass().getComponentType()); // class java.lang.String

java.lang.reflect包中有一个针对数组的专门的类Array,提供了对于数组的一些反射支持,以便于统一处理多种类型的数组,请看如下代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 创建指定元素类型、指定长度的数组
public static Object newInstance(Class<?> componentType, int length)
// 创建多维数组
public static Object newInstance(Class<?> componentType, int... dimensions)
// 获取数组array指定的索引位置index处的值
public static native Object get(Object array, int index)
// 修改数组array指定的索引位置index处的值为value
public static native void set(Object array, int index, Object value)
// 返回数组的长度
public static native int getLength(Object array)

需要注意的是,在Array类中,数组是用Object而非 Object[] 表示的,这是为了方便处理多种类型的数组,int[]String[]都不能与Object[]相互转换,但可以与Object相互转换,请看如下代码:

1
2
int[] intArr = (int[])Array.newInstance(int.class, 10);
String[] strArr = (String[])Array.newInstance(String.class, 10);

除了以Object类型操作数组元素外,Array也支持以各种基本类型操作数组元素,请看如下代码:

1
2
3
4
public static native double getDouble(Object array, int index)
public static native void setDouble(Object array, int index, double d)
public static native void setLong(Object array, int index, long l)
public static native long getLong(Object array, int index)

1.10、反射与枚举

枚举类型也有一个专门方法,可以获取所有的枚举常量,请看如下代码:

1
public T[] getEnumConstants()

二、反射与泛型

虽然泛型参数在运行时会被擦除,但是在类信息Class中依然有关于泛型的一些信息,可以通过反射得到。Class类提供了相关方法来获取类的泛型参数信息,请看如下代码:

1
2
// TypeVariable对象的数组,表示此泛型声明所声明的类型变量
public TypeVariable<Class<T>>[] getTypeParameters()

Field类也提供了相关方法,请看如下代码:

1
public Type getGenericType()

Method类也提供了相关方法,请看如下代码:

1
2
3
public Type getGenericReturnType()
public Type[] getGenericParameterTypes()
public Type[] getGenericExceptionTypes()

Constructor类也提供了相关方法,请看如下代码:

1
public Type[] getGenericParameterTypes()

Type是一个接口,Class实现了TypeType的其它子接口还有:

1、TypeVariable:类型参数,可以有上界,比如T extends Number

2、ParameterizedType:参数化的类型,有原始类型和具体的类型参数,比如List

3、WildcardType:通配符类型,比如?、? extends Number? super Integer

举个例子,请看如下代码:

 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
29
30
31
32
33
34
35
36
public class GenericDemo {
    static class GenericTest<U extends Comparable<U>, V> {
        U u;
        V v;
        List<String> list;
        public U test(List<? extends Number> numbers) {
            return null;
        }
    }

    public static void main(String[] args) throws Exception {
        Class<?> cls = GenericTest.class;
        // 类的类型参数
        for(TypeVariable t : cls.getTypeParameters()) {
            System.out.println(t.getName() + " extends " +
                Arrays.toString(t.getBounds()));
        }
        // 字段:泛型类型
        Field fu = cls.getDeclaredField("u");
        System.out.println(fu.getGenericType());
        // 字段:参数化的类型
        Field flist = cls.getDeclaredField("list");
        Type listType = flist.getGenericType();
        if(listType instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) listType;
            System.out.println("raw type: " + pType.getRawType()
                + ",type arguments:"
                + Arrays.toString(pType.getActualTypeArguments()));
        }
        // 方法的泛型参数
        Method m = cls.getMethod("test", new Class[] { List.class });
        for(Type t : m.getGenericParameterTypes()) {
            System.out.println(t);
        }
    }
}

程序输出如下。

1
2
3
4
5
U extends [java.lang.Comparable<U>]
V extends [class java.lang.Object]
U
raw type: interface java.util.List,type arguments:[class java.lang.String]
java.util.List<? extends java.lang.Number>

反射虽然是灵活的,但一般情况下,并不是我们优先建议的,主要原因是:

1、反射更容易出现运行时错误,因为类型是运行时才知道的,编译器无能为力。

2、反射的性能要低一些,在访问字段、调用方法前,反射先要查找对应的Field/Method,要慢一些。

简而言之,如果能用接口实现同样的灵活性,就不要使用反射。