文档章节

JAVA反射机制

木乃伊_li
 木乃伊_li
发布于 2019/05/06 09:57
字数 2168
阅读 560
收藏 10

 反射可以让我们在运行时动态的加载类,访问、调用类的字段、方法、构造器。反射机制是构建框架的基础,如Spring框架核心,各种RPC框架等。

Class:class的抽象

 首先我们需要知道在java中,类的信息是由Class对象来描述的,每个类对应一个Class对象,它表示一个类或者接口(一个annotation也算是一个接口)在运行时的类型信息。Class只有一个private的构造器,你并不能手动的去创建它,Class对象只能由JVM在加载一个类或者是在调用一个方法的时候去创建它。  获取一个类的Class对象有以下几种方式:

  • Class.forName(String className)
  • obj.getClass()
  • ClassName.class

 相较于forName()的方式,.class方式能获得编译期的检查更加安全。以上三种方式都可以获得类的Class对象,但是他们之间也有一些差别,Class.forName(String className)默认会对这个类进行初始化操作,而.class的方式则不会自动初始化该Class对象。

import java.util.Random;

public class InitObj1 {

    public static final int value1 = 1;//编译期常量,不需要初始化

    public static final int value2 = new Random().nextInt();//需要初始化

    static{
        System.out.println("static block initializing.");
    }
}
public class InitObj2 {
    public static final int value1 = 1;

    static{
        System.out.println("static block initializing.");
    }
}
public class ClassInitTest {

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

        System.out.println("----通过.class获取Class对象-----");
        Class c1 = InitObj1.class;
        System.out.println("created c1");
        System.out.println("value1:" + InitObj1.value1);
        System.out.println("value2:" + InitObj1.value2);

        System.out.println("----通过forName获取Class对象-----");
        Class c2 = Class.forName("me.l4j.thinkingInJava.reflectionAPI.InitObj2");
        System.out.println("created c2");
        System.out.println("value1:" + InitObj2.value1);
    }
}

输出:
----通过.class获取Class对象-----
created c1
value1:1
static block initializing.
value2:-2130950913
----通过forName获取Class对象-----
static block initializing.
created c2
value1:1

从结果可以看到.class获取Class引用不会引发初始化,延迟到了对value2的访问,但是forName()方法会立即引发初始化。

获取类型信息的简单示例

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;

/**
 * Created by lsj on 2017-9-6.
 */
public class ReflectString {

    public static void main(String[] args) {

        Class<?> strC = String.class;

        //获取类名
        System.out.println(strC.getName());

        //获取实现的接口
        Class<?>[] inter = strC.getInterfaces();
        System.out.println("String implements interfaces:");
        for (Class c : inter)
            System.out.println("    --" + c.getName());

        //getXXX方法只能获取到public,getDeclaredXXX可以获取到所有定义了的元素
        //获取构造器
        Constructor<?>[] cons = strC.getDeclaredConstructors();
        System.out.println("All Constructors:");
        for (Constructor csr : cons) {
            System.out.println("    constructor name:"+csr.getName());
            System.out.println("    constructor modifier:" + Modifier.toString(csr.getModifiers()));
            Arrays.stream(csr.getParameterTypes()).forEach(x-> System.out.println("    param:"+x.getName()));

            System.out.println("    ------------------------------------");
        }

        //获取字段
        Field[] fields = strC.getDeclaredFields();

        System.out.println("All fields:");
        for (Field f : fields) {
            System.out.println("    name:" + f.getName());
            System.out.println("    modifier:"+Modifier.toString(f.getModifiers()));
            System.out.println("    ------------------------------------");
        }


        //获取方法
        Method[] methods = strC.getDeclaredMethods();
        System.out.println("All methods:");
        for (Method m : methods){
            System.out.println("    name:"+m.getName());
            System.out.println("    modifier:"+Modifier.toString(m.getModifiers()));
            System.out.println("    returnType:"+m.getReturnType().getName());
            Arrays.stream(m.getParameterTypes()).forEach(x-> System.out.println("    param:"+x.getName()));
            Arrays.stream(m.getAnnotations()).forEach(x-> System.out.println("    annotation:"+x.annotationType().getName()));
            System.out.println("    ------------------------------------");
        }

        // 创建一个String对象cs,向上转型为CharSequence,其类型信息任然是一个String
        CharSequence cs = new String("str");
        Class csClass = cs.getClass();
        System.out.println(csClass.getName());

    }
}

类型判断

 对类型的判断主要有istanceof关键字、Class.isInstance(Object obj)方法和"=="三种方式。

import java.io.Serializable;

