一、概述
Java类的加载、连接和初始化知识比较底层,但掌握这些底层的运行原理有助于对Java程序的运行有更好的把握。而Java语言中的反射机制可以操作(读和改)字节码文件,获取某个对象、某个类的运行时信息,并可以动态地创建Java对象,动态地调用Java方法,访问并修改指定对象的成员变量值。Java中反射机制的相关类在java.lang.reflect包下。
反射机制中重要的类:
- java.lang.Class:代表整个字节码文件,代表一个类型。
- java.lang.reflect.Method:代表字节码中的方法字节码
- java.lang.reflect.Constructor:代表字节码中的构造方法字节码。
java.lang.reflect.Field:代表字节码中的属性字节码。
二、反射机制
1.获取Class的三种方式
第一种:Class c = Class.forName(”完整的类名带包名”)
- 第二种:Class c = 对象.getClass()
第三种:Class c = 任何类型.class ```java public class Test{ public static void main(String[] args) throws ClassNotFoundException {
// 方式一Class c1 = Class.forName("java.lang.String");//c1代表String.class文件,或者String类型Class c2 = Class.forName("java.lang.Integer");//完整的带包名的类名Class c3 = Class.forName("java.lang.System");// 方式二String s = "abc";Class x = s.getClass();//x代表String.class字节码文件System.out.println(c1 == x);//true,表示c1和x指向的内存地址相同// 方式三,Java语言中任何一种类型,包括基本数据类型,它都有.class属性Class c4 = String.class;Class c5 = int.class;System.out.println(c1 == c4);//true
} }
<a name="nZLNR"></a>## <a name="Pm2fM"></a>## 2.通过反射实例化对象```javaclass User{}public class Test{public static void main(String[] args) throws ClassNotFoundException {// 通过反射实例化对象Class c = Class.forName("com.sundegan.User");try {//newInstance()调用无参数构造方法完成对象创建//必须保证无参构造方法存在,否则会有InstantiationException异常Object obj = c.newInstance();} catch (InstantiationException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();}}}
注:newInstance()调用无参数构造方法完成对象创建,必须保证无参构造方法存在,否则会有InstantiationException异常。
3.通过读属性文件实例化对象
//userinfo.properties文件className=java.util.Date
public class Test{public static void main(String[] args) throws ClassNotFoundException, IOException, InstantiationException, IllegalAccessException {FileReader reader = new FileReader("userinfo.properties");Properties pro = new Properties();pro.load(reader);reader.close();String className = pro.getProperty("className");//通过反射机制实例化对象Class c = Class.forName(className);Object obj = c.newInstance();System.out.println(obj);}}//Sun Oct 10 15:45:57 CST 2021
优点:反射机制可以使程序更加灵活,在不改变Java代码的情况下,只需更改配置文件,就可以做到不同对象的实例化。符合设计模式中的开闭原则,对扩展开放,对修改关闭。
4.Class.forName( )作用
如果只是希望一个类的静态代码块执行,其他代码一律不执行,可以使用Class.forName(”完整类名”)方法,这个方法会导致类加载,而静态代码块在类加载时执行,且只执行一次。
提示:在JDBC技术中会使用到。
class Myclass{static {//静态代码块在类加载时执行,且只执行一次。System.out.println("静态代码块执行了...");}}public class Test{public static void main(String[] args) throws ClassNotFoundException {//Class.forName()方法的执行会导致类加载Class.forName("com.sundegan.Myclass");}}
5.获取类路径下文件的绝对路径
https://www.bilibili.com/video/BV1mE411x7Wt?p=657&share_source=copy_web
6.以流的形式返回
https://www.bilibili.com/video/BV1mE411x7Wt?p=658&share_source=copy_web
7.资源绑定器
https://www.bilibili.com/video/BV1mE411x7Wt?p=659&share_source=copy_web
三、类加载机制
1.类的加载、连接和初始化
1.1 JVM和类
系统可能在第一次使用某个类时加载该类,也可能采用预加载机制来加载某个类。
当运行某个Java程序时,将会启动一个Java虚拟机进程,不管该Java程序多么复杂,该程序启动了多少个线程,它们都处于该Java虚拟机进程里。同一个JVM下的所有线程、所有变量都处于同一个进程里,它们都使用该JVM进程的内存区。当Java程序运行结束时,JVM进程结束,该进程在内存中的状态将会丢失。
class A{public static int a = 6;}public class Test{public static void main(String[] args) throws ClassNotFoundException {var a = new A();a.a++;//让类变量a增加System.out.println(a.a);//7}}
class Test1{public static void main(String[] args) throws ClassNotFoundException {var a = new A();System.out.println(a.a);//6}}
Test程序输出7无问题,Test1程序为什么输出6呢?因为运行第二个Test1程序时,程序两次运行JVM进程,第一次运行JVM结束后,它对A类所做的修改将全部丢失,第二次运行JVM时将再次初始化A类。
1.2 类的加载
当程序主动使用某个类时,如果该类还未被加载到内存中,则系统会通过加载、连接、初始化三个步骤来对该类进行初始化。如果没有意外,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或类初始化。
类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象。即类也是一种对象,系统中所有的类实际上也是实例,它们都是java.lang.Class的实例。
类的加载由类加载器完成,类加载器通常由JVM提供,JVM提供的这些类加载器通常称为系统类加载器,除此之外,开发者可以通过继承ClassLoader基类来创建自己的类加载器。通过使用类加载器,可以从不同的来源加载类的二进制数据,通常有如下几种来源:
- 从本地文件系统加载class文件。
- 从JAR包加载class文件。
- 通过网络加载class文件。
- 把一个Java源文件动态编译,并执行加载。
类加载器通常无须等到“首次使用”该类时才加载该类,Java虚拟机规范允许系统预先加载某些类。
1.3 类的连接
当类被加载之后,系统为之生成一个对应的Class对象,接着进入连接阶段,连接阶段负责把类的二进制数据合并到JRE中。
1.4 类的初始化
在类的初始化阶段,虚拟机负责对类进行初始化,主要就是对类变量进行初始化。在Java类中对类变量指定初始值有两种方式:
- 声明类变量时指定初始值;
使用静态初始化块为类变量指定初始值。
public class Test{static{b = 6;//使用静态初始化块为变量b指定初始值}static int a = 5;static int b = 9;static int c;public static void main(String[] args) {System.out.println(Test.b);}}//9
声明变量时指定初始值,静态初始化块都将被当成类的初始化语句,JVM会按这些语句在程序中的排列顺序依次执行它们。、
JVM初始化一个类包含如下几个步骤:假如这个类还没有被加载和连接,则程序先加载并连接该类。
- 假如该类的直接父类还没有被初始化,则先初始化其直接父类。
- 假如类中有初始化语句,则系统依次执行这些初始化语句。
依次类推,所以JVM最先初始化的总是java.lang.Object类,当程序使用任何一个类时,系统会保证该类及所有父类都会被初始化。
1.5 类初始化的时机
当Java程序首次通过下面6种方式来使用某个类或接口时,系统就会初始化该类或接口。
- 创建类的实例。创建实例的方式包括:使用new操作符来创建实例;通过反射来创建实例;通过反序列化的方式来创建实例。
- 调用某个类的类方法。
- 访问某个类或接口的类变量,或为该类变量赋值。
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
- 初始化某个类的子类。当初始化某个类的子类时,该子类的所有父类都会被初始化。
- 直接使用java.exe命令来运行某个主类。当运行某个主类时,程序会先初始化该主类。
除此之外,下面的几种情形需要特别指出。
情形一:对于一个final型的类变量,如果该类变量的值在编译时就可以确定下来,那么这个类变量相当于“宏变量”。Java编译器会在编译时直接把这个类变量出现的地方替换成它的值,因此即使程序中使用该静态类变量,也不会导致该类的初始化。
class MyTest{static{System.out.println("静态初始化块...");}static final String s = "abc";}public class Test{public static void main(String[] args) {//访问输出MyTest类中的s类变量System.out.println(MyTest.s);}}//abc
上面的程序中s类变量在编译时就会被替换成abc,所以该代码不会导致初始化MyTest类。
注:当某个类变量使用了final修饰,而且它的值可以在编译时就确定下来,那么程序其他地方使用该类变量时,实际上并没有使用该类变量,而是相当于使用常量。
情形二:当使用ClassLoader类的loadClass( )方法来加载某个类时,该方法只是加载该类,并不会执行该类的初始化。使用Class的forName( )方法才会导致强制初始化该类。
class MyTest{static{System.out.println("静态初始化块...");}}public class Test{public static void main(String[] args) throws ClassNotFoundException {ClassLoader c = ClassLoader.getSystemClassLoader();c.loadClass("com.sundegan.MyTest");//仅仅只是加载该类System.out.println("系统加载MyTest类...");Class.forName("com.sundegan.MyTest");//会导致类的初始化}}//系统加载MyTest类...//静态初始化块...
2.类加载器
JDK中自带了3个类加载器,启动类加载器、扩展类加载器、应用类加载器。代码在执行之前,需要将用到的类全部加载到JVM当中,首先通过启动类加载器加载,专门加载jdk包中jre\lib\rt.jar包下的类文件,rt.jar包中都是JDK最核心的类库。如果通过启动类加载器加载不到的时候,会通过“扩展类加载器”加载,专门加载jre\lib\ext*.jar包下的所有文件。如果扩展类加载器没有加载到,会通过“应用类加载器”加载,专门加载classpath下的jar包。
四、反射应用
1.通过反射查看类信息
前面介绍了获取Class类对象的三种方式,获得该类对象后,即可通过Class类提供的大量方法来获取该Class对象所对应类的详细信息,如获取该类的构造器、方法、属性、注解等。
2.使用反射生成并操作对象
2.1 反射类的属性Field(了解)
class Student{//4个采用不同访问控制符修饰的Fieldpublic int id;private String name;protected int age;boolean sex;}public class Test{public static void main(String[] args) throws ClassNotFoundException {//获取类对象Class studentClass = Class.forName("com.sundegan.Student");//获取类中所有的public修饰的FieldField[] field = studentClass.getFields();System.out.println(field.length);//1System.out.println(field[0].getName());//id//获取类中所有的FieldField[] fields = studentClass.getDeclaredFields();System.out.println(fields.length);//4for (Field f:fields) {//getName获取属性的名字System.out.println(f.getName());//getType获取属性的类型Class fieldType = f.getType();System.out.println(fieldType.getName());//获取属性的修饰符int i = f.getModifiers();//返回的是修饰符代号//通过Modifier类的静态方法将修饰符代号转换成字符System.out.println(Modifier.toString(i));}}}
