Java反射

反射

一、框架

后端常见框架有Spring、SpringMVC、Mybatis、SpringBoot等,他们都不约而同或多或少的使用了:

注解+反射+设计模式=框架

显而易见,反射对于学习框架的重要性。

二、Java 反射机制概述

  1. Reflection(反射)是被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内 部属性及方法。

  2. 加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。

  3. 举个列子:
    这个Java中的反射特别像初中学习光的反射一般。

三、理解Class类并获取Class实例 *

3.1 new关键字

  1. new能干什么

    • 使用new关键字,我们能在类的外部创建对象,从而使用属性,调用方法。
  2. new不能干什么

    • 但是在类的外部我们不能通过new创建的对象调用其内部的私有属性和私有方法以及私有的构造器

3.2 反射作用

  1. 反射能干什么

    • 创建对象【无论是否私有,都可以】
    • 使用属性【无论是否私有,都可以】
    • 调用方法【无论是否私有,都可以】

    ...

3.3 反射与封装是否矛盾

根据3.1和3.2我们很容易产生这样的疑问,既然通过反射能调用私有构造器、私有属性、私有方法,那么这与封装的初衷是否矛盾了呢?

答:这两种技术并不矛盾。封装建议我去调什么,反射是调你能调的事

3.4 理解Class类型的对象

如何理解这句话“加载完类之后,在堆内存的方法区中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象就包含了完整的类的结构信息。我们可 以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的结构,所以,我们形象的称之为:反射。”中的“Class类型的对象”?

我们先来明确几点:

  1. 对象是由类产生的。

    • 那么Class类型的对象,对应的类必然是Class。就好比Animal类型的Dog,对应的类是Animal。
  2. Java是纯面向对象语言,万事万物皆对象。类也是对象

    • Java的API给我们提供了很多类,如File类、Date类...。同时我们自己使用时也自定义了很多类如Person类、Order类、Goods类...
    • 想一想,这些都是类,是不是有很多共同的特征,如都有属性、方法...。我们是不是可以把这些共同的东西抽离出来,成为一个类。没错这个类就是Class类。

3.5 获取Class实例

有四种方式

  1. 方式一:调用运行时类的属性:.class

    //方式一:调用运行时类的属性:.class
            Class<Person> personClass = Person.class;
            System.out.println(personClass);
    
  2. 方式二:通过运行时类的对象,调用getClass()

    //方式二:通过运行时类的对象,调用getClass()
            Person person = new Person();
            Class personClass1 = person.getClass();
            System.out.println(personClass1);
    
  3. 方式三:调用Class的静态方法:forName(String classPath)

    //方式三:调用Class的静态方法:forName(String classPath)
            Class personClass2 = Class.forName("com.noah.Person");
            System.out.println(personClass2);
    
  4. 方式四:使用类的加载器:ClassLoader (了解)

     //方式四:使用类的加载器:ClassLoader  (了解)
            ClassLoader classLoader = ReflectionTest.class.getClassLoader();
            Class personClass3 = classLoader.loadClass("com.noah.Person");
            System.out.println(personClass3);
    

3.6 理解类的加载

  1. 类的加载过程:
    程序经过javac.exe命令以后,会生成一个或多个字节码文件(.class结尾)。
    接着我们使用java.exe命令对某个字节码文件进行解释运行。相当于将某个字节码文件
    加载到内存中。此过程就称为类的加载。加载到内存中的类,我们就称为运行时类,此
    运行时类,就作为Class的一个实例。 这也就是为什么让这些实例对象进行地址比较为true的原因

    	   System.out.println(personClass==personClass1);// true
            System.out.println(personClass==personClass2);// true
            System.out.println(personClass==personClass3);// true
            System.out.println(personClass2==personClass3);// true
    
  2. 换句话说,Class的实例就对应着一个运行时类。

  3. 加载到内存中的运行时类,会缓存一定的时间。在此时间之内,我们可以通过不同的方式
    来获取此运行时类。

3.5 理解Class类

这个类的实例(对象)不能通过new关键字创建,因为3.6中类的加载“加载到内存中的类,我们就称为运行时类,此
行时类,就作为Class的一个实例。
”在内存中已经存在了,并且每个类只有一个实例对象。我们可以通过4种方式去获取Class类的对象,而不是去创建。

四、创建运行时类的对象 *

目前创建对象有2种方式,一种是new关键字,另一种是反射【注意:本质上也是调用构造器来创建的】。那么我们通过反射如何创建对象呢?

  • 调用newInstance()方法

    @Test
        public void test2() throws IllegalAccessException, InstantiationException {
            // 注意:这里指定泛型,下面就不用强转了
            Class<Person> personClass = Person.class;
            // 通过反射的方式去创建Person对象
    
            // newInstance():调用此方法,创建对应的运行时类的对象。内部调用了运行时类的空参的构造器。
            /*
            * 要想此方法正常的创建运行时类的对象,要求:
            1.运行时类必须提供空参的构造器
            2.空参的构造器的访问权限得够。通常,设置为public。
    
    
            在javabean中要求提供一个public的空参构造器。原因:
            1.便于通过反射,创建运行时类的对象
            2.便于子类继承此运行时类时,默认调用super()时,保证父类有此构造器
            * */
            Person person = personClass.newInstance();
            person.show();
        }
    

五、调用运行时类的指定结构 *

主要是属性和方法

5.1 获取属性

// 调用运行时类的指定结构---获取属性
    @Test
    public void test3() throws IllegalAccessException, InstantiationException, NoSuchFieldException {
        // 获取Class实例
        Class<Person> personClass = Person.class;
        // 创建person实例
        Person person = personClass.newInstance();

        // 1. 使用反射的方式获取指定的属性
        Field declaredName = personClass.getDeclaredField("name");
        // 2. 保证当前的属性是能访问的
        declaredName.setAccessible(true);
        // 3. 设置属性的值
        declaredName.set(person,"大黄");
       // 打印属性的值
        System.out.println(declaredName.get(person));
    }

5.2 获取方法

 // 调用运行时类的指定结构---获取方法
    @Test
    public void test4() throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        // 一、非静态的方法

        // 1.获取Class实例
        Class<Person> p = Person.class;
        // 2.创建person对象
        Person person = p.newInstance();

        // 3. 获取指定的方法
        //  private String showName(String name)
        /**
         * getDeclaredMethod方法参数:
         * 参数1: 方法的名字
         * 参数2: 指明获取方法的形参列表
         */
        Method showName = p.getDeclaredMethod("showName", String.class);
        // 4. 保证当前的方法是可访问的
        showName.setAccessible(true);
        /**
         * invoke():
         * 参数一:方法的调用者
         * 参数二:给方法形参赋值的实参
         * invoke的返回值即为对应类中调用的方法的返回值
         */
        // 5. 调用invoke方法
        Object returnValue = showName.invoke(person, "孙悟空");
        System.out.println((String)returnValue);

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

        // 一、调用静态的方法
        // private static void desc()
        Method desc = p.getDeclaredMethod("desc");
        desc.setAccessible(true);
        desc.invoke(Person.class);
    }

使用反射重点的在于动态性。

posted @ 2020-10-09 21:40  宇宙砍柴人  阅读(150)  评论(0编辑  收藏  举报