public class ClassType {
    public static void main(String[] args) {

        //String和StringBuilder都实现了CharSequence接口和Serializable接口,将str和sb都向上转型为一个CharSequence
        CharSequence str = new String();
        CharSequence sb = new StringBuilder();

        //istanceof和isInstance()表示这个对象是否是这个类或者接口的实例,即这个对象是否是这个类型
        System.out.println("str instanceof CharSequence:" + (str instanceof CharSequence));
        System.out.println("str instanceof String:" + (str instanceof String));
        System.out.println("str instanceof Serializable:" + (str instanceof Serializable));

        System.out.println();

        System.out.println("CharSequence.class.isInstance(str): "+CharSequence.class.isInstance(str));
        System.out.println("String.class.isInstance(str): "+String.class.isInstance(str));

        System.out.println();

        //==只能与对象实际类型相同时才能返回true
        System.out.println("String.class == str.getClass(): " + (String.class == str.getClass()));
        System.out.println("CharSequence.class == str.getClass(): " + (CharSequence.class == str.getClass()));


        //虽然str和sb都向上转型为CharSequence,但是他们两的实际类型并不相同
        System.out.println("str.getClass(): "+str.getClass());
        System.out.println("sb.getClass(): "+sb.getClass());
    }
}

instanceofisInstance()保持了类型的概念(是否是这个类,或是否是这个类的派生类),而==比较的是实际的class对象。

类型转换

 因为知道一只狗一定是一只动物,所以编译器允许自由的向上转型,不需要任何显示的转换。但是一直动物是不是一只狗这是不确定的,在进行“向下转型”的时候需要显式的进行转换(Dog)object,如果这个object并不属于Dog,那么在运行时会触发ClassCastException异常。在进行“向下转型”时我们可以先使用instanceof或者isInstance()进行类型检查,以避免触发异常。  在JAVA SE5开始我们可以使用Class引用的cast()进行转型,暂没有发现这种转型语法的特异功能。

Animal a = new Dog();
Class<?> dogCls = Dog.class;
Dog d = dogCls.cast(a);

反射

 通过java反射机制,我们可以动态的创建对象,体现出很大的灵活性。相较于静态编译,通过反射执行的操作要慢得多。  java在java.lang.reflect包下提供了反射的工具和接口,reflect包只是java反射机制的一部分,其他还包括前面提到的Class类以及Object类等。

ReflectionAPI简介

 在reflect包中Field、Method和Constructor这三个类比较常用。我们可以通过Constrcutor创建新的对象(Class引用的newInstance方法也可以创建对象,但是需要这个类有无参构造器),利用Field中的set()get()方法来读取和修改字段,以及使用invoke()方法来调用与Method关联的方法。下面的实例展示了这三个类的基本用法:

import java.util.List;

public class Student {
    private String name;
    private int age;
    private List<String> courses;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public Student(){
        this.name = "John Doe";
        this.age = -1;
    }
    public String getName() {
        return name;
    }

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

    public int getAge() {
        return age;
    }

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

    public List<String> getCourses() {
        return courses;
    }

    public void setCourses(List<String> courses) {
        this.courses = courses;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", courses=" + courses +
                '}';
    }
}
import java.lang.reflect.*;
import java.util.Arrays;

public class ReflectAPI {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {

        // 利用泛型的Class来指明这个Class对象为Student的Class对象
        Class<Student> stCls = Student.class;

        // 利用Class的newInstance()方法,调用无参构造器来创建对象
        Student sDefault = stCls.newInstance();
        System.out.println("调用无参构造器创建Student对象: "+sDefault);

        // 获取带String和int类型参数的构造器
        Constructor ctr = stCls.getConstructor(String.class, int.class);
        Student s = (Student)ctr.newInstance("john", 20);
        System.out.println(s);


        // 为s这个对象增加课程
        // Student中courses字段为private,getField("courses")会触发NoSuchFieldException异常
        // Field fld = stCls.getField("courses");
        Field fld = stCls.getDeclaredField("courses");

        // 由于courses字段为private的,不设置Field.setAccessible(true)会触发IllegalAccessException异常
        fld.setAccessible(true);
        fld.set(s, Arrays.asList("计算机网络","操作系统"));
        System.out.println(fld.get(s));

        // 设置为private的字段,一般来说是不希望你去直接访问它的。
        // 一种合法的修改这个值的方法就是调用其public的set方法去修改它
        // 这里使用getMethod()方法去获取修饰为public的方法,如果这个方法为private或者不存在会触发NoSuchFieldException异常
        Method m = stCls.getMethod("setAge", int.class);
        m.invoke(s, 24);
        System.out.println(s);
    }
}

