Java核心知识:反射

反射主要用来动态操纵java代码,能够动态查询类能力。

能够分析类能力的程序成为反射。

Class类

在程序运行期间,Java运行时系统始终为所有对象维护一个运行时类型标识。这个信息会跟踪每个对象所属的类。虚拟机利用运行时类型信息选择要执行的正确方法。

不过,在我们写代码时,可以 使用一个也是的Java类访问这些信息。保存这些信息的类名为Class。

获取Class对象有两种方法:

  • Object类的getClass()方法;
  • 静态方法forName(Sring name)获得类名name对应的Class对象。

通过class对象的getConstructor()方法可以获取类的构造器,通过构造器的newInstance()方法可以构造出对象。其中getConstructor方法会有受检异常抛出NoSuchMethodException异常,newInstance会抛出 InstantiationException, IllegalAccessException, IllegalArgumentException异常。

示例:

先创建两个实体类,一个员工类Employee,一个后端开发Backend继承Employee。

Employee

public class Employee {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

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

Backend

public class Backend extends Employee{
    private String Computer;

    public Backend() {
    }

    public Backend(String computer) {
        Computer = computer;
    }

    public String getComputer() {
        return Computer;
    }

    public void setComputer(String computer) {
        Computer = computer;
    }
}

测试类

import java.lang.reflect.InvocationTargetException;
import java.util.Random;

public class ReflectTest01 {
    public static void main(String[] args) throws Exception {
        Backend backend = new Backend();
        Class<? extends Backend> cl = backend.getClass();
        System.out.println(cl.getName());

        Random random = new Random();
        System.out.println(random.getClass().getName());

        // 还可以通过Class.forName获取类对象
        Class<?> backend1 = Class.forName("demo03.Backend");
        System.out.println(backend1.getName());

        // 虚拟机为每个类型管理一个唯一的Class对象。因此可以通过==比较两个类对象
        if (backend.getClass() == Backend.class) {
            System.out.println("backend is Backend instance");
        }

        // 通过类对象构造实例
        Class<?> random2 = Class.forName("java.util.Random");
        Object obj = random2.getConstructor().newInstance();
        System.out.println(obj);

    }
}

反射分析类的方法

使用反射机制,可以检查类的结构。
java.lang.reflect包中有FieldMethodConstructor三个类,分别用于描述类的字段、方法和构造器。

java.lang.Class对象有以下方法:

  1. Field[] getFields():返回一个包含Field对象的数组,这些对象对应这个类或其父类的公共字段。没有则返回空数组。
  2. Field[] getDeclaredFields():返回的是这个类的全部字段对象的Field数组。
  3. Method[] getMethods():返回一个包含Method对象的数组,这些对象对应这个类或其父类的公共方法。
  4. Method[] getDeclaredMethods():返回这个类或接口的全部方法,但是不包括由父类继承来的方法。
  5. Constructor[] getConstructors():返回一个包含Constructor对象的数组,包含所有公共构造器。
  6. Constructor[] getDeclaredConstructors():返回一个包含Constructor对象的数组,这个类的全部构造器。
  7. isInterface:如果这个Class对象描述一个interface,则返回true。
  8. isEnum:如果这个Class对象描述一个enum,则返回true。
  9. getPackageName:得到这个类的包的包名,如果这个类型时一个数组,则返回元素类型所属的包,如果这个类型时基本类型,则返回"java.lang"。

其中FieldMethodConstructor三个类的对象还可以做进一步分析,有如下方法:

  1. Class getDeclaringClass():返回一个Class对象,表示定义了这个构造器、方法或字段的类。
  2. String getName():返回构造器名、方法名或字段名。
  3. int getModifiers:返回一个整数,描述这个构造器、方法或字段的修饰符。进一步,可使用Modifier类的方法来分析这个返回值。
  4. Class[] getParameterTypes():构造器对象或方法对象使用,返回一个Class对象数组,其中各个对象表示参数的类型。
  5. Class[] getReturnTypes():方法对象使用,返回一个Class对象数组,其中各个对象表示返回值的类型。
  6. Class[] getExceptionTypes():构造器对象或方法对象使用,返回一个Class对象数组,其中各个对象表示返回值的类型。

上面已经讲到,Modifier类可以进一步分析,包含以下方法:

  1. static String toString(int Modifiers):返回一个字符串,包含tModifiers中设置的二进制位所对应的修饰符。
  2. static boolean isAbstract(int Modifiers)
  3. static boolean isFinal(int Modifiers)
  4. static boolean isInterface(int Modifiers)
  5. static boolean isPrivate(int Modifiers)
  6. static boolean isPublic(int Modifiers)
  7. static boolean isStatic(int Modifiers)
  8. static boolean isSynchronized(int Modifiers)

简单示例如下:

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;

public class ReflectionTest02 {

