学静思语
Published on 2025-02-15 / 0 Visits
0
0

反射

反射

一、 反射的举例

1. 面向对象中创建对象,调用指定结构(属性、方法)等功能,可以不使用反射,也可以使用反射。请问有什么区别?

  • 不使用反射,我们需要考虑封装性。比如:出了Person类之后,就不能调用Person类中私有的结构
  • 使用反射,我们可以调用运行时类中任意的构造器、属性、方法。包括了私有的属性、方法、构造器。

2. 以前创建对象并调用方法的方式,与现在通过反射创建对象并调用方法的方式对比的话,哪种用的多?场景是什么?

  • 从我们作为程序员开发者的角度来讲,我们开发中主要是完成业务代码,对于相关的对象、方法的调用都是确定的。所以,我们使用非反射的方式多一些。

  • 因为反射体现了动态性(可以在运行时动态的获取对象所属的类,动态的调用相关的方法),所以我们在设计框架的时候,会大量的使用反射。意味着,如果大家需要学习框架源码,那么就需要学习反射。

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

3. 单例模式的饿汉式和懒汉式中,私有化类的构造器了! 此时通过反射,可以创建单例模式中类的多个对象吗?

  • 是的!

4. 通过反射,可以调用类中私有的结构,是否与面向对象的封装性有冲突?是不是Java语言设计存在Bug?

  • 不存在bug!

  • 封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用。
    反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所有我们就有能力进行调用。

二、Class类

1.Class类的理解(掌握)

  • 针对于编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。接着,我们使用java.exe命令对指定的.class文件进行解释运行。这个解释运行的过程中,我们需要将.class字节码文件加载(使用类的加载器)到内存中(存放在方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例。比如:加载到内存中的Person类或String类或User类,都作为Class的一个一个的实例
Class clazz1 = Person.class;  //运行时类
Class clazz2 = String.class;
Class clazz3 = User.class;
Class clazz4 = Comparable.class;
  • 说明:运行时类在内存中会缓存起来,在整个执行期间,只会加载一次。

2. 体会:Class看做是反射的源头

3.获取Class实例的几种方式(掌握前三种)

 //方式1
  Class<Person> personClass = Person.class;
  System.out.println(personClass);
 //方式2
  Person person = new Person();
  Class aClass = person.getClass();
  System.out.println(aClass);
 //方式3
  Class aClass1 = Class.forName("com.leon.reflex.Person");
  System.out.println(aClass1);
  //方式4
  Class aClass2 = ClassLoader.getSystemClassLoader().loadClass("com.leon.reflex.Person");
  System.out.println(aClass2);

4. Class的实例都可以指向哪些结构呢?(熟悉)

  • 简言之,所有Java类型!
    (1)class:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类
    (2)interface:接口
    (3)[]:数组
    (4)enum:枚举
    (5)annotation:注解@interface
    (6)primitive type:基本数据类型
    (7)void

5.类的加载过程(了解)

  • 过程1:类的装载(loading)
    将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成

  • 过程2:链接(linking)

  • 验证(Verify):确保加载的类信息符合JVM规范,例如:以cafebabe开头,没有安全方面的问题。
    准备(Prepare):正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。解析(Resolve):虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。

  • 过程3:初始化(initialization)

  • 执行类构造器()方法的过程。

  • 类构造器()方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。

6. 关于类的加载器(了解、JDK8版本为例)

6.1 作用:负责类的加载,并对应于一个Class的实例。

6.2 分类(分为两种):

  • BootstrapClassLoader:引导类加载器、启动类加载器使用C/C++语言编写的,不能通过Java代码获取其实例负责加载Java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)

  • 继承于ClassLoader的类加载器

  • ExtensionClassLoader:扩展类加载器负责加载从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录jre/lib/ext子目录下加载类库

  • SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器

          > 我们自定义的类,默认使用的类的加载器。
    
  • 用户自定义类的加载器

          > 实现应用的隔离(同一个类在一个应用程序中可以加载多份);数据的加密。
    

6.3 以上的类的加载器是否存在继承关系? 不存在

class ClassLoader{
    ClassLoader parent;
    public ClassLoader(ClassLoader parent){
        this.parent = parent;
    }
}
//测试代码:
ClassLoader loader0 = new ClassLoader();
ClassLoader loader1 = new ClassLoader(loader0);
我们就把loader0叫做loader1的父类加载器。

7.(掌握)使用类的加载器获取流,并读取配置文件信息

  //创建Properties对象
        Properties ps = new Properties();
        //通过类加载器获取输入流
        InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream("info1.properties");


        try {
            //加载文件
            ps.load(resourceAsStream);
            //获取属性
            String name = ps.getProperty("name");
            String password = ps.getProperty("password");

            System.out.println(name + ":" + password);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            try {
                //关闭资源
                if (resourceAsStream != null)
                    resourceAsStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

三、反射的应用

1.(掌握)反射的应用1:创建运行时类的对象

1.1 如何实现?

通过Class的实例调用newInstance()方法即可。

  • 1.2 要想创建对象成功,需要满足:

  • 条件1:要求运行时类中必须提供一个空参的构造器

  • 条件2:要求提供的空参的构造器的权限要足够。

  • 1.3 回忆:JavaBean中要求给当前类提供一个公共的空参的构造器。有什么用?

  • 场景1:子类对象在实例化时,子类的构造器的首行默认调用父类空参的构造器。

  • 场景2:在反射中,经常用来创建运行时类的对象。那么我们要求各个运行时类都提供一个空参的构造器,便于我们编写通用的 创建运行时类对象的代码。

  • 1.4 在jdk9中标识为过时,替换成什么结构
    通过Constructor类调用newInstance(…)

2. 反射应用2:获取运行时类的内部结构

  • 2.1 (了解)获取运行时类的内部结构1:所有属性、所有方法、所有构造器
  • 2.2 (熟悉)获取运行时类的内部结构2:父类、接口们、包、带泛型的父类、父类的泛型等

3.(掌握)反射的应用3:调用指定的结构:指定的属性、方法、构造器

  • 3.1 调用指定的属性(步骤)

  • 步骤1.通过Class实例调用getDeclaredField(String fieldName),获取运行时类指定名的属性

  • 步骤2. setAccessible(true):确保此属性是可以访问的

  • 步骤3. 通过Filed类的实例调用get(Object obj) (获取的操作) 或 set(Object obj,Object value) (设置的操作)进行操作。

  • 3.2 调用指定的方法(步骤)

  • 步骤1.通过Class的实例调用getDeclaredMethod(String methodName,Class … args),获取指定的方法

  • 步骤2. setAccessible(true):确保此方法是可访问的

  • 步骤3.通过Method实例调用invoke(Object obj,Object … objs),即为对Method对应的方法的调用。
    invoke()的返回值即为Method对应的方法的返回值特别的:如果Method对应的方法的返回值类型为void,则invoke()返回值为null

  • 3.3 调用指定的构造器(步骤)

  • 步骤1.通过Class的实例调用getDeclaredConstructor(Class … args),获取指定参数类型的构造器

  • 步骤2.setAccessible(true):确保此构造器是可以访问的

  • 步骤3.通过Constructor实例调用newInstance(Object … objs),返回一个运行时类的实例。

4.(了解)反射的应用4:通过反射获取注解的信息(见com.atguigu04.other.annotation包的测试)

  • 复习:自定义注解

  • ① 参照@SuppressWarnings 进行创建即可。

  • ② 注解要想通过反射的方式获取,必须声明元注解:@Retention(RetentionPolicy.RUNTIME)


Comment