Java基础整理(二十一)

本文详细介绍了Java反射机制的基本概念,包括Class、Method、Constructor和Field的用法。重点讲解了通过Class.forName获取类字节码的三种方式,以及如何利用反射创建对象和理解Class.forName对类加载的影响。还探讨了类加载器的工作原理及文件路径获取的移植性方法。

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

反射机制(上)

传送门:反射机制(下)

1. 基本概念

  • 反射机制有什么用?

    通过Java语言中的反射机制可以操作字节码文件(class文件)

  • 反射机制的相关类在java.lang.reflect.*包

  • 反射机制相关的重要的类有哪些?

    • java.lang.Class:代表整个字节码,代表一个类型,整个类
    • java.lang.reflect.Method:代表字节码中的方法字节码,代表类中的方法
    • java.lang.reflect.Constructor:代表字节码中的构造方法字节码,代表类中的构造方法
    • java.lang.reflect.Field:代表字节码中的属性字节码,代表类中的成员变量(静态变量+实例变量)

2. 获取类字节码的三种方式

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

  • 第一种方式:Class.forName(“完整类名带包名”)

    • forName为静态方法方法的参数是一个字符串
    • 字符串需要是一个完整的类名(必须带包名)
    public class Test {
        public static void main(String[] args) {
    
            try {
                //c1代表String.class文件,或者说c1代表String类型
                CLass c1 = Class.forName("java.lang.String"); 
                Class c2 = Class.forName("java.lang.Integer");//c2代表Integer类型
                Class c3 = Class.forName("java.util.Date"); //c3代表Date类型
                System.out.println(c1);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
        }
    }
    
  • 第二种方式:对象.getClass()

    Java中任何一个对象都有getClass()方法

    public class Test {
        public static void main(String[] args) {
    
    
            Class c1 =null;
            try {
               c1 = Class.forName("java.lang.String");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            String s = "abc";
            Class x = s.getClass();
            System.out.println(c1==x);//true(说明两个地址对象的地址相同)
        }
    }
    
  • 第三种方式:数据类型.class属性

    Java语言中任何一种类型,包括基本数据类型,都有.class属性

    public class Test {
        public static void main(String[] args) {
    
    
            Class c1 =null;
    
            try {
               c1 = Class.forName("java.lang.String");
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
    
            String s = "abc";
            Class x = s.getClass();
            System.out.println(c1==x);
    
            Class n = String.class;
            Class z = int.class;
            System.out.println(n==x);//true
        }
    }
    
  • 总结

    无论通过何种方式,只要获取同一类型的类字节码Class,存取它们的地址一定是相同的

在这里插入图片描述


3. 获取Class的作用

通过反射机制,获取Class,通过Class来实例化对象

public class Test {
    public static void main(String[] args) {
        
        //不使用反射机制创建对象
        User user = new User();
        System.out.println(user);

        //通过反射机制的方式创建对象
        try {
            //c代表User类型
            Class c = Class.forName("com.study.www.threadshou.User");
            //newInstance()方法会调用User类的无参数构造方法,完成对象的创建
            Object obj =c.newInstance();
            System.out.println(obj);//com.study.www.threadshou.User@1b6d3586
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

class User{

}

注:newInstance()调用的是无参构造,必须保证无参构造的存在,否则会出现异常


4. 反射机制的灵活性

通过反射机制的方式创建对象看起来有些复杂,但是这样会使程序更灵活

  • 实例

    classinfo.properties文件

    className=com.study.www.threadshou.User
    

    Test.java

    public class Test {
        public static void main(String[] args) throws Exception {
    
            //通过IO流读取classinfo.properties文件
            FileReader reader = new FileReader("./classinfo.properties");
            //创建属性类对象Map,key value都是String类型
            Properties pro = new Properties();
            //加载
            pro.load(reader);
            //关闭流
            reader.close();
            //通过key获取value
            String className = pro.getProperty("className");
            //通过反射机制实例化对象
            Class c = Class.forName(className);
            Object obj = c.newInstance();
            System.out.println(obj);
    
        }
    }
    
    class User{
    
        public User(){
            System.out.println("调用了无参构造");
        }
    
    }
    

    输出结果:在这里插入图片描述

以上代码比较灵活,代码不需要改动,可以修改配置文件,配置文件修改之后,可以创建不同的实例对象

例如:将配置文件修改成className=java.util.Date

则就创建的是Date的实例对象


5. Class.forName()

研究一下,当调用Class.forName()的时候发生了什么?

public class Test {
    public static void main(String[] args) {

        try {
            Class.forName("com.study.www.threadshou.User");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

    }
}

class User{

    //静态代码块在类加载初始化时执行,并且只执行一次
    static{
        System.out.println("User类的静态代码块执行了");
    }

}

输出结果:在这里插入图片描述

结论Class.forName()这个方法的执行会导致类加载初始化

在开发中,如果你只是希望一个类的静态代码块执行,其他代码一律不执行,就可以使用Class.forName(“完整类名"),这个方法的执行会导致类加载,类加载初始化时,静态代码块执行。(该方法会应用到JDBC技术中)


6. 文件路径问题

我们之前都是直接采用相对路径或者是绝对路径来获取文件,但是这种获取文件的方式移植性比较差,比如说需要将程序打包到Linux系统下运行,而Linux系统下不可能存在C盘、D盘之类的盘符,这样就会导致我们的路径找不到所需要的文件,所以这节我们讨论一下通过一种新的方式来获取文件的绝对路径,有更好的移植性

注:使用如下方式的前提是文件必须在类路径下(可以看成在src路径下,但不代表类路径就是src)

(1)方法一

在这里插入图片描述

public class Test {
    public static void main(String[] args) throws Exception{

        String path = Thread.currentThread().getContextClassLoader().getResource("classinfo.properties").getPath();
        ///E:/Java-Project/Java-Code/out/production/Java-Code/classinfo.properties
        System.out.println(path);
        
        FileReader reader = new FileReader(path);
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        String className = pro.getProperty("className");
        //com.study.www.threadshou.User
        System.out.println(className);

    }
}
  • Thread.currentThread():当前线程对象
  • getContextClassLoader()是线程对象的方法,可以获取当前线程的类加载器对象
  • getResource()是类加载器对象的方法,当前线程的累加器默认从类的根路径下加载资源

如果想获取src子目录下的文件可以通过:

 String path = Thread.currentThread().getContextClassLoader().getResource("com/study/www/.../classinfo.properties").getPath();

(2)方法二

方法一是直接返回的绝对路径,方法二可以直接返回一个流

public class Test {
    public static void main(String[] args) throws IOException {

        InputStream reader = Thread.currentThread().getContextClassLoader().getResourceAsStream("classinfo.properties");
        Properties pro = new Properties();
        pro.load(reader);
        reader.close();
        String className = pro.getProperty("className");
        System.out.println(className);
    }
}


(3)方法三

java.util包下提供了一个资源绑定器,便于获取属性配置文件中的内容,使用以下这种方式,配置属性文件xxx.properties必须放到类路径下,且必须绑定的是xxx.properties文件,并且在写路径的时候,路径后面的扩展名不能写

public class Test {
    public static void main(String[] args) throws IOException {

        ResourceBundle bundle = ResourceBundle.getBundle("classinfo");
        //ResourceBundle bundle = ResourceBundle.getBundle("com/study/www/.../classinfo");
        String className = bundle.getString("className");
        System.out.println(className);
    }
}

7. 类加载器

  • 什么是类加载器?

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

  • JDK中自带的3个类加载器

    • 启动类加载器
    • 扩展类加载器
    • 应用类加载器
  • 假设有这样一段代码

    String s = "abc";
    

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

    • 首先通过”启动加载器“加载,启动加载器专门加载…\jdk1.8\jre\lib\rt.jar,rt.jar是JDK最核心的类库
    • 如果通过”启动加载器“加载不到的时候,会通过”扩展类加载器“加载,扩展类加载器专门加载…\jdk1.8\jre\lib\ext*.jar
    • 如果”扩展类加载器“没有加载到,那么会通过”应用类加载器“加载,应用类加载器专门加载classpath中的类

    这样的加载顺序,提供安全特性,比如说某个用户想通过自己的String类对系统进行攻击(属于应用类加载器的范畴),但是JVM会用启动类加载器中的String。

    这种机制叫做双亲委派机制,优先从启动类加载器中加载,这个成为”父“。”父“无法加载到,再从扩展类加载器中加载,这个称为”母“。双亲委派,如果都加载不到,才会考虑从应用类加载器中加载,知道加载到为止。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值