Java反射机制

一、反射的概述

1、什么是反射

Java的反射机制是在运行状态中,对于任意一个类都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的的任意一个属性和方法,这种动态获取信息以及动态调用方法的功能称为Java语言的反射机制。

简单的来说:
1.通过new关键字创建对象操作对象,在编译时就已经确定。
2.通过反射可以在程序运行过程中动态的操作对象,可以获得编译期无法获得的信息,动态操作最大限度发挥了java扩展性。

要想解剖一个类,必须先要获取到该类的字节码文件对象。而解剖使用的就是Class类中的方法。所以先要获取到每一个字节码文件对应的Class类型的对象.。

反射就是把Java类中的各种成分映射成一个个的Java对象。

例如:
一个类有:成员变量、方法、构造方法、包等等信息,利用反射技术可以对一个类进行解剖,把各个组成部分映射成一个个对象。
(其实:一个类中这些成员方法、构造方法,在加入类中都有一个类来描述)
如图是类的正常加载过程:反射的原理在于class对象。

熟悉一下加载的时候:Class对象的由来是将class文件读入内存,并为之创建一个Class对象。

2、反射机制的作用

有效的降低类之间的耦合度,很多框架都用到了反射的原理,例如hibernate 的实体类,Spring 的 AOP等等都有反射的实现

二、反射的具体实现

想要实现反射,就必须先拿到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息,每一个类对应着一个字节码文件也就对应着一个Class类型的对象,也就是字节码文件对象.

1、第一步,获取字节码文件对象(Class),共有三种方式获取

    /**
     * 方式一:
     * Object中的getClass方法来获取Class对象
     * 使用这方式必须有具体的类,并创建对象。
     * 这种方式使用的少,一般是传的是Object,不知道类型的时候才使用。
     */
      Object obj=new Proson();
	  Class clazz1 =obj.getClass();
	  System.err.println("通过getClass():"+clazz1);


    /**
     * 方式二:
     * 直接通过 类名.class来获取Class对象。
     * 任何类型中都具有隐含的静态成员变量class,使用简单,但是扩展性还是不足。
     */
      Class clazz2=Proson.class;
	  System.err.println("通过类名.class:"+clazz2);


    /**
     * 方式三:
     * 通过Class 对象的forName()静态方法来获取Class对象。
     * 使用最多,通过类的全限定名,但可能抛出ClassNotFoundException异常
     */
      Class clazz3 = Class.forName("com.xiaoli.bean.Proson");
	  System.err.println("通过类的全限定名:"+clazz3);

      //比较三种方法对象是否是相同实例。
	  System.err.println(clazz1==clazz2);
	  System.err.println(clazz1==clazz3);
	  System.err.println(clazz2==clazz3);

3种方式运行结果相同,所以一个类在 JVM 中只会有一个 Class 实例

2、第二步,创建对象

