java基础知识——反射

…衆ロ難τιáo~ 提交于 2020-02-15 02:14:16

反射原理

除了int,float等基本类型外,Java的其他类型全部都是class(包括interface),即class的本质是数据类型(Type)。而class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时(也是第一次要使用时才读取),将其加载进内存。每加载一种class,JVM就为其创建一个Class类型的实例(例如:Object 类中的 getClass( ) 方法就将返回一个 Class 类型的实例),并关联起来。注意:这里的Class类型是一个名叫Class的class。源码如下:

public final class Class {
    private Class() {}
}

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

Class cls = new Class(String);

这个Class实例是JVM内部创建的,我们自己的Java程序是无法创建Class实例的,因为Class类的构造方法是private,所以,JVM持有的每个Class实例都指向一个数据类型(类或interface)
在这里插入图片描述
并且,一个Class实例包含了该class的所有完整信息:
在这里插入图片描述
由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等。因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。这种通过Class实例获取class信息的方法就称之为反射(Reflection)。

获取Class实例

那如何获取一个class的Class实例呢?有如下三个方法:

方法一

直接通过一个class的静态变量".class"获取:

Class cls = String.class;
方法二

如果我们有一个实例变量(对象),可以通过该实例变量提供的getClass()方法获取:

Object o = new Object();
Class cls = o.getClass();
方法三

如果知道一个class的完整类名,可以通过静态方法Class.forName()方法获取:

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

注意四点:

  • 因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例,可以用==比较。
  • 一个 Class 对象实际上表示的是一个类型,而这个类型未必一定是一种类。例如, int 不是类,但 int.class 是一个 Class 类型的对象(基础类型都是如此)。此外String[].class这类数组的类是[Ljava.lang.String,多了一个"[L",这是java的历史遗留造成的,会返回一个奇怪的名称
  • instanceof与Class实例比较的区别:instanceof不但匹配指定类型,还匹配指定类型的子类。而用==判断class实例可以精确地判断数据类型,但不能作子类型比较。
  • JVM在第一次读取到一种class类型时(也是第一次要使用时才读取),才将其加载进内存,这就是JVM动态加载class的特性,此特性对于Java程序非常重要。你可以在运行期间根据条件来控制加载哪一个class,比如用Class.forName(name)没找到该类,就加载另一个什么的。

简单应用

public static void main(String[] args) throws ClassNotFoundException {
     
    //用class的静态变量,获取Class实例
    Class stringClass = String.class;
    printClassInfo(stringClass);
        
    //用实例对象提供的getClass()方法,获取Class实例
    Object object = new Object();
    Class objectClass = object.getClass();
    printClassInfo(objectClass);
        
    //用静态方法Class.forName()获取Class实例
    Class runnableClass = Class.forName("java.lang.Runnable");
    printClassInfo(runnableClass);
}
    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());
}

访问对象内容

上一节中主要讲了怎么在类的角度使用反射,那么站在对象的角度,反射又能做什么呢?
对任意的一个Object对象,只要我们获取了它的Class,就可以获取它的一切信息,包括私有变量与方法。

获取对象字段

会用到以下几个Class类的方法,(这些不是Field类的方法,只是返回Field类的对象而已,getName(),getType(),getModifiers()才是)

方法 用途
getField(String name) 根据字段名获取某个public的field(包括父类)
getDeclaredField(String name) 根据字段名获取当前类的某个field(不包括父类)
getFields() 获取所有public的field(包括父类)
getDeclaredFields() 获取当前类的所有field(不包括父类)

利用反射拿到字段的一个Field实例只是第一步,我们还可以拿到一个实例对应的该字段的值。
例如,对于一个Person实例,我们可以先拿到name字段对应的Field,再获取这个实例的name字段的值:

