java反射

本文详细介绍了Java反射机制的概念、原理及应用。通过实例演示如何利用反射动态获取类信息、调用方法、创建对象等,同时展示了反射在配置文件读取和泛型检查中的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java反射

反射概述

原作者总结的非常到位,这里只划上重点。

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

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

个人理解:

众所周知,一个类编译后会生成且只会生成一个与该类相对应的字节码文件。我们也知道,当我们定义了一个java类,可以用这个java类实例化任意多个对象,实例化得到对象之后,我们可以给对象属性赋值,并且操作对象的方法。上述步骤如下:定义的Class —> new Instance()实例化对象 —> instance.set() instance.get()调用方法。这些操作都是在编译前,编译之后会生成该类的字节码文件,而反射恰恰相反。

首先反射的核心在于Class类,而Class类依赖于字节码文件,字节码文件都有了肯定是编译后,也就是说,反射是编译后来操作类的,也就是运行时的。我们通过Class类的API可以得到已经定义的任何一个类的所有信息,这些信息包括该类的构造方法,成员变量,成员方法等等,我们可以调用这些方法,修改属性,完成一系列操作。上述步骤如下:编译后 —> 字节码文件 —> Class类的API获取需要的类 —> 通过这个类实例化对象,操作方法。

深入理解的话,这里偷一张图:

img

类加载

类加载时机

  • 当程序使用到某个类,如果这个类还没被加载到内存中,则加载该类。
  • 系统会通过加载、连接、初始化来对类进行初始化
    • 加载:将class文件读入内存,并为之创建一个class对象,任何类被使用时系统都会建立一个class对象
    • 连接:验证是否正确的内部连接,并和其他类协调一致,准备负责为类的静态成员分配内存,并设置默认初始化值
    • 初始化成员变量等等
  • 加载时机
    • 创建类的实例
    • 访问类中的静态变量,或者为静态变量赋值
    • 调用静态方法
    • 初始化某个类的子类
    • 使用反射方式强制创建某个类对应或接口对应的java.lang.class对象

类加载器

  • 什么是类加载器(class loader)

    • 负责将.class文件加载到内存中,并生成对应的Class对象
    • 虽然我们不需要关心类怎么加载,但是了解这个机制后有利于我们理解程序的运行
  • 类加载器分类

    根类加载器、扩展类加载器、系统类加载器

反射

  • 创建一个对象的三个阶段

    源文件阶段.java文件,字节码阶段.class,创建对象阶段,new类名

  • 内省

    在运行时能够获取Javabean当中的属性名和getset方法

  • 反射

    java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法。这种动态的获取类的信息以及调用类的方法的机制成为java反射机制。想要使用反射,必须得有字节码文件

反射API

假设已经有一个标准的Student bean类,得到该类的Class对象的三种方式:

首先写一个Student类,如下:

public class TestAPI {
    public static void main(String[] args) {
        // 获取class对象的三种方式:
        // 第一种方式
        Student stu1 = new Student("lzj", 21);
        Class stu1class1 = stu1.getClass();
        System.out.println(stu1class1.getName());

        // 第二种方式
        Class stu1class2 = Student.class;
        System.out.println("判断Student.class和stu1.getClass()获得的class是否是同一对象");
        System.out.println(stu1class1 == stu1class2);

        // 第三种方式
        try {
            Class stu1class3 = Class.forName("test_reflection.Student");// 全路径,加包名
            System.out.println("判断Student.class和Class.forName()获得的class是否是同一对象");
            System.out.println(stu1class2 == stu1class3);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

打印结果:

test_reflection.Student
判断Student.class和stu1.getClass()获得的class是否是同一对象
true
判断Student.class和Class.forName()获得的class是否是同一对象
true

由结果来看,这三种方式得到的Class对象都是同一个对象,也证明了:在运行期间,一个类只有一个Class对象,对于这三种方式常用第三种,第一种对象都有了还要反射干什么。第二种需要导入类的包,依赖太强,不导包就抛编译错误。一般都第三种,一个字符串可以传入也可写在配置文件中等多种方法。 此外第三种更安全性能更好。

使用反射

得到这个Class对象后,我们可以解析其中的属性和方法。

  1. 使用构造方法,API比较简单,这里直接贴代码了

    package test_reflection;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    class Person {
    
    // ---------------构造方法-------------------
    // (默认的构造方法)
    Person(String str) {
        System.out.println("(默认)的构造方法 s = " + str);
    }
    
    // 无参构造方法
    public Person() {
        System.out.println("调用了公有、无参构造方法执行了。。。");
    }
    
    // 有一个参数的构造方法
    public Person(char name) {
        System.out.println("姓名:" + name);
    }
    
    // 有多个参数的构造方法
    public Person(String name, int age) {
        System.out.println("姓名:" + name + "年龄:" + age);// 这的执行效率有问题,以后解决。
    }
    
    // 受保护的构造方法
    protected Person(boolean n) {
        System.out.println("受保护的构造方法 n = " + n);
    }
    
    // 私有构造方法
    private Person(int age) {
        System.out.println("私有的构造方法   年龄:" + age);
    }
    
    }
    
    public class ReflectConstructors {
    public static void main(String[] args) {
        try {
            /**
             * 获取Person的Class类
             */
            Class personClass = Class.forName("test_reflection.Person");
    
            /**
             * 获取所有公有构造方法
             */
            System.out.println("***********获取所有公有构造方法***********");
            Constructor[] publicConstructors = personClass.getConstructors();
            for (int i = 0; i < publicConstructors.length; i++) {
                System.out.println(publicConstructors[i]);
            }
            System.out.println("***********获取所有公有构造方法***********" + "\n");
    
            /**
             * 获取特定公有构造方法
             */
            System.out.println("***********获取特定公有构造方法***********");
            // 根据参数类型来获取
            Constructor publicConstructor1 = personClass.getConstructor(null);
            System.out.println(publicConstructor1);
            Constructor publicConstructor2 = personClass
                    .getConstructor(char.class);
            System.out.println(publicConstructor2);
            Constructor publicConstructor3 = personClass.getConstructor(
                    String.class, int.class);
            System.out.println(publicConstructor3);
            System.out.println("***********获取特定公有构造方法***********\n");
    
            /**
             * 获取所有构造方法,包括公有、私有、受保护、默认
             */
            System.out.println("***********获取所有构造方法,包括公有、私有、受保护、默认***********");
            Constructor[] allConstructors = personClass
                    .getDeclaredConstructors();
            for (int i = 0; i < allConstructors.length; i++) {
                System.out.println(allConstructors[i]);
            }
            System.out
                    .println("***********获取所有构造方法,包括公有、私有、受保护、默认***********\n");
    
            /**
             * 获取特定的私有构造方法,并调用实例化一个对象
             */
            System.out.println("***********获取特定的私有构造方法,并调用***********");
            Constructor protectedConstructor = personClass
                    .getDeclaredConstructor(boolean.class);
            System.out.println(protectedConstructor);
            Constructor privateConstructor = personClass
                    .getDeclaredConstructor(int.class);
            System.out.println(privateConstructor);
            // 暴力访问(忽略掉访问修饰符)
            protectedConstructor.setAccessible(true);
            privateConstructor.setAccessible(true);
            Object protectedObject = protectedConstructor.newInstance(true);
            Object privateObject = privateConstructor.newInstance(13);
            System.out.println("***********获取特定的私有构造方法,并调用***********\n");
            catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    }

    输出:

    *获取所有公有构造方法*
    public test_reflection.Person()
    public test_reflection.Person(char)
    public test_reflection.Person(java.lang.String,int)
    *获取所有公有构造方法*

    *获取特定公有构造方法*
    public test_reflection.Person()
    public test_reflection.Person(char)
    public test_reflection.Person(java.lang.String,int)
    *获取特定公有构造方法*

    *获取所有构造方法,包括公有、私有、受保护、默认*
    test_reflection.Person(java.lang.String)
    public test_reflection.Person()
    public test_reflection.Person(char)
    public test_reflection.Person(java.lang.String,int)
    protected test_reflection.Person(boolean)
    private test_reflection.Person(int)
    *获取所有构造方法,包括公有、私有、受保护、默认*

    *获取特定的私有构造方法,并调用*
    protected test_reflection.Person(boolean)
    private test_reflection.Person(int)
    受保护的构造方法 n = true
    私有的构造方法 年龄:13
    *获取特定的私有构造方法,并调用*

    总结:

    • getConstruct(parameterTypes)系列API获得公开的构造方法,可以通过参数类型选定需要的构造方法。无参则传null,返回全部方法后面加s表示多个。
    • getDeclaredConstructor(parameterTypes)系列API获取所有构造方法,一般用于获得私有构造方法,规则同上,暴力访问加上:setAccessible(true)。
  2. 使用成员变量

    package test_reflection;
    
    import java.lang.reflect.Field;
    import java.lang.reflect.InvocationTargetException;
    
    class Student1 {
    public Student1() {
    
    }
    
    // **********字段*************//
    public String name;
    protected int age;
    char sex;
    private String phoneNum;
    public static int staticFiled;
    
    @Override
    public String toString() {
        return "Student [name=" + name + ", age=" + age + ", sex=" + sex
                + ", phoneNum=" + phoneNum + "]";
    }
    }
    
    public class ReflectFields {
    public static void main(String[] args) {
        try {
            /**
             * 获取Class对象
             */
            Class student1class = Class.forName("test_reflection.Student1");
    
            /**
             * 获取所有公有字段,获取特定字段等同构造方法
             */
            Field[] publicFields = student1class.getFields();
            for (int i = 0; i < publicFields.length; i++) {
                System.out.println(publicFields[i]);
            }
    
            /**
             * 这里再举一个获取私有字段并给它赋值的例子
             * 可以看到,获取特定字段的标志是字段名:phoneNum
             */
            Field privateField = student1class.getDeclaredField("phoneNum");
            System.out.println(privateField);
            //暴力访问,无视修饰符
            privateField.setAccessible(true);
            //在给这个字段赋值之前,需要创建一个对象,否则不知道是哪个对象的字段
            //这里也用反射实例化一个对象
            Object studentObj = student1class.getConstructor(null).newInstance(null);
            //这里的set方法就是给此字段赋值,如果字段是私有的,可以暴力访问
            privateField.set(studentObj, "1845111....");
            System.out.println(studentObj);
    
            /**
             * 获取静态字段
             */
            Field staticField = student1class.getDeclaredField("staticFiled");
            System.out.println(staticField);
            staticField.setAccessible(true);
            staticField.set(null, 12);
            System.out.println(Student1.staticFiled);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    }

    打印结果:

    public java.lang.String test_reflection.Student1.name
    public static int test_reflection.Student1.staticFiled
    private java.lang.String test_reflection.Student1.phoneNum
    Student [name=null, age=0, sex= , phoneNum=1845111….]
    public static int test_reflection.Student1.staticFiled
    12

    API比较简单,这里总结下:

    • 获取字段:getDeclaredField(name)获取字段,传入的参数是字段名。

    • 使用字段:给字段赋值:privateField.set(studentObj, “1845111….”);第一个参数是该字段所属对象,第二个参数是赋值内容。

    • 思考,如果一个字段不仅仅属于一个对象,而是属于该类,也就是静态成员变量,那该如何获取和使用?

      猜测:获取还是照常获取,使用的话第一个参数不需要传字段所在对象,直接传null,果然不出所料,静态成员变量是不需要依赖某一实例化对象的。

              /**
               * 获取静态字段
               */
              Field staticField = student1class.getDeclaredField("staticFiled");
              System.out.println(staticField);
              staticField.setAccessible(true);
              staticField.set(null, 12);
              System.out.println(Student1.staticFiled);
  3. 调用成员方法

    package test_reflection;
    
    import java.lang.reflect.Method;
    
    class Student2 {
    // **************成员方法***************//
    public void show1(String s) {
        System.out.println("调用了:公有的,String参数的show1(): s = " + s);
    }
    
    protected void show2() {
        System.out.println("调用了:受保护的,无参的show2()");
    }
    
    void show3() {
        System.out.println("调用了:默认的,无参的show3()");
    }
    
    private String show4(int age) {
        System.out.println("调用了,私有的,并且有返回值的,int参数的show4(): age = " + age);
        return "abcd";
    }
    
    private static int display(String print){
        System.out.println(print);
        return 1;
    }
    }
    
    public class ReflectMethods {
    public static void main(String[] args) {
        /**
         * 和获取构造方法并调用一样,只不过newInstance()方法换成了invoke()
         * 调用方法也需要知道调用哪个对象的方法,所以第一个参数是对象,后面是参数列表
         */
        try {
            //得到Class对象
            Class studentClass = Class.forName("test_reflection.Student2");
            //获得一个特定方法,第一个参数是方法名,后面是参数列表的类型
            Method privateMethod = studentClass.getDeclaredMethod("show4", int.class);
            //调用对象的方法,首先得有个对象,除非是静态方法,所以先用反射实例化一个对象
            Object obj = studentClass.getDeclaredConstructor(null).newInstance(null);
            privateMethod.setAccessible(true);
            //调用方法并获得返回值
            String result = (String) privateMethod.invoke(obj, 13);
            System.out.println(result);
    
            //调用静态方法
            Method staticMethod = studentClass.getDeclaredMethod("display", String.class);
            staticMethod.setAccessible(true);
            int ret = (Integer) staticMethod.invoke(null, "123");
            System.out.println(ret);
    
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    }

    打印:

    调用了,私有的,并且有返回值的,int参数的show4(): age = 13
    abcd
    123
    1

    可以看到,如果调用静态方法的话,像使用静态成员变量一样不需要传入对象,可以直接调用。

反射的应用

  • 应用一:通过配置文件配置反射类的信息

    配置文件:pro.txt

    className = test_reflection.Animal
    
    methodName = print

    源文件:

    package test_reflection;
    
    import java.io.FileReader;
    import java.io.IOException;
    import java.util.Properties;
    
    class Animal{
    public void print(){
        System.out.println("print");
    }
    }
    public class UseReflectionByConfigFile {
    public static void main(String[] args) {
        try {
            Class clazz = Class.forName(getValue("className"));
            Object obj = clazz.getDeclaredConstructor(null).newInstance(null);
            clazz.getDeclaredMethod(getValue("methodName"), null).invoke(obj, null);
    
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    
    }
    
      //此方法接收一个key,在配置文件中获取相应的value  
      public static String getValue(String key) throws IOException{  
          Properties pro = new Properties();//获取配置文件的对象  
          FileReader in = new FileReader("src/test_reflection/pro.txt");//获取输入流  
          pro.load(in);//将流加载到配置文件对象中  
          in.close();  
          return pro.getProperty(key);//返回根据key获取的value值  
      }  
    }

    将类的信息都保存在配置文件中,包括类名,成员方法名,字段名,等等。现在需要修改方法名,只需要在配置文件中修改完后,其他地方可以继续引用配置文件里的信息,通过引入配置文件,降低了耦合度,程序可扩展性增强。现在有需求,修改方法名为print1。

    源文件:

    class Animal{
    public void print1(){
        System.out.println("print1");
    }
    }

    如果没有引入配置文件,那么反射所有用到print方法的地方全部都要修改成print1,但是有配置文件之后就只用修改配置文件中的信息:

    className = test_reflection.Animal
    
    methodName = print1

    所有引用配置信息的代码完全不用改动。

  • 应用二:通过反射越过泛型检查

    泛型:参数化类型

    参数化类型究竟是什么意思,个人理解是通过类型参数来在编译阶段限定你所想要的类型。

    对泛型有一定了解的朋友应该都知道泛型擦除,泛型在编译阶段会有语法检查,而在编译过后的字节码文件中,泛型就不存在了,是什么类还是什么类,跟泛型参数没任何关系。所以可以这么理解,泛型其实就是一种语法检查机制,为了统一集合类型而存在的安全机制。

    在前面我们提到了,反射作用于java运行时,也就是生成字节码文件之后。因为泛型在编译生成字节码文件后会被擦除,所以可以利用这一点使用反射越过泛型的语法检查。

    public class ReflectionAndGeneric {
    public static void main(String[] args) {
        List<Integer> strings = new ArrayList<Integer>();
        strings.add(123);
        strings.add(456);
        System.out.println(strings);
    
        Class listClass = strings.getClass();
        try {
            Method listAddMethod = listClass.getDeclaredMethod("add", Object.class);
            listAddMethod.invoke(strings, "你好~");
            System.out.println(strings);
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    }

    打印结果:

    [123, 456]
    [123, 456, 你好~]

    两者相互证明:泛型在编译后擦除,反射是java运行时的

    参考资料:https://blog.youkuaiyun.com/sinat_38259539/article/details/71799078

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值