跳转至
阅读量:

Java学习之路——反射

概述

定义

反射(Reflection)被视为动态语言的关键,通过反射机制可以运行程序在运行期间 获取任何对象的所有信息,并且能够直接操作对象的属性与方法。

Java 反射机制可以动态地创建对象并调用其属性,这样的对象的类型在编译期是未知的。所以我们可以通过反射机制直接创建对象,即使这个对象的类型在编译期是未知的。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性,它不需要事先(写代码的时候或编译期)知道运行对象是谁。

功能

Java 反射主要提供以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法(通过反射甚至可以调用private方法);
  • 在运行时调用任意一个对象的方法和属性;
  • 在运行时获取泛型的信息;
  • 在运行时处理注解;
  • 生成动态代理。

区别

通过反射构造对象与正常方式有着如下区别:

  • 正常情况下:
graph LR
  A[引入需要的类] --> B[通过 new 实例化]
  B --> C[获取实例对象]
  • 反射机制下:
graph LR
  A[实例化对象] --> B[调用 getClass 方法]
  B --> C[得到完整的类]
  C --> D[构造进的实例对象]

一、Class 类

定义

在 Java 程序种除了八种基本类型外,其他类型全部都是class(包括interface)。而class是由JVM在执行过程中动态加载的。JVM在第一次读取到一种class类型时,将其加载进内存。

每加载一种class,JVM就为其创建一个Class(名叫 Class 的 类)类型的实例,并关联起来。

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

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

获取 Class 类

  • 方式一:通过类的 class 属性获取
Class clazz = String.class;
  • 方式二:过该实例变量提供的getClass()方法获取
String s = "Hello";
Class clazz = s.getClass();
  • 如果知道一个class的完整类名,可以通过静态方法Class.forName()获取
Class clazz = Class.forName("java.lang.String");

二、获取字段

Class类提供了以下几个方法来获取字段:

  • Field getField(name):根据字段名获取某个 public 的 field(包括父类);
  • Field getDeclaredField(name):根据字段名获取当前类的某个 field(不包括父类);
  • Field[] getFields():获取所有 public 的 field(包括父类);
  • Field[] getDeclaredFields():获取当前类的所有 field(不包括父类)。

三、调用方法

Class类提供了以下几个方法来获取Method

  • Method getMethod(name, Class...):获取某个publicMethod(包括父类);
  • Method getDeclaredMethod(name, Class...):获取当前类的某个Method(不包括父类);
  • Method[] getMethods():获取所有publicMethod(包括父类);
  • Method[] getDeclaredMethods():获取当前类的所有Method(不包括父类)。

四、调用构造器

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

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

五、获取继承关系

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

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

六、使用反射

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

public class Demo {
    public static void main(String[] args) throws Exception {
        // 构建 Person 实例
        Person p1 = new Person( "小明",15,60);
        System.out.println(p1);

        // 获取 Person 类结构
        Class clazz = Person.class;

        // 获取类构造器
        Constructor constructor = clazz.getConstructor(String.class, int.class, int.class);
        // 构造一个新的实例对象
        Object p2 = constructor.newInstance("小刚", 18, 70);
        System.out.println(p2);

        // 获取属性
        Field name = clazz.getDeclaredField("name");
        // 修改对象属性值
        name.set(p1, "小红");
        System.out.println(p1.name);

        // 获取方法
        Method sayName = clazz.getDeclaredMethod("sayName");
        // 调用对象的方法
        sayName.invoke(p1);

        // 获取私有构造器
        Constructor cons = clazz.getDeclaredConstructor(int.class);
        // 允许反射获取,获取私有结构时必须添加本行
        cons.setAccessible(true);
        Person p3 = (Person) cons.newInstance(50);
        System.out.println(p3);
    }
}


class Person {
    public String name;
    public int age;
    private final int weight;

    public Person(String name, int age, int weight) {
        this(weight);
        this.name = name;
        this.age = age;
    }

    private Person(int weight) {
        this.weight = weight;
    }

    public void sayName() {
        System.out.println(name);
    }

    public void setAge(int age) {
        this.age = age;
    }

    private void sayWeight() {
        System.out.println(weight);
    }

    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", weight=" + weight +
                '}';
    }
}

七、动态代理

我们知道,在 Java 程序中所有interface类型的变量总是通过向上转型并指向某个实例的。

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

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

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

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

public class Demo {
    public static void main(String[] args) throws Exception {
        InvocationHandler handler = (proxy , method , args1) -> {
            System.out.println(method);
            if (method.getName().equals("morning")) {
                System.out.println("Good morning, " + args1[0]);
            }
            return null;
        };

        Hello hello = (Hello) Proxy.newProxyInstance(
                Hello.class.getClassLoader(), // 传入ClassLoader
                new Class[] { Hello.class }, // 传入要实现的接口
                handler); // 传入处理调用方法的InvocationHandler
        hello.morning("Bob");
    }
}


interface Hello {
    void morning(String name);
}

评论