1. 反射
1.1. 反射引入
- 编译时知道类或对象的具体信息,此时直接对类和对象进行操作即可,无需反射
- 如果编译不知道类或对象的具体信息,就使用反射来实现。比如类名、属性和属性值放在XML文件中,需要在运行时读取XML文件,动态获取类的信息
public class Test {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
// 编码/编译的时候,已经知道要创建类对象时,不需要反射
// 创建对象
/*Animal an = new Cat();
an.nickName = "wangwang";
an.run();*/
// 编码/编译时,不知道要创建类对象,只有根据运行时动态获取的内容来创建对象,使用反射
// 使用Properties类读取属性文件,最终得到了类的完整路径字符串
String className = "com.wyb.why.Cat";
Class clazz = Class.forName(className);
Object an = clazz.newInstance();
// 操作属性
// 执行方法
}
}
- 反射的应用场合
- 在编译时无法知道对象或类可能属于哪些类,程序只依靠运行时信息来发现该对象和类的真实信息。
- 比如log4j、Servlet、SSM框架技术都使用了反射
比如:log4j
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
比如:Servlet
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>servlet.HelloServlet</servlet-class>
</servlet>
比如:SSM
<bean id="tm" class="org..jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
- 反射的作用
- 动态创建对象
- 动态操作属性
- 动态调用方法
- 在JDK中,主要由以下类来实现Java反射机制,这些类都位于java.lang.reflect包中
- Class类:代表一个类
- Constructor类:代表类的构造方法
- Field类:代表类的成员变量(属性)
- Method类:代表类的成员方法
1.2. 反射的入口-Class类
- Class类是Java反射机制的起源和入口
- 用于获取与类相关的各种信息
- 提供了获取类信息的相关方法
- Class类继承自Object类
- Class类是所有类的共同的图纸
- 每个类有自己的对象,好比图纸和实物的关系
- 每个类也可看作是一个对象,有共同的图纸Class,存放类的结构信息,如类名、属性、方法、构造方法、父类和结构,能够通过相应方法取出相应信息
- Class类的对象称为类对象
package src.com.db1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class TestClass1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
// 1. 获取一个类的结构信息(类对象、Class对象)
Class clazz = Class.forName("com.wyb.why.Dog");
// 2. 从类对象中获取类的各种结构信息
// 2.1. 获取基本结构信息
System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
System.out.println(clazz.getSuperclass());
System.out.println(Arrays.toString(clazz.getInterfaces()));
// 2.2. 获取构造方法
// 只能得到public修饰的构造方法
// Constructor[] constructors = clazz.getConstructors();
// 可以得到所有的构造方法
Constructor[] constructors = clazz.getDeclaredConstructors();
System.out.println(constructors.length);
for (Constructor con : constructors) {
System.out.println(con.getName() + "||" + Modifier.toString(con.getModifiers()) + "||" + Arrays.toString(con.getParameterTypes()));
}
// 获取无参构造方法
// Constructor con = clazz.getConstructor();
// Constructor con = clazz.getConstructor(String.class, String.class);
Constructor con = clazz.getDeclaredConstructor(String.class, String.class);
System.out.println(con);
// 2.3. 获取属性
// Field[] fields = clazz.getFields();
Field[] fields = clazz.getDeclaredFields();
System.out.println(fields.length);
for (Field f : fields) {
System.out.println(f);
}
// Field f = clazz.getField("color");
// private默认protected、public都可以获取,但不包括父类的
Field f = clazz.getDeclaredField("age");
System.out.println(f);
// 2.4. 获取方法
// Method[] methods = clazz.getMethods();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method);
}
// Method m = clazz.getMethod("shout", String.class);
// Method m = clazz.getMethod("run"); // public
Method m = clazz.getDeclaredMethod("run");
System.out.println(m);
}
}
- Class类的常用方法
- 获取一个类的类对象的多种方式
public class TestClass2 {
public static void main(String[] args) throws ClassNotFoundException {
// 1. 获取一个类的结构信息(类对象、Class对象)
// 1.1. Class.forName(类的完整路径字符串)
// Class clazz = Class.forName("java.lang.String");
// 1.2. 类名.class
// Class clazz = String.class;
// 1.3. 对象名.getClass()
String str = "wyb";
Class clazz = str.getClass();
// Integer in = new Integer(20);
// 2. 从类对象中获取类的各种结构信息
System.out.println(clazz.getName());
System.out.println(clazz.getSimpleName());
System.out.println(clazz.getSuperclass());
System.out.println(Arrays.toString(clazz.getInterfaces()));
}
}
其中,类名.class、对象名.getClass()方式在编码时已经知道了要操作的类,而Class.forName()方式在操作时,可以知道也可以不知道要操作的类,所以当编码时还不知道要操作的类时,只能使用Class.forName()的方式
3.3. 使用反射创建对象
调用无参构造方法创建对象
- 方法1:通过Class的newInstance()方法
- 该方法要求该Class对象的对应类有无参构造方法
- 执行newInstance()实际上就是执行无参构造方法来创建该类的实例
- 方法2:通过Constructor的newInstance()方法
- 先使用Class对象获取指定的Constructor对象
- 再调用Constructor对象的newInstance()创建Class对象对应类的对象
- 通过该方法可选择使用指定构造方法来创建对象
/*
* 通过Class的newInstance()方法创建对象
* */
public class TestConstructor {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
String className = "com.wyb.why.Dog";
Class clazz = Class.forName(className);
// 直接使用Class的方法创建对象
Object obj = clazz.newInstance();
System.out.println(obj.toString());
}
}
/*
* 通过Constructor的newInstance()方法创建对象
* */
public class TestConstructor1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String className = "com.wyb.why.Dog";
Class clazz = Class.forName(className);
// 获取无参数构造方法
Constructor con = clazz.getConstructor();
// 使用无参数构造方法来创建对象
Object obj = con.newInstance();
}
}
调用有参数构造方法创建对象:只能通过Construtor的newInstance()方法来创建对象
/*
* 通过Constructor的newInstance()方法来创建对象
* */
public class TestConstructor2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String className = "com.wyb.why.Dog";
Class clazz = Class.forName(className);
Constructor con = clazz.getDeclaredConstructor(String.class, String.class);
// 使用反射创建对象
// 突破封装性的限制,即使是private、默认的也可以访问
con.setAccessible(true);
Object obj = con.newInstance("dog1", "black");
}
}
- 反射优点:功能强大
- 编码时不知道具体的类型,可以使用反射动态操作
- 突破封装的限制,即使private的成员也可以进行操作
- 反射缺点
- 代码繁琐,可读性差
- 突破封装的显示,即使private的成员也可以进行操作
2. 反射技术
2.1. 使用反射操作属性
通过Class对象的getFields()或getField()方法可以获得该类所包括的全部Field属性或指定Field属性。
- getXxx(Object obj):获取obj对象该Field的属性值。此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用get(Object obj)
- setXxx(Object obj, Xxx val):将obj对象的该Field赋值val。此处的Xxx对应8个基本数据类型,如果该属性类型是引用类型则直接使用set(Object obj, Object val)
- setAccessible(Boolean flag):若flag为true,则取消属性的访问权限控制,即使private属性也可以进行访问
public class TestField {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
String className = "com.wyb.why.Dog";
Class clazz = Class.forName(className);
Object dog = clazz.getConstructor().newInstance();
// 获取属性
Field f1 = clazz.getField("color");
Field f2 = clazz.getDeclaredField("age");
// 给属性复制
f1.set(dog, "black");
f2.setAccessible(true);
f2.set(dog, 10);
// 输出给属性
System.out.println(f1.get(dog)); // dog.color
System.out.println(f2.get(dog)); // dog.age
System.out.println(dog);
}
}
2.2. 使用反射执行方法
- 通过Class对象的getMethod()方法可以获得该类所包括的全部方法,返回值是Method[]
- 通过Class对象的getMethod()方法可以获得该类所包括的指定方法,返回值是Method
- 每个Method对象对应一个方法,获得Method对象后,可以调用其invoke()来调用对象方法
- Object invoke(Object obj, Object[] args):obj代表当前方法所属的对象的名字,args代表当前方法的参数列表,返回值Object是当前方法的返回值
public class TestMethod {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
String className = "com.wyb.why.Dog";
Class clazz = Class.forName(className);
Object dog = clazz.getConstructor().newInstance();
// 获取方法
Method m1 = clazz.getMethod("shout");
Method m2 = clazz.getMethod("add", int.class, int.class);
// 使用反射执行方法
m1.invoke(dog);
Object result = m2.invoke(dog, 10, 20);
}
}
2.3. 使用反射操作泛型
没有出现泛型之前,Java中的所有数据类型包括:
- primitive types:基本类型
- raw type:原始类型。不仅指平常所指的类,还包括数组、接口、注解、枚举等结构
Class类的一个具体对象代表一个指定的原始类型和基本类型
泛型出现之后,也就扩充了数据类型: - parameterized types(参数话类型):就是平常用到的泛型List<T>、Map<K, V>的List和Map
- type variables(类型变量):比如List<T>中的T等
- arrat types(数组类型):带泛型的数组:List<T>[]、T[]
- WildcardType(泛型表达式类型,通配符类型):List<? extends Number>
使用泛型,擦出的是方法体中局部变量上定义的泛型,在泛型类、泛型接口中定义的泛型,在成员变量、成员方法上定义的泛型,依旧会保存。保留下来的信息可以通过反射获取。
Class类的一个具体对象代表一个指定的基本类型和原始类型
为了能够通过反射操作泛型,但是实现扩展性而不影响之前操作,Java就新增了ParameterizedType, TypeVariable, GenericArrayType, WildcardType几个类型来代表不能被归一道Class类中的类型,和原始类型齐名。
public class TestGeneric {
public void method1(Map<Integer, Student> map, List<Student> list, String str) {
}
public Map<Integer, Student> method2() {
return null;
}
public static void main(String[] args) throws NoSuchMethodException {
Class clazz = TestGeneric.class;
Method method1 = clazz.getMethod("method1", Map.class, List.class, String.class);
// 获取参数类型(不带泛型)
Class[] paramTypes = method1.getParameterTypes();
for (Class clazz2 : paramTypes) {
System.out.println(clazz2);
}
// 获取参数类型(带泛型)
Type[] types = method1.getGenericParameterTypes();
System.out.println(types.length);
for (Type type : types) {
System.out.println(type);
if(type instanceof ParameterizedType) {
Type[] typeArgs = ((ParameterizedType) type).getActualTypeArguments();
for (Type arg : typeArgs) {
System.out.println("\t" + arg);
}
}
}
// 获取返回值类型(不带泛型)
Method method2 = clazz.getMethod("method2");
Class returnType = method2.getReturnType();
System.out.println(returnType);
// 获取返回值类型(带泛型)
Type returnType1 = method2.getGenericReturnType();
Type[] typeArgs = ((ParameterizedType) returnType1).getActualTypeArguments();
for (Type type : typeArgs) {
System.out.println("\t" + type);
}
// 获取数组元素的类型
Student[] arr = new Student[10];
Class componentType = arr.getClass().getComponentType();
System.out.println(componentType);
}
}
给集合添加泛型后,可以限制元素类型,提高安全性。使用反射还可以突破泛型的限制
public class TestGeneric1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
List<String> list = new ArrayList<>();
Class clazz = list.getClass();
// 获取add放阿法
Method method = clazz.getMethod("add", Object.class);
// 使用反射调用add方法
method.invoke(list, 100);
method.invoke(list, new Date());
System.out.println(list);
}
}
3. 反射应用:完善DBUtil,提取select()
3.1. 认识ResultSetMetaData
利用ResultSet的getMetaData方法可以获得ResultSetMeta对象,而ResultSetMetaData存储了ResultSet的MetaData
MetaData:元数据,就是描述及解释含义的数据
ResultSet是以表格的形式存在的,所以MetaData就包括了数据的字段名称、类型以及数目等表格所具备的信息。
3.2. 提取DBUtil的select()
public class TestResultSetMetaData {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 将相应数据库的jar包放入项目
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
// 加载驱动
String driver = "com.mysql.cj.jdbc.Driver";
String url = "jdbc:mysql://localhost:3006/table";
String user = "root";
String password = "root";
Class.forName(driver);
// 建立数据库的连接
conn = DriverManager.getConnection(url, user, password);
// 创建一个SQL命令发送器
stmt = conn.createStatement();
// 使用SQL命令发送器来发送SQL命令并得到结果
String sql = "select empno, ename, hiredate, sal from emp;";
rs = stmt.executeQuery(sql);
// 得到结果集的结构
ResultSetMetaData rsmd = rs.getMetaData();
System.out.println(rsmd.getColumnCount());
for (int i = 0; i < rsmd.getColumnCount(); i++) {
System.out.println(rsmd.getColumnName(i + 1) + "\t" + rsmd.getColumnTypeName(i + 1) + "\t" + rsmd.getColumnClassName(i + 1));
}
// 关闭数据库连接
}
}
方法摘要:
public abstract class DBUtil {
public static<T> List<T> executeQuery(String sql, Object params[], String className) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<T> list = new ArrayList<T>();
try {
// 获取连接
conn = DBUtil.getConnection();
// 创建Statement
pstmt = conn.prepareStatement(sql);
// 使用Statement发送SQL命令并得到结果
for (int i = 0; i < params.length; i++) {
pstmt.setObject(i + 1, params[i]);
}
rs = pstmt.executeQuery();
ResultSetMetaData rsmd = rs.getMetaData();
// 处理结果(封装到List中)
while (rs.next()) {
// 使用反射创建一个对象
Class clazz = Class.forName(className);
T entity = (T)clazz.newInstance();
// 取出当前行的某列并存入对象(使用反射调用方法)
for (int i = 0; i < rsmd.getColumnCount(); i++) {
// 获取结果集当前列的名称
String columnName = rsmd.getColumnName(i + 1).toLowerCase();
// 根据当前列的名称或当前列的值
Object value = rs.getObject(columnName);
// 通过反射方法调用方法entity.setEmpNo(value);
String methodName = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
Class paramType = Class.forName(rsmd.getColumnClassName(i + 1));
Method method = clazz.getMethod(methodName, paramType);
method.invoke(entity, value);
// 将对象加入到集合中
list.add(entity);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DBUtil.closeAll(rs, pstmt, conn);
}
return list;
}
public static<T> List<T> executeQuery(String sql, Object params[], Class clazz) {}
}
提取了两个重载的方法,区别在于第三个参数,可以传入类的完整路径字符串,也可以直接传入类的Class信息
3.3. 简化DAO的select 方法
public class EmployeeDaoImpl implements EmplpyeeDao{
@Override
public Employee findById(int empNo) {
String sql = "select * from emp where empno = ?";
Object[] params = {empNo};
List<Employee> empList = DBUtil.executeQuery(sql, params, "com.wyb.entity.Employee");
if(empList.size() == 0) {
return null;
}else {
return empList.get(0);
}
}
@Override
public List<Employee> findAll() {
String sql = "select * from emp";
Object params[] = {};
return DBUtil.executeQuery(sql, params, Employee.class);
}
}
可以看到,提取了DBUtil的查询方法之后,DAO层的查询方法代码大大简化了。数据库框架Hibernate、MyBatis的底层就是这样实现的