反射

何为反射

反射就是Reflection,Java的反射是指程序在运行期可以拿到一个对象的所有信息。即Java反射机制是在运行状态时,对于任意一个类,都能够获取到这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性(包括私有的方法和属性),这种动态获取的信息以及动态调用对象的方法的功能就称为java语言的反射机制。

class(包括interface)的本质是数据类型(Type

class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。

每加载一种class,JVM就为其创建一个Class类型的实例,并关联起来。注意:这里的Class类型是一个名叫Classclass。它长这样:

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

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

Class cls = new Class(String);

这个Class实例是JVM内部创建的,如果我们查看JDK源码,可以发现Class类的构造方法是private,只有JVM能创建Class实例,我们自己的Java程序是无法创建Class实例的。

所以,JVM持有的每个Class实例都指向一个数据类型(classinterface

由于JVM为每个加载的class创建了对应的Class实例,并在实例中保存了该class的所有信息,包括类名、包名、父类、实现的接口、所有方法、字段等,因此,如果获取了某个Class实例,我们就可以通过这个Class实例获取到该实例对应的class的所有信息。

这种通过Class实例获取class信息的方法称为反射(Reflection)。

获取class的Class实例

获取一个classClass实例,有4个方法:

方法一

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

Class cls = String.class;

方法二

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

String s = "Hello";
Class cls = s.getClass();

方法三

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

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

方法四

利用classLoader

Class cls = ClassLoader.getSystemClassLoader().loadClass("java.lang.Runtime")

比较

因为Class实例在JVM中是唯一的,所以,上述方法获取的Class实例是同一个实例。可以用==比较两个Class实例:

Class cls1 = String.class;

String s = "Hello";
Class cls2 = s.getClass();

boolean sameClass = cls1 == cls2; // true

获取基本信息

获取class的基本信息

package org.example;

import java.util.ArrayList;

public class App{
    public static void main(String[] args) {
        Class<String> cls1 = String.class;

        ArrayList a = new ArrayList();
        Class cls2 = a.getClass();

        printInfo(cls1);
        printInfo(cls2);
    }

    static void printInfo(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());
    } }

小结

  1. JVM为每个加载的classinterface创建了对应的Class实例来保存classinterface的所有信息;
  2. 获取一个class对应的Class实例后,就可以获取该class的所有信息;
  3. 通过Class实例获取class信息的方法称为反射(Reflection);
  4. JVM总是动态加载class,可以在运行期根据条件来控制加载class。

访问字段

对任意的一个Object实例,只要我们获取了它的Class,就可以获取它的一切信息。

我们先看看如何通过Class实例获取字段信息。

获取字段的一些信息

import java.util.Arrays;

public class Test{
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Class ot = OtherTest.class;
        System.out.println(Arrays.toString(ot.getFields()));    // 获取所有public的field(包括父类)
        System.out.println(Arrays.toString(ot.getDeclaredFields()));    // 获取当前类的所有field(不包括父类)
        System.out.println(ot.getField("a"));   // 根据字段名获取某个 public 的field(包括父类)
        System.out.println(ot.getDeclaredField("b")); // 根据字段名获取当前类的某个field(不包括父类)

        System.out.println(ot.getField("a").getName()); // 字段名称
        System.out.println(ot.getField("a").getType()); // 字段类型,也是一个Class实例
        System.out.println(ot.getField("a").getModifiers()); // 修饰符
    }
}

class OtherTest extends emmTest{
    public int a = 5;
    private int b;
}

class emmTest {
    public float cc;
}
[public int OtherTest.a, public float emmTest.cc]
[public int OtherTest.a, private int OtherTest.b]
public int OtherTest.a
private int OtherTest.b
a
int
1

获取字段的值

先获取Class实例,再获取Field实例,然后,用Field.get(Object)获取指定实例的指定字段的值。

package org.example;


import java.lang.reflect.Field;

public class App {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        OtherTest ot = new OtherTest("haha");

        Class cls = ot.getClass();
        Field f = cls.getDeclaredField("name");
        f.setAccessible(true);  // 设置访问权限,一律为true,不然不能访问 private 的
        Object value = f.get(ot);   // 从对象ot中获取值,因为所有的同类型class共用一个Class,所以获取内容要选定对象
        System.out.println(value);
    }
}

class OtherTest {
    private String name;

    public OtherTest(String name) {
        this.name = name;
    }
}

// 输出 haha

反射是一种非常规的用法,使用反射,首先代码非常繁琐,其次,它更多地是给工具或者底层框架来使用,目的是在不知道目标实例任何信息的情况下,获取特定字段的值。