public static void main(String[] args) throws Exception {
    Object p = new Person("Xiao Ming");
    //获取Person的Class实例
    Class c = p.getClass();
    //获取Class实例即Person类的字段信息
    Field f = c.getDeclaredField("name");
    //获取p对象的字段值内容,用Field的field.get(Object)方法
    Object value = f.get(p);
    System.out.println(value); // "Xiao Ming"
}
    static class Person {
    private String name;
    public Person(String name) {
        this.name = name;
    }

正常情况下,我们已经获取到值了,但是这里我们会报一个“java.lang.IllegalAccessException”错误,这是因为name被定义为一个private字段,正常情况下,Main类无法访问Person类的private字段。要修复错误,可以将private改为public,或者,在调用Object value = f.get ( p );前,先加一句:

//无论是否是public权限,都可以访问其值
field.setAccessible(true);

当然可以get()值,自然Field类也可以设置值

//第一个Object参数为指定实例,第二个为修改值
//如果是非public值,也要设置field.setAccessible(true)
field.set(Object, Object)

这里已经很明显可以看出,封装性被破坏了,那封装还有什么意义呢?
答案是:正常情况下我们总是通过p.name来访问Person的name字段,有权限限制,这样就达到了数据封装的目的,不会用到反射。反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,反射更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值,但是如果想进阶,反射是一定要会的,此外,setAccessible(true)也可能会失败,如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。

获取对象方法

属性能获取,自然方法也能获取,Class类提供了以下几个方法来获取Method,Method对象也提供了一些方法,例如:getName()、getReturnType()、getParameterTypes()、getModifiers()

方法 用途
getMethod(String name, Class…<?> parameterTypes) 获取某个public的Method(包括父类)
getDeclaredMethod(String name, Class…<?> parameterTypes) 获取当前类的某个Method(不包括父类)
getMethods() 获取所有public的Method(包括父类)
getDeclaredMethods() 获取当前类的所有Method(不包括父类)

以下代码可以反射调用String类的substring(int a)方法

public static void main(String[] args) throws Exception {
    //String对象
    String string = "Hello World!";
    //String的Class类实例
    Class stringClass = String.class;
    //获取String类的substring(int)方法,参数为int.class,IDE还会提示你选哪个方法与参数
    Method substringMethod = stringClass.getMethod("substring", int.class);
    //在string对象上调用该方法并获取结果
    String r = (String) substringMethod.invoke(string, 6);
    System.out.println(r);
}

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

// 获取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)允许其调用,代码用下

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;
    }
}

此外,setAccessible(true)也可能会失败,原因和Field一样,SecurityManager会根据规则进行检查,有可能阻止setAccessible(true)。

多态下调用方法

一个Person类定义了hello()方法,并且它的子类Student也覆写了hello()方法,那么,从Person.class获取的Method,作用于Student实例时,调用的方法到底是哪个?
使用子类的方法,反射调用方法时,仍然遵循多态原则:即总是调用实际类型的覆写方法(如果存在)。

调用构造方法

方法 用途
getConstructor(Class…<?> parameterTypes) 获得该类中与参数类型匹配的公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes) 获得该类中与参数类型匹配的构造方法
getConstructors() 获得该类的所有公有构造方法
getDeclaredConstructors() 获得该类所有构造方法

通过反射来创建新的实例,可以调用Class提供的newInstance()方法:

Person p = Person.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。
为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似,不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例:

public static void main(String[] args) throws Exception {
    // 获取构造方法Integer(int):
    Constructor cons = Integer.class.getConstructor(int.class);
    // 调用构造方法:
    Integer n = (Integer) cons.newInstance(123);
    System.out.println(n);
}

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。
调用非public的Constructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

获取继承关系

通过Class对象可以获取继承关系,即它的父类与接口

方法 用途
getSuperclass() 获得当前类继承的父类的名字
getInterfaces() 获得当前类实现的类或是接口

注意:

  • 除Object外,其他任何非interface的Class都必定存在一个父类类型。对Object类使用getSuperclass()方法,会返回null
  • 对所有interface的Class调用getSuperclass()返回的是null,获取接口的父接口要用getInterfaces(),如果一个类没有实现任何interface,那么getInterfaces()返回空数组。
