反射【java全端课22】

48 阅读10分钟

一、反射

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();
    }
}