此外,setAccessible(true)可能会失败。如果JVM运行期存在SecurityManager,那么它会根据规则进行检查,有可能阻止setAccessible(true)。例如,某个SecurityManager可能不允许对javajavax开头的package的类调用setAccessible(true),这样可以保证JVM核心库的安全。

修改字段的值

package org.example;


import java.lang.reflect.Field;

public class App {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        OtherTest ot = new OtherTest("haha");

        Class cls = ot.getClass();
        Field f = cls.getDeclaredField("name");
        f.setAccessible(true);  // 设置访问权限,一律为true,不然不能访问 private 的
        f.set(ot, "modify");    // 反射修改值

        System.out.println(ot.getName());
    }
}

class OtherTest {
    private String name;

    public OtherTest(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}

小结

Java的反射API提供的Field类封装了字段的所有信息:

  1. 通过Class实例的方法可以获取Field实例:getField()getFields()getDeclaredField()getDeclaredFields()
  2. 通过Field实例可以获取字段信息:getName()getType()getModifiers()
  3. 通过Field实例可以读取或设置某个对象的字段,如果存在访问限制,要首先调用setAccessible(true)来访问非public字段。
  4. 通过反射读写字段是一种非常规方法,它会破坏对象的封装。

调用方法(‼️)

获取方法

通过Class实例获取所有Method信息。Class类提供了以下几个方法来获取Method

import java.util.Arrays;

public class Test{
    public static void main(String[] args) throws NoSuchMethodException {
        Class<OtherTest> cls = OtherTest.class; // Class cls = ot.getClass();
        System.out.println(Arrays.toString(cls.getMethods()));  // 获取所有public的Method(包括父类)
        System.out.println(Arrays.toString(cls.getDeclaredMethods()));  // 获取当前类的所有Method(不包括父类)
        System.out.println(cls.getMethod("echoEver", String.class));    // 获取某个public的Method(包括父类) //.getMethod(方法名,这个方法的参数类型)
        System.out.println(cls.getDeclaredMethod("echoEver", String.class));    // 获取当前类的某个Method(不包括父类)
    }
}

class OtherTest{
    public void echoEver(String thing){
        System.out.println(thing);
    }
}

// =====

/*
[public void org.example.OtherTest.echoEver(java.lang.String), public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException, public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException, public final void java.lang.Object.wait() throws java.lang.InterruptedException, public boolean java.lang.Object.equals(java.lang.Object), public java.lang.String java.lang.Object.toString(), public native int java.lang.Object.hashCode(), public final native java.lang.Class java.lang.Object.getClass(), public final native void java.lang.Object.notify(), public final native void java.lang.Object.notifyAll()]

[public void org.example.OtherTest.echoEver(java.lang.String)]

public void org.example.OtherTest.echoEver(java.lang.String)

public void org.example.OtherTest.echoEver(java.lang.String)
*/

调用方法

  • 获取Class实例
  • 反射获取方法
  • invoke调用方法
package org.example;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class App {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        OtherTest ot = new OtherTest();
        Class cls = ot.getClass();
        Method echoEver = cls.getDeclaredMethod("echoEver", String.class);
        echoEver.setAccessible(true);
        echoEver.invoke(ot,"test");    // 第一个参数是调用该方法的对象,第二个参数是一个可变长参数,是这个方法的需要传入的参数

    }
}

class OtherTest{
    private void echoEver(String thing){
        System.out.println(thing);
    }
}

小结

Java的反射API提供的Method对象封装了方法的所有信息:

  1. 通过Class实例的方法可以获取Method实例:getMethod()getMethods()getDeclaredMethod()getDeclaredMethods()
  2. 通过Method实例可以获取方法信息:getName()getReturnType()getParameterTypes()getModifiers()
  3. 通过Method实例可以调用某个对象的方法:Object invoke(Object instance, Object... parameters)
  4. 通过设置setAccessible(true)来访问非public方法;
  5. 通过反射调用方法时,仍然遵循多态原则。

调用构造方法

举例

我们通常使用new操作符创建新的实例:

Person p = new Person();

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

Person p = Person.class.newInstance();

调用Class.newInstance()的局限是,它只能调用该类的public无参数构造方法。如果构造方法带有参数,或者不是public,就无法直接通过Class.newInstance()来调用。


为了调用任意的构造方法,Java的反射API提供了Constructor对象,它包含一个构造方法的所有信息,可以创建一个实例。Constructor对象和Method非常类似不同之处仅在于它是一个构造方法,并且,调用结果总是返回实例

package org.example;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

