Java反射-类信息
2018-10-12 | 分类 Programing-language | 标签 Java Reflection

什么是反射

反射(Reflection) 机制为在运行时改变Java程序行为提供了可能。通过反射机制,我们可以在运行时扩展程序的功能、获取运行时信息。比如:在运行时动态创建对象、动态创建代理、基于注解(Annotation)实现特定的功能等等。

但是天下没有免费的午餐。虽然反射可以在静态语言的基础上提供给我们动态修改程序行为的能力,但是使用反射机制也存在弊端。比如:反射有性能开销、反射会破坏封装,暴露程序的细节、反射会导致程序引入安全隐患。

反射是一把双刃剑,合理利用反射机制可以为我们带来意想不到的好处。下面我们来学习如何使用Java的反射机制。

从Class对象开始

每个Java源文件编译以后会生成一个 .class 后缀的字节码文件。当JVM运行程序的时候,需要加载这个class文件到内存中。JVM中每个被加载的class文件都有一个对应的 Class 对象。Class 对象存储了一个类的所有信息。Java中的每个类型,包括8个 原始类型(primitive type):long、int、short、float、double、boolean、char、byte,以及void。都有一个 Class 对象对应。Java的反射机制就是基于 Class 对象来获取类的运行时信息。

getClass()

Java中引用类型的基类 java.lang.Object 中有一个getClass()实例方法。通过调用这个方法,可以从类的实例上获取该类型的 Class 对象。

public static void main(String... args) throws Exception {
    String s = "Hello World";
    
    // 通过getClass()获取class对象
    Class clazz1 = s.getClass();
    
    // 通过getClass()获取class对象
    Class clazz2 = String.class;
}

type.class

如果事先知道类型的名称,则可以通过type.class获取类的 Class 对象。比如:对于 原始类型(primitive type),由于没法通过getClass()直接获取 Class 对象,对于元素类型,可以通过type.class的方式获取 Class 对象。

public static void main(String... args) throws Exception {
    Class clazz = int.class;
}

type.TYPE

对于原始类型的包装类型,如:Integer、Boolean、Long等,还可以通过type.TYPE获取 Class 对象。

public static void main(String... args) throws Exception {
    Class clazz = Integer.TYPE; // equals to int.class
}

forName()

除了通过对象或类型获取 Class 对象外,Class 对象中的forName()静态方法支持通过类的全限定名称(可以通过Class.getName()方法获取)获取 Class 对象。

public static void main(String... args) throws Exception {
    Class clazz = Class.forName("java.lang.Integer"); // equals to int.class
}

获取类名称

Class 对象中提供了几个方法获取类的名称:

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    public String getName() {};
    public String getCanonicalName() {};
    public String getSimpleName() {};
    
    // Java8引入
    public String getTypeName() {};
}

getName()

