Java基础(二十三):反射(reflection)


一、反射机制

1.1 快速入门

通过外部配置文件,在不修改源码的情况下,来控制程序,符合设计模式的ocp原则(开闭原则:不修改源码,扩容功能)
配置文件re.properties

classfullpath=ReflectionExercise.Cat
method=hi

Cat类

package ReflectionExercise;

public class Cat {
    private String name = "招财猫";
    public int age = 10;
    public Cat(){
    }
    public Cat(String name) {
        this.name = name;
    }
    public void hi(){
        System.out.println("喵喵1");
    }
    public void cry(){
        System.out.println("喵喵2");
    }
}

实操

package ReflectionExercise;


import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author 神代言
 * @version 1.0
 */
public class RefelctionQuestion {
    public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
         // 反射机制的优点:要实现调用Cat的hi方法 变成 调用cry方法
        //     传统方法:cat.hi() -> cat.cry()
        //     反射机制:直接修改配置文件的method=hi -> method=cry,不用修改源码


        // 理解反射
        //    例:从re.properties配置文件中实现方法的调用
        Properties properties = new Properties();
        properties.load(new FileInputStream("src\\re.properties"));
        String classfullpath = properties.get("classfullpath").toString();
        String methodName = properties.get("method").toString();
        System.out.println("classfullpath = " + classfullpath);
        System.out.println("method = " + methodName);

        //(1)加载类
        Class cls = Class.forName(classfullpath);
        //(2)得到加载类的 对象实例o
        Object o = cls.newInstance();
        System.out.println("运行类型:" + o.getClass());
        //(3)得到加载类的对象实例的 方法对象method
        //    即:在反射中,方法可以视为对象
        Method method = cls.getMethod(methodName);
        //(4)通过 方法对象method 实现调用方法
        //    传统:对象.方法()
        //    反射:方法.invoke(对象)
        method.invoke(o);
    }
}

1.2 反射机制原理

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

反射机制原理图
在这里插入图片描述


二、反射相关类

在这里插入图片描述
实操

package ReflectionExercise;

import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Properties;

/**
 * @author 神代言
 * @version 1.0
 */
public class Reflection01 {
    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 methodName = properties.get("method").toString();


        //(1)Class:加载类
        Class cls = Class.forName(classfullpath);
        // 得到加载类的 对象实例o
        Object o = cls.newInstance();
        System.out.println("运行类型:" + o.getClass());
        //(2)Method:得到加载类的对象实例的 方法对象method
        //    即:在反射中,方法可以视为对象
        Method method = cls.getMethod(methodName);
        // 通过 方法对象method 实现调用方法
        //    传统:对象.方法()
        //    反射:方法.invoke(对象)
        method.invoke(o);

        //(3)Field: getField不能得到私有成员变量
        Field age = cls.getField("age");
        System.out.println(age.get(o));

        //(4)Constructor
        Constructor constructor1 = cls.getConstructor();// 无参构造
        System.out.println(constructor1);

        Constructor constructor2 = cls.getConstructor(String.class);//有参构造
        System.out.println(constructor2);

    }
}

输出:
运行类型:class ReflectionExercise.Cat
喵喵1
10
public ReflectionExercise.Cat()
public ReflectionExercise.Cat(java.lang.String)

三、反射调用性能优化

反射优点和缺点:
优点: 可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。
缺点: 使用反射基本是解释执行,对执行速度有影响。

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

  1. Method和Field、Constructor对象都有setAccessible()方法;
  2. setAccessible作用是启动和禁用访问安全检查的开关;
  3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率;数值为false则表示反射的对象执行访问检查。

四、Class类

4.1 基本介绍

  1. Class也是类,因此也继承Object类
  2. Class类对象不是new出来的,而是系统创建的
  3. 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  4. 每个类的实例都会记得自己是由哪个 Class 实例所生成
  5. 通过Class对象可以完整地得到一个类的完整结构,通过一系列API
  6. Class对象是存放在堆的
  7. 类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据(包括 方法代码变量名,方法名,访问权限等等)

