Java反射专题笔记总结:一篇文章带你理清反射

Posted 温文艾尔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java反射专题笔记总结:一篇文章带你理清反射相关的知识,希望对你有一定的参考价值。

> 文章参考于B站-韩顺平

文章目录


一、一个需求引出反射

1.根据配置文件re.properties指定信息,创建Cat对象并调用方法hi

re.properties:

classfullpath=com.hspedu.Cat
method=hi

这样的需求在学习框架的时候特别多,即通过外部文件配置,在不修改源码情况下来控制程序,这符合设计模式的ocp原则(开闭原则:不修改源码,开扩容功能)

利用反射代码实现

package com.hspedu.reflection.question;

import com.hspedu.Cat;

import javax.annotation.Resource;
import java.io.FileInputStream;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * Description
 * User:
 * Date:
 * Time:
 */
public class ReflectionQuestion {

    public static void main(String[] args) throws Exception {
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String method = properties.get("method").toString();
        //(1)加载类,返回Class类型的对象clazz
        Class<?> clazz = Class.forName(classfullpath);
        //(2)获得对象实例
        Object o = clazz.newInstance();
        //(3)通过cls得到你加载的类com.hspedu.Cat的methodName"hi" 的方法对象
        //在反射中,可以把方法视为对象(万物皆对象)
        Method declaredMethod = clazz.getDeclaredMethod(method);
        //通过方法对象来实现调用方法
        declaredMethod.invoke(o);

    }
}

如果Cat类中还有一个cry方法,输出cry cat

public void cry(){
    System.out.println("cry cat");
}

我们如果想要执行cry方法,只能将cat.hi()方法修改成cat.cry()方法,这样不仅很麻烦而且涉及到修改源码,如果使用反射,我们便可以不修改源码,只修改配置文件便可控制方法的调用

二、反射机制:

1.Java Reflection

  1. 反射机制允许程序在执行期借助于ReflectionAPI取得任何类的内部信息(比如成员变量没构造器,成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用到
  2. 加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,

2.Java反射机制原理示意图

在这里插入图片描述

Java反射机制可以完成以下事情:

  1. 在运行时判断任意一个对象所属的类
  2. 在运行时构造任意一个类的对象
  3. 在运行时得到任意一个类所具有的成员变量和方法
  4. 在运行时调用任意一个对象的成员变量和方法
  5. 生成动态代理

Java反射相关的主要类:

  1. java.lang.Class:代表一个类,Class对象表示某个类加载后在堆中的对象
  2. java.lang.reflect.Method:代表类的方法
  3. java.lang.reflect.Field:代表类的成员变量
  4. java.lang.reflect.Constructor:代表类的构造方法

Field的使用:

        //得到name字段的名字
        Field fieldName = clazz.getDeclaredField("name");
        fieldName.setAccessible(true);
        System.out.println("name:"+fieldName.get(o));

Constructor的使用:

    //获得无参构造器
    Constructor<?> Noconstructor = clazz.getConstructor();
    System.out.println("无参constructor:"+Noconstructor);

    //获得有参构造器
    Constructor<?> Allconstructor = clazz.getConstructor(String.class);
    System.out.println("无参constructor:"+Allconstructor);

3.反射优点和缺点:

优点:

可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑

缺点

使用反射基本是解释执行,对执行速度有印象,直接new出对象速度是反射出对象的几十倍

4.传统方式与反射的效率对比:

package com.hspedu.reflection;

import com.hspedu.Cat;

import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * Description:测试反射的性能和优化方案
 * User:
 * Date:
 * Time:
 */
public class Reflection02 {

    public static void main(String[] args) throws Exception {
        m1();
        m2();
    }

    public static void m1(){
        //普通new
        Cat cat = new Cat();
        long Newstart = System.currentTimeMillis();
        for (int i =0;i<1000000;i++){
            cat.hi();
        }
        long Newend = System.currentTimeMillis();
        System.out.println("传统方式耗时:"+(Newend-Newstart));
    }

    public static void m2() throws Exception{
        //反射创建
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        Class<?> clazz = Class.forName(classfullpath);
        Method hi = clazz.getMethod("hi");
        Cat cat2 = (Cat)clazz.newInstance();
        long Refstart = System.currentTimeMillis();
        for (int i =0;i<1000000;i++){
            hi.invoke(cat2);
        }
        long Refend = System.currentTimeMillis();
        System.out.println("反射方式耗时:"+(Refend-Refstart));
    }

}


在这里插入图片描述
对反射方式进行优化:
setAccessible(true)

反射调用优化-关闭访问检查:

  1. Method和Field、Constructor对象都有setAccessible()方法

  2. setAccessible作用是启动和禁用访问安全检查的开关

  3. 参数为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

优化之后:
在这里插入图片描述

三、Class类

1.Class也是类,因此也继承Object类

Class类图结构
在这里插入图片描述

2.Class类对象不是new出来的,而是系统创建的

传统的new出对象的方式

Cat cat = new Cat();

我们进入创建过程:
在这里插入图片描述
反射的方式
在这里插入图片描述
由此可见:

两者都是通过ClassLoader类加载Class对象

3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次

分别执行下面图中的代码

图一:
在这里插入图片描述
图二:
在这里插入图片描述
随着图一debug我们进入了loadClass方法:
在这里插入图片描述
随着图二debug我们没有进入loadClass方法

这是因为图二在执行Cat cat = new Cat()时已经将Cat类加载到内存中,在堆中只会存在一份Class类对象,所以执行时不会再次进入loadClass方法,

对象.getClass()

4.通过Class对象可以完整地得到一个类的完整结构,通过一系列API

每个类的实例都会记得自己是由哪个Class实例所生成

  1. Class getSuperClass() 返回当前Class对象的父类的Class对象

  2. Class[] getInterfaces() 返回当前Class对象的所有接口

  3. ClassLoader getClassLoader()返回该类的类加载器

  4. Class getSuperclass() 返回表示此Class所表示的实体的超类的Class

5.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括方法代码,变量名,方法名,访问权限等等)