public class App {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class<Integer> cls = Integer.class;
        System.out.println(cls.getName());
        System.out.println(Arrays.toString(cls.getConstructors()));

        // Integer.class.getConstructor(int.class);
        Constructor<Integer> cons1 = cls.getConstructor(int.class);
        Integer int1 = cons1.newInstance(123);
        System.out.println(int1);

        Constructor<Integer> cons2 = cls.getConstructor(String.class);
        System.out.println(cons2.newInstance("456"));
    }
}


/*
java.lang.Integer
[public java.lang.Integer(int), public java.lang.Integer(java.lang.String) throws java.lang.NumberFormatException]
123
456
*/

通过Class实例获取Constructor的方法如下:

  • getConstructor(Class...):获取某个publicConstructor
  • getDeclaredConstructor(Class...):获取某个Constructor
  • getConstructors():获取所有publicConstructor
  • getDeclaredConstructors():获取所有Constructor

注意Constructor总是当前类定义的构造方法,和父类无关,因此不存在多态的问题。

调用非publicConstructor时,必须首先通过setAccessible(true)设置允许访问。setAccessible(true)可能会失败。

小结

Constructor对象封装了构造方法的所有信息;

  1. 通过Class实例的方法可以获取Constructor实例:getConstructor()getConstructors()getDeclaredConstructor()getDeclaredConstructors()

  2. 通过Constructor实例可以创建一个实例对象:newInstance(Object... parameters); 通过设置setAccessible(true)来访问非public构造方法。

获取继承关系

获取父类class和interface

import java.util.Arrays;

public class Test{
    public static void main(String[] args) {
        OtherTest ot = new OtherTest("emm");
        Class cls = ot.getClass();
        System.out.println(cls.getSuperclass());    // 获取父类class
        System.out.println(Arrays.toString(cls.getInterfaces()));   // 获取接口

        System.out.println("".getClass().getSuperclass());  // 获取 String 的父类
    }
}

class OtherTest extends Emmm implements Aaa{
    private String name;
    public OtherTest(String name){
        this.name = name;
    }

    @Override
    public void echo() {
        System.out.println("666");
    }
}

class Emmm {
    private int aa;
}

interface Aaa{
    public void echo();
}


/*
class org.example.Emmm
[interface org.example.Aaa]
class java.lang.Object
*/

小结

通过Class对象可以获取继承关系:

  • Class getSuperclass():获取父类类型;
  • Class[] getInterfaces():获取当前类实现的所有接口。

  • 通过Class对象的isAssignableFrom()方法可以判断一个向上转型是否可以实现。

动态代理

有没有可能不编写实现类,直接在运行期创建某个interface的实例呢?

这是可能的,因为Java标准库提供了一种动态代理(Dynamic Proxy)的机制:可以在运行期动态创建某个interface的实例。

所谓动态代理,是和静态相对应的。我们来看静态代码怎么写:

// 创建接口
public interface Hello {
    void morning(String name);
}

// 实现接口Hello
public class HelloWorld implements Hello {
    public void morning(String name) {
        System.out.println("Good morning, " + name);
    }
}

// 创建实例,调用
public static void main(String[] args) {
    Hello hello = new HelloWorld();
    hello.morning("Bob");
}

动态如下 过程 ,不需要单独实现接口,而是动态实现接口。

过程

在运行期动态创建一个interface实例的方法如下:

  1. 定义一个InvocationHandler实例,它负责实现接口的方法调用
  2. 通过Proxy.newProxyInstance()创建interface实例,它需要3个参数:
    1. 使用的ClassLoader,通常就是接口类的ClassLoader
    2. 需要实现的接口数组,至少需要传入一个接口进去;
    3. 用来处理接口方法调用的InvocationHandler实例。
  3. 将返回的Object强制转型为接口。
package org.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class App{
    public static void main(String[] args) {
        InvocationHandler handler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println(method);
                System.out.println(args.length);
                // 实现对应的方法
                if (method.getName().equals("echo")){
                    System.out.println(args[0]);
                }
                return null;
            }
        };

        Hello hello = (Hello) Proxy.newProxyInstance(Hello.class.getClassLoader(), new Class[]{Hello.class}, handler);
        hello.echo("9999");
    }
}


interface Hello{
    public void echo(String s);
}

/*
1
9999
*/

小结

Java标准库提供了动态代理功能,允许在运行期动态创建一个接口的实例;

动态代理是通过Proxy创建代理对象,然后将接口方法“代理”给InvocationHandler完成的。

Copyright © d4m1ts 2023 all right reserved,powered by Gitbook该文章修订时间: 2021-12-25 18:51:59

results matching ""

    No results matching ""