Java反射

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");
    }
}
  • 反射优点:功能强大
    1. 编码时不知道具体的类型,可以使用反射动态操作
    2. 突破封装的限制,即使private的成员也可以进行操作
  • 反射缺点
    1. 代码繁琐,可读性差
    2. 突破封装的显示,即使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的底层就是这样实现的

  • 9
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Qi妙代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值