4.2 使用

package ReflectionExercise;

import java.lang.reflect.Field;

/**
 * @author 神代言
 * @version 1.0
 */
public class Class01 {
    public static void main(String[] args) throws Exception {
        // 一、Class类常用方法
        String classAllPath = "ReflectionExercise.Cat";
        Class cls = Class.forName(classAllPath);

        System.out.println("Class对象 " + cls);
        System.out.println("运行类型 " + cls.getClasses());
        System.out.println("包名 " + cls.getPackage().getName());
        System.out.println("全类名 " + cls.getName());
        // 创建实例
        Cat cat = (Cat)cls.newInstance();
        System.out.println("cat的toString " + cat);
        // 通过反射获取属性
        Field name = cls.getField("name");
        System.out.println("获取属性 " + name.get(cat));

        name.set(cat,"叮当猫");
        System.out.println("修改属性 " + name.get(cat));

        Field[] fields = cls.getFields();
        for (Field f: fields) {
            System.out.println("属性 " + f.getName());
        }

        // 二、获取Class类对象的方式
        // 1.前提: 已知一个类的全类名,且该类在类路径下,
        // 可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,
        // 实例: Class cls1 =Class.forName( "java.lang.Cat);
        // 应用场景:多用于配置文件,读取类全路径,加载类
        String path = "ReflectionExercise.Cat";
        Class cls1 = Class.forName(path);
        System.out.println(cls1);

        // 2.前提:若已知具体的类,通过类的class 获取,
        // 该方式 最为安全可靠,程序性能最高
        // 实例:Class cls2 = Cat.class;
        // 应用场景:多用于参数传递,比如通过反射得到对应构造器对象
        Class cls2 = Cat.class;
        System.out.println(cls2);

        // 3.前提: 已知某个类的实例,调用该实例的getClass()方法获取Class对象,
        // 实例:Class cls3 = 对象.getClass();
        // 应用场景: 通过创建好的对象,获取Class对象
        Cat cat1 = new Cat();
        Class cls3 = cat1.getClass();
        System.out.println(cls3);

        // 4.类加载器
        // ClassLoader cl = 对象.getClass().getClassLoader();
        // Class cls4 = cl.loadClass(“类的全类名”);
        ClassLoader cl = cat1.getClass().getClassLoader();
        Class cls4 = cl.loadClass(path);
        System.out.println(cls4);

        // 5.基本数据(int,char,boolean,float,double,byte,long,short)
        // 按如下方式得到Class类对象
        // Class cls = 基本数据类型.class
        Class<Integer> cls5 = int.class;
        System.out.println(cls5);

        // 6.基本数据类型对应的包装类,可以通过 .type 得到Class类对象
        // Class cls = 包装类.TYPE
        Class<Integer> cls6 = Integer.TYPE;
        System.out.println(cls6);

        // 这几种方式[1-4][5-6]创建的的都是同一个Class对象
        System.out.println(cls1.hashCode());
        System.out.println(cls2.hashCode());
        System.out.println(cls3.hashCode());
        System.out.println(cls4.hashCode());
        System.out.println(cls5.hashCode());
        System.out.println(cls6.hashCode());
    }
}

运行结果

