Java基础知识(十)
类的生命周期:
硬盘阶段
在 Java 开发过程中,硬盘阶段主要涉及 Java 源代码文件和字节码文件在硬盘上的存储。
- Java 源代码文件(.java):开发人员编写的 Java 代码保存在以
.java
为扩展名的文件中,这些文件存储在硬盘上。此时的代码是人类可读的高级语言形式,包含了类的定义、方法、属性等内容。例如,定义一个简单的HelloWorld
类:
public class HelloWorld {public static void main(String[] args) {System.out.println("Hello, World!");}
}
- 字节码文件(.class) :使用
javac
命令对.java
文件进行编译,会生成对应的.class
字节码文件, 这些字节码文件同样存储在硬盘上。字节码是一种中间形式,它不依赖于具体的操作系统和硬件,是 Java 实现 “一次编写,到处运行” 特性的关键。比如对上述HelloWorld.java
编译后,会生成HelloWorld.class
文件。
类阶段
类阶段涵盖了从类加载到类卸载的一系列过程:
- 加载(Loading):当 Java 虚拟机(JVM)需要使用某个类时,类加载器会根据类的全限定名(包名 + 类名)找到对应的
.class
字节码文件,并将其内容加载到 JVM 中。加载过程中,会在方法区中创建该类的相关数据结构,并在堆中创建一个java.lang.Class
对象,作为对该类的访问入口。例如,当执行java HelloWorld
命令运行程序时,JVM 会先加载HelloWorld
类。 - 验证(Verification):确保加载进来的字节码文件符合 JVM 规范,保证其安全性和正确性。比如验证字节码文件格式是否正确、访问控制是否合理等,防止恶意代码或错误代码对 JVM 造成损害。
- 准备(Preparation):为类变量(使用
static
修饰的变量)分配内存并设置初始默认值,这些内存是在方法区中分配的。例如,对于public static int num = 10;
,在准备阶段,num
会被初始化为 0,而不是 10,10 的赋值操作是在初始化阶段完成。 - 解析(Resolution):将常量池中的符号引用转换为直接引用。符号引用是一种描述性的引用方式,比如使用类名、方法名等字符串来表示引用;直接引用则是指向目标的指针、句柄等。解析操作可以在类加载的初始化之前或之后进行,它主要针对类或接口、字段、类方法、接口方法等。
- 初始化(Initialization):真正执行类中定义的静态代码块、对类变量进行赋值操作等。JVM 会根据代码中定义的顺序,执行静态代码块和类变量的赋值语句。例如:
public class StaticInitialization {public static int num;static {num = 10;}
}
在初始化阶段,num
会被赋值为 10 。
6. 使用(Using):类被加载、初始化完成后,就可以在程序中创建类的实例(对象),调用类的方法、访问类的属性等。比如HelloWorld
类,在使用阶段就会执行main
方法中的代码,输出Hello, World!
。
7. 卸载(Unloading):当类不再被引用,满足一定条件(如类的所有实例都被回收、加载该类的类加载器被回收等)时,JVM 会将类从内存中卸载,释放相关的内存资源。
Java 反射机制详解
反射(Reflection)是 Java 语言的一个重要特性,它允许程序在运行时获取类的信息并操作类或对象的属性、方法等成员。通过反射,我们可以在编译时未知类信息的情况下,动态地创建对象、调用方法和访问属性。
1. 反射的基本概念
反射机制允许程序:
- 在运行时获取任意类的字节码信息
- 在运行时构造任意类的对象
- 在运行时调用任意类的方法
- 在运行时访问和修改任意类的属性
反射的核心是java.lang.Class
类,它代表了一个类的字节码,并提供了获取类信息的方法。
2. 获取 Class 对象的三种方式
获取Class
对象是使用反射的第一步,有三种常用方式:
// 方式1:通过类名.class获取
Class<?> clazz1 = String.class;// 方式2:通过对象的getClass()方法获取
String str = "Hello";
Class<?> clazz2 = str.getClass();// 方式3:通过Class.forName()方法获取(最常用,可动态加载类)
Class<?> clazz3 = Class.forName("java.lang.String");
3. 反射常用类
Java 反射主要涉及以下类(均位于java.lang.reflect
包):
Class
:代表类的字节码Constructor
:代表类的构造方法Method
:代表类的方法Field
:代表类的成员变量Parameter
:代表方法的参数(Java 8+)
4. 反射的基本操作
4.1 创建对象
通过反射创建对象有两种方式:
public class Person {private String name;private int age;public Person() {}public Person(String name, int age) {this.name = name;this.age = age;}// getter、setter和toString方法省略
}
// 获取Class对象
Class<?> personClass = Class.forName("com.example.Person");// 方式1:通过无参构造方法创建对象(要求类必须有无参构造)
Person person1 = (Person) personClass.newInstance();// 方式2:通过指定构造方法创建对象
Constructor<?> constructor = personClass.getConstructor(String.class, int.class);
Person person2 = (Person) constructor.newInstance("Alice", 20);
4.2 获取和调用方法
// 获取所有公共方法(包括继承的)
Method[] methods = personClass.getMethods();// 获取类自身声明的所有方法(包括私有)
Method[] declaredMethods = personClass.getDeclaredMethods();// 获取指定公共方法
Method setNameMethod = personClass.getMethod("setName", String.class);// 获取指定私有方法
Method privateMethod = personClass.getDeclaredMethod("privateMethod");
// 设置访问权限(关键)
privateMethod.setAccessible(true);// 调用方法
Person person = new Person();
setNameMethod.invoke(person, "Bob"); // 调用public方法
privateMethod.invoke(person); // 调用private方法
4.3 获取和操作属性
// 获取所有公共属性(包括继承的)
Field[] fields = personClass.getFields();// 获取类自身声明的所有属性(包括私有)
Field[] declaredFields = personClass.getDeclaredFields();// 获取指定公共属性
Field publicField = personClass.getField("publicField");// 获取指定私有属性
Field nameField = personClass.getDeclaredField("name");
// 设置访问权限
nameField.setAccessible(true);// 操作属性
Person person = new Person();
nameField.set(person, "Charlie"); // 设置私有属性值
String name = (String) nameField.get(person); // 获取私有属性值
5. 反射的应用场景
- 框架开发:Spring、MyBatis 等框架大量使用反射实现对象创建和依赖注入
- 动态代理:AOP 实现的基础
- 序列化与反序列化:将对象转换为字节流或从字节流恢复对象
- 注解处理:通过反射获取注解信息并处理
- ORM 框架:实现对象与数据库表的映射
6. 反射示例:简易 ORM 框架
import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;public class SimpleORM {// 保存对象到数据库public static void save(Object obj) throws Exception {Class<?> clazz = obj.getClass();StringBuilder sql = new StringBuilder("INSERT INTO ");sql.append(clazz.getSimpleName().toLowerCase()).append(" (");Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {sql.append(field.getName()).append(",");}// 移除最后一个逗号sql.deleteCharAt(sql.length() - 1);sql.append(") VALUES (");for (int i = 0; i < fields.length; i++) {sql.append("?,");}// 移除最后一个逗号sql.deleteCharAt(sql.length() - 1);sql.append(")");// 实际开发中需要获取数据库连接Connection conn = null; // 获取连接的代码省略PreparedStatement pstmt = conn.prepareStatement(sql.toString());for (int i = 0; i < fields.length; i++) {fields[i].setAccessible(true);pstmt.setObject(i + 1, fields[i].get(obj));}pstmt.executeUpdate();// 关闭资源的代码省略}
}
7. 反射的优缺点
优点
- 提高程序灵活性和扩展性
- 实现动态加载类和创建对象
- 框架开发的基础
缺点
- 性能开销较大:反射操作比直接调用慢
- 破坏封装性:可以访问私有成员
- 代码可读性降低:反射代码相对复杂
- 安全问题:可能绕过安全检查
8. 反射性能优化
- 缓存 Class 对象:避免多次获取 Class 对象
- 设置 setAccessible (true):关闭安全检查,提高性能
- 使用 MethodHandle(Java 7+):比反射性能更好
- 避免在循环中使用反射:将反射操作移到循环外
// 缓存Method示例
public class ReflectionCache {private static Map<String, Method> methodCache = new HashMap<>();public static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {String key = clazz.getName() + "#" + methodName + Arrays.toString(parameterTypes);if (!methodCache.containsKey(key)) {Method method = clazz.getMethod(methodName, parameterTypes);method.setAccessible(true);methodCache.put(key, method);}return methodCache.get(key);}
}
9. 注意事项
- 反射会破坏封装,应谨慎使用
- 反射可能导致代码难以理解和维护
- 某些安全管理器可能会禁止反射操作
- 对于 final 字段,反射修改可能不会生效(取决于 JVM 实现)
- 使用反射时应处理各种可能的异常
总结
反射是 Java 中一个强大的特性,它允许程序在运行时操作类和对象的成员,为框架开发提供了极大的灵活性。然而,反射也有其缺点,如性能开销和封装性问题。在实际开发中,应根据具体需求权衡使用,并注意性能优化。
反射的核心是Class
类,通过它可以获取类的各种信息,并借助Constructor
、Method
和Field
等类实现对类成员的操作。合理使用反射可以极大地提高代码的灵活性和扩展性。
整理成笔记,尽量详细
Java 反射实操笔记
一、反射核心 API 梳理
1.1 类对象获取方式
获取方式 | 语法 | 适用场景 |
---|---|---|
Class.forName() | Class clazz = Class.forName("全类名"); | 编译时未知类,需动态加载(如读取配置文件) |
类名.class | Class clazz = 类名.class; | 编译时已知类,直接通过类名获取 |
对象.getClass() | Class clazz = 对象名.getClass(); | 已有对象实例,通过对象反推类信息 |
1.2 成员变量操作 API
1. 获取成员变量
getDeclaredFields()
:获取类中所有成员变量(包括 private、public、protected 修饰的)Field[] fields = pclass.getDeclaredFields(); for(Field item : fields) {System.out.println(item); }
getDeclaredField(String name)
:获取指定名称的成员变量(包括私有)Field field_age = pclass.getDeclaredField("age");
getFields()
:仅获取类中 public 修饰的成员变量Field[] public_fields = pclass.getFields();
getField(String name)
:获取指定名称的 public 成员变量Field field_from = pclass.getField("from");
- 设置成员变量值:
field.set(Object obj, Object value)
- 获取成员变量值:
field.get(Object obj)
- 访问私有成员变量:需要先调用
setAccessible(true)
取消访问检查// 设置 public 成员变量 field_from.set(person, "中国");// 设置 private 成员变量 field_age.setAccessible(true); // 关键:允许访问私有变量 field_age.set(person, 20);// 获取成员变量值 System.out.println(field_age.get(person)); System.out.println(field_from.get(person));
- 设置成员变量值:
方法 | 访问权限范围 | 功能描述 |
---|---|---|
getDeclaredFields() | 类自身声明的所有成员变量(包括 private、protected、public) | 获取类的所有成员变量数组 |
getDeclaredField(String name) | 类自身声明的指定成员变量(任意权限) | 获取单个指定名称的成员变量 |
getFields() | 类及父类的 public 成员变量 | 仅获取公共成员变量数组 |
getField(String name) | 类及父类的指定 public 成员变量 | 获取单个指定名称的公共成员变量 |
set(Object obj, Object value) | 无(需配合setAccessible(true) ) | 给指定对象的成员变量赋值 |
get(Object obj) | 无(需配合setAccessible(true) ) | 获取指定对象的成员变量值 |
setAccessible(true) | 暴力解除访问权限检查 | 让 private 成员变量可被操作(核心!) |
1.3 成员方法操作 API
获取方法
getDeclaredMethods()
:获取类中所有方法(包括私有)Method[] methods = pclass.getDeclaredMethods();
getDeclaredMethod(String name, Class<?>... parameterTypes)
:获取指定方法(包括私有)Method method_run = pclass.getDeclaredMethod("run");
getMethods()
:获取类中所有 public 方法(包括继承的)Method[] public_method = pclass.getMethods();
getMethod(String name, Class<?>... parameterTypes)
:获取指定的 public 方法Method method_get = pclass.getMethod("getAge");
2. 调用方法
- 使用
method.invoke(Object obj, Object... args)
调用方法 - 调用私有方法需先设置
setAccessible(true)
// 调用 public 方法 method_get.invoke(person);// 调用 private 方法 method_run.setAccessible(true); method_run.invoke(person);
方法 | 访问权限范围 | 功能描述 |
---|---|---|
getDeclaredMethods() | 类自身声明的所有方法(任意权限) | 获取类的所有方法数组 |
getDeclaredMethod(String name, Class<?>... paramTypes) | 类自身声明的指定方法(任意权限) | 获取单个指定名称 + 参数类型的方法 |
getMethods() | 类及父类的 public 方法 | 仅获取公共方法数组(含 Object 类方法) |
getMethod(String name, Class<?>... paramTypes) | 类及父类的指定 public 方法 | 获取单个指定名称 + 参数类型的公共方法 |
invoke(Object obj, Object... args) | 无(需配合setAccessible(true) ) | 执行指定对象的方法,args 为方法参数 |
getName() | 无 | 获取方法的名称(字符串形式) |
setAccessible(true) | 暴力解除访问权限检查 | 让 private 方法可被调用 |
1.4 构造方法操作 API
. 获取构造器
getDeclaredConstructors()
:获取所有构造器(包括私有)Constructor[] constructors = pclass.getDeclaredConstructors();
getDeclaredConstructor(Class<?>... parameterTypes)
:获取指定构造器(包括私有)Constructor constructor = pclass.getDeclaredConstructor(); Constructor constructor2 = pclass.getDeclaredConstructor(String.class, int.class);
getConstructors()
:获取所有 public 构造器Constructor[] public_constructors = pclass.getConstructors();
getConstructor(Class<?>... parameterTypes)
:获取指定的 public 构造器Constructor public_constructor = pclass.getConstructor(); Constructor public_constructor2 = pclass.getConstructor(String.class);
2. 创建对象
- 通过构造器的
newInstance(Object... initargs)
方法创建对象// 无参构造器创建对象 Object object = constructor.newInstance();// 有参构造器创建对象 Person object2 = (Person) constructor2.newInstance("lili", 20);
方法 | 访问权限范围 | 功能描述 |
---|---|---|
getDeclaredConstructors() | 类自身声明的所有构造方法(任意权限) | 获取所有构造方法数组 |
getDeclaredConstructor(Class<?>... paramTypes) | 类自身声明的指定构造方法(任意权限) | 获取单个指定参数类型的构造方法(无参则传空) |
getConstructors() | 类自身声明的 public 构造方法 | 仅获取公共构造方法数组 |
getConstructor(Class<?>... paramTypes) | 类自身声明的指定 public 构造方法 | 获取单个指定参数类型的公共构造方法 |
newInstance(Object... args) | 无(需配合setAccessible(true) ) | 通过构造方法创建对象实例,args 为构造参数 |
1.5 类信息获取 API
方法 | 返回值 | 功能描述 |
---|---|---|
getName() | String | 获取类的全名称(包名 + 类名,如com.qcby.reflect.Person ) |
getSimpleName() | String | 获取类的简单名称(仅类名,如Person ) |
二、配置文件与核心类
2.1 配置文件:refconfig.properties
用于存储反射所需的类名和方法名,实现 “配置化加载”,避免硬编码。
# 全类名(包名+类名)
reflect.className=com.qcby.reflect.Person
# 要调用的方法名
reflect.methodName=getAge
2.2 目标类:Person.java
反射操作的目标类,包含不同权限的成员变量、方法和构造方法。
package com.qcby.reflect;public class Person {// 私有成员变量private String name;private int age;// 公共成员变量public String from;public String height;// 公共getter/setter(访问私有变量)public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {System.out.println("getAge方法执行");return age;}public void setAge(int age) {this.age = age;}// 私有方法private void run() {System.out.println("run方法执行");}// 构造方法(无参、1参、2参,均为public)public Person() {}public Person(String name) {this.name = name;}public Person(String name, int age) {this.name = name;this.age = age;}// 重写toString,便于打印对象信息@Overridepublic String toString() {return "Person [name=" + name + ", age=" + age + " , from = "+from+" , height = "+height+"]";}
}
三、反射实操案例
3.1 步骤 1:获取类对象
三种获取Person
类对象的方式,文档 3 中均有演示:
// 方式1:通过Class.forName(推荐,支持动态加载)
Class pclass1 = Class.forName("com.qcby.reflect.Person");// 方式2:通过类名.class(编译时已知类)
Class pclass2 = Person.class;// 方式3:通过对象.getClass(已有对象实例)
Person person = new Person();
Class pclass3 = person.getClass();
3.2 步骤 2:操作成员变量
2.1 获取成员变量
// 1. 获取类自身所有成员变量(包括private)
Field[] allFields = pclass.getDeclaredFields();
for (Field item : allFields) {System.out.println(item); // 输出:private java.lang.String com.qcby.reflect.Person.name、private int com.qcby.reflect.Person.age 等
}// 2. 获取单个指定私有成员变量(如age)
Field ageField = pclass.getDeclaredField("age");
System.out.println(ageField); // 输出:private int com.qcby.reflect.Person.age// 3. 获取所有public成员变量(包括父类,此处父类为Object,无额外public变量)
Field[] publicFields = pclass.getFields();
for (Field item : publicFields) {System.out.println(item); // 输出:public java.lang.String com.qcby.reflect.Person.from、public java.lang.String com.qcby.reflect.Person.height
}// 4. 获取单个指定public成员变量(如from)
Field fromField = pclass.getField("from");
System.out.println(fromField); // 输出:public java.lang.String com.qcby.reflect.Person.from
2.2 赋值与取值
// 1. 给public变量赋值(直接操作,无需暴力反射)
Person person = new Person();
fromField.set(person, "中国"); // 给person的from变量赋值为“中国”// 2. 给private变量赋值(需先暴力解除权限)
ageField.setAccessible(true); // 关键:解除private权限检查
ageField.set(person, 20); // 给person的age变量赋值为20// 3. 获取变量值
System.out.println(ageField.get(person)); // 输出:20(获取private变量age的值)
System.out.println(fromField.get(person)); // 输出:中国(获取public变量from的值)
System.out.println(person); // 输出:Person [name=null, age=20 , from = 中国 , height = null](通过toString验证)
3.3 步骤 3:操作成员方法
3.1 获取成员方法
// 1. 获取类自身所有方法(包括private)
Method[] allMethods = pclass.getDeclaredMethods();
for (Method item : allMethods) {System.out.println(item); // 输出:private void com.qcby.reflect.Person.run()、public int com.qcby.reflect.Person.getAge() 等
}// 2. 获取单个指定private方法(如run,无参数)
Method runMethod = pclass.getDeclaredMethod("run");
System.out.println(runMethod); // 输出:private void com.qcby.reflect.Person.run()// 3. 获取所有public方法(含父类Object的方法,如toString、equals)
Method[] publicMethods = pclass.getMethods();
for (Method item : publicMethods) {System.out.println(item); // 输出:public int com.qcby.reflect.Person.getAge()、public java.lang.String com.qcby.reflect.Person.toString() 等System.out.println(item.getName()); // 输出方法名,如getAge、toString
}// 4. 获取单个指定public方法(如getAge,无参数)
Method getAgeMethod = pclass.getMethod("getAge");
System.out.println(getAgeMethod); // 输出:public int com.qcby.reflect.Person.getAge()
3.2 调用方法
Person person = new Person();
// 1. 调用public方法(直接调用)
getAgeMethod.invoke(person); // 输出:getAge方法执行(方法内部打印)// 2. 调用private方法(需先暴力解除权限)
runMethod.setAccessible(true); // 关键:解除private权限检查
runMethod.invoke(person); // 输出:run方法执行(方法内部打印)
3.4 步骤 4:操作构造方法
4.1 获取构造方法
// 1. 获取类自身所有构造方法(包括非public,此处Person构造均为public)
Constructor[] allConstructors = pclass.getDeclaredConstructors();
for (Constructor item : allConstructors) {System.out.println(item); // 输出:public com.qcby.reflect.Person()、public com.qcby.reflect.Person(java.lang.String)、public com.qcby.reflect.Person(java.lang.String,int)
}// 2. 获取单个指定构造方法(无参)
Constructor noArgConstructor = pclass.getDeclaredConstructor();
System.out.println(noArgConstructor); // 输出:public com.qcby.reflect.Person()// 3. 获取单个指定构造方法(2参:String + int)
Constructor twoArgConstructor = pclass.getDeclaredConstructor(String.class, int.class);
System.out.println(twoArgConstructor); // 输出:public com.qcby.reflect.Person(java.lang.String,int)// 4. 获取所有public构造方法(与getDeclaredConstructors()结果一致,因Person构造均为public)
Constructor[] publicConstructors = pclass.getConstructors();
for (Constructor item : publicConstructors) {System.out.println(item);
}// 5. 获取单个指定public构造方法(1参:String)
Constructor oneArgConstructor = pclass.getConstructor(String.class);
System.out.println(oneArgConstructor); // 输出:public com.qcby.reflect.Person(java.lang.String)
4.2 创建对象实例
// 1. 通过无参构造创建对象
Object person1 = noArgConstructor.newInstance();
System.out.println(person1); // 输出:Person [name=null, age=0 , from = null , height = null]// 2. 通过2参构造创建对象(需强转为Person类型)
Person person2 = (Person) twoArgConstructor.newInstance("lili", 20);
System.out.println(person2); // 输出:Person [name=lili, age=20 , from = null , height = null]
person2.getAge(); // 调用对象方法,输出:getAge方法执行
3.5 步骤 5:结合配置文件动态反射
通过Properties
读取配置文件,动态获取类名和方法名,实现 “不修改代码,仅改配置即可切换反射目标”。
// 1. 创建Properties对象,用于读取配置文件
Properties properties = new Properties();// 2. 加载配置文件(通过类加载器获取输入流,路径为“包名/文件名”)
InputStream in = Person.class.getClassLoader().getResourceAsStream("com/qcby/reflect/refconfig.properties");
properties.load(in); // 加载配置文件内容// 3. 从配置文件中获取类名和方法名
String className = properties.getProperty("reflect.className"); // 取值:com.qcby.reflect.Person
String methodName = properties.getProperty("reflect.methodName"); // 取值:getAge// 4. 动态获取类对象
Class pclass = Class.forName(className);
System.out.println(pclass); // 输出:class com.qcby.reflect.Person// 5. 后续可继续动态操作(如获取方法、调用方法)
// 示例:获取所有成员变量
Field[] fields = pclass.getDeclaredFields();
for (Field item : fields) {System.out.println(item); // 输出Person的所有成员变量
}
四、关键注意事项
setAccessible(true)
的作用:
解除 Java 的访问权限检查,仅对getDeclaredXXX()
获取的私有成员有效,getXXX()
获取的公共成员无需此操作。- 异常处理:
反射操作可能抛出ClassNotFoundException
(类找不到)、NoSuchFieldException
(字段找不到)、NoSuchMethodException
(方法找不到)、IllegalAccessException
(权限不足)等,需在方法上声明throws Exception
或捕获处理。 - 类型强转:
通过newInstance()
创建的对象默认是Object
类型,需根据实际类强转为目标类型(如(Person) constructor.newInstance()
)。 - 性能问题:
反射操作比直接调用慢(需动态解析类信息),频繁操作时建议缓存Class
、Method
、Field
对象,减少重复获取。