Java 基础 - 反射

Class 类

  • 在 Java 中大部分元素都是对象(但也有例外,如静态成员、基本数据类型);
  • 类是 java.lang.Class 类的实例对象,这个对象称为该类的类类型;
  • Class 类是私有类,不能直接用于创建对象,只有 JVM 可以访问;
  • 通过 Class 可以访问系统为所有对象维护运行时的类型标识、从而可以通过类的类类型创建类的实例对象。

对于一个普通的类,可以使用 new 创建其对象

class Foo {
    void print() {
        System.out.println("foo");
    }
}
Foo foo1 = new Foo();

Class 类不能直接访问;任何一个类都是 Class 类的实例对象,这个实例对象有三种表达方式

// 第一种表达方式,表示任何一个类都有一个隐含的静态成员变量 class
Class c1 = Foo.class;

// 第二种表达方式,已经知道该类的对象通过 getClass 方法
Class c2 = foo1.getClass();

// c1、c2 表示了 Foo 类的类类型(class type)
System.out.println(c1 == c2);

// 第三种表达方式
Class c3 = null;
c3 = Class.forName("com.ywh.reflect.Foo");
System.out.println(c2 == c3);

“可以通过类的类类型创建类的实例对象”,指的是在这里可以通过 c1c2c3 创建 Foo 的实例

try {
    Foo foo = (Foo) c1.newInstance();    // 需要在类中定义无参数的构造方法,否则会抛出异常
    foo.print();
} catch (InstantiationException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

动态加载与静态加载

Class.forName("com.ywh.reflect.Foo")

  • 表示类的类类型,同时代表动态加载类;
  • 编译时加载类(使用 new 创建对象,类不存在时编译无法通过)是静态加载类,运行时加载类是动态加载类。

静态加载类

要求所有工具类都写在 Office 类中,耦合度高(Excel 不存在,Word 也不可用,因为无法通过编译)。

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

动态加载类

  • Word 和 Excel 继承标准接口,在动态加载时代替必须指定其中的某个类型的强制类型转换;
  • 当需要扩展工具时,只需要添加符合标准(继承接口)的工具类,不需要再修改 Office 类;
// Word 和 Excel 的共同标准接口
interface OfficeAble {
    public void start();
}

class Office {
    public static void main(String[] args) {
        try {
            Class c = Class.forName(args[0]);
            OfficeAble oa = (OfficeAble) c.newInstance();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
}

编译、运行

javac Office.java
java Office Word

获取类的信息

其他类型(基本数据类型、关键字等)都存在类类型

Class c1 = int.class;    // int 的类类型
Class c2 = String.class;    // String 类的类类型,相当于 String 类字节码
Class c3 = double.class;
Class c4 = Double.class;
Class c5 = void.class;    // package 没有,因为不是在类中声明的

System.out.println(c1.getName());
System.out.println(c2.getName());
System.out.println(c2.getSimpleName());    // 不包含包名的类的名称
System.out.println(c5.getName());

对于一个类,Method 存放类的方法对象,Field 存放类的成员对象。

获取类方法的信息

public static void printClassMethodMessage(Object obj) {
    // 首先要获取类的类类型
    Class c = obj.getClass();    // 传递的是哪个子类的对象,c 就是该子类的类类型
    System.out.println("类的名称是:" + c.getName());    // 获取类的名称

    Method[] ms = c.getMethods();    // 获取所有有 public 方法,包括父类继承而来的
    // 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() + "(");    // 得到方法的名称
        Class[] paramTypes = ms[i].getParameterTypes();    // 获取参数类型,即参数列表的类型的类类型
        for (Class class1 : paramTypes) {
            System.out.print(class1.getName() + ",");
        }
        System.out.println(")");
    }
}

获取类成员的信息

  • 成员变量也是对象(java.lang.reflect.Field 的对象);
  • 其中 Field 类封装了关于成员变量的操作。
public static void printFieldMessage(Object obj) {
    Class c = obj.getClass();
    // Field[] fs = c.getFields();    // 获取所有的public的成员变量的信息
    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);
    }
}

获取类构造方法的信息

  • 构造方法也是对象(java.lang.Constructor 的对象)
public static void printConMessage(Object obj) {
    Class c = obj.getClass();
    // Constructor[] cs = c.getConstructors();    // 获取所有的public的构造函数
    Constructor[] cs = c.getDeclaredConstructors();    // 得到所有的构造函数
    for (Constructor constructor : cs) {
        System.out.print(constructor.getName() + "(");
        Class[] paramTypes = constructor.getParameterTypes();    // 获取构造函数的参数列表(参数列表的类类型)
        for (Class class1 : paramTypes) {
            System.out.print(class1.getName() + ",");
        }
        System.out.println(")");
    }
}

反射的基本操作

  • 方法由名称、参数列表决定;
  • 使用方法对象的 invoke(对象, 参数列表) 方法来实现反射操作。

对于一个普通类

class A {
    public void print() {
        System.out.println("helloworld");
    }

    public void print(int a, int b) {
        System.out.println(a + b);
    }

    public void print(String a, String b) {
        System.out.println(a.toUpperCase() + "," + b.toLowerCase());
    }
}

执行反射操作

  • 先获取类的类类型;
  • 由名称、参数列表决定获取的方法;
  • 使用方法对象来调用方法。
try {
    A a1 = new A();
    Class c = a1.getClass();
    
    // 获取 a1 中,名称为 “print”、第一个参数类型为 “int”、第二个参数类型为 “int” 的方法并调用
    Method m0 = c.getMethod("print", int.class, int.class);
    Object o = m0.invoke(a1, 10, 20);    // 等价于a1.print(10, 20)
    
    Method m1 = c.getMethod("print", String.class, String.class);
    o = m1.invoke(a1, "hello", "WORLD");
    
    Method m2 = c.getMethod("print");
    m2.invoke(a1);

} catch (Exception e) {
    e.printStackTrace();
}

反射是常用于系统程序中的技术,但由于把程序逻辑插入到运行时、绕过了编译器的判断,从而让编译器无法帮助发现程序中的错误,很可能在运行时出现预期以外的错误而难以解决,因此不应在应用程序中过多使用反射。

泛型的本质

Java 中的集合常使用泛型来防止错误输入(不能放入类型不兼容的元素):

ArrayList list1 = new ArrayList();
ArrayList list2 = new ArrayList<>();
list2.add("hello");
// list2.add(1);

但在以上两个集合中,其类类型是相等的

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

由于反射的操作都是编译后的操作,c1 == c2 返回 true 表明编译之后集合是去泛型化的(编译之后没有泛型),所以泛型只在编译阶段有效,绕过编译就无效了。

实例:通过方法反射操作绕过编译

try {
    Method m = c2.getMethod("add", Object.class);
    m.invoke(list1, 20);    // 绕过编译操作,给 String 集合添加 int 类型
    System.out.println(list1.size());
    System.out.println(list1);
//  for (String string : list1) {
//      System.out.println(string);
//  }   //现在不能这样遍历,会报类型错误
} catch (Exception e) {
    e.printStackTrace();
}

你可能感兴趣的:(Java 基础 - 反射)