Class对象 class ReflectionExercise.Cat
运行类型 [Ljava.lang.Class;@2503dbd3
包名 ReflectionExercise
全类名 ReflectionExercise.Cat
cat的toString Cat{name='招财猫', age=10}
获取属性 招财猫
修改属性 叮当猫
属性 name
属性 age
class ReflectionExercise.Cat
class ReflectionExercise.Cat
class ReflectionExercise.Cat
class ReflectionExercise.Cat
int
int
1265094477
1265094477
1265094477
1265094477
312714112
312714112

4.3 哪些类型有Class对象

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

在这里插入图片描述


五、类加载

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

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

举例说明
在这里插入图片描述

类加载时机

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

类加载流程图
在这里插入图片描述

在这里插入图片描述

  1. 加载阶段:
    JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是 class 文件、也可能是 jar 包,甚至网络)转化为二进制字节流加载到内存中,并生成一个代表该类的 java.lang.Class 对象。
  2. 连接阶段-验证:
    1)目的是为了确保 Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全;
    2)包括: 文件格式验证(是否以魔数 oxcafebabe开头)、元数据验证、字节码验证和符号引用验证;
    3)可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。
  3. 连接阶段-准备:
    JVM 会在该阶段对静态变量分配内存并初始化(对应数据类型的默认初始值,如 0、0L、null、false 等) 。这些变量所使用的内存都将在方法区中进行分配。
    在这里插入图片描述
  4. 连接阶段-解析:
    虚拟机将常量池内的 符号引用 替换为 直接引用 的过程
  5. Initialization (初始化):
    1)到初始化阶段,才真正开始执行类中定义的 Java 程序代码,此阶段是执行< clinit >() 方法的过程。
    2)< clinit >()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并
    3)虚拟机会保证一个类的 < clinit >() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 < clinit >()方法,其他线程都需要阻塞等待,直到活动线程执行 < clinit >() 方法完毕。

六、获取类的结构信息

Class、Field、Method、Constructor

package ReflectionExercise;

import org.junit.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class ReflectionUtils {
    public static void main(String[] args) throws Exception{
        ReflectionUtils reflectionUtils = new ReflectionUtils();
        reflectionUtils.api_01();
        reflectionUtils.api_02();
        reflectionUtils.api_03();
        reflectionUtils.api_04();
    }

    @Test
    public void api_01() throws Exception {
        System.out.println("------第一组java.lang.Class------");
        Class<?> personClass = Class.forName("ReflectionExercise.Person");
        System.out.println(personClass.getName());
        System.out.println(personClass.getSimpleName());
        Field[] fields1 = personClass.getFields();
        for (Field field : fields1) {
            System.out.println("本类和父类的public属性:" + field.getName());
        }
        Field[] fields2 = personClass.getDeclaredFields();
        for (Field field : fields2) {
            System.out.println("本类的所有属性:" + field.getName());
        }
        Method[] methods1 = personClass.getMethods();
        for (Method method : methods1) {
            System.out.println("本类和父类的public方法:" + method.getName());
        }
        Method[] methods2 = personClass.getDeclaredMethods();
        for (Method method : methods2) {
            System.out.println("本类的所有方法:" + method.getName());
        }
        Constructor<?>[] constructors1 = personClass.getConstructors();
        for (Constructor<?> constructor : constructors1) {
            System.out.println("本类的public构造器:" + constructor.getName());
        }
        Constructor<?>[] constructors2 = personClass.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors2) {
            System.out.println("本类的所有构造器:" + constructor.getName());
        }
        System.out.println("包名:" + personClass.getPackage());
        System.out.println("父类的class对象:" + personClass.getSuperclass());
        Class<?>[] interfaces = personClass.getInterfaces();
        for (Class<?> anInterface : interfaces) {
            System.out.println("接口:" + anInterface);
        }
        Annotation[] annotations = personClass.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println("注解:" + annotation);
        }
    }

    @Test
    public void api_02() throws Exception {
        System.out.println("------第二组java.lang.reflect.Field------");
        Class<?> personClass = Class.forName("ReflectionExercise.Person");
        Field[] fields2 = personClass.getDeclaredFields();
        // 说明:默认修饰符是0,public是1,private是2,protected是4
        //       static是8,final是16
        for (Field field : fields2) {
            System.out.println("本类的所有属性名字:" + field.getName()
                    + " 该属性的修饰符的值:" + field.getModifiers()
                    + " 该属性的类型:" + field.getType());
        }
    }

    @Test
    public void api_03() throws Exception {
        System.out.println("------第二组java.lang.reflect.Method------");
        Class<?> personClass = Class.forName("ReflectionExercise.Person");
        Method[] methods2 = personClass.getDeclaredMethods();
        // 说明:默认修饰符是0,public是1,private是2,protected是4
        //       static是8,final是16
        for (Method method : methods2) {
            System.out.println("本类的所有方法:" + method.getName()
                    + " 该方法的修饰符的值:" + method.getModifiers()
                    + " 该方法的返回类型:" + method.getReturnType());
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该方法的形参类型:" + parameterType);
            }
            System.out.println("-------------");
        }
    }

    @Test
    public void api_04() throws Exception {
        System.out.println("------第二组java.lang.reflect.Constructor------");
        Class<?> personClass = Class.forName("ReflectionExercise.Person");
        Constructor<?>[] constructors2 = personClass.getDeclaredConstructors();
        for (Constructor<?> constructor : constructors2) {
            System.out.println("本类的所有构造器:" + constructor.getName());
            Class<?>[] parameterTypes = constructor.getParameterTypes();
            for (Class<?> parameterType : parameterTypes) {
                System.out.println("该构造器的形参类型:" + parameterType);
            }
            System.out.println("-------------");
        }
    }
}