如果 Class 对象是一个非 Array 引用类型的类对象,那么getName()方法返回类的全限定名。如果 Class 对象是原始类型的类对象,那么 getName() 将返回原始类型的名称(int、short、boolean等)。如果 Class 对象是一个数组类型的类对象,那么 getName() 返回一个由元素类型编码(定义见下表)和 [ 组合表示的数组类型名称,其中 [ 的个数表示数组的维数。下面是一些例子:

public static void main(String... args) throws Exception {
    System.out.println(int.class.getName());
    System.out.println(String.class.getName());
    System.out.println(int[].class.getName());
    System.out.println(boolean[][].class.getName());
    System.out.println(String[][][].class.getName());
    System.out.println(Map.Entry.class.getName());
    System.out.println(new Serializable() {}.getClass().getName());
}

private static void printClassName(Object object) {
    System.out.println(object.getClass().getName());
}

输出结果:

$ java Main
int
java.lang.String
[I
[[Z
[[[Ljava.lang.String;
java.util.Map$Entry
org.foo.Main$1

关于数组类型中元素编码的定义如下:

元素类型 缩写
boolean Z
byte B
char C
double D
float F
int I
long J
short S
class / interface Lclassname;

其中对于类或接口作为数组元素的数组类型名称,classname表示类的全限定名称。

getCanonicalName()

方法 getCanonicalName()返回类的官方名称。

public static void main(String... args) throws Exception {
    System.out.println(int.class.getCanonicalName());
    System.out.println(String.class.getCanonicalName());
    System.out.println(int[].class.getCanonicalName());
    System.out.println(int[][].class.getCanonicalName());
    System.out.println(Map.Entry.class.getCanonicalName());
    System.out.println(new Serializable() {}.getClass().getCanonicalName());
}

输出结果:

$ java Main
int
java.lang.String
int[]
int[][]
java.util.Map.Entry
null

可以发现,数组类型的CanonicalName更加可读;内部匿名类的getCanonicalName()返回了null;而内部类的getCanonicalName()返回的名字里 $ 符号变成了点号(.)。

getSimpleName()

方法getSimpleName()只返回类的名称,不包含类的包名前缀。

public static void main(String... args) throws Exception {
    System.out.println(int.class.getSimpleName());
    System.out.println(String.class.getSimpleName());
    System.out.println(int[].class.getSimpleName());
    System.out.println(int[][].class.getSimpleName());
    System.out.println(Map.Entry.class.getSimpleName());
    System.out.println(new Serializable() {}.getClass().getSimpleName());
}

输出结果:

$ java Main
int
String
int[]
int[][]
Entry

最后一个匿名内部类的getSimpleName()返回的是一个空字符串。

getTypeName()

方法getTypeName()是Java8新引入的方法,返回一个类的全限定名称。getTypeName()getName()类似,但是前者是 Type 接口中的一个方法。

public static void main(String... args) throws Exception {
    System.out.println(int.class.getTypeName());
    System.out.println(String.class.getTypeName());
    System.out.println(int[].class.getTypeName());
    System.out.println(int[][].class.getTypeName());
    System.out.println(Map.Entry.class.getTypeName());
    System.out.println(new Serializable() {}.getClass().getTypeName());
}

输出结果:

$ java Main
int
java.lang.String
int[]
int[][]
java.util.Map$Entry
org.foo.Main$1

一些例子

通过一些例子看下上面这四个方法返回值的区别:

package org.foo;

import java.io.Serializable;
import java.util.HashMap;

public class Main {
    public static void main(String ...args) {
        System.out.println("> primitive");
        //primitive
        System.out.printf("%-20s: %s\n", "getName", int.class.getName());
        System.out.printf("%-20s: %s\n", "getCanonicalName", int.class.getCanonicalName());
        System.out.printf("%-20s: %s\n", "getSimpleName", int.class.getSimpleName());
        System.out.printf("%-20s: %s\n", "getTypeName", int.class.getTypeName());

        System.out.println("\n> class");
        //class
        System.out.printf("%-20s: %s\n", "getName", String.class.getName());
        System.out.printf("%-20s: %s\n", "getCanonicalName", String.class.getCanonicalName());
        System.out.printf("%-20s: %s\n", "getSimpleName", String.class.getSimpleName());
        System.out.printf("%-20s: %s\n", "getTypeName", String.class.getTypeName());

        System.out.println("\n> inner class");
        //inner class
        System.out.printf("%-20s: %s\n", "getName", HashMap.Entry.class.getName());
        System.out.printf("%-20s: %s\n", "getCanonicalName", HashMap.Entry.class.getCanonicalName());
        System.out.printf("%-20s: %s\n", "getSimpleName", HashMap.Entry.class.getSimpleName());
        System.out.printf("%-20s: %s\n", "getTypeName", HashMap.Entry.class.getTypeName());

        System.out.println("\n> anonymous inner class");
        //anonymous inner class
        System.out.printf("%-20s: %s\n", "getName", new Serializable(){}.getClass().getName());
        System.out.printf("%-20s: %s\n", "getCanonicalName", new Serializable(){}.getClass().getCanonicalName());
        System.out.printf("%-20s: %s\n", "getSimpleName", new Serializable(){}.getClass().getSimpleName());
        System.out.printf("%-20s: %s\n", "getTypeName", new Serializable(){}.getClass().getTypeName());
    }
}

输出结果:

$ java Main
> primitive
getName             : int
getCanonicalName    : int
getSimpleName       : int
getTypeName         : int

> class
getName             : java.lang.String
getCanonicalName    : java.lang.String
getSimpleName       : String
getTypeName         : java.lang.String

> inner class
getName             : java.util.Map$Entry
getCanonicalName    : java.util.Map.Entry
getSimpleName       : Entry
getTypeName         : java.util.Map$Entry

> anonymous inner class
getName             : org.foo.Main$1
getCanonicalName    : null
getSimpleName       : 
getTypeName         : org.foo.Main$4

从输出结果中可以看得出,getName()方法返回的值是一个类的全限定名称,可以被Class.forName()用来加载类对象。而getCanonicalName()方法的返回值可以唯一标识一个类。getSimpleName()反复的返回值只是一个类名称的简单表示,不能再全局范围内唯一表示一个类。getTypeName()getName()类似。

获取Package信息

getPackage()

Class 类中的getPackage()方法可以获取类所在 Package 的信息。package中的信息包括:版本、厂商等信息。这些信息一般来自jar包中的 manifest 文件,由类加载器加载类的时候初始化。

public static void main(String ...args) {
    Package pkg = String.class.getPackage();

    System.out.println("Package: \n");

    System.out.printf("%25s: %s\n", "Name", pkg.getName());

    System.out.printf("%25s: %s\n", "SpecificationTitle", pkg.getSpecificationTitle());
    System.out.printf("%25s: %s\n", "SpecificationVendor", pkg.getSpecificationVendor());
    System.out.printf("%25s: %s\n", "SpecificationVersion", pkg.getSpecificationVersion());

    System.out.printf("%25s: %s\n", "ImplementationTitle", pkg.getImplementationTitle());
    System.out.printf("%25s: %s\n", "ImplementationVendor", pkg.getImplementationVendor());
    System.out.printf("%25s: %s\n", "getImplementationVersion", pkg.getImplementationVersion());

}

输出结果:

$ java Main
Package: 

                     Name: java.lang
       SpecificationTitle: Java Platform API Specification
      SpecificationVendor: Oracle Corporation
     SpecificationVersion: 1.8
      ImplementationTitle: Java Runtime Environment
     ImplementationVendor: Oracle Corporation
 getImplementationVersion: 1.8.0_101

解析类修饰符

Java在定义类的时候可以使用多个修饰符修饰类。访问性修饰符包括:publicprotectedprivate 以及默认的包内可见。声明抽象类的时候使用 abstract 关键字。在类上修饰的,表示类不可以被继承的 final 关键字等。

Java反射提供的API可以动态获取和解析类的修饰符信息。在 java.lang.reflect.Modifier 类中包含了Java中所有修饰符的定义。下面是类上的修饰符定义:

修饰符
PUBLIC 0x00000001
PRIVATE 0x00000002
PROTECTED 0x00000004
STATIC 0x00000008
FINAL 0x00000010
INTERFACE 0x00000200
ABSTRACT 0x00000400
STRICT 0x00000800

getModifiers()

Class 类中有一个Class.getModifiers()方法,在类的 Class 对象上调用这个方法可以获取这个类的修饰符掩码值。掩码值是一个int类型的整数,由上表中定义的修饰符值进行或(|)运算获得。解析的时候可以通过位运算里的与运算(&)来判断修饰符的类型:(modifiers | Modifier.PUBLIC) != 0。JDK提供的 Modifier 类中也包含了一系列is*()静态方法来判断修饰符:

public static boolean isPublic(int mod);
public static boolean isPrivate(int mod);
public static boolean isProtected(int mod);
public static boolean isStatic(int mod);
public static boolean isFinal(int mod);
public static boolean isInterface(int mod);
public static boolean isAbstract(int mod);

下面是获取和解析类修饰符的一个例子:

public static void main(String ...args) {
    Class clazz = String.class;
    int modifiers = clazz.getModifiers();
    System.out.println(Modifier.toString(modifiers));

    System.out.printf("%-10s: %s\n", "public", Modifier.isPublic(modifiers));
    System.out.printf("%-10s: %s\n", "abstract", Modifier.isAbstract(modifiers));
    System.out.printf("%-10s: %s\n", "final", Modifier.isFinal(modifiers));
}

输出结果:

$ java Main
public final
public    : true
abstract  : false
final     : true

INTERFACE修饰符

上面表格中有一个 INTERFACE 修饰符比较特殊,在Java语法层面好像并没有这个修饰符,只有一个声明接口的关键字 interface 。如果 Class 对象表示的是一个接口的类对象,那么类的修饰符掩码值中的interface和abstract掩码位都会被设置:

public static void main(String ...args) {
    int modifiers = Serializable.class.getModifiers();
    System.out.println(Modifier.toString(modifiers));

    System.out.printf("%-10s: %s\n", "public", Modifier.isPublic(modifiers));
    System.out.printf("%-10s: %s\n", "abstract", Modifier.isAbstract(modifiers));
    System.out.printf("%-10s: %s\n", "interface", Modifier.isInterface(modifiers));
}

输出结果:

$ java Main
public abstract interface
public    : true
abstract  : true
interface : true

获取父类的类对象

getSuperClass()

Class 类中有一个getSuperClass()方法可以获取类的父类 Class 对象。如果当前类没有父类,则返回 null 。如果是数组类型,那么getSuperClass()方法返回 java.lang.Object 的类对象。

下面以java.lang.Integer为例看下如何获取它的父类的 Class 对象:

// java.lang.Integer的定义
public final class Integer extends Number implements Comparable<Integer> {
  // ....
}
public static void main(String ...args) {
    Class clazz = Integer[].class.getSuperclass();
    if (clazz != null) {
        System.out.println(clazz.getName());
    }

    clazz = Integer.class.getSuperclass();
    if (clazz != null) {
        System.out.println(clazz.getName());
    }
}

输出结果:

$ java Main
java.lang.Object
java.lang.Number

可以看到,String[]的父类 Class 对象是 java.lang.ObjectClass 对象。而 Integer 的父类是 Number,所以对应的父类 Class 对象是java.lang.NumberClass 对象。

获取实现的接口类对象

getInterfaces()

Class 类中有一个getInterfaces()方法可以获取当前类所有实现的Interface的 Class 对象。Java支持实现多个接口,所以getInterfaces()返回的是一个 Class 对象数组,数组中接口类对象的顺序是定义这个类的时候接口的声明顺序。比如,一个类的声明如下:public class ClassA implements InterfaceA, InterfaceB {},那么通过getInterfaces()方法返回的数组中,getInterfaces()[0]对应的是InterfaceA的 Class 对象,getInterfaces()[1]对应的是InterfaceB的 Class 对象。

如果当前的 Class 对象是一个接口的 Class 对象,那么getInterfaces()方法返回的是这个接口通过 extends 继承的所有接口的列表,数组中类对象的顺序是继承(extends)接口的时候接口的声明顺序。

如果类没有实现任何接口或者类对象表示的是一个原始类型(primitive type)或 void 类型的类对象,那么getInterfaces()返回一个长度为0的空数组。如果类对象是一个数组的类对象,那么该方法返回的是一个包含了java.lang.Cloneablejava.io.Serializable这两个类对象的数组。

// InterfaceA
public interface InterfaceA {
}

// InterfaceB
public interface InterfaceB extends InterfaceA {
}

// Main
public class Main implements InterfaceA, InterfaceB {
    public static void main(String ...args) {
        Integer[] intArray = new Integer[1];

        Class[] interfaceClazz = intArray.getClass().getInterfaces();
        System.out.println("Array interface: " + interfaceClazz.length);

        for (int i = 0; i < interfaceClazz.length; i++) {
            System.out.printf("    [%d]: %s\n", i, interfaceClazz[i].getName());
        }

        interfaceClazz = Main.class.getInterfaces();
        System.out.println("\nMain implements interface: " + interfaceClazz.length);
        for (int i = 0; i < interfaceClazz.length; i++) {
            System.out.printf("    [%d]: %s\n", i, interfaceClazz[i].getName());
        }

        interfaceClazz = InterfaceB.class.getInterfaces();
        System.out.println("\nInterfaceB extends interface: " + interfaceClazz.length);
        for (int i = 0; i < interfaceClazz.length; i++) {
            System.out.printf("    [%d]: %s\n", i, interfaceClazz[i].getName());
        }
    }
}

输出结果:

$ java Main
Array interface: 2
    [0]: java.lang.Cloneable
    [1]: java.io.Serializable

Main implements interface: 2
    [0]: org.foo.InterfaceA
    [1]: org.foo.InterfaceB

InterfaceB extends interface: 1
    [0]: org.foo.InterfaceA

获取成员类的类对象

方法Class.getClasses()Class.getDeclaredClasses()可以获取一个类中定义的成员类的类对象。

getClasses()

getClasses()方法返回当前类以及在该类的继承体系中所有父类中声明为 public 的成员类、接口和枚举的类对象。如果当前的类对象是原始类型或数组类型的类对象,那么该方法返回长度为0的类对象数组。

// SuperClassA
package org.foo;

public class SuperClassA {
    public class PublicInnerClass {}
    public interface PublicInnerInterface {}
    public enum PublicInnerEnum {}
    protected class ProtectedInnerClass {}
    protected interface ProtectedInnerInterface {}
    protected enum ProtectedInnerEnum {}
    class InnerClass {}
    interface InnerInterface {}
    enum InnerEnum {}
    private class PrivateInnerClass {}
    private interface PrivateInnerInterface {}
    private enum PrivateInnerEnum {}
}

// Main
package org.foo;

public class Main extends SuperClassA {
    public static void main(String ...args) {
        Class[] clazzArray = Main.class.getClasses();

        System.out.println("Member class: ");
        for (Class clazz : clazzArray) {
            System.out.printf("    %s\n", clazz.getName());
        }
    }

    public class PublicInnerClass {}
    public interface PublicInnerInterface {}
    public enum PublicInnerEnum {}
    protected class ProtectedInnerClass {}
    protected interface ProtectedInnerInterface {}
    protected enum ProtectedInnerEnum {}
    class InnerClass {}
    interface InnerInterface {}
    enum InnerEnum {}
    private class PrivateInnerClass {}
    private interface PrivateInnerInterface {}
    private enum PrivateInnerEnum {}
}

输出结果:

$ java Main
Member class: 
  org.foo.Main$PublicInnerEnum
  org.foo.Main$PublicInnerInterface
  org.foo.Main$PublicInnerClass
  org.foo.SuperClassA$PublicInnerEnum
  org.foo.SuperClassA$PublicInnerInterface
  org.foo.SuperClassA$PublicInnerClass

getDeclaredClasses()

getDeclaredClasses()方法返回当前类中声明的所有成员类、接口和枚举的类对象。

// SuperClassA
package org.foo;

public class SuperClassA {
    public class PublicInnerClass {}
    public interface PublicInnerInterface {}
    public enum PublicInnerEnum {}
    protected class ProtectedInnerClass {}
    protected interface ProtectedInnerInterface {}
    protected enum ProtectedInnerEnum {}
    class InnerClass {}
    interface InnerInterface {}
    enum InnerEnum {}
    private class PrivateInnerClass {}
    private interface PrivateInnerInterface {}
    private enum PrivateInnerEnum {}
}

// Main
package org.foo;

public class Main extends SuperClassA {
    public static void main(String ...args) {
        Class[] clazzArray = Main.class.getDeclaredClasses();

        System.out.println("Member class: ");
        for (Class clazz : clazzArray) {
            System.out.printf("    %s\n", clazz.getName());
        }
    }

    public class PublicInnerClass {}
    public interface PublicInnerInterface {}
    public enum PublicInnerEnum {}
    protected class ProtectedInnerClass {}
    protected interface ProtectedInnerInterface {}
    protected enum ProtectedInnerEnum {}
    class InnerClass {}
    interface InnerInterface {}
    enum InnerEnum {}
    private class PrivateInnerClass {}
    private interface PrivateInnerInterface {}
    private enum PrivateInnerEnum {}
}

输出结果:

$ java Main
Member class: 
  org.foo.Main$PrivateInnerEnum
  org.foo.Main$PrivateInnerInterface
  org.foo.Main$PrivateInnerClass
  org.foo.Main$InnerEnum
  org.foo.Main$InnerInterface
  org.foo.Main$InnerClass
  org.foo.Main$ProtectedInnerEnum
  org.foo.Main$ProtectedInnerInterface
  org.foo.Main$ProtectedInnerClass
  org.foo.Main$PublicInnerEnum
  org.foo.Main$PublicInnerInterface
  org.foo.Main$PublicInnerClass

成员类获取声明它的类对象

getDeclaringClass()

上面的 获取成员类的类对象 这一节中介绍了如何获取声明的成员类的 Class 对象。反过来,如果类A里面有一个成员类B,运用反射机制,我们可以通过成员类B知道是哪个类定义了B。在 Class 对象中有一个getDeclaringClass()方法可以实现反向查询。下面通过例子了解下这个方法的作用:

package org.foo;

public class Main {
    public static void main(String ...args) {
        Class clazz = InnerClass.class.getDeclaringClass();
        System.out.println(clazz.getName());
    }

    private class InnerClass {}
}

输出结果:

$ java Main
org.foo.Main

可以看到,通过在 Main 这个类中声明的 InnerClass 内部类,调用getDeclaringClass()方法可以拿到 Main 这个类的 Class 对象。

获取直接外围类对象

方法Class.getEnclosingClass()的作用和前面提到的Class.getDeclaringClass()方法类似。区别是:对于内部匿名类,通过getDeclaringClass()方法获取不到定义这个匿名类的那个类的类对象,而通过getEnclosingClass()方法可以获取定义这个匿名类的那个类的类对象。

通过一个例子来看下区别:

package org.foo;

public class Main {
    public static void main(String ...args) {
        System.out.println("InnerClass: ");
        System.out.println("    getDeclaringClass(): " + InnerClass.class.getDeclaringClass());
        System.out.println("    getEnclosingClass(): " + InnerClass.class.getEnclosingClass());

        Runnable runnable = new Runnable() {
            @Override
            public void run() {

            }
        };

        System.out.println("\nInner Anonymous Class: ");
        System.out.println("    getDeclaringClass(): " + runnable.getClass().getDeclaringClass());
        System.out.println("    getEnclosingClass(): " + runnable.getClass().getEnclosingClass());
    }

    public class InnerClass {}
}

输出结果:

$ java Main
InnerClass: 
  getDeclaringClass(): class org.foo.Main
  getEnclosingClass(): class org.foo.Main

Inner Anonymous Class: 
  getDeclaringClass(): null
  getEnclosingClass(): class org.foo.Main

可以看到,对于匿名内部类getDeclaringClass()这个方法取不到类对象,所以返回了 null 值。

TOP