动态代理

 首先了解下什么是代理模式:其目的是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。而动态代理可以动态的创建代理并动态的处理对所代理方法的调用。  JDK实现的动态代理只能代理实现了某接口的被代理对象。java动态代理主要用到java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口。动态代理的实现主要包括以下步骤:

  1. 实现InvocationHandler接口创建调用处理器
  2. 调用Proxy的newInstance()方法创建动态代理实例

 以下是一个动态代理的示例程序:

public interface WorkerInterface {
    void doJob(String job);
}
public class Worker implements WorkerInterface{
    @Override
    public void doJob(String job) {
        System.out.println("doing "+job);
    }
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyProxyHandler implements InvocationHandler {

    private Object proxied;

    public MyProxyHandler(Object proxied) {
        this.proxied = proxied;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("----------------info------------------------");
        System.out.println("Proxy: "+proxy.getClass());
        System.out.println("Method: "+method);
        System.out.println("args: "+args);
        System.out.println("----------------info------------------------");
        return method.invoke(proxied, args);
    }
}
public class SimpleProxy{
    public static void doJob(WorkerInterface wi) {
        System.out.println("Get ready to start work");
        wi.doJob("Whitewashing");
        System.out.println("Finish the work");
    }

    public static void main(String[] args) {
        Worker w = new Worker();

        WorkerInterface proxy = (WorkerInterface)Proxy.newProxyInstance(WorkerInterface.class.getClassLoader(),
                new Class[]{WorkerInterface.class},
                new MyProxyHandler(w));

        doJob(proxy);
    }
}

 我们在调用Proxy.newInstance方法时实际上是经历了一下三步:

  1. 创建代理类的类对象Class<?> cl = getProxyClass0(loader, intfs)
  2. 反射从生成的类对象cls中获取构造器对象:Constructor<?> cons = cl.getConstructor(constructorParams)
  3. 用上一步获取的构造器对象创建动态代理类实例cons.newInstance(new Object[]{h})

 通过动态代理我们可以为目标类预处理消息、添加修饰等。

© 著作权归作者所有

木乃伊_li
粉丝 9
博文 7
码字总数 21967
作品 0
成都
程序员
私信 提问
加载中

评论(0)

Android 4.4(KK)中利用APP打开关闭数据流量

在Android 4.4中,在app中打开或关闭数据流量 如果有这方面需求可以参考。 思路 利用JAVA的反射机制(Reflection),来调用CONNECTIVITY_SERVICE完成相关操作。 关于JAVA的反射机制,可以参考...

W_X
2014/12/17
817
4
最最最常见的Java面试题总结——第二周

String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的? String和StringBuffer、StringBuilder的区别 可变性   String类中使用字符数组:保存字符串,所以String对象是...

Amsour丶
2018/08/13
0
0
【免费】全网独家:详解Java反射机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/silencezwm/article/details/85115991 【免费】全网独家:这是一份非常值得珍藏的Android知识体系!!! 本文...

silencezwm
2018/12/20
0
0
黑马程序员--JDK1.5新特性(二)

----------------------android培训、java培训、期待与您交流!---------------------- Java反射机制定义: Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性、方法...

长平狐
2013/07/01
76
0
云妹总结了一些最最最常见的Java面试题供大家参考

云妹导读:String和StringBuffer、StringBuilder的区别是什么?String为什么是不可变的? String和StringBuffer、StringBuilder的区别 可变性 String类中使用字符数组:private final cha...

阿里云科技快讯
2018/08/17
0
0

没有更多内容

加载失败,请刷新页面

加载更多

搞不定Kafka重复消费?来看看就不一样了

前言 今天我们聊一个话题,这个话题大家可能在面试过程中,或者是工作当中经常遇到 :point_right: 如何保证 Kafka 消息不重复消费? 我们在做开发的时候为了程序的健壮性,在使用 Kafka 的时...

Java进阶程序员xx
6分钟前
13
0
pandas操作excel-07-数据筛选

import pandas as pddef age_18_to_30(a): return 18 <= a < 30def level_a(s): return 85 <= s <= 100students = pd.read_excel('D:/output.xlsx', index_col='idx')# 筛......

烽焱10仴
12分钟前
14
0
springcloud微服务实战_05_服务容错保护

5.1 Hystrix 服务降级 前言 在微服务架构中,我们将系统拆分成了一个个的服务单元,各单元应用间通过服务注册与订阅的方式互相依赖。由于每个单元都在不同的进程中运行,依赖通过远程调用的方...

SP_K
20分钟前
32
0
Java压缩解压(tar.gz)

package com.hxm.learn.util;import org.apache.commons.compress.archivers.tar.TarArchiveEntry;import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;import org......

itazi
21分钟前
41
0
2.29日记

忽视股票每天的价格波动。每年超越市场一点点,长期就能变得非常富有。

js工程师
27分钟前
55
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部