当类加载完毕后,我们的字节码对应的二进制数据加载到方法去中,除此之外在堆中会产生

一个Class类对象,这其实是一种数据结构,可以把成员变量映射成一个对象并对其操作

Class类方法演示:

创建Car类

public class Car {
    public String brand="品牌";
    public int price;
    public String color;
    
}
package com.hspedu.reflection.class_;

import com.hspedu.Car;

import java.lang.reflect.Field;

/**
 * Description:演示Class类的常用方法
 * User:
 * Date:
 * Time:
 */
public class Class02 {

    public static void main(String[] args) throws Exception {
        String classAllPath = "com.hspedu.Car";
        Class<?> clazz = Class.forName(classAllPath);
        //1.输出clazz
        System.out.println(clazz);//显示clazz对象,是哪个类的Class对象 com.hspedu.Car
        System.out.println(clazz.getClass());//输出clazz运行类型 java.lang.Class
        //2.得到包名
        System.out.println(clazz.getPackage().getName());
        //3.得到全类名
        System.out.println(clazz.getName());
        //4.通过clazz创建对象实例
        Car o = (Car)clazz.newInstance();
        //5.通过反射获取属性
        Field brand = clazz.getDeclaredField("brand");
        System.out.println(brand.get(o));
        //通过反射给属性赋值
        brand.set(o,"奔驰");
        System.out.println(brand.get(o));
        //遍历得到所有的属性
        Field[] fields = clazz.getFields();
        for (Field field : fields) {
            System.out.println(field.get(o));
        }
    }
}

获取反射的四种方式

不同阶段获取Class的不同方式

1.前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException

创建方式: Class.forName()

应用场景:多用于配置文件,读取类全路径,加载类

2.前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高

创建方式: 类.class

应用场景:多用于参数传递,比如通过反射得到对应构造器对象

3.前提:已知某个类的实例,调用该实例的geiClass()方法获取Class对象

创建方式:对象.getClass()

应用场景:通过创建好的对象,获取Class对象

4.其他方式

ClassLoader cl = 对象.getClass().getClassLoader();

创建方式:Class clazz =cl.getloadClass(“类的全类名”);

代码示例:

package com.hspedu.reflection.class_;

import com.hspedu.Car;

/**
 1. Description:演示获取Class对象的四种方式
 2. User:
 3. Date:
 4. Time:
 */
public class GetClass {
    public static void main(String[] args) throws Exception {

        //1.Class.forName()
        Class<?> cls1 = Class.forName("com.hspedu.Car");
        //2.类名.class,应用场景:多用于参数传递
        Class<Car> cls2 = Car.class;
        //3.对象名.getClass()
        Car car = new Car();
        Class<? extends Car> cls3 = car.getClass();
        //4.通过类加载器来获取类的Class对象
        //先得到类加载器
        ClassLoader classLoader = car.getClass().getClassLoader();
        Class<?> cls4 = classLoader.loadClass("com.hspedu.Car");
        
    }
}

四、哪些类型有Class对象