简单应用
public static void main(String[] args) {
        
        Class i = Integer.class;
        Class n = i.getSuperclass();
        System.out.println(n); //class java.lang.Number

        Class[] is = i.getInterfaces();
        for (Class s : is) {
            System.out.println(s); //返回三个接口分别是Comparable,Constable,ConstantDesc
        }

        //对接口调用getSuperclass()总是返回null,获取接口的父接口要用getInterfaces()
        System.out.println(java.io.Closeable.class.getSuperclass()); //null
        Class[] closeableSuper = java.io.Closeable.class.getInterfaces();
        for (Class cs : closeableSuper) {
            System.out.println(cs); //interface java.lang.AutoCloseable
        }
    }

反射相关类与常用方法

以下是四种反射相关类,与常用方法,上文使用的方法,都可以查的到

类名 用途
Class类 代表类的实体,在运行的Java应用程序中表示类和接口
Field类 代表类的成员变量(成员变量也称为类的属性)
Method类 代表类的方法
Constructor类 代表类的构造方法

Class类

获取类相关的方法
方法 用途
asSubclass(Class<U> clazz ) 把传递的类的对象转换成代表其子类的对象
Cast 把对象转换成代表类或是接口的对象
getClassLoader() 获得类的加载器
getClasses() 返回一个数组,数组中包含该类中所有公共类和接口类的对象
getDeclaredClasses() 返回一个数组,数组中包含该类中所有类和接口类的对象
forName(String className) 根据类名返回类的Class实例
getName() 获得类的完整路径名字
newInstance() 创建类的实例
getPackage() 获得类的包
getSimpleName() 获得类的名字
getSuperclass() 获得当前类继承的父类的名字
getInterfaces() 获得当前类实现的类或是接口

注:带有Declared修饰的方法可以反射到私有的方法,没有Declared修饰的只能用来反射公有的方法。其他的Annotation、Field、Constructor也是如此。

获取类中属性相关的方法
方法 用途
getField(String name) 根据字段名获取某个public的field(包括父类)
getDeclaredField(String name) 根据字段名获取当前类的某个field(不包括父类)
getFields() 获取所有public的field(包括父类)
getDeclaredFields() 获取当前类的所有field(不包括父类)
获得类中方法相关的方法
方法 用途
getMethod(String name, Class…<?> parameterTypes) 获取某个public的Method(包括父类)
getDeclaredMethod(String name, Class…<?> parameterTypes) 获取当前类的某个Method(不包括父类)
getMethods() 获取所有public的Method(包括父类)
getDeclaredMethods() 获取当前类的所有Method(不包括父类)
获得类中构造器相关的方法
方法 用途
getConstructor(Class…<?> parameterTypes) 获得该类中与参数类型匹配的公有构造方法
getDeclaredConstructor(Class…<?> parameterTypes) 获得该类中与参数类型匹配的构造方法
getConstructors() 获得该类的所有公有构造方法
getDeclaredConstructors() 获得该类所有构造方法
获取类中注解相关的方法
方法 用途
getAnnotation(Class<A> annotationClass) 返回该类中与参数类型匹配的公有注解对象
getDeclaredAnnotation(Class<A> annotationClass) 返回该类中与参数类型匹配的所有注解对象
getAnnotations() 返回该类所有的公有注解对象
getDeclaredAnnotations() 返回该类所有的注解对象
类中其他重要的方法
方法 用途
isAnnotation() 如果是注解类型则返回true
isAnnotationPresent(Class<? extends Annotation> annotationClass) 如果是指定类型注解类型则返回true
isAnonymousClass() 如果是匿名类则返回true
isArray() 如果是一个数组类则返回true
isEnum() 如果是枚举类则返回true
isInstance(Object obj) 如果obj是该类的实例则返回true
isInterface() 如果是接口类则返回true
isLocalClass() 如果是局部类则返回true
isMemberClass() 如果是内部类则返回true

Field类

Field代表类的成员变量(成员变量也称为类的属性)

方法 用途
equals(Object obj) 属性与obj相等则返回true
get(Object obj) 获得obj中对应的属性值
set(Object obj, Object value) 设置obj中对应属性值

Method类

Method代表类的方法

方法 用途
invoke(Object obj, Object… args) 传递object对象及参数调用该对象对应的方法

Constructor类

Constructor代表类的构造方法

方法 用途
newInstance(Object… initargs) 根据传递的参数创建类的对象
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!