JavaSE Reflect 反射

本文详细介绍了Java反射机制,包括如何获取类的字节码、实例化对象、通过配置文件创建实例以及类加载器的工作原理。通过实例展示了反射在代码灵活性和框架中的重要应用,同时提到了资源绑定器和类加载器的层次结构。

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

Java反射机制,reflect

写在前面:这个教程适合边看边练这种方式,如果你手边没有电脑又是第一次了解这部分知识的,建议不用看了!

反射机制的作用:

通过Java语言的反射机制可以操作字节码文件(class文件),有点类似与黑客(可以读和修改字节码文件),通过反射机制可以操作代码片段。

反射机制相关类的包在:

java.lang.reflect.*

反射机制相关重要的几个类:

java.lang.Class,代表整个字节码,代表一个类型,代表一个类;

java.lang.reflect.Method,代表字节码中的方法字节码,代表类中的方法;

java.lang.reflect.Constructor,代表字节码中的构造方法字节码,代表类中的构造方法;

java.lang.reflect.Field,代表字节码中的属性字节码,代表类中的成员变量(静态和实例变量);

// Class
public class User{
    // Field
    int no;
    // Constructor
    public User(){
        
    }
    public User(int no){
        this.no = no;
    }
    // Method
    public void setNo(int no){
        this.no = no;
    }
    public int getNo(){
        return no;
    }
}

1、获取字节码文件

要操作一个类的字节码,需要首先获取这个类的字节码,怎么获取java.lang.Class实例?

有三种方法:

  • 第一种:Class c = Class.forName(“完整的类名带包名”);
  • 第二种:Class c = 对象.getClass();
  • 第三种:Class c = 任何类型.class;
package javase.reflect;

import java.util.Date;

public class ReflectTest01 {
    public static void main(String[] args) {
        /*
            Class.forName()的说明:
            1. 静态方法;
            2. 方法的参数是一个字符串;
            3. 字符串需要的是一个完整的类名(包括包名);
         */
        Class c1 = null;
        Class c2 = null;
        try {
            // c1 代表String.class文件,或者说c1代表String类型
            c1 = Class.forName("java.lang.String");
            // c2 代表Date类型
            c2 = Class.forName("java.util.Date");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // java中任何一个对象都有一个方法(Object中定义的):getClass()
        String s = "abc";
        // x 代表String.class字节码文件,即String类型
        Class x = s.getClass();
        // 这里c1和x两个变量中保存的内存地址都是一样的,都指向方法区中的字节码文件,所以返回true
        System.out.println(c1 == x); // true , 双等号比较的两个对象保存的内存地址

        Date time = new Date();
        Class y = time.getClass();
        System.out.println(c2 == y); // true

        // java语言中任何一种类型,包括基本数据类型,它都有.class属性
        Class z = String.class; // z 代表String类型
        Class k = Date.class; // k 代表Date类型
        Class f = int.class; // f 代表int类型
        System.out.println(z == x); // true
    }
}

image-20220612204212324

2、通过反射获取实例

获取到Class能干啥?

  • 通过Class的newInstance()方法来实例化对象。
  • 注意:newInstance()方法内部实际上调用了类的无参数构造方法,必须保证无参构造方法存在才可以。
package javase.reflect;


import javase.bean.User;

public class ReflectTest02 {
    public static void main(String[] args) {
        // 不使用反射机制创建对象
        User user = new User();
        
        // 使用反射机制创建对象,相对于上面的那种方式,反射机制更加灵活!!!
        try {
            // 通过反射机制获取Class,在通过Class来实例化对象
            // 这里的 c 的类型为 User,也可以将 c 理解为 User 的对象
            Class c = Class.forName("javase.bean.User");
            // newInstance()这个方法会调用User这个类的无参数构造方法,完成对象的创建
            /*
                注:当我们编写一个类时,无参构造方法默认就已经存在了;
                但是如果我们只在类中定义有参构造而没有编写无参构造方法时,这时使用newInstance()方法
                会出现著名的:InstantiationException异常。所以一般在编写完类之后,习惯性的就将无参构造编写好。
             */
            // 重点: newInstance()方法调用的是类的无参构造方法;
            Object obj = c.newInstance();
            System.out.println(obj);
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
    }
}

User类

package javase.bean;

public class User {
    String no;
    public User() {
        System.out.println("无参数构造方法被执行了");
    }

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

3、通过配置文件创建实例

验证反射机制的灵活性:

  • java代码写一遍,在不修改java源代码的基础之上,可以到不同对象的实例化,非常之灵活!

  • 符合OCP开闭原则:即对扩展开放,对修改关闭。

  • 后期在学习高级框架以及工作的过程之中使用的都是高级框架,如:ssh、ssm…

  • 即:Spring Struts Hibernate 、Spring SpringMVC Mybatis…

