一、反射
1.1 什么是反射
反射操作的第一步:获取某个类的Class对象。
反射机制使得Java可以实现动态编程语言的效果,可以在运行时再确定是哪个类,再创建对象,再调用具体的方法等。
有反射
package com.mytest.reflect;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public class TestUseReflect {
public static void main(String[] args) throws Exception{
//下面所有""引起来的,都是字符串,它们可以通过键盘输入,或者读取xx配置文件来动态获取
//而不是在代码中写死
Class clazz = Class.forName("com.mytest.reflect.Teacher");
Constructor constructor = clazz.getConstructor();//获取Teacher类的无参构造
Object t = constructor.newInstance();
Field idField = clazz.getDeclaredField("id");
idField.setAccessible(true);
idField.set(t, 1);
Field nameField = clazz.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(t,"李四");
System.out.println(t);
}
}
上述代码,编译没有任何问题,完全通过。因为Teacher类只要保证在运行时能正常加载就可以了。
以下代码只要保证运行时是存在的,上面的代码就可以正常运行。
package com.mytest.reflect;
public class Teacher {
private int id;
private String name;
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
1.2 获取Class对象(掌握)
一共有四种方式来获取Class对象:
- 类型名.class
- 这里的类型是指Java中的任意数据类型,包括8种基本数据类型,void,引用数据类型(数组、类、接口、枚举、注解、记录类、密封类等)。
- 对象.getClass()
- 这个方法是Object类中定义的,适用于所有引用数据类型
- Class.forName("类型的全名称")
- 这个方法适用于核心类库中或自定义的引用数据类型。不适用于基本数据类型和void、数组,因为它们是JVM内置的数据类型。它们找不到对应的.class文件。
- 类加载器对象.loadClass("类型的全名称")
- 同Class.forName("类型的全名称")
package com.mytest.reflect;
import org.junit.Test;
import java.io.Serializable;
import java.time.Month;
public class TestClass {
@Test
public void test()throws Exception{
Class c1 = int.class;//基本数据类型
Class c2 = void.class;//空类型
Class c3 = int[].class;//数组类型
Class c4 = String.class;//类类型
Class c5 = Serializable.class;//接口类型
Class c6 = Month.class;//枚举类型
Class c7 = Override.class;//注解类型
Class c8 = Class.class;//Class是一个类
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c5);
System.out.println(c6);
System.out.println(c7);
System.out.println(c8);
}
@Test
public void test2()throws Exception{
String str = "hello";
Class c1 = str.getClass();//获取的是str变量中引用的对象的“类型”
Object obj = "world";
Class c2 = obj.getClass();//获取的是obj变量中引用的对象的“类型”
//getClass()是获取对象的运行时类型,看右边的类型,即看new的对象,
//"",比较特殊,看不到new,等价于 new String对象
System.out.println(c1);
System.out.println(c2);
System.out.println(c1 == c2);
}
@Test
public void test3()throws Exception{
Class c1 = String.class;
Object obj = "world";
Class c2 = obj.getClass();//获取的是obj变量中引用的对象的“类型”
Class<?> c3 = Class.forName("java.lang.String");
ClassLoader classLoader = ClassLoader.getSystemClassLoader();//获取类加载器对象
Class<?> c4 = classLoader.loadClass("java.lang.String");
System.out.println(c1);
System.out.println(c2);
System.out.println(c3);
System.out.println(c4);
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
System.out.println(c1 == c4);//true
}
@Test
public void test4()throws Exception{
Class c1 = int.class;
Class c2 = String.class;
System.out.println(c1==c2);//false
}
@Test
public void test5()throws Exception{
Class c1 = int[].class;
Class c2 = int[][].class;
Class c3 = String[].class;
System.out.println(c1 == c2);//false 维度不同,一个一维,一个二维
System.out.println(c3 == c2);//false 维度和元素类型都不同
System.out.println(c3 == c1);//false 元素类型不同
int[] arr = {1,2,3,4,5};
int[] nums = {1,2,2,5,3,3,5,6,8,7,8,10};
Class<? extends int[]> c4 = arr.getClass();
Class<? extends int[]> c5 = nums.getClass();
System.out.println(c4 == c5);//true
}
}
强调:每一种数据类型,在JVM中有且只有唯一的Class对象。
- 相同的数据类型,Class对象是同一个。
- 不同的数据类型,Class对象是不同的。
1.3 反射的应用
1.3.1 查看类型的详细信息
package com.mytest.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestStringInfo {
@Test
public void test1()throws Exception{
Class c1 = Class.forName("java.lang.String");
Package pkg = c1.getPackage();
System.out.println("包名:" + pkg.getName());
int modifiers = c1.getModifiers();
System.out.println("修饰符:" + modifiers);
System.out.println("修饰符:" + Modifier.toString(modifiers));
/*
Java中每一种修饰符都有一个数字代表它:
十六进制 十进制 二进制(以1个字节为例)
public static final int PUBLIC = 0x00000001; 1 00000001
public static final int PRIVATE = 0x00000002; 2 00000010
public static final int PROTECTED = 0x00000004; 4 00000100
public static final int STATIC = 0x00000008; 8 00001000
public static final int FINAL = 0x00000010; 16 00010000
public static final int SYNCHRONIZED = 0x00000020; 32 00100000
。。。。
String类的修饰符是 17
17的二进制: 00010001
*/
}
@Test
public void test2()throws Exception{
Class c1 = Class.forName("java.lang.String");
System.out.println("类名:" + c1.getName());
Class superclass = c1.getSuperclass();//String的父类
System.out.println("String的父类:" + superclass);
System.out.println("String的父接口们:");
Class[] interfaces = c1.getInterfaces();//String的父接口们
for (Class f : interfaces) {
System.out.println(f);
}
}
@Test
public void test3()throws Exception{
Class c1 = Class.forName("java.lang.String");
/*
类的成员:成员变量、成员方法、构造器、代码块、内部类
除了代码块都能获取。
为什么代码块获取不了呢?
因为代码块在从.java文件 -> .class文件的编译过程中,就被组装的对应的方法中了。
静态代码块 -> <clinit>类初始化方法
非静态代码块 会和构造器等代码一起 -> <init>实例初始化方法中
或者另一种角度来说,代码块是随着别的代码自动执行的,不需要单独调用。
静态代码块 -> 随着类加载自动执行。
静态代码块 -> 随着new对象自动执行。
*/
}
@Test
public void test4()throws Exception{
Class c1 = Class.forName("java.lang.String");
//获取String类的所有成员变量
Field[] declaredFields = c1.getDeclaredFields();
for (Field field : declaredFields) {
System.out.println(field);
}
}
@Test
public void test5()throws Exception{
Class c1 = Class.forName("java.lang.String");
//获取String类的所有构造器
Constructor[] declaredConstructors = c1.getDeclaredConstructors();
for (Constructor c : declaredConstructors) {
System.out.println(c);
}
}
@Test
public void test6()throws Exception{
Class c1 = Class.forName("java.lang.String");
//获取String类的所有方法
Method[] declaredMethods = c1.getDeclaredMethods();
for (Method m : declaredMethods) {
System.out.println(m);
}
}
@Test
public void test7()throws Exception{
Class c1 = Class.forName("java.lang.String");
//获取String类的所有内部类
Class[] declaredClasses = c1.getDeclaredClasses();
for (Class c : declaredClasses) {
System.out.println(c);
}
}
@Test
public void test8()throws Exception{
Class<?> map = Class.forName("java.util.Map");
Class<?>[] declaredClasses = map.getDeclaredClasses();
for (Class<?> c : declaredClasses) {
System.out.println(c);//java.util.Map.Entry
System.out.println(c.getDeclaringClass());
System.out.println(c.getEnclosingClass());
}
}
}
1.3.2 反射创建对象
1、构造器的权限修饰符是可见的
- 获取类型对应的Class对象
- 获取构造器对应的Constructor对象
- Class对象.getDeclaredConstructor() 获取无参构造
- Class对象.getDeclaredConstructor(构造器的形参的类型的Class对象...) 获取无参构造
- 例如:public Teacher(int id, String name)构造器
- Class对象.getDeclaredConstructor(int.class, String.class)
- 调用Constructor对象.newInstance(【实参列表】)方法来创建这个类的实例对象
package com.mytest.reflect;
public class Teacher {
private int id;
private String name;
public Teacher() {
}
public Teacher(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Teacher{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
package com.mytest.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestCreateObject {
@Test
public void test1()throws Exception{
//(1)获取Class对象,如果要创建Teacher类的对象,就获取Teacher类的Class对象
Class<?> clazz = Class.forName("com.mytest.reflect.Teacher");
//(2)你要用哪个构造器创建对象,就要获取这个构造器的Constructor对象
Constructor<?> constructor = clazz.getDeclaredConstructor();
//getDeclaredConstructor()的()中空着,表示获取无参构造
//如果Teacher类没有无参构造,就报java.lang.NoSuchMethodException: com.mytest.reflect.Teacher.<init>()
//没有无参的实例初始化方法<init>()
//(3)用构造器new对象
Object obj = constructor.newInstance();
//因为constructor现在代表无参构造,因此newInstance()的()里面空着
//obj的编译时是Object类型,运行时要看clazz是代表哪个类,它现在代表Teacher,obj就是Teacher类的对象
System.out.println(obj);
}
@Test
public void test2()throws Exception{
//(1)获取Class对象,如果要创建Teacher类的对象,就获取Teacher类的Class对象
Class<?> clazz = Class.forName("com.mytest.reflect.Teacher");
//(2)你要用哪个构造器创建对象,就要获取这个构造器的Constructor对象
//例如:想要用public Teacher(int id, String name)构造器创建Teacher类对象
Constructor<?> constructor = clazz.getDeclaredConstructor(int.class, String.class);
//constructor此时是有参构造
//(3)用构造器new对象
Object obj = constructor.newInstance(1,"张三");
//因为constructor现在代表有参构造,因此newInstance()的()里面不能空着
System.out.println(obj);
}
}
2、构造器的权限修饰符是不可见
package com.mytest.reflect;
public class Employee {
private int id;
private String name;
private Employee(){
}
private Employee(int id, String name) {
this.id = id;
this.name = name;
}
@Override
public String toString() {
return "Employee{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
package com.mytest.reflect;
import org.junit.Test;
import java.lang.reflect.Constructor;
public class TestCreateObject2 {
@Test
public void test1()throws Exception{
//演示用Employee类的私有化的构造器创建员工类的对象
Class c = Class.forName("com.mytest.reflect.Employee");
Constructor noArgsConstructor = c.getDeclaredConstructor();
//getDeclaredConstructor()的()空着
//noArgsConstructor代表无参构造
/*
因为Employee的构造器是private,所以下面的 newInstance()报错了
java.lang.IllegalAccessException: 非法访问异常。
class com.mytest.reflect.TestCreateObject2(测试类)
cannot access a member of (不能直接访问xx类的成员)
class com.mytest.reflect.Employee(员工类)
with modifiers "private"(用private修饰)
*/
Object obj = noArgsConstructor.newInstance();
System.out.println(obj);
}
@Test
public void test2()throws Exception{
//演示用Employee类的私有化的构造器创建员工类的对象
Class c = Class.forName("com.mytest.reflect.Employee");
Constructor noArgsConstructor = c.getDeclaredConstructor();
noArgsConstructor.setAccessible(true);//无论这个构造器的权限修饰符是private,缺省,protected,public都可以访问
Object obj = noArgsConstructor.newInstance();
System.out.println(obj);
}
@Test
public void test3()throws Exception{
//演示用Employee类的私有化的构造器创建员工类的对象
Class c = Class.forName("com.mytest.reflect.Employee");
//private Employee(int id, String name)
Constructor allArgsConstructor = c.getDeclaredConstructor(int.class, String.class);
allArgsConstructor.setAccessible(true);//无论这个构造器的权限修饰符是private,缺省,protected,public都可以访问
Object obj = allArgsConstructor.newInstance(1,"chai");
System.out.println(obj);
}
public static void main(String[] args) throws Exception{
//演示用核心类库中LocalDate等构造器私有化的类创建对象
//LocalDate date = new LocalDate(2024,1,1);
//错误,因为private LocalDate(int year, int month, int dayOfMonth)
// LocalDate date = LocalDate.of(2024,1,1);//不用反射的方式,得到LocalDate的对象
Class c = Class.forName("java.time.LocalDate");
Constructor allArgsConstructor = c.getDeclaredConstructor(int.class, int.class, int.class);
allArgsConstructor.setAccessible(true);
//上面的setAccessible(true)代码报异常 java.lang.reflect.InaccessibleObjectException 无法访问异常
//Unable to make private java.time.LocalDate(int,int,int) accessible: 不能设置 LocalDate的私有构造器变的可以访问
// module java.base does not "opens java.time" to unnamed module @682a0b20 java.base模块的私有成员没有对我们开放
/*
如果想要正常运行这个代码,必须设置一个VM Options。
Run菜单 -> Edit Configurations ->
左边 看Application (不要用JUnit,即要用main方法)
右边 在VM Options中加 --add-opens java.base/java.time=ALL-UNNAMED
*/
Object obj = allArgsConstructor.newInstance(2024,12,25);
System.out.println(obj);
}
}
--add-opens java.base/java.time=ALL-UNNAMED
如果是JRE核心类库中的其他包,那么要看他在哪个模块,哪个包,即java.base/java.time 看具体情况的。因为LocalDate类在java.base模块的java.time包。
1.3.3 反射操作属性
(1)操作是实例变量
- 获取类型的Class对象
- 创建这个类的实例对象
- 获取你要操作的实例变量的Field对象
- 如果该实例变量是私有的,需要调用 Field对象.setAccessible(true);
- Field对象.set(实例对象名, 属性值) 设置/修改属性值 或 Field对象.get(实例对象名) 获取属性值
(2)操作静态变量
- 获取类型的Class对象
- 获取你要操作的静态变量的Field对象
- 如果该静态变量是私有的,需要调用 Field对象.setAccessible(true);
- Field对象.set(null, 属性值) 设置/修改属性值 或 Field对象.get(null) 获取属性值
1.3.4 反射操作方法
(1)调用一个类的静态方法
- 获取类型的Class对象
- 获取你要操作的静态方法的Method对象
- 如果该静态方法是私有的,需要调用 Method对象.setAccessible(true);
- Method对象.invoke(null 【, 调用方法所需要的实参列表】)
(2)调用一个类的实例方法/非静态方法
- 获取类型的Class对象
- 创建这个类的实例对象(请看第1.3.2小节)
- 获取你要操作的非静态方法的Method对象
- 如果该非静态方法是私有的,需要调用 Method对象.setAccessible(true);
- Method对象.invoke(实例对象 【, 调用方法所需要的实参列表】)
1.4 实际应用场景演示
1.4.1 JDBC
Java Database Connectivity,Java连接数据库。后期MyBatis框架中,都会用到反射,根据数据库表中的数据自动映射为Java对象。
- 安装MySQL服务器软件
- 启动MySQL服务器软件
- 下载MySQL的驱动jar
- 创建123vwu数据库,创建部门表t_department,包含3列:did,dname,description,添加模拟数据
package com.mytest.jdbc;
import java.sql.*;
public class TestJDBC {
public static void main(String[] args) throws Exception{
//这个程序相当于一个客户端,MySQL是服务器端
//不用接收返回值,这个驱动的Class对象,是给下面的类用
Class.forName("com.mysql.cj.jdbc.Driver");
String url = "jdbc:mysql://localhost:3306/123vwu";//网址
//http://www.123vwu.com
//jdbc:mysql: 协议
//localhost:主机名,对应的IP地址 127.0.0.1
//3306:端口号
//123vwu:数据库名,不同项目,数据库名是不同的
String username = "root";
String password = "123456";
Connection conn = DriverManager.getConnection(url, username, password);
String sql = "select * from t_department";
Statement statement = conn.createStatement();
//比喻:socket.getOutputStream()
ResultSet res = statement.executeQuery(sql);
//给服务器发送一条sql语句
//执行完,服务器会把结果直接返回,返回的结果封装为一个ResultSet的结果集
ResultSetMetaData metaData = res.getMetaData();
int columnCount = metaData.getColumnCount();
while(res.next()){//while循环一次,遍历一行
for(int i=1; i<=columnCount; i++){//for循环循环一次,遍历一个单元格,一列
System.out.print(res.getObject(i)+"\t");
}
System.out.println();
}
res.close();
statement.close();
conn.close();
}
}