Java反射机制

通俗地说,反射机制就是可以把一个类,类的成员(函数,属性),当成一个对象来操作,希望读者能理解,也就是说,类,类的成员,我们在运行的时候还可以动态地去操作它们.

Class类

在面向对象对象的世界中,万事万物皆是对象。Java语言中,静态的成员和普通数据类型不是对象(有其相应的包装类)。

万事万物都可以抽象成,而这些类也是_java.lang.Class_的实例对象。

测试类

public class ClassTest {
    public static void main(String[] args) {
        // Student类的实例对象
        Student student = new Student(); // student就表示了Student类的实例对象

        /*
         * Student这个类本身也是一个实例对象,是Class类的实例对象
         * 但是不能new出来(私有的无参构造器),只有Java的虚拟机能创建Class类的实例对象
         */

        // 任意一个类都是Class类的实例对象,其表示(获得)方式有如下三种

        // 1 (任何一个类都有一个隐含的静态成员变量class)
        Class s1 = Student.class;
        // 2 (已知该类的实例对象,通过getClass()方法)
        Class s2 = student.getClass();
        // 3 (Class.forName("com.sh2zqp.Student"))
        Class s3 = null;
        try {
            s3 = Class.forName("com.sh2zqp.Student");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        // s1,s2,s3都表示了Student类的类类型(class type),也就是Class类的实例对象
        // 它们三者是等价的,一个类只可能有Class类的一种实例对象
        System.out.println(s1);
        System.out.println(s2);
        System.out.println(s3);
        System.out.println(s1 == s2); // true
        System.out.println(s2 == s3); // true

        // 我们可以通过Student类的类类型 s1 or s2 or s3 来创建Student类的实例对象
        try {
            Student s = (Student) s1.newInstance(); // Student需要有无参的构造器
            //Student s = (Student) s2.newInstance();
            //Student s = (Student) s3.newInstance();
            s.play();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

Student

public class Student {

    private int id;
    private String name;

    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 void play() {
        System.out.println("play......");
    }
}

Java动态加载类

Class.forName(“类的全路径”)

上面的式子不仅可以获得类的类类型,还代表了一种类的加载技术,既是动态加载类。

编译:把.java文件变成.class文件
运行:由java虚拟机来运行.class文件

编译时加载的类就是静态加载类
运行时加载的类就是动态加载类

不适用IDE,而使用命令行来编译,运行程序

javac 编译
java 运行

class Office {
    public static void main(String[] args) {
        if ("Word".equals(args[0])) {
            Word word = new Word();
            word.start();
        }
        if ("Excel".equals(args[0])) {
            Excel excel = new Excel();
            excel.start();
        }
    }       
}

此时编译此文件

javac Office.java

Word和Excel类找不到

新建一个Word类

class Word {
    public void start() {
        System.out.println("word...start...");
    }
}

javac Word.java

编译得到Word.class文件,再次编译Office.java

和Word相关的报错消失

new创建对象 是静态加载类,在编译时就需要使用可能需要的类,如果我们目前只有Word类,而没有Excel类,就会一直无法使用该程序,所以我们可以通过动态加载类解决上面问题。

public class Office {
    public static void main(String[] args) {
        try {
        // 动态加载类,在运行时加载
            Class c = Class.forName(args[0]);
            // 通过类类型创建类的实例对象
            OfficeAble office = (OfficeAble) c.newInstance();
            office.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

public interface OfficeAble {
    public void start();
}

public class Word implements OfficeAble {
    @Override
    public void start() {
        System.out.println("Word...Start...");
    }
}

动态加载运行,各种功能模块互不影响,对以后想添加功能也比较方便。

基本数据类型以及一些特殊的类类型

public class BaseDataTypeClassTest {
    public static void main(String[] args) {
        Class c1 = int.class; // int的类类型(int类的字节码)
        Class c2 = float.class;
        Class c3 = Double.class;
        Class c4 = String.class;
        Class c5 = void.class;

        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c3);
        System.out.println(c4);
        System.out.println(c5);

        System.out.println(c1.getName());
        System.out.println(c1.getSimpleName());
        System.out.println(c3.getName());
        System.out.println(c3.getSimpleName());
        System.out.println(c5.getName());
        System.out.println(c5.getSimpleName());
    }
}

运行结果

Class类的基本API

Method相关

/**
   * Method类,方法的对象
   * 一个成员方法就是一个Method的实例对象
   * c.getMethods()获取所有的public修饰的函数,包括父类继承来的方法
   * c.getDeclaredMethods()获取所有该类自己声明的方法,不考虑访问权限
   */
  Method[] methods = c.getMethods();
  //Method[] declaredMethods = c.getDeclaredMethods();

  for (Method m : methods) {
      // 方法的返回值类型
      Class returnValueType = m.getReturnType();
      System.out.print(returnValueType.getSimpleName() + " ");
      // 方法名
      System.out.print(m.getName());
      // 方法的参数类型
      Class[] parameterTypes = m.getParameterTypes();
      System.out.print("(");
      for (Class cl : parameterTypes) {

      }
      for (int i = 0; i < parameterTypes.length; i++) {
          if (i != (parameterTypes.length-1) ) {
              System.out.print(parameterTypes[i].getSimpleName() + ",");
          } else {
              System.out.print(parameterTypes[i].getSimpleName());
          }
      }
      System.out.println(")");
  }

Field相关

// 要获取object对象类的信息,先获取object对象的所属类的类类型
        Class c = object.getClass(); // 2
        /**
         * Field类,成员变量也是对象
         * 一个成员变量就是一个Field的实例对象
         * c.getFields()获取所有的public修饰的成员变量,包括父类继承来的成员变量
         * c.getDeclaredFields()获取所有该类自己声明的成员变量,不考虑访问权限
         */
        Field[] fields = c.getFields();
        Field[] declaredFields = c.getDeclaredFields();
        for (Field f : declaredFields) {
            // 得到成员变量类的类类型的名称
            Class fieldClassType = f.getType();
            System.out.print(fieldClassType.getSimpleName() + " ");
            // 得到成员变量的名称
            System.out.println(f.getName());
        }

构造函数相关

// 要获取object对象类的信息,先获取object对象的所属类的类类型
        Class c = object.getClass(); // 2
        /**
         * Constructor类,构造函数也是对象
         * 一个构造函数就是一个Constructor的实例对象
         * c.getConstructors()获取所有的public修饰的构造函数
         * c.getDeclaredFields()获取所有该类的构造函数
         */
        //Constructor[] constructors = c.getConstructors();
        Constructor[] declaredConstructors = c.getDeclaredConstructors();
        for (Constructor constructor : declaredConstructors) {
            // 方法名
            System.out.print(constructor.getName());
            // 方法的参数类型
            Class[] parameterTypes = constructor.getParameterTypes();
            System.out.print("(");
            for (int i = 0; i < parameterTypes.length; i++) {
                if (i != (parameterTypes.length-1) ) {
                    System.out.print(parameterTypes[i].getSimpleName() + ",");
                } else {
                    System.out.print(parameterTypes[i].getSimpleName());
                }
            }
            System.out.println(")");
        }

其他等等,只要获得该类的类类型即可获得全面的类信息

Class中方法的反射操作

  • 如何获取某个方法(方法的名称和方法的参数列表才能唯一的决定某个方法)
  • 方法的反射操作

    method.invoke(对象,参数列表)

    public class MethodInvokeTest {

    public static void main(String[] args) {
        Student student = new Student();
        // 获取Student类的print(int , int)方法
        // 1. 要获取一个类的方法,就是获取该类的类信息
        Class c = student.getClass();
        // 2. 获取方法的名称和参数列表来决定该方法是否是我们需要的
        try {
            // 获得public方法
            Method cMethod = c.getMethod("print", new Class[] {int.class, int.class});
            //Method cMethod = c.getMethod("print", int.class, int.class);
            // 获得自己声明的任意方法(不考虑权限)
            //Method cDeclaredMethod = c.getDeclaredMethod();
    
            // 方法的反射操作
            student.print(11,22); // student对象操作方法print
            // 方法有返回值返回具体的返回值,无返回值则返回null
            Object o = cMethod.invoke(student, 11, 22); // print方法对象cMethod操作对象student
            //Object o = cMethod.invoke(student, new Object[] {11, 22});
            System.out.println(o); // null
    
            System.out.println("=====================");
            Method cMethod1 = c.getMethod("print", String.class, String.class);
            cMethod1.invoke(student, "a", "b");
    
            System.out.println("=====================");
            //Method cMethod2 = c.getMethod("print");
            Method cMethod2 = c.getMethod("print", new Class[] {});
            //cMethod2.invoke(student);
            cMethod2.invoke(student, new Object[] {});
    
            /**
             * 用Java反射机制来调用private方法
             *
             * AccessibleObject类是Field、Method、和Constructor对象的基类。
             * 它提供了将反射的对象标记为在使用时取消默认Java语言访问控制检查的能力。
             * 对于公共成员、默认(打包)访问成员、受保护成员和私有成员,在分别使用
             * Field、Method和Constructor对象来设置或获得字段、调用方法,
             * 或者创建和初始化类的新实例的时候,会执行访问检查。
             * 当反射对象的accessible标志设为true时,则表示反射的对象在使用时应该取消Java语言访问检查。
             * 反之则检查。由于JDK的安全检查耗时较多,所以通过setAccessible(true)的方式关闭安全检查来提升反射速度。
             */
            System.out.println("=====================");
            // //getDeclaredMethod可以获取到所有方法,而getMethod只能获取public
            Method cMethodPrivate = c.getDeclaredMethod("privateMethod", new Class[] {});
            cMethodPrivate.setAccessible(true);// //压制Java对访问修饰符的检查
            cMethodPrivate.invoke(student, new Object[] {});
    
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    

    }

反射在集合泛型中的应用(本质)

通过Class,Method来认识泛型的本质,什么是泛型(防止输入错误的数据类型),泛型什么时候有效

public class GenericClassTest {
    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        ArrayList<String> list2 = new ArrayList<>();

        list2.add("aaaa");
        // list2.add(20); // 错误

        Class c1 = list1.getClass();
        Class c2 = list2.getClass();
        System.out.println(c1);
        System.out.println(c2);
        System.out.println(c1 == c2); // true

        // 反射的操作都是编译后的操作,即变成字节码之后的操作(运行时)
        // c1==c2说明编译之后的集合是去泛型化的
        // Java中的泛型是防止错误输入的,只在编译阶段有效,绕过编译就无效了
        // 验证:通过方法的反射来操作,绕过编译
        try {
            Method c2Method = c2.getMethod("add", Object.class);
            c2Method.invoke(list2, 100);
            System.out.println(list2.size());
            System.out.println(list2); // [aaaa, 100],此时不能再用for循环遍历,会有类型转换错误
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

反射的应用场景

  • 工厂模式:Factory类中用反射的话,添加了一个新的类之后,就不需要再修改工厂类Factory了
  • 数据库JDBC中通过Class.forName(Driver).来获得数据库连接驱动
  • 分析类文件:毕竟能得到类中的方法等等
  • 访问一些不能访问的变量或属性:破解别人代码

参考资料

反射——Java高级开发必须懂的
java反射详解
一个例子让你了解Java反射机制

QinPeng Zhu wechat
扫一扫,关注我的公众号获取更多资讯!
学习分享,感谢鼓励!