  • 这些高级框架底层实现原理都是采用了反射机制。所以反射机制还是很重要的。

package javase.reflect;

import javase.bean.User;

import java.io.FileReader;
import java.io.IOException;
import java.util.Properties;

public class ReflectTest03 {
    public static void main(String[] args) {
        // 这种方式代码就写死了,只能创建 User 类型的对象
        User user = new User();

        // 以下代码是灵活的,代码不需要改动,可以在修改配置文件的基础上创建出不同的实例对象。
        // 通过IO流读取classinfo.properties文件
        FileReader reader = null;
        Properties properties = new Properties();
        try {
            reader = new FileReader("javase-reflect/classinfo.properties");
            properties.load(reader);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (reader != null)
                    reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 通过key获取value
        String className = properties.getProperty("className");
        Object o = null;
        // 通过反射机制创建对象
        try {
            Class c = Class.forName(className);
            o = c.newInstance();
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
            e.printStackTrace();
        }
        System.out.println(o);
    }
}

4、Class.forName()执行过程

研究一下:Class.forName()执行过程放生了什么?

  • 重点:如果只是希望一个类的静态代码块执行,其他代码一律不执行,则可以使用Class.forName(“完整类名”);
  • 这个方法的执行会导致类加载,类加载时,被反射类的静态代码块会被执行;
  • 提示:这个在后面JDBC技术的时候需要。
package javase.reflect;


public class ReflectTest04 {
    public static void main(String[] args) {
        try {
            // Class.forName()这个方法的执行会导致类的加载
            Class.forName("javase.reflect.MyClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

class MyClass{
    // 静态代码快在类加载时会被执行,并且只执行一次
    static {
        System.out.println("MyClass类的静态代码块执行了");
    }
}

5、类路径下文件的绝对路径

研究文件路径:

  • 怎么获取一个文件的绝对路径?一下讲解的这种方式是通用的,但前提该文件必须是在类路径下才能使用这种方式。
package javase.reflect;

import java.io.FileReader;
import java.io.InputStream;
import java.util.Properties;

/**
 * 研究文件路径:
 *  怎么获取一个文件的绝对路径?一下讲解的这种方式是通用的,但前提该文件必须是在类路径下才能使用这种方式。
 */
public class AboutPath {
    public static void main(String[] args) throws Exception{
        // 这种方式的路径缺点是:移植性查,在IDEA中默认当前路径是project的根。
        // 这种写法只要将文件的位置稍微更换一下,这个路径就无效了。
        // FileReader fileReader = new FileReader("javase-reflect/classinfo.properties");

        /*
            接下来讲解的这种方式比较通用,即使是代码位置换了,这样编写仍然是通用的;
            注意:这种方法使用前提是:改文件必须在类路径下;
            什么是类路径?即src目录下的都是类路径,src被称为类的根路径;
         */
        /*
            Thread.currentThread() 表示当前线程对象
            getContextClassLoader() 是当前线程对象的方法,可以获取到当前线程的类加载器对象
            getResource() 这是类加载器对象的方法,当前线程的类加载器默认从类的根路径下加载资源
         */
        // 这种方式可以获取到类路径下文件的绝对路径
        String dbPath = Thread.currentThread().getContextClassLoader()
                .getResource("javase/bean/db.properties").getPath();
        System.out.println(dbPath);

        // 通过以上的方法获取文件绝对路径变得更加灵活了,下面直接使用流的形式返回文件内容
        // 以前获取文件流的方式为:
        //FileReader fileReader = new FileReader(dbPath);

        // 直接以流的形式返回
        InputStream resource = Thread.currentThread().getContextClassLoader()
                .getResourceAsStream("javase/bean/db.properties");
        Properties properties = new Properties();
        properties.load(resource);
        // 关闭流
        resource.close();
        // 通过key获取value
        String username = properties.getProperty("username");
        System.out.println(username);
    }
}

6、资源绑定器

  • java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容;
  • 使用资源绑定器获取的属性配置文件xxx.properties必须位于类路径下(即以src作为根路径)。
package javase.reflect;

import java.util.ResourceBundle;

public class ResourceBundleTest {
    public static void main(String[] args) {
        // 资源绑定器,只能绑定properties文件,并且在写文件路径时,属性配置文件的后缀不用写
        ResourceBundle db = ResourceBundle.getBundle("db");
        ResourceBundle info = ResourceBundle.getBundle("javase/bean/info");
        String username = db.getString("username");
        String service = info.getString("service");
        System.out.println(username);  // root
        System.out.println(service);   // javaweb
    }
}

7、类加载器(了解)

关于JDK中自带的类加载器,什么是类加载器?

专门负责加载类的命令/工具:ClassLoader

JDK中自带了三个类加载器:

启动类加载器:rt.jar

扩展类加载器:ext/*.jar

应用类加载器:classpath

假设有这样一段代码:

String s = "abc";

代码在开始执行之前,会将所需要的类全部加载到JVM当中,通过类加载器加载,看到以上代码类加载器会找到String.class文件,找到就加载,那么是如何加载的呢?

首先通过“启动类加载器”加载:

注意:启动类加载器专门加载:%JAVA_HOME%/jre/lib/rt.jar,rt.jar中都是JDK最核心的类库。

如果通过“启动类加载器”加载不到的时候,会通过“扩展类加载器”加载:

扩展类加载器专门加载:%JAVA_HOME%/jre/lib/ext/*.jar

如果扩展类加载器没有加载到,那么会通过“应用类加载器”加载:

应用类加载器专门加载:classpath中的类

Java中为了保证类加载的安全,使用了双亲委派机制。优先从启动类加载器中加载,这个称为“父”,当它无法加载到时,再从扩展类加载器中加载,这个称为“母”。双亲委派如果都加载不到,才会考虑从应用类加载器中加载,直到加载到位置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值