13-类加载-反射
✨你好啊,我是“ 罗师傅”,是一名程序猿哦。
🌍主页链接:楚门的世界 - 一个热爱学习和运动的程序猿
☀️博文主更方向为:分享自己的快乐 briup-jp3-ing
❤️一个“不想让我曾没有做好的也成为你的遗憾”的博主。
💪很高兴与你相遇,一起加油!
前言
目标:Java高级编程,灵活运用反射,线程,IO和网络等进行编程
基础回顾
温故而知新:01-Java基础入门
JVM虚拟机
JVM(Java Virtual Machine) 是Java平台的核心组件,它提供了跨平台的能力,使得Java程序能够在不同的操作系统上运行。JDK中的JVM负责解释和执行Java字节码文件,同时还提供了内存管理、垃圾回收等功能,使得Java程序能够高效、安全地运行。
JVM内存结构
- 类加载器(Class Loader):类加载器负责加载Java字节码文件(.class文件),并将其转换为可执行的代码。它将类加载到JVM的运行时数据区域中,并解析类的依赖关系
- 运行时数据区(Runtime Data Area):运行时数据区域时JVM用于存储程序运行时的数据的区域。它包括以下几个部分:
- 方法区(Method Area):用于存储类的结构信息、常量池、静态变量等
- 堆(Heap):用于存储对象实例和数组内存
- 栈(Stack):也叫做虚拟机栈,方法调用执行、局部变量所需内存由它提供
- 本地方法栈(Native Method Stack):本地方法栈与虚拟机栈所发挥的作用非常相似,其区别是虚拟机栈为虚拟机栈是为虚拟机执行Java方法服务,而本地方法栈则是为虚拟机使用到的本地(Native)方法服务
- 程序计数器(Program Counter):用于存储当前线程执行的字节码指令的地址
- 执行引擎(Execution Engine):执行引擎负责执行编译后的字节码指令,将其 转换为机器码并执行。它包括解释器和即时编译器(Just-In-Time Compiler, JIT)两个部分,用于提高程序的执行效率(其具体工作原理,下一章节具体讨论)
- 垃圾回收器(Garbage Collector):垃圾回收器负责自动回收不在使用的对象和释放内存空间。它通过标记-清除、复制、标记-整理等算法来进行垃圾回收
- **本地方法接口(Native Method Interface)**:本地方法接口允许Java程序调用本地方法 即使用其他语言(C、C++)编写的代码
类加载
JVM架构及执行流程如下:
- 解释执行
class文件内容,需要交给JVM进行解释执行,简单理解就是JVM解释一行就 执行一行代码。所以如果Java代码全是这样的运行方式的话,效率会稍低一 些。
- JIT(Just In Time)即使编译
执行代码的另一种方式,JVM可以把Java中的 热点代码 直接编译成计算机可 以运行的二进制指令,这样后续再调用这个热点代码的时候,就可以直接运 行编译好的指令,大大提高运行效率。
类加载器
类加载器可以将编译得到的 .class文件 (存储在磁盘上的物理文件)加载在 到内存中。
加载时机
当第一次使用到某个类时,该类的class文件会被加载到内存方法区。
- 使用 java 命令来运行某个主类(main方法)
- 创建类的实例(对象)
- 调用类的static方法
- 访问类或接口的static成员,或者为该类static成员赋值
- 初始化某个类时,其父类会被自动加载
- 使用反射方式来获取类的字节码对象时,会加载某个类或接口的class文件
加载过程
类的加载过程:加载 、验证、准备、解析、初始化
具体加载步骤:
- 加载 Loading
将类的字节码文件加载到内存方法区中。这个阶段由类加载器完成,类加载器 根据类的全限定名来定位并读取类的字节码文件,然后将字节码转换为JVM内部 的数据结构。
链接 Linking
- 验证 Verification
对加载的字节码进行验证,确保字节码的结构和语义是正确的(确保class文 件中的信息符合虚拟机规范,有没有安全隐患)。
验证过程包括文件格式验证、元数据验证、字节码验证和符号引用验证等, 以确保字节码的安全性和正确性。
- 准备 Preparation
为类的 static静态变量分配内存,并设置默认初始值
这个阶段会在方法区中为类的静态变量分配内存空间,并设置默认初始值 (如0、0.0、null等),但不会执行静态变量的初始化代码。
- 解析 Resolution
将类的符合引用解析为直接引用
在解析阶段,将符号引用(如类名、方法名、字段名)转换为直接引用(如直接指向方法、字段的指针或偏移量),以便于后续的访问和调用
初始化 Initialization
对类进行初始化,包括执行静态变量的赋值和静态代码块的初始化。
在此阶段,会按照程序的顺序执行类的静态变量赋值和静态代码块中的初始化 代码,完成类的初始化工作。
注意事项:
类加载过程时按需进行的,即在首次使用类时才会触发类的加载和初始化。此外,类加载过程是由Java虚拟机的类加载器负责完成的,不同的类加载器可能有不同的加载策略和行为。
类加载小结:
JVM的类加载过程包括加载、验证、准备、解析和初始化等阶段,它们共同完成将Java类加载内存中,并为类的静态变量分配内存、解析符号引用、执行静态代码块等操作,最终使得类可以被正确的使用和执行。
加载器分类
JDK8类加载器可以分为以下四类:
- Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,通常表示为null。
它是Java虚拟机的一部分,负责加载Java核心类库,比如rt.jar等。根加载器是所有类加载器的顶级加载器,它不是一个Java对象,而是由JVM实现的一部分
类一般存在**%JAVA_HOME%\jre\lib\rt.jar**中
- Extension ClassLoader 扩展类加载器
负责加载Java的扩展类库,也可以通过 java.ext.dirs 系统属性来指定扩 展类库的路径
这些类一般存在 %JAVA_HOME%\jre\lib\ext\ 下的jar包中
- System ClassLoader 系统类加载器
它负责加载应用程序的类,包括用户自定义的类和第三方库等。它是大多数Java应用程序默认的类加载器。
系统类加载器的搜索路径包括当前工作目录和CLASSPATH环境变量指定的路径
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 public class Test034_ClassLoader {
public static void main(String[] args) {
// 获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 获取系统类加载器的父加载器 -- 扩展类加载器
ClassLoader exClassLoader = systemClassLoader.getParent();
// 获取平台类加载器的父加载器 -- 根类加载器
ClassLoader bootClassLoader = exClassLoader.getParent();
System.out.println("systemClassLoader = " + systemClassLoader);
System.out.println("exClassLoader = " + exClassLoader);
System.out.println("bootClassLoader = " + bootClassLoader);
}
}
// systemClassLoader = sun.misc.Launcher$AppClassLoader@18b4aac2
// exClassLoader = sun.misc.Launcher$ExtClassLoader@1b6d3586
// bootClassLoader = null
双亲委派
双亲委派机制是Java类加载器的一种工作机制,通过层级加载和委托父类加载器来加载类,确保类的唯一性、安全性、和模块化。
- 问题引入
用户自定义的 java.lang.String ,在测试类main方法中使用该类,思考: 类加载器到底加载是哪个类,是JDK提供的String,还是用户自定义的String?(代码自己写哈~)
结果:最终加载的类是JDK提供的 java.lang.String 为什么?答案是双亲委托机制!
- 双亲委派机制
一开始看图有点难懂:听我细细道来,有这样一个场景 :孔融得到一个梨,他先问自己的父亲吃不吃?他的父亲又问一下他的爷爷吃不吃?,他的爷爷因为牙疼吃不了,就把梨让给了他爸爸吃,他的爸爸因为刚刚已经吃饱了,于是又把梨给了他吃。现在孔融自己决定吃不吃。
现在回到真实场景:现在来了一个用户自定义类(java.lang.String) –> Application(往上抛给) —> Extension(继续往上抛) –> Bootstrap(看看自己有没有) 有的话自己加载,没有的话放行下—> Extension(看看自己有没有)有的话自己加载,没有的话放行 –> Application(有加载),没有—> ClassNotFondException异常。
注意:这里的有没有指的是 比如在Bootstrap 的 rt.jar 的一堆.class 有没有对应的类,像这个传进来的java.lang.String因为在 boostrap中有对应的类了,所以直接加载,就不会再去加载用户自己写的java.lang.String类了
常用方法
案例展示:
准备一个jdbc的配置文件 db.properties ,借助类加载器中方法解析,遍 历输出其配置内容。
- db.properties
1
2
3
4 driver=com.mysql.cj.jdbc.Driver
url=jdbc://mysql:3306/db01?serverTimezone=UTC
username=root
password=root
- 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 public class Test036_LoadFile {
public static void main(String[] args) {
// 1、获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// 2、利用加载器区加载一个指定的文件
InputStream is = systemClassLoader.getResourceAsStream("com/briup/chap13/test/db.properties");
// 3、实例化Properties对象,解析配置文件内容并输出
Properties properties = new Properties();
try {
properties.load(is);
// 配置文件内容遍历
Set<Map.Entry<Object, Object>> entries = properties.entrySet();
for (Map.Entry<Object, Object> entry : entries) {
String key = (String) entry.getKey();
String value = (String) entry.getValue();
System.out.println(key + "=" + value);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 4、关闭流
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}注意事项:getResourceAsStream(String path),参数path是相对路径,相对当前测试类class文件所在的目录!
反射
反射概述
Java反射机制是指在Java程序在运行状态下,动态地获取、检查和操作类的信息和对象的能力。
反射机制作用:
- 对于任意一个类,都能够知道这个类的所有属性和方法
- 对于任意一个对象,都能够调用它的任意一个方法和属性
这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。
当一个类被使用的时候,类加载器会把该类的字节码文件装入内存(类加载),同时在对空间创建一个 字节码对象(Class类对象),这个对象是Java反射机制的核心,它包含了一个类运行时信息。
反射核心类
在Java中,Class类是一个重要的核心类,它用于表示一个类或接口的运行时信息。每个类在Java虚拟机种都有一个对应的Class对象,可以通过该对象获取类的构造函数、方法、属性等信息,并且可以进行实例化对象、方法调用和数据成员访问等操作。
Class核心类JavaSE源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package java.lang;
//字节码类
public final class Class<T> implements java.io.Serializable,
GenericDeclaration,
Type,
AnnotatedElement {
//省略...
//获取类的所有构造方法
public Constructor<?>[] getConstructors() throws SecurityException {}
//获取类的所有数据成员
public Field[] getFields() throws SecurityException {}
//获取类的所有成员方法
public Method[] getMethods() throws SecurityException {}
}在Java反射中, Class 、 Constructor 、 Method 和 Field 是表示类的不同部分的关键类。它们提供了访问和操作类的构造函数、方法和字段的方法。
- Class类:表示一个类的运行时信息。通过Class类可以获取类的构造函数、方法和字段等信息。可以使用Class.forName()方法获取一个类的Class对象,也可以通过对象的getClass() 方法获取其对应的Class对象。(其实是.class的一个镜像)
- Constructor类:表示一个类的构造函数。通过Constructor类可以创建类的实例。可以使用Class对象的getConstructors() 或 getConstructor() 方法获取构造函数的对象。
- Method 类:表示一个类的方法。通过 Method 类可以调用类的方法。可以使用 Class 对象的 getMethods() 或 getMethod() 方法获取方法的对象。
- Field 类:表示一个类的字段。通过 Field 类可以访问和修改类的字段的 值。可以使用 Class 对象的 getFields() 或 getField() 方法获取字段的 对象。
字节码对象
JVM虚拟机对类进行加载时,会在堆空间创建一个 字节码对象(Class类对象)
简单来说:如果要用反射机制,则必须先获取类的字节码对象
获取Class对象方式:
- 使用类字面常量:类名.class
- Object类中方法:对象.getClass() public final native Class<?> getClass();
- 借助Class类中方法:Class.forName(“类的全包名”) public static Class<?> forName(String className);
1
2
3
4
5
6
7
8
9
10 public class Test043_Class {
public static void main(String[] args) {
String s = "hello";
Class<? extends String> aClass = s.getClass();
Class<String> aClass1 = String.class;
System.out.println("aClass = " + aClass); // aClass = class java.lang.String
System.out.println("aClass1 = " + aClass1); // aClass1 = class java.lang.String
System.out.println(aClass == aClass1); // true
}
}使用3种不同方式,获取自定义类的字节码对象,并验证是否唯一
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 public class Test043_Class {
public static void main(String[] args) throws ClassNotFoundException {
Student stu = new Student();
Class<? extends Student> aClass = stu.getClass();
Class<?> aClass1 = Class.forName("com.briup.chap13.pojo.Student");
Class<Student> aClass2 = Student.class;
System.out.println("aClass = " + aClass);
System.out.println("aClass == aClass1 : " + (aClass == aClass1));
System.out.println("aClass2 == aClass1 : " + (aClass2 == aClass1));
System.out.println("aClass == aClass2 : " + (aClass == aClass2));
}
}
// aClass = class com.briup.chap13.pojo.Student
// aClass == aClass1 : true
// aClass2 == aClass1 : true
// aClass == aClass2 : true一个字节码对象,有且只有一个
补充:其他类型字节码对象获取
- 基本数据类型获取字节码对象固定格式:数据类型.class
- 数据类型获取格式:数据类型[].class; 数组名.getClass();
1
2
3
4
5
6
7
8
9
10
11
12 public static void main(String[] args) {
// 基本数据类型
Class<Integer> integerClass = int.class;
System.out.println(integerClass); // int
System.out.println(double.class); // double
// 数据类型
Class<int[]> aClass = int[].class;
int[] arr = new int[10];
Class<? extends int[]> aClass1 = arr.getClass();
System.out.println("aClass = " + aClass); // class [I
System.out.println("aClass1 = " + aClass1); // class [I
}
构造方法
通过反射可以获取类的构造方法(含private)对象,并借助其实例化对象。
- 构造器相关方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 public class Test044_Constructor {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException {
// 1、获取字节码对象
Class<?> clazz = Class.forName("com.briup.chap13.pojo.Student");
// 2、由字节码对象获取所有的public构造器
Constructor<?>[] constructors = clazz.getConstructors();
for (Constructor<?> con : constructors) {
System.out.println(con);
}
System.out.println("---------------------------------------------");
// 3.获取所有构造方法(含private)
Constructor<?>[] declaredConstructors = clazz.getDeclaredConstructors();
for (Constructor<?> con : declaredConstructors) {
System.out.println(con);
}
System.out.println("---------------------------------------------");
// 4、返回指定的构造方法
Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class, String.class, int.class);
System.out.println(declaredConstructor);
}
}
- Constructor类创建对象方法
1
2
3
4
5
6
7
8
9
10
11 // 获取空参构造器创建对象
Class<?> clazz = Class.forName("com.briup.chap13.pojo.Student");
Constructor<?> con = clazz.getConstructor();
Student stu = (Student) con.newInstance();
System.out.println("stu = " + stu); // stu = Student{id='null', name='null', age=0}
// 获取私有private构造器
Constructor<?> con2 = clazz.getDeclaredConstructor(String.class);
con2.setAccessible(true);
Student stu2 = (Student)con2.newInstance("1001");
System.out.println("stu2 = " + stu2); // stu2 = Student{id='1001', name='null', age=0}
成员变量
通过反射可以获取类的所有数据成员(含private)对象,进而实现数据成员值的获取与设置。
- Filed相关方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 public class Test045_Field {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
Class<?> clazz = Class.forName("com.briup.chap13.pojo.HighStudent2");
System.out.println("clazz = " + clazz);
System.out.println("-----------------------------------------------");
// 获取所有的公共变量(包含继承的)
Field[] fields = clazz.getFields();
for (Field field : fields) {
System.out.println(field);
}
System.out.println("-----------------------------------------------");
// 获取所有的属性包含 private的,但是没有继承过来的
Field[] declaredFields = clazz.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
// 获取指定的public属性(含继承的)
Field accumulatedMoney = clazz.getField("accumulatedMoney");
System.out.println("accumulatedMoney = " + accumulatedMoney);
}
}
- 属性获取及设置方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class Test045_Field {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// 1、获取字节码对象
Class<?> clazz = Class.forName("com.briup.chap13.pojo.Student");
System.out.println(clazz);
// 2、获取public属性
Field f1 = clazz.getField("id");
Student stu = new Student("1001", "lwsj", 21);
String id = (String) f1.get(stu);
System.out.println("id = " + id);
// 3.借助属性对象 获取属性值
// 属性是依赖对象而存在的,所以:
// 注意:通过反射里面属性 来获取 属性值,一定要 依赖普通对象
f1.set(stu, "1002");
System.out.println("after set, stu" + stu);
// 4、获取private属性
Field f2 = clazz.getDeclaredField("name");
// 设置可以访问
f2.setAccessible(true);
// 5、获取private属性值输出,然后修改
String name = (String) f2.get(stu);
System.out.println("name = " + name);
f2.set(stu,"peter");
System.out.println("stu = " + stu);
}
}
成员方法
通过反射可以获取类里面所有的成员(含私有)方法,并调用。
- Method获取相关方法
- Method对象调用方法
- 参数一:用obj对象调用方法
- 参数二:调用方法的传递的参数(如果没有就不写)
- 返回值:方法的返回值(如果没有就不写)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26 public class Test046_Method {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 1、获取字节码对象
Class<Student> clazz = Student.class;
// 2、获取所有的public method对象(含继承的)
Method[] methods = clazz.getMethods();
for (Method m : methods) {
System.out.println(m);
}
System.out.println("--------------------------------");
// 3、获取指定的public 方法
Method m1 = clazz.getMethod("getId");
System.out.println("m1 = " + m1);
Student stu = new Student("10001", "lwsj", 23);
// 4、使用method对象调用方法
// 不带参数但是有返回值
String id = (String) m1.invoke(stu);
System.out.println("id = " + id);
// 带参数没有返回值
Method m2 = clazz.getMethod("setName", String.class);
// 如果是私有方法记得给访问权限
// m2.setAccessible(true);
m2.invoke(stu, "peter");
System.out.println("stu = " + stu);
}
}
面试题
1
2
3
4 现有一个集合定义如下:
List<Integer> list = new ArrayList<>();
要求,往list集合中添加元素:"hello"、123、3.14
请编码实现。
1
2
3
4
5
6
7
8
9
10
11
12
13
14 public class Test047_Question {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
ArrayList<Integer> list = new ArrayList<>();
Class<? extends ArrayList> clazz = list.getClass();
Method m1 = clazz.getMethod("add", Object.class);
m1.invoke(list, "hello");
m1.invoke(list,123);
m1.invoke(list,3.14);
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}注意:泛型只在编译阶段做语法检查,运行期间会被自动忽略
- 测试类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 public class Test {
public static void main(String[] args) {
List<Integer> list1 = new ArrayList<>();
List<String> list2 = new ArrayList<>();
Class<? extends List> clazz1 = list1.getClass();
Class<? extends List> clazz2 = list2.getClass();
System.out.println(clazz1.getName());
System.out.println(clazz2.getName());
System.out.println(clazz1 == clazz2);
}
}
// java.util.ArrayList
// java.util.ArrayList
// true
❤️❤️❤️忙碌的敲代码也不要忘了浪漫鸭!
三军可夺帅也,匹夫不可夺志也。💪