Java 反射是一种强大的特性,允许程序在运行时检查和操作类、方法、字段等。通过反射,Java 程序可以动态地加载类、创建对象、调用方法和访问字段,而无需在编译时知道这些类的具体信息。
什么是反射?
反射(Reflection)是一种Java程序运行期间的动态技术,可以在运行时(runtime)检查、
修改其自身结构或行为。通过反射,程序可以访问、检测和修改它自己的类、对象、方法、属性等成员。
反射的主要用途
- 动态加载类:程序可以在运行时动态地助加载类库中的类;
- 动态创建对象:反射可以基于类的信息,程序可以在运行时,动态创建对象实例;
- 调用方法:反射可以根据方法名称,程序可以在运行时,动态地调用对象的方法(即使方法在编写程序时还没有定义):
- 访问成员变量:反射可以根据成员变量名称,程序可以在运行时,访问和修改成员变量(反射可以访问私有成员变量):
- 运行时类型信息:反射允许程序在运行时,查询对象的类型信息,这对于编写通用的代码和库非常有用
- Spring框架使用反射来自动装配组件,实现依赖注入;
- MyBatis框架使用反射来创建resultType对象,封装数据查询结果:
反射的优点
增加程序的灵活性
反射允许程序在运行时动态地访问和操作类的属性和方法,这意味着开发者可以根据需要,在程序执行过程中改变程序的行为,而无需在编译时硬编码这些行为。这种灵活性使得程序能够适应不同的运行环境或需求变化。
避免将固有的逻辑程序写死到代码里
通过反射,开发者可以将一些决策逻辑或行为延迟到运行时进行,而不是在编译时就确定下来。这有助于减少代码中的硬编码,提高代码的可维护性和可扩展性。
提高代码的复用率
反射使得开发者可以编写一些通用的函数或方法,这些函数或方法能够动态地敞处理不同类型的对象或方法。这有助于提高代码的复用率,减少重复代码的编写。
支持动态代理和动态配置
通过反射,开发者可以在运行时动态地创建代理对象,从而实现对目标对象的代理。这种机制在AOP(面向切面编程)等领域中非常有用。此外,反射还支持在运行时读取和修改程序的配置参数,从而实现动态配置。
支持自动化测试和代码生成
反射技术可以应用于自动化测试中,通过动态地创建测试用例来验证程序的行为。同时,反射还可以用于在运行时生成程序代码,这在某些需要动态构建程序结构的场景中非常有用。自由度高,可以无视访问权限限制
在某些情况下,反射可以绕过JaVa等语言的访问控制机制,直接访问私有成员。虽然这可能会带
来一些安全风险,但在某些特定的应用场景中,这种能力是非常有用的:
反射的缺点
- 性能开销:反射操作通常比直接代码调用更慢,因为涉及到额外的检查和解析步骤。
- 安全问题:如果允许程序修改私有成员或调用私有方法,可能会破坏封装性和安全性。
- 代码可读性和可维护性:过度使用反射可能会使代码难以理解和维护,因为它隐藏了类型之间的依赖关系。
Java如何实现反射?
- 在 JDK中,主要由以下类来实现 Java 反射机制:
- Class类:表示正在运行的Java应用程序中的class类型信息的实例;
- Field 类:提供有关类的属性信息,以及对它的动态访问权限。它是一个封装反射类的属性的类;
- Constructor类:提供有关类的构造方法的信息,以及对它的动态访问权限。它是一个封装反射类的构造方法的类;
- Method 类:提供关于类的方法的信息,包括抽象方法。它是用来封装反射类方法的一个类;
获取Class对象的三种方式
- 第一种方式:Class.forName(类名)是通过类的完全限定名获取 Class 对象,这也是平时最常用
- 的反射获取C1ass 对象的方法:
- 第二种方式:类名.class,通过类名的class属性获取Class对象;
- 第三种方式:对象·getClass(),通过对象的getclass()方法获取class 对象,该方法由Object 提供。
反射的使用场景
- 使用JDBC连接数据库时,使用Class.forName(驱动类名),通过反射,动态动加载当前程序中的数据库驱动类;
- Spring框架的IOC(动态动加载管理Bean创建对象以及AOP(动态代理)使用反射进行底层实现:
- MyBatis框架可以通过反射实现Mapper接口代理对象的创建。以及解析SQL【映射文件中resultType指定的类型信息后,通过反射动态创建对象,进行查询结果集的封装:
反射相关的类:
- Class类:
封装类型信息
获取“构造器”,“成员变量”,“方法”
- Constructor类:
每个Constructor对象,都代表一个构造器(构造方法)
作用:创建目标对象
- Field类:
每个Field对象,都代表一个成员变量
作用:将值(数据)存入目标对象中的指定成员变量中
- Method类:
每个Method对象,都代表一个方法
作用:执行方法逻辑
Java 反射的主要功能
一、对象的创建
可以使用 Class.forName()
方法加载类,并通过 newInstance()
方法创建对象
public class Test01 {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//编译器
//硬编码的方式创建
Order o1=new Order();
//运行期
//“反射”的方式创建对象
String className="com.xn.test.Test01";
Class classinfo=Class.forName(className);
Object o2=classinfo.newInstance();
System.out.println(o1);
System.out.println(o2);
}
}
class Order{
}
二、获取类的信息
使用 Class
类可以获取类的名称、父类、接口、构造方法、方法和字段等信息
1、获取 Java 类对象(Class
对象)的方法
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException {
//方式1:通过类名
Class stringClass1=String.class;
//方式2:通过Class类的forName()方法
Class stringClass2=Class.forName("java.lang.String");
//方式3:通过对象调用getClass()方法
Class stringClass3="".getClass();
System.out.println(stringClass1.hashCode());
System.out.println(stringClass2.hashCode());
System.out.println(stringClass3.hashCode());
}
}
2、获取类的名称、包名、构造方法、成员方法方法和成员变量等信息
//获取丰富的类型内容
public class Test04 {
public static void main(String[] args) throws ClassNotFoundException {
Class clz=Class.forName("java.util.HashMap");
//获取类名
System.out.println("完全限定名:"+clz.getName());
System.out.println("简单的类名:"+clz.getSimpleName());
System.out.println();
//获取包名
System.out.println("package包名:"+clz.getPackage().getName());
System.out.println();
//获取成员变量
Field[] fieldArray=clz.getDeclaredFields();
System.out.println("成员变量(字段)");
for (Field field:fieldArray){
System.out.println(field);
}
System.out.println();
//获取成员方法
Method[] methodArray=clz.getDeclaredMethods();
System.out.println("成员方法");
for(Method method:methodArray){
System.out.println(method);
}
}
}
3、通过反射的方式,创建对象
注:当类中没有无参构造方法时,默认一个无参构造方法(不会报错),当我们自定义一个无参构造方法再删除之后,将不会再有默认无参构造方法。
//通过反射的方式,创建对象
public class Test05 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class clz = Class.forName("com.xn.test.Document");
//方式1:直接通过Class对象,调用newInstance()方法
Object objectx=clz.newInstance();
//方式2:通过构造器(构造方法)
//无参构造方法
Constructor constructor1=clz.getDeclaredConstructor();//获取无参构造方法
System.out.println(constructor1);
Object object1=constructor1.newInstance();//执行构造器(构造方法),创建对象
//有参构造方法
Constructor constructor2=clz.getDeclaredConstructor(String.class);//获取有参构造方法
System.out.println(constructor2);
Object object2=constructor2.newInstance("番茄炒蛋");
Constructor constructor3=clz.getDeclaredConstructor(int.class);//获取有参构造方法
System.out.println(constructor3);
Object object3=constructor3.newInstance(55);
Constructor constructor4=clz.getDeclaredConstructor(String.class,int.class);//获取有参构造方法
System.out.println(constructor4);
Object object4=constructor4.newInstance("豆浆油条",88);
System.out.println(objectx);
System.out.println(object1);
System.out.println(object2);
System.out.println(object3);
System.out.println(object4);
}
}
三、调用方法
4、通过反射机制获取构造器信息
- 获取公共构造器:只获取public修饰的构造器
- 获取所有构造器(包括私有构造器):获取所有构造器(包括private)
- 获取指定构造器:获取一个接受 指定类型参数的构造器
- 调用私有构造器:通过调用
setAccessible(true)
方法,允许访问私有构造器。
public class Test06 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class clz = Class.forName("com.xn.test.Document");
//获取一组构造器
Constructor[] constructorArray1 = clz.getConstructors();
Constructor[] constructorArray2 = clz.getDeclaredConstructors();
//获取指定构造器
Constructor constructor = clz.getDeclaredConstructor(String.class);
System.out.println(constructor);
//调用私有构造器,必须设置它的访问权限
constructor.setAccessible(true);
//调用构造器,创建对象
Object object = constructor.newInstance("长安三万里");
System.out.println(object);
}
}
5、通过反射,访问并使用成员变量
//通过反射,访问并使用成员变量
public class Test07 {
public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, ClassNotFoundException, InstantiationException {
//硬编码的方式
// Document doc1 = new Document();
// doc1.name="";
// doc1.FavCount=45;
//反射的方式
Class clz = Class.forName("com.xn.test.Document");//获取类型信息
Object obj = clz.newInstance();
//获取指定名称的成员变量
Field nameField = clz.getDeclaredField("name");
Field favCountField = clz.getDeclaredField("favCount");
//访问私有的成员变量,必须设置权限
nameField.setAccessible(true);
favCountField.setAccessible(true);
//使用成员变量,将指定数据存入对象中
nameField.set(obj, "喜马拉雅");
favCountField.setInt(obj, 9897);
System.out.println(obj);
}
}
6、使用 Java 反射机制调用对象的方法
public class Test08 {
private Object doc1;
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
//硬编码的方式
// Document doc1=new Document();
// doc1.setName("海底两万里");
// doc1.setFavCount(20000);
//反射的方式
Class clz = Class.forName("com.xn.test.Document");//获取类型信息
Object doc1 = clz.newInstance();
//获取指定名称和参数类型的方法
Method setNameMethod = clz.getMethod("setName", String.class);
Method setFavCountMethod = clz.getMethod("setFavCount", int.class);
//执行方法
//doc1.setName("海底两万里");
setNameMethod.invoke(doc1, "海底两万里");
// doc1.setFavCount(20000);
setFavCountMethod.invoke(doc1, 20000);
System.out.println(doc1);
}
}
7、使用 Java 反射机制调用静态方法
public class Test09 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {
//硬编码的方式
//调用静态方法
// Document.dosth();
String ret1 = String.format("HashMap的默认初始容量是%d,加载因子默认是%.2f", 16, 0.75f);
// System.out.println(ret1);
//反射的方式
// Class clz=Class.forName("com.xn.Document");
// Method dosthMethod=clz.getMethod("dosth");
// dosthMethod.invoke(null);
Class clz = String.class;
Method formatMethod = clz.getMethod("format", String.class, Object[].class);
String ret2 = formatMethod.invoke(null, "HashMap的默认初始容量是%d,加载因子默认是%.2f", new Object[]{16, 0.75f}).toString();
System.out.println(ret2);
}
}
案例
#sql=select * from subject
#sql=SELECT id,city_name_pinyin AS cityNamePinyin,city_name AS cityName,first_char AS firstChar,is_hot AS isHot from city
sql=SELECT id,title ,url ,playable ,is_new AS isNew,rate ,cover,type from subject
创建SQLHandler
的类,它用于执行 SQL 查询并将结果映射到 Java 对象中。代码中使用了 JDBC 连接数据库、PreparedStatement 执行 SQL 语句以及反射机制来创建结果对象。
//SQL执行器
public class SQLHandler {
private static final String JDBC_URL = "jdbc:mysql://localhost:3306/mybatis_demo?serverTimezone=GMT";
private static final String DB_USER = "root";
private static final String DB_PASS = "123456";
public static <T> List<T> execute(String sql, Class<T> resultType) throws NoSuchFieldException,SecurityException{
try {
//创建Connection连接对象
Connection con = DriverManager.getConnection(JDBC_URL, DB_USER, DB_PASS);
//创建PreparedStatement执行对象
PreparedStatement pst = con.prepareStatement(sql);
//执行sql语句,获取结果集
ResultSet rs = pst.executeQuery();
//获取结果集的字段名称
ResultSetMetaData resultSetMetaData=rs.getMetaData();
//创建一个用于保存查询结果的list集合
List<T> queryList = new ArrayList<T>();
while (rs.next()) {
//每行数据,对应一个数据对象(反射凡是创建)
Object resultData = resultType.newInstance();
//按照字段的顺序,获取字段别名
for(int i=1;i<=resultSetMetaData.getColumnCount();i++){
//获取当前字段名
String columnLabel=resultSetMetaData.getColumnLabel(i);
//根据字段名称,获取成员变量对象
Field field=resultType.getDeclaredField(columnLabel);
field.setAccessible(true);
//判断当前字段的类型
//根据字段名称,虎丘当前行中的指定数据
switch (resultSetMetaData.getColumnType(i)){
case Types.INTEGER:
field.setInt(resultData,rs.getInt(columnLabel));
break;
case Types.DOUBLE:
field.setDouble(resultData,rs.getDouble(columnLabel));
break;
default:
String value=rs.getString(columnLabel);
if("true".equals(value)||"false".equals(value)){
field.setBoolean(resultData,Boolean.valueOf(value));
}else {
field.set(resultData,value);
}
}
}
queryList.add((T) resultData);
}
return queryList;
} catch (InstantiationException e) {
e.printStackTrace();
return Arrays.asList();
} catch (IllegalAccessException e) {
e.printStackTrace();
return Arrays.asList();
} catch (SQLException e) {
e.printStackTrace();
return Arrays.asList();
}
}
}
Test测试类
public class Test {
public static void main(String[] args) throws IOException, NoSuchFieldException ,SecurityException{
//创建properties对象
Properties props=new Properties();
//读取执行的配置文件
InputStream in=Test.class.getResourceAsStream("sql.properties");
props.load(in);
String sql=props.getProperty("sql");
System.out.println(sql);
List<Subject> lsubject= SQLHandler.execute(sql, Subject.class);
System.out.println("查询结果数量:"+lsubject.size());
for (Subject subject:lsubject) {
System.out.println(subject);
}
// List<City> lcity = SQLHandler.execute(sql, City.class);
// System.out.println("查询结果数量:" + lcity.size());
// for(City city : lcity) {
// }
}
}