前言

  反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。

  一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

Pear pear = new Pear(); //直接初始化,「正射」
pear.setPrice(3);

  上面这样子进行类对象的初始化,我们可以理解为「正」。

  而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。这时候,我们使用 JDK 提供的反射 API 进行反射调用:

Class clazz = Class.forName("com.ducky.reflect.Pear");
Method method = clazz.getMethod("setPrice", int.class);
Constructor constructor = clazz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);

  上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Pear),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.ducky.reflect.Pear)。所以说什么是反射?
反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

基本数据类型

Class c1 = int.class;
Class c2 = String.class;

// int
c1.getName();
// Java.lang.String
c2.getName();

Class 类的使用

1、万物皆对象
类是 java.lang.Class 类的实例对象。
2、这个对象如何表示

Pear pear = new Pear();

Class c1 = Pear.class;
Class c2 = pear.getClass();

// Class 是类类型
// 不管c1 or c2 都是 Pear 类的类类型,一个类只可能是 Class 类的一个实例对象。

Class c3 = null;
c3 = Class.forName("com.ducky.reflect.Pear");

// 可以通过类类型来创建该类对象实例
// 需有无参构造方法
Pear pear = (Pear) c1.newInstance();

Class 动态加载类

编译时刻加载类是静态加载类。
运行时刻加载类是动态加载类。

// new 创建对象,是静态加载类。
// 在编译时刻就需要加载所有的可能用到的类
Pear pear = new Pear();

// 动态加载类
Class clazz = Class.forName("类的全称");

OA oa = (OA) clazz.newInstance();

Java 反射常用API

获取反射中的 Class 对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的 Class 对象。

在 Java API 中,获取 Class 类对象有三种方法:

第一种,使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clazz = Class.forName("java.lang.String");

第二种,使用 .class 方法。

这种方法只适合在编译前就知道操作的 Class。

Class clazz = String.class;

第三种,使用类对象的 getClass() 方法。

String str = new String("Hello");
Class clazz = str.getClass();

通过反射创建类对象

通过反射创建类对象主要有两种方式:通过 Class 对象的 newInstance() 方法、通过 Constructor 对象的 newInstance() 方法。

第一种:通过 Class 对象的 newInstance() 方法。

Class clazz = Pear.class;
Pear pear = (Pear) clz.newInstance();

第二种:通过 Constructor 对象的 newInstance() 方法

Class clz = Pear.class;
Constructor constructor = clz.getConstructor();
Pear pear = (Pear) constructor.newInstance();

通过 Constructor 对象创建类对象可以选择特定构造方法,而通过 Class 对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

Class clazz = Pear.class;
Constructor constructor = clz.getConstructor(String.class, int.class);
Pear pear = (Pear) constructor.newInstance("梨子", 15);

通过反射获取类属性、方法、构造器

我们通过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

Class clazz = Pear.class;
Field[] fields = clazz.getFields();
for (Field field : fields) {
    System.out.println(field.getName());
}
输出结果是:
price

而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class clazz = Pear.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
    System.out.println(field.getName());
}
输出结果是:
name
price

与获取类属性一样,当我们去获取类方法、类构造器时,如果要获取私有方法或私有构造器,则必须使用有 declared 关键字的方法。

打印类的信息

包括类的成员函数,成员变量。


public class ClassUtil {

  public static void printClassMessage(Object obj) {
    Class c = obj.getClass();
    System.out.println("类的名称:"+c.getName());
    
    /*
     *    获取成员函数
     */
    
    // 所有public方法
    Method[] ms = c.getMethods();
    // 该类所有自己声明的方法
    // c.getDeclaredMethods()
    
    for (int i = 0;i<ms.length;i++) {
      
      // 得到方法返回值的 类类型
      Class returnType = ms[i].getReturnType();
      System.out.print(returnType.getName() + " ");
      
      // 得到方法名称
      System.out.print(ms[i].getName());
      
      System.out.print("(");
      // 获取参数类型
      // 得到参数列表的类型的类类型。
      Class[] paramTypes = ms[i].getParameterTypes();
      for (Class class1:ParamTypes) {
        System.out.print(class1.getName() + ",");
      }
      System.out.print(")");
      
    }
    
    /*
     *   获取成员变量
     */
    
    // 获取所有共有成员变量信息
    // Field[] fs = c.getFields();  
    
    // 获取该类所有自己声明的成员变量信息
    Field[] fs = c.getDeclaredFields();
    
    for (Field field : fs) {
      Class fieldType = field.getType();
      String typeName = fieldtype.getName();
      String fieldName =  field.getName();
      
      System.out.println(typeName + " " + fieldName);
    }
    
    /*
     *   获取构造函数
     *   getConstructors 获取所有公有
     *   getDeclaredConstructors 得到自己声明的
     *    (构造方法必须自己声明)
     */
    
    Constructor[] cs = c.getConstructors();
    
    for (Constructor constructor : cs) {
      System.out.print(constructor.getName() + "(");
      
      // 获取构造参数的参数列表
      // 参数列表的类类型
      Class[] paramTypes = constructor.getParameterTypes();
      for (Class clazz :paramTypes) {
        System.out.print(clazz.getName() + ",");
      }
       System.out.println(")");
    }
    
  }
}

集合泛型

通过 Class, Method 来认识泛型的本质。

ArrayList list = new ArrayList();
ArrayList<String> list1 = new ArrayList<String>();

// ok
list1.add("hello");
// 抛出异常
list1.add(20);

Class c1 = list.getClass();
Class c2 = list1.getClass();

System.out.println(c1 == c2);
// True
// 也就是说编译之后,两个集合是一样的。
// 即,编译后集合的泛型是去泛型化的。

// 尝试编译后对该集合操作。
Method m = c2.getMethod("add", object.class);
// 绕过编译操作
m.invoke(list1, 20); 

System.out.println(list1.size())
System.out.println(list1);

// ["hello", 20]

本文作者:
文章标签:程序猿Java
文章标题:Java 反射:入门、使用、原理
本文地址:https://www.ducky.vip/archives/47.html
版权说明:若无注明,本文皆 iDuckie's Blog 原创,转载请保留文章出处。
最后修改:2024 年 03 月 13 日
如果觉得我的文章对你有用,请随意赞赏