如下类型有Class对象

  1. 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  2. interface:接口
  3. 数组
  4. enum:枚举
  5. annotation:注解
  6. 基本数据类型
  7. void

五、动态加载和静态加载:

类加载基本说明:

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载

  1. 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
  2. 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不会报错,降低了依赖性

类加载时机:

  1. 当创建对象时(new)//静态加载
  2. 当子类被加载时,父类也加载//静态加载
  3. 调用类中的静态成员时//静态加载
  4. 通过反射//动态加载

代码示例:

在这里插入图片描述
也就是说,静态加载是不论程序是否会执行A代码,A都会被加载,而动态加载是说,只有在真正要用到A段代码时,A代码才会被加载

类加载过程图:

在这里插入图片描述
类加载的三个阶段的任务:
在这里插入图片描述

类加载各个阶段的介绍:

一、加载阶段Loading

JVM在该阶段的主要目的时将字节码从不同的数据源(可能是class文件、也可能是jar包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class对象

二、连接阶段Linking

连接阶段-验证:

  1. 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机中的要求,并且不会危害虚拟机自身的安全
  2. 包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据校验、字节码验证和符号饮用验证
  3. 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间

连接阶段-准备:

代码案例:

class A{
    //连接阶段-准备
    //1.n1是实例属性,不是静态变量,因此在连接阶段-准备时不会被分配内存
    //2.n2是静态变量,分配内存 n2 是默认在此阶段初始化,值为0,只有在连接阶段后的初始化阶段值才会被初始化为20
    //3.n3是常量,他和静态变量不一样,因为一旦赋值就不会改变,所以n3此阶段值为30
    public int n1=10;
    public static int n2=20;
    public static final int n3=30;
}

连接阶段-解析:

虚拟机将常量池内的符号引用替换为直接引用的过程

比如A类中有B类的引用,但只作为符号存在,只有在解析阶段才会将这类的符号引用解析为直接引用

三、初始化阶段Initialization

  1. 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
  2. clinit()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并进行合并
  3. 虚拟机会保证一个类的()方法在多线程环境中被正确地加锁,同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()方法完毕

代码展示:

package com.hspedu.reflection.classload_;

/**初始化过程
 * Description
 * User:
 * Date:
 * Time:
 */
public class ClassLoad03 {
    public static void main(String[] args) {
        //1.加载B类,并生成B的Class对象
        //2.连接 num = 0;
        //3.初始化
        // 依次自动收集类中所有静态变量的赋值动作和静态代码块中的语句,并合并,
        //先执行静态代码块中的代码,再执行静态变量
        /*
            clinit() {
                System.out.println("B 静态代码块被执行");
                num = 300;
                static int num = 100;
            }
            合并:num=100
         */
//        new B();
        System.out.println(B.num);
    }
}
class B{
    static {
        System.out.println("B 静态代码块被执行");
        num = 300;
    }
    static int num = 100;
    public B(){
        System.out.println("B() 构造器被执行");
    }
}

JVM对于clinit方法的加锁机制,在加载类的时候有同步机制控制

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
    }

六、通过反射获取类的结构信息

java.lang.Class类方法:

  1. 1.getName:获取全类名
  2. getSimpleName:获取简单类名
  3. getFields:获取所有public 修饰的属性,包括本类以及父类的
  4. getDeclareFields:获取本类中的所有属性
  5. getMethods:获取所有public修饰的方法,包括本类以及父类的
  6. getDeclaredMethods:获取本类中的所有方法
  7. getConstructors:获取所有public修饰的构造器
  8. getDeclaredConstructors:获取本类中所有构造器
  9. getPackage:以Package形式返回包信息
  10. getSuperClass:以Class形式返回父类信息
  11. getInterfaces:以Class[]形式返回接口信息
  12. getAnnotations:以Annotation[]形式返回注解信息
    代码示例:
package com.hspedu.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * Description:演示如何通过
 * User:
 * Date:
 * Time:
 */
public class ReflectionUtils {
    public static void main(String[] args) throws Exception {
        api1();

    }
    public static void api1() throws Exception{

        Class<?> clazz = Class.forName("com.hspedu.reflection.A");
        Object o = clazz.newInstance();

//        1. getName:获取全类名
        System.out.println("获取全类名:"+clazz.getName());
//        2. getSimpleName:获取简单类名
        System.out.println("获取简单类名:"+clazz.一文带你了解Java反射机制

java反射专题一

Java讲课笔记36:初探反射机制

关于JAVA 反射 基础知识/编码经验的一些总结

关于JAVA 反射 基础知识/编码经验的一些总结

一篇文章带你搞懂 Java 注解的原理