class A {
    public String hobby;

    public void hi() {
    }

    public A() {
    }
}

interface IA {
}

interface IB {
}

class Person extends A implements IA, IB {
    public String name;
    protected int age;
    String job;
    private double sal;

    public Person() {
    }

    public Person(String name) {
    }

    private Person(String name, int age) {
    }

    public void m1(String name) {
    }

    protected void m2(String name, int age) {
    }

    void m3() {
    }

    private void m4() {
    }
}

运行结果

------第一组java.lang.Class------
ReflectionExercise.Person
Person
本类和父类的public属性:name
本类和父类的public属性:hobby
本类的所有属性:name
本类的所有属性:age
本类的所有属性:job
本类的所有属性:sal
本类和父类的public方法:m1
本类和父类的public方法:hi
本类和父类的public方法:wait
本类和父类的public方法:wait
本类和父类的public方法:wait
本类和父类的public方法:equals
本类和父类的public方法:toString
本类和父类的public方法:hashCode
本类和父类的public方法:getClass
本类和父类的public方法:notify
本类和父类的public方法:notifyAll
本类的所有方法:m4
本类的所有方法:m3
本类的所有方法:m2
本类的所有方法:m1
本类的public构造器:ReflectionExercise.Person
本类的public构造器:ReflectionExercise.Person
本类的所有构造器:ReflectionExercise.Person
本类的所有构造器:ReflectionExercise.Person
本类的所有构造器:ReflectionExercise.Person
包名:package ReflectionExercise
父类的class对象:class ReflectionExercise.A
接口:interface ReflectionExercise.IA
接口:interface ReflectionExercise.IB
------第二组java.lang.reflect.Field------
本类的所有属性名字:name 该属性的修饰符的值:1 该属性的类型:class java.lang.String
本类的所有属性名字:age 该属性的修饰符的值:4 该属性的类型:int
本类的所有属性名字:job 该属性的修饰符的值:0 该属性的类型:class java.lang.String
本类的所有属性名字:sal 该属性的修饰符的值:2 该属性的类型:double
------第二组java.lang.reflect.Method------
本类的所有方法:m4 该方法的修饰符的值:2 该方法的返回类型:void
-------------
本类的所有方法:m3 该方法的修饰符的值:0 该方法的返回类型:void
-------------
本类的所有方法:m2 该方法的修饰符的值:4 该方法的返回类型:void
该方法的形参类型:class java.lang.String
该方法的形参类型:int
-------------
本类的所有方法:m1 该方法的修饰符的值:1 该方法的返回类型:void
该方法的形参类型:class java.lang.String
-------------
------第二组java.lang.reflect.Constructor------
本类的所有构造器:ReflectionExercise.Person
该构造器的形参类型:class java.lang.String
该构造器的形参类型:int
-------------
本类的所有构造器:ReflectionExercise.Person
该构造器的形参类型:class java.lang.String
-------------
本类的所有构造器:ReflectionExercise.Person
-------------