已知一个Proson类

 public class Proson {
    //私有属性
    private String name;
    //公有属性
    public  Integer age;
    //无参构造
    public Proson() {
    }
    //有参构造
    public Proson(String name, Integer age) {
        this.name = name;
        this.age = age;
    }
    //私有方法
     private void method1(){
        System.err.println("method1——run");
    }
    //公有方法
    public void method2(String param){
        System.err.println("method1=2——run :"+param);
    }


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

实例化对象的方式
通过new关键字与反射相互对比加深对反射的认识,详解代码如下

	 // 不用反射: 创建对象,需要手动new对象不能动态创建。
	 // new的时候根据new的类名寻找字节码文件,加载进入内存,创建Class对象,并接着创建对应的Proson对象。
         Proson p=new Proson();

        //通过反射:只需要一个名字,就可以创建对象。
        String className="com.xiaoli.bean.Proson";
        //寻找该名称的类文件,加载进内存,并创建Class对象。
        Class clazz = Class.forName(className);
        //通过Class对象的newInstance()创建类对象。
        Object o = clazz.newInstance();


调用有参构造方法初始化变量
通过有参构造对比反射,详解代码如下:

        //不用反射:通过构造方法实例化并初始化变量
        Object obj1=new Proson("name",18);
        System.err.println("不用反射: "+obj1);
        //通过反射:Class对象的getConstructor方法拿到构造器。
        Constructor constructor = clazz.getConstructor(String.class, Integer.class);
        //通过构造器的newInstance方法实例化并初始化变量
        Object obj2=constructor.newInstance("name2",22);
        System.err.println("通过反射: "+obj2);

 Class 类中方法详解

获取公共构造器 getConstructors()
获取所有构造器 getDeclaredConstructors()
获取该类对象 newInstance()
获取类名包含包路径 getName()
获取类名不包含包路径 getSimpleName()
获取类公共类型的所有属性 getFields()
获取类的所有属性 getDeclaredFields()
获取类公共类型的指定属性 getField(String name)
获取类全部类型的指定属性 getDeclaredField(String name)
获取类公共类型的方法 getMethods()
获取类的所有方法 getDeclaredMethods()
获得类的特定公共类型方法: getMethod(String name, Class[] parameterTypes)
获取内部类 getDeclaredClasses()
获取外部类 getDeclaringClass()
获取修饰符 getModifiers()
获取所在包 getPackage()
获取所实现的接口 getInterfaces()

实例代码:

       //这里方便操作演示用第二种发方法获取字节码对象
        Class clazz2=Proson.class;
        //通过反射调用有参构造函数初始化对象(上面演示过) 
        Constructor constructor = clazz2.getConstructor(String.class, Integer.class);
        Object obj = constructor.newInstance("lixiaoli", 18);

       //获得类完整的名字
        String className1 = clazz2.getName();
        System.err.println("1类完整的名字: "+className1);

       //获得类名不包含包路径
        String className2 = clazz2.getSimpleName();
        System.err.println("2类名不含路径: "+className2);

        //获得类中公共类型(public)属性
        Field[] fields = clazz2.getFields();
        String fieldName="";
        for(Field field : fields){
            fieldName+=field.getName()+"  ";
        }
        System.err.println("3类中公共属性: "+fieldName);

        //获得类中全部类型(包括私有)属性
        Field[] fieldsAll = clazz2.getDeclaredFields();
        fieldName="";
        for(Field field : fieldsAll){
            fieldName+=field.getName()+"  ";
        }
        System.err.println("4类中全部属性: "+fieldName);

        //获得公共指定属性值
        Field age = clazz2.getField("age");
        Object o = age.get(obj);
        System.err.println("5公共指定属性: "+o);

        //获得私有指定属性值
        Field name = clazz2.getDeclaredField("name");
        // Field name = clazz2.getField("name");只能获取共有属性 故此方法会抛出NoSuchFieldException异常,所以选用getDeclaredField();
        name.setAccessible(true); //设置为true才能获取私有属性
        Object o2 = name.get(obj);
        System.err.println("6类中私有指定属性值: "+o2);

        //获取类所有公共类型方法   这里包括 Object 类的一些方法
        Method[] methods = clazz2.getMethods();
        String methodsName="";
        for(Method method : methods){
            methodsName+=method.getName()+"  ";
        }
        System.err.println("7类公共类型方法: "+methodsName);

        //获取该类中的所有方法(包括私有)
        Method[] methodsAll = clazz2.getDeclaredMethods();
        methodsName="";
        for(Method method : methodsAll){
            methodsName+=method.getName()+"  ";
        }
        System.err.println("8类中的所有方法: "+methodsName);

        //获取并使用指定方法
        Method method1 = clazz2.getDeclaredMethod("method1");//获取无参私有方法
        method1.setAccessible(true);//设置为true才能获取私有方法
        method1.invoke(obj);//调用无参方法

        Method method2 = clazz2.getMethod("method2",String.class);//获取有参数方法
        method2.invoke(obj,"666");//调用有参方法

三、反射源码分析

当我们懂得了如何使用反射后,今天我们就来看看 JDK 源码中是如何实现反射的。或许大家平时没有使用过反射,但是在开发 Web 项目的时候会遇到过下面的异常:

java.lang.NullPointerException 
...
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:369)

可以看到异常堆栈指出了异常在 Method 的第 369 的 invoke 方法中,其实这里指的 invoke 方法就是我们反射调用方法中的 invoke。

例如我们经常使用的 Spring 配置中,经常会有相关 Bean 的配置:

<bean class="com.xxp.Phone">
</bean>

当我们在 XML 文件中配置了上面这段配置之后,Spring 便会在启动的时候利用反射去加载对应的 Phone类。而当 Apple 类不存在或发生启发异常时,异常堆栈便会将异常指向调用的 invoke 方法。

从这里可以看出,我们平常很多框架都使用了反射,而反射中最重要的就是 Method 类的 invoke 方法了。

我们知道反射机制允许程序在运行时取得任何一个已知名称的class的内部信息,包括包括其modifiers(修饰符),fields(属性),methods(方法)等,并可于运行时改变fields内容或调用methods。那么我们便可以更灵活的编写代码,代码可以在运行时装配,无需在组件之间进行源代码链接,降低代码的耦合度;还有动态代理的实现;JDBC原生代码注册驱动;hibernate 的实体类;Spring的AOP等等。但是凡事都有两面性,反射使用不当会造成很高的资源消耗!

 

泛型的本质:编译之后的泛型是去泛型化的

Java中集合的泛型,是防止错误输入的,只在编译阶段有效,绕过编译就无效了

验证,我们可以通过方法的反射操作,绕过编译,绕过编译操作就绕过了泛型

动态代理中,要执行某个类的方法时,先生成该类的代理类,通过代理类去调用目标类的方法,从而可以在代理类中执行 额外的逻辑。为了动态生成代理类,而不事先生成,我们就需要利用到反射。

 

资料

https://www.iteye.com/blog/china-jianchen-728774

https://www.jianshu.com/p/1fc45c89e76b

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值