    public static void main(String[] args) throws ClassNotFoundException {

        Class<?> cl = Class.forName("demo03.Backend");
        String modifiers = Modifier.toString(cl.getModifiers());

        if (modifiers.length() > 0) System.out.println("modifiers:" + modifiers);

        if (cl.isEnum()) System.out.println("enum " + cl.getName());
        else if (cl.isInterface()) System.out.println("interface:" + cl.getName());
        else System.out.println("class:" + cl.getName());

        // 获取父类的Class对象
        Class<?> superclass = cl.getSuperclass();
        if (superclass != null && superclass != Object.class) {
            System.out.println("super class:" + superclass.getName());
        }

        // 分析constructor
        printConstructors(cl);

        // 分析fields
        printFields(cl);
    }

    public static void printConstructors(Class cl) {
        System.out.println("===== constructor");
        Constructor[] constructors = cl.getDeclaredConstructors();
        for (Constructor c : constructors) {
            String name = c.getName();
            System.out.println("constructor name:" + name);

            String modifiers = Modifier.toString(c.getModifiers());
            System.out.println("modifiers:"+ modifiers);

            // 参数类型
            Class[] parameterTypes = c.getParameterTypes();
            for (Class parameterType : parameterTypes) {
                System.out.println(parameterType.getName());
            }
        }
    }

    public static void printFields(Class cl) {
        System.out.println("===== fields");
        Field[] declaredFields = cl.getDeclaredFields();
        for (Field f : declaredFields) {
            Class<?> type = f.getType();
            System.out.println("type:" + type.getName());
            String name = f.getName();
            System.out.println("name:" + name);
            String modifiers = Modifier.toString(f.getModifiers());
            System.out.println("modifiers:"+ modifiers);
        }
    }
}

Field 对象分析

上面已经可以利用反射来获取Class对象的field,利用反射机制,还可以查看在编译时还不知道的对象字段。

方法如下:

  • Field getField(String name):得到指定名字的公共字段。
  • Field[] getFields():得到所有字段的一个数组。
  • Field getDeclaredField(String name):得到l类中声明的指定名字的公共字段。
  • Field getDeclaredField():得到l类中声明的所有字段的一个数组。

同时Field对象有如下方法:

  • Object get(Object obj):返回obj对象中用这个Field对象描述的字段的值。
  • void set(Object obj, Object value):将obj对象中这个Field对象描述的字段设置为一个新值。

但是,如果字段是一个私有字段,则没有方法权限,反射机制受限于Java的访问机制,所以get和set方法会抛出IllegalAccessException异常。不过可以使用setAccessible方法覆盖Java的访问控制。

  • void setAccessible(boolean flag):设置或取消访问对象的可访问标志,如果拒绝访问则抛出IllegalAccessException异常。
  • boolean trySetAccessible():为这个可访问对象设置可访问标志,如果拒绝则返回false。

示例:

import java.lang.reflect.Field;

public class ReflectTest03 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        Employee employee = new Employee("张三", 30);
        Class<? extends Employee> cl = employee.getClass();
        Field f = cl.getDeclaredField("name");
        f.setAccessible(true);
        Object v = f.get(employee);
        System.out.println(v);
        f.set(employee, "李四");
        System.out.println(employee.getName());
    }
}

Method对象分析

Method类有一个invoke方法,允许调用包装在当前Method对象中的方法。

  • Object invoke(Object obj, Object... args)

第一个参数时隐式参数,其余的对象是显式参数。对于静态方法,第一个参数会忽略,可以设置为null。
如果返回类型时基本类型,则invoke返回其包装器类型。

除了使用getDeclaredMethods等方法获取Method数组,同Field对象的getField类似,还可以使用Class类的getMethod方法:

  • Method getMethod(String name, Class... parameterTypes)

示例:

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

public class ReflectTest04 {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Employee e = new Employee("张三", 30);
        Method m = e.getClass().getMethod("getName");
        String s = (String) m.invoke(e);
        System.out.println(s);
    }
}

虽然可以实现上述功能,但是很容易出错,不是很推荐,如果在调用方法的时候提供了错误的参数,则会抛异常。

另外,invoke的参数和返回值都是Object类型。这意味着必须来回进行多次强转。编译时无法检查错误。步进如此,使用烦着获得方法指针的代码比直接调用方法的代码慢很多。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ethan-running

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值