本文我们通过一个实际的例子来演示反射在编程中的应用,可能之前大家对反射的学习,仅仅是停留在概念层面,不知道反射究竟应用在哪,所以是一头雾水。相信通过这篇教程,会让你对反射有一个更深层次的认知。
概念
Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
如何理解反射?简单的一句话解释,将传统的开发思路反向逆转。
传统的方式是通过类创建对象:类 ---> 对象。
反射就是将这个过程逆转,通过对象得到类:对象 ---> 类。
通过对象得到的这个类该如何表示?
使用Class类来表示,此类是Java反射的源头,是用来描述其他类的类,Class类的每一个实例化对象就是对其他类的描述。
在Object类中定义了以下的方法,此方法将被所有子类继承:
public final Class getClass()。
也就是说每一个类,都可以调用getClass()方法获取对应的Class对象,用来描述目标类,我们将这个Class类叫做目标类的运行时类。
有了Class对象,能做什么?
调用Class对象的newInstance()方法,可以动态创建目标类的对象。
要求:
1)目标类必须有无参数构造方法。
2)外部方法有足够的权限访问目标类的构造方法。
除了动态创建目标类的对象,反射也可以动态调用对象的各种方法,访问成员变量。
Java反射机制主要提供下面几种用途:
-
在运行时判断任意一个对象所属的类
-
在运行时构造任意一个类的对象
-
在运行时判断任意一个类所具有的成员变量和方法
-
在运行时调用任意一个对象的方法
反射相关的主要API
java.lang.Class:描述一个类。
java.lang.reflect.Method:描述类的方法。
java.lang.reflect.Field:描述类的成员变量。
java.lang.reflect.Constructor:描述类的构造方法。
Class用来描述目标类的结构,叫做目标类的运行时类。
Class的常用方法:
方法 | 描述 |
---|---|
public Class<?>[] getInterfaces() | 返回运行时类实现的全部接口。 |
public Class<? Super T> getSuperclass() | 返回运行时类的父类。 |
public Constructor<T>[] getConstructors() | 返回运行时类的public构造方法。 |
public Constructor<T>[] getDeclaredConstructors() | 返回运行时类的全部构造方法。 |
public Method[] getMethods() | 返回运行时类的public方法。 |
public Method[] getDeclaredMethods() | 返回运行时类的全部方法。 |
public Field[] getFields() | 返回运行时类的public成员变量。 |
public Field[] getDeclaredFields() | 返回运行时类的全部成员变量。 |
Method用来描述运行时类的方法。
Method的常用方法:
方法 | 描述 |
---|---|
public Class<?> getReturnType() | 返回方法的返回值类型。 |
public Class<?>[] getParameterTypes() | 返回方法的参数列表。 |
public int getModifiers() | 返回方法的访问权限修饰符。 |
public String getName(); | 返回方法名。 |
Field用来描述运行时类的成员变量。
Field的常用方法:
方法 | 描述 |
---|---|
public int getModifiers() | 返回成员变量的访问权限修饰符。 |
public Class<?> getType() | 返回成员变量的数据类型。 |
public String getName() | 返回成员变量的名称。 |
反射的应用
反射在实际中的应用主要是动态创建对象,动态调用对象的方法。
1.创建对象:
调用Class类的newInstance()方法创建对象。
2.调用指定方法:
(1)通过Class类的getMethod(String name,Class…parameterTypes)方法获取一个Method对象,并设置此方法操作时所需要的参数类型。
(2)调用Object invoke(Object obj, Object[] args)方法,并向方法中传递目标obj对象的参数信息。
首先看一个简单的例子,通过这个例子来理解Java的反射机制是如何工作的。
package com.chenHao.reflection; import java.lang.reflect.Method; /** * Java 反射练习。 * * @author chenHao */ public class ForNameTest { /** * 入口函数。 * * @param args * 参数 * @throws Exception * 错误信息 */ public static void main(String[] args) throws Exception { // 获得Class Class<?> cls = Class.forName("java.lang.String"); // 通过Class获得所对应对象的方法 Method[] methods = cls.getMethods(); // 输出每个方法名 for (Method method : methods) { System.out.println(method); } } }
输出如下结果:
public boolean java.lang.String.equals(java.lang.Object) public java.lang.String java.lang.String.toString() public int java.lang.String.hashCode() public int java.lang.String.compareTo(java.lang.String) public int java.lang.String.compareTo(java.lang.Object) public int java.lang.String.indexOf(int) public int java.lang.String.indexOf(int,int) public int java.lang.String.indexOf(java.lang.String) public int java.lang.String.indexOf(java.lang.String,int) public static java.lang.String java.lang.String.valueOf(int) public static java.lang.String java.lang.String.valueOf(char) public static java.lang.String java.lang.String.valueOf(boolean) public static java.lang.String java.lang.String.valueOf(float) public static java.lang.String java.lang.String.valueOf(char[],int,int) public static java.lang.String java.lang.String.valueOf(double) public static java.lang.String java.lang.String.valueOf(char[]) public static java.lang.String java.lang.String.valueOf(java.lang.Object) public static java.lang.String java.lang.String.valueOf(long) public char java.lang.String.charAt(int) public int java.lang.String.codePointAt(int) public int java.lang.String.codePointBefore(int) public int java.lang.String.codePointCount(int,int) public int java.lang.String.compareToIgnoreCase(java.lang.String) public java.lang.String java.lang.String.concat(java.lang.String) public boolean java.lang.String.contains(java.lang.CharSequence) public boolean java.lang.String.contentEquals(java.lang.CharSequence) public boolean java.lang.String.contentEquals(java.lang.StringBuffer) public static java.lang.String java.lang.String.copyValueOf(char[]) public static java.lang.String java.lang.String.copyValueOf(char[],int,int) public boolean java.lang.String.endsWith(java.lang.String) public boolean java.lang.String.equalsIgnoreCase(java.lang.String) public static java.lang.String java.lang.String.format(java.lang.String,java.lang.Object[]) public static java.lang.String java.lang.String.format(java.util.Locale,java.lang.String,java.lang.Object[]) public byte[] java.lang.String.getBytes(java.lang.String) throws java.io.UnsupportedEncodingException public void java.lang.String.getBytes(int,int,byte[],int) public byte[] java.lang.String.getBytes() public byte[] java.lang.String.getBytes(java.nio.charset.Charset) public void java.lang.String.getChars(int,int,char[],int) public native java.lang.String java.lang.String.intern() public boolean java.lang.String.isEmpty() public int java.lang.String.lastIndexOf(java.lang.String) public int java.lang.String.lastIndexOf(int,int) public int java.lang.String.lastIndexOf(int) public int java.lang.String.lastIndexOf(java.lang.String,int) public int java.lang.String.length() public boolean java.lang.String.matches(java.lang.String) public int java.lang.String.offsetByCodePoints(int,int) public boolean java.lang.String.regionMatches(boolean,int,java.lang.String,int,int) public boolean java.lang.String.regionMatches(int,java.lang.String,int,int) public java.lang.String java.lang.String.replace(java.lang.CharSequence,java.lang.CharSequence) public java.lang.String java.lang.String.replace(char,char) public java.lang.String java.lang.String.replaceAll(java.lang.String,java.lang.String) public java.lang.String java.lang.String.replaceFirst(java.lang.String,java.lang.String) public java.lang.String[] java.lang.String.split(java.lang.String) public java.lang.String[] java.lang.String.split(java.lang.String,int) public boolean java.lang.String.startsWith(java.lang.String) public boolean java.lang.String.startsWith(java.lang.String,int) public java.lang.CharSequence java.lang.String.subSequence(int,int) public java.lang.String java.lang.String.substring(int) public java.lang.String java.lang.String.substring(int,int) public char[] java.lang.String.toCharArray() public java.lang.String java.lang.String.toLowerCase() public java.lang.String java.lang.String.toLowerCase(java.util.Locale) public java.lang.String java.lang.String.toUpperCase() public java.lang.String java.lang.String.toUpperCase(java.util.Locale) public java.lang.String java.lang.String.trim() public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException public final void java.lang.Object.wait() throws java.lang.InterruptedException public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException public final native java.lang.Class java.lang.Object.getClass() public final native void java.lang.Object.notify() public final native void java.lang.Object.notifyAll()
这样就列出了java.lang.String类的所有方法名、及其限制符、返回类型及抛出的异常。这个程序使用Class类forName方法载入指定的类,然后调用getMethods方法返回指定类的方法列表。java.lang.reflect.Method用来表述某个类中的单一方法。
使用java的反射机制,一般需要遵循三步:
-
获得你想操作类的Class对象
-
通过第一步获得的Class对象去取得操作类的方法或是属性名
-
操作第二步取得的方法或是属性
Java运行的时候,某个类无论生成多少个对象,他们都会对应同一个Class对象,它表示正在运行程序中的类和接口。如何取得操作类的Class对象,常用的有三种方式:
-
调用Class的静态方法forName,如上例;
-
使用类的.class语法,如:Class cls = String.class;
-
调用对象的getClass方法,如:String str = "abc";Class cls = str .getClass();
下面将通过实例讲述如何通过前面所诉的三步来执行某对象的某个方法:
1 package com.chenHao.reflection; 2 3 import java.lang.reflect.Method; 4 5 /** 6 * Java 反射练习。 7 * 8 * @author chenHao 9 */ 10 public class ReflectionTest { 11 public static void main(String[] args) throws Exception { 12 DisPlay disPlay = new DisPlay(); 13 // 获得Class 14 Class<?> cls = disPlay.getClass(); 15 // 通过Class获得DisPlay类的show方法 16 Method method = cls.getMethod("show", String.class); 17 // 调用show方法 18 method.invoke(disPlay, "chenHao"); 19 } 20 } 21 22 class DisPlay { 23 public void show(String name) { 24 System.out.println("Hello :" + name); 25 } 26 }
前面说过,Java程序的每个类都会有个Class对象与之对应。Java反射的第一步就是获得这个Class对象,如代码14行。当然,每个类的方法也必有一个Method对象与之对应。要通过反射的方式调用这个方法,就要首先获得这个方法的Method对象,如代码16行,然后用Method对象反过来调用这个方法,如代码18行。
注意:16行getMethod方法的第一个参数是方法名,第二个是此方法的参数类型,如果是多个参数,接着添加参数就可以了,因为getMethod是可变参数方法。执行18行代码的invoke方法,其实也就是执行show方法,注意invoke的第一个参数,是DisPlay类的一个对象,也就是调用DisPlay类哪个对象的show方法,第二个参数是给show方法传递的参数。类型和个数一定要与16行的getMethod方法一直。
上例讲述了如何通过反射调用某个类的方法,下面将再通过一个实例讲述如何在运行时创建类的一个对象,如何通过反射给某个类的属性赋值:
1 package com.chenHao.reflection; 2 3 import java.lang.reflect.Field; 4 5 /** 6 * Java 反射之属性练习。 7 * 8 * @author chenHao 9 */ 10 public class ReflectionTest { 11 public static void main(String[] args) throws Exception { 12 // 建立学生对象 13 Student student = new Student(); 14 // 为学生对象赋值 15 student.setStuName("chenHao"); 16 student.setStuAge(24); 17 // 建立拷贝目标对象 18 Student destStudent = (Student) copyBean(student); 19 // 输出拷贝结果 20 System.out.println(destStudent.getStuName() + ":" 21 + destStudent.getStuAge()); 22 } 23 24 /** 25 * 拷贝学生对象信息。 26 * 27 * @param from 28 * 拷贝源对象 29 * @param dest 30 * 拷贝目标对象 31 * @throws Exception 32 * 例外 33 */ 34 private static Object copyBean(Object from) throws Exception { 35 // 取得拷贝源对象的Class对象 36 Class<?> fromClass = from.getClass(); 37 // 取得拷贝源对象的属性列表 38 Field[] fromFields = fromClass.getDeclaredFields(); 39 // 取得拷贝目标对象的Class对象 40 Object ints = fromClass.newInstance(); 41 for (Field fromField : fromFields) { 42 // 设置属性的可访问性 43 fromField.setAccessible(true); 44 // 将拷贝源对象的属性的值赋给拷贝目标对象相应的属性 45 fromField.set(ints, fromField.get(from)); 46 } 47 48 return ints; 49 } 50 } 51 52 /** 53 * 学生类。 54 */ 55 class Student { 56 /** 姓名 */ 57 private String stuName; 58 /** 年龄 */ 59 private int stuAge; 60 61 public String getStuName() { 62 return stuName; 63 } 64 65 public void setStuName(String stuName) { 66 this.stuName = stuName; 67 } 68 69 public int getStuAge() { 70 return stuAge; 71 } 72 73 public void setStuAge(int stuAge) { 74 this.stuAge = stuAge; 75 } 76 }
注意:Field提供了get和set方法获取和设置属性的值,但是由于属性是私有类型,所以需要设置属性的可访问性为true,如代码50~51行。也可以在为整个fields设置可访问性,在40行下面使用AccessibleObject的静态方法setAccessible,如:AccessibleObject.setAccessible(fromFields, true);
推荐博客
简化版的MyBatis工具
需求:
创建一个查询数据库的工具类,自动将SQL语句查询出的结果集,封装成不同的对象返回,一个简化版的MyBatis工具。
思路:
工具类查询方法的参数列表:Connection对象,SQL语句,目标运行时类对象clazz,数据表的id值。
1.通过Connection对象,SQL语句,id值查询出对应的结果集。
2.利用反射机制调用clazz的无参构造方法创建目标对象。
3.获取clazz的Filed,即目标类的所有成员变量。
4.找到与成员变量名相同的结果集字段,并获取字段值。
5.通过成员变量名找到对应的setter方法。
6.利用反射机制调用setter方法完成赋值。
实现步骤:
1.导入mysql驱动,c3p0数据源相关jar包。
2.创建c3p0-config.xml。
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <named-config name="testc3p0"> <!-- 指定连接数据源的基本属性 --> <property name="user">root</property> <property name="password">root</property> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/school?useUnicode=true&characterEncoding=UTF-8</property> <!-- 若数据库中连接数不足时, 一次向数据库服务器申请多少个连接 --> <property name="acquireIncrement">5</property> <!-- 初始化数据库连接池时连接的数量 --> <property name="initialPoolSize">5</property> <!-- 数据库连接池中的最小的数据库连接数 --> <property name="minPoolSize">5</property> <!-- 数据库连接池中的最大的数据库连接数 --> <property name="maxPoolSize">10</property> <!-- C3P0 数据库连接池可以维护的 Statement 的个数 --> <property name="maxStatements">20</property> <!-- 每个连接同时可以使用的 Statement 对象的个数 --> <property name="maxStatementsPerConnection">5</property> </named-config> </c3p0-config>
3.创建数据表student,user。
DROP TABLE IF EXISTS `student`; CREATE TABLE `student` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(11) DEFAULT NULL, `address` varchar(11) DEFAULT NULL, `tel` varchar(255) DEFAULT NULL, `score` double(11,1) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) DEFAULT NULL, `age` int(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
4.创建实体类Student,User。
package com.southwind.entity; public class Student { private int id; private String name; private String address; private String tel; private double score; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public String getTel() { return tel; } public void setTel(String tel) { this.tel = tel; } public double getScore() { return score; } public void setScore(double score) { this.score = score; } @Override public String toString() { return "Student [id=" + id + ", name=" + name + ", address=" + address + ", tel=" + tel + ", score=" + score + "]"; } }
package com.southwind.entity; public class User { private int id; private String name; private int age; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } @Override public String toString() { return "User [id=" + id + ", name=" + name + ", age=" + age + "]"; } }
5.创建JDBCTools工具类。
package com.southwind.util; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import com.mchange.v2.c3p0.ComboPooledDataSource; public class JDBCTools { private static ComboPooledDataSource dataSource; static{ dataSource = new ComboPooledDataSource("testc3p0"); } /** * 获取Connection * @return */ public static Connection getConnection(){ try { return dataSource.getConnection(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } /** * 释放资源 * @param conn * @param stmt * @param rs */ public static void release(Connection conn,Statement stmt,ResultSet rs){ if(conn != null){ try { conn.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(stmt != null){ try { stmt.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(rs != null){ try { rs.close(); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
6.创建数据库查询工具类MyQueryRunner,核心代码。
package com.southwind.util; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; /** * 通用工具类 * @author southwind * */ public class MyQueryRunner { /** * 将结果集动态封装成对象 * @param conn * @param sql * @param clazz * @param id * @return */ public Object query(Connection conn,String sql,Class clazz,int id){ PreparedStatement pstmt = null; ResultSet rs = null; Object obj = null; try { pstmt = conn.prepareStatement(sql); pstmt.setInt(1, id); rs = pstmt.executeQuery(); obj = clazz.newInstance(); if(rs.next()){ //遍历实体类属性集合,依次将结果集中的值赋给属性 Field[] fields = clazz.getDeclaredFields(); //获取ResultSet数据 ResultSetMetaData rsmd = rs.getMetaData(); for(int i = 0; i < fields.length; i++){ Object value = setFieldValueByResultSet(fields[i],rsmd,rs); //通过属性名找到对应的setter方法 String name = fields[i].getName(); name = name.substring(0, 1).toUpperCase() + name.substring(1); String MethodName = "set"+name; Method methodObj = clazz.getMethod(MethodName,fields[i].getType()); //调用setter方法完成赋值 methodObj.invoke(obj, value); } } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return obj; } /** * 根据将结果集中的值赋给对应的属性 * @param field * @param rsmd * @param rs * @return */ public Object setFieldValueByResultSet(Field field,ResultSetMetaData rsmd,ResultSet rs){ Object result = null; try { int count = rsmd.getColumnCount(); for(int i=1;i<=count;i++){ //找到与属性名相同的结果集字段 if(field.getName().equals(rsmd.getColumnName(i))){ //获取属性的数据类型 String type = field.getType().getName(); switch (type) { case "int": result = rs.getInt(field.getName()); break; case "java.lang.String": result = rs.getString(field.getName()); break; case "double": result = rs.getDouble(field.getName()); break; } } } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } return result; } }
7.测试
通过id查询student表,调用工具方法,直接返回Student对象。
package com.southwind.test; import java.sql.Connection; import com.southwind.entity.Student; import com.southwind.util.MyQueryRunner; import com.southwind.util.JDBCTools; public class Test { public static void main(String[] args) { Connection conn = JDBCTools.getConnection(); String sql = "select * from student where id = ?"; MyQueryRunner myQueryRunner = new MyQueryRunner(); Student student = (Student) myQueryRunner.query(conn, sql, Student.class, 1); System.out.println(student); } }
通过id查询user表,调用工具方法,直接返回User对象。
package com.southwind.test; import java.sql.Connection; import com.southwind.entity.User; import com.southwind.util.MyQueryRunner; import com.southwind.util.JDBCTools; public class Test { public static void main(String[] args) { Connection conn = JDBCTools.getConnection(); String sql = "select * from users where id = ?"; MyQueryRunner myQueryRunner = new MyQueryRunner(); User user = (User) myQueryRunner.query(conn, sql, User.class, 30); System.out.println(user); } }
至此,Java反射机制的常用机能(运行时调用对象的方法、类属性的使用、创类类的对象)已经介绍完了。
补充:在获得类的方法、属性、构造函数时,会有getXXX和getgetDeclaredXXX两种对应的方法。之间的区别在于前者返回的是访问权限为public的方法和属性,包括父类中的;但后者返回的是所有访问权限的方法和属性,不包括父类的。