七、反射-创建实例、操作属性和方法(爆破)

package ReflectionExercise;

import org.junit.jupiter.api.Test;

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

/**
 * @author 神代言
 * @version 1.0
 */
public class Reflection03 {
    public static void main(String[] args) throws Exception {
        Reflection03 reflection03 = new Reflection03();
        reflection03.CreateInstance();
        reflection03.accessProperty();
        reflection03.accessMethod();
    }

    @Test
    public void CreateInstance() throws Exception {
        System.out.println("------通过反射创建实例-------");
        Class<?> userClass = Class.forName("ReflectionExercise.User");
        // 1.无参构造创建实例
        Object o = userClass.newInstance();
        System.out.println(o);
        // 2.public有参构造创建实例
        /*
            public User(String name){
                this.name = name;
            }
         */
        Constructor<?> constructor1 = userClass.getConstructor(String.class);
        Object hsp = constructor1.newInstance("hsp");
        System.out.println(hsp);

        // 3.public有参构造创建实例
        /*
            private User(int age,String name){
                this.age = age;
                this.name = name;
            }
         */
        Constructor<?> constructor2 = userClass.getDeclaredConstructor(int.class, String.class);
        constructor2.setAccessible(true);// 爆破:操作私有private构造器
        Object o1 = constructor2.newInstance(12, "小黑");
        System.out.println(o1);
    }

    @Test
    public void accessProperty() throws Exception {
        System.out.println("------通过反射操作属性-------");
        Class<?> userClass = Class.forName("ReflectionExercise.User");
        Object o = userClass.newInstance();
        System.out.println(o);

        // 操作public属性
        Field age = userClass.getField("age");
        age.set(o, 88);
        // 操作private属性
        Field name = userClass.getDeclaredField("name");
        name.setAccessible(true);// 爆破:操作私有private属性
        name.set(o, "张三丰");
        // name.set(null,"张无忌");// 静态属性,o可以写成null
        System.out.println(o);

    }

    @Test
    public void accessMethod() throws Exception {
        System.out.println("------通过反射操作方法-------");
        Class<?> userClass = Class.forName("ReflectionExercise.User");
        Object o = userClass.newInstance();
        System.out.println(o);

        // 操作public方法
        Method hi = userClass.getMethod("hi",String.class);
        hi.invoke(o,"赵敏");
        // 操作private方法
        Method declaredMethod = userClass.getDeclaredMethod("say",int.class,String.class,char.class);
        declaredMethod.setAccessible(true);
        //   因为该方法为static,因此o也可以写成null
        System.out.println(declaredMethod.invoke(o,1000,"太上老君",'男'));
    }
}

class User {
    public int age;
    private static String name = "万某人";

    public User() {

    }

    public User(String name) {
        this.name = name;
    }

    private User(int age, String name) {
        this.age = age;
        this.name = name;
    }

    private static String say(int n, String s, char c) {
        return n + " " + s + " " + c;
    }

    public void hi(String s) {
        System.out.println("hi " + s);
    }

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

运行结果

------通过反射创建实例-------
User{age=0, name='万某人'}
User{age=0, name='hsp'}
User{age=12, name='小黑'}
------通过反射操作属性-------
User{age=0, name='小黑'}
User{age=88, name='张三丰'}
------通过反射操作方法-------
User{age=0, name='张三丰'}
hi 赵敏
1000 太上老君 男

特别说明
本文章是个人整理的学习笔记,参考b站韩顺平老师的课程(【零基础 快速学Java】韩顺平 零基础30天学会Java)。老师讲的非常好,有兴趣的可以去看一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值