0%

Java 反射(一):Class 类

1. Java 中的动态特性有哪些

  • 反射、注解、动态代理、类加载器等

2. Java 中的这些动态特性的作用和使用场景有

  • 作用:利用 Java 的动态特性,可以优雅地实现一些灵活通用的功能,它们经常用于各种框架、库和系统程序中
  • 场景
    • Jackson,利用反射和注解实现了通用的序列化机制
    • 有多种库(如 Spring MVC、Jersey)用于处理 Web 请求,利用反射和注解,能方便地将用户的请求参数和内容转换为 Java 对象,将 Java 对象转变为响应内容
    • 有多种库(如 Spring、Guice)利用 Java 的动态特性实现了对象管理容器,方便程序员管理对象的生命周期以及其中复杂的依赖关系
    • 应用服务器(如 Tomcat)利用类加载器实现不同应用之间的隔离,JSP 技术利用类加载器实现修改代码不用重启就能生效的特性
    • 面向切面的编程 AOP(Aspect Oriented Programming) 将编程中通用的关注点(如日志记录、安全检查等)与业务的主体逻辑相分离,减少冗余代码,提高程序的可维护性,AOP 需要依赖上面的这些特性来实现

3. 怎样理解反射

  • Java 是强类型语言,一般在操作数据的时候都是要依赖于数据类型的,编译器也是根据类型进行代码的检查编译的
  • 反射不一样,反射是在运行时,而非编译时,动态获取类型的信息,比如接口信息、成员信息、方法信息、构造方法信息等,根据这些动态获取到的信息创建对象、访问/修改成员、调用方法等
  • 反射的入口是名称为 Class 的类

4. 怎样理解 Class 类?不同数据类型的 Class 是怎样的

  • 每个已加载的类在内存都有一份类信息,每个对象都有指向它所属类信息的引用
  • Java 中,类信息对应的类就是 java.lang.Class。注意不是小写的 classclass 是定义类的关键字
  • 所有类的根父类 Object 有一个方法,可以获取对象的 Class 对象:public final native Class<?> getClass()Class 是一个泛型类

5. 除了实例对象方法外,还有哪些方法可以获取 Class 对象

  • 获取 Class 对象不一定需要实例对象,如果在写程序时就知道类名,可以使用类名.class获取 Class 对象,比如:Class<Date> cls = Date.class;

  • 接口也有 Class 对象,且这种方式对于接口也是使用的,比如:Class<Comparable> cls = Comparable.class;

  • 枚举也有对应的 Class,比如

    1
    2
    3
    4
    5
    enum Size {
    SMALL, MEDIUM, BIG
    }

    Class<Size> cls = Size.class;

6. 基本数据类型怎样获取 Class 对象

  • 基本类型没有 getClass() 方法,但也都有对应的 Class 对象,类型参数为对应的包装类型。比如

    1
    2
    3
    4
    Class<Integer> intCls = int.class;
    Class<Byte> byteCls = byte.class;
    Class<Character> charCls = char.class;
    Class<Double> doubleCls = double.class;
  • void 作为特殊的返回类型,也有对应的 Class: Class<Void> voidCls = void.class;

  • 对于数组,每种类型都有对应数组类型的 Class 对象,每个维度都有一个,即一维数组有一个,二维数组有一个不同的类型。比如

    1
    2
    3
    4
    5
    6
    String[] strArr = new String[10];
    int[][] twoDimArr = new int[3][2];
    int[] oneDimArr = new int[10];
    Class<? extends String[][]> strArrCls = strArr.getClass(); //TODO: 类型参数有待商榷
    Class<? extends int[][]> twoDimArrCls = twoDimArr.getClass();
    Class<? extends int[]> oneDimArrCls = oneDimArr.getClass();
  • Class 有一个静态方法 forName(),可以根据类名直接加载 Class,获取 Class 对象,比如

    1
    2
    3
    4
    5
    6
    try {
    Class<?> cls = Class.forName("java.util.HashMap"); //forName() 方法可能抛出 ClassNotFoundException 异常
    System.out.println(cls.getName());
    } catch(ClassNotFoundException e) {
    e.printStackTrace();
    }

7. Class 中可以获取与名称有关的信息的方法有哪些

  • public String getName():返回的是 Java 内部使用的真正的名称
  • public String getSimpleName():返回的名称不带包信息
  • public String getCanonicalName():返回的名称更友好
  • public Package getPackage():返回的是包信息

8. 填写下面表格并解释说明

Class 对象 getName() getSimpleName() getCanonicalName() getPackage()
int.class int int int null
int[].class [I int[] int[] null
int[][].class [[I int[] int[] null
String.class java.lang.String String java.lang.String java.lang
String[].class [Ljava.lang.String; String[] java.lang.String[] null
HashMap.class java.util.HashMap HashMap java.util.HashMap java.util
Map.Entry.class java.util.Map$Entry Entry java.util.Map.Entry java.util
  • 需要说明的是数组类型的 getName() 返回值,它使用前缀 [ 表示数组,有几个 [ 表示是几维数组
  • 数组的类型用一个字符表示,I 表示 intL 表示类或接口,其他类型与字符的对应关系为:boolean(Z)byte(B)char(C)double(D)float(F)long(J)short(S)
  • 对于引用类型的数组,注意最后有一个分号;

9. Class 中获取字段信息的方法有

  • 类中定义的静态实例变量都被称为字段,用类 Field 表示,位于包java.lang.reflect
    • public Field[] getFields():返回所有的 public 字段,包括其父类的,如果没有字段,返回空数组
    • public Field[] getDeclaredFields():返回本类声明的所有字段,包括非 public 的,但不包括父类的
    • public Field getField(String name):返回本类或父类中指定名称的 public 字段,找不到抛出 NoSuchFieldException 异常
    • public Field getDeclaredField(String name):返回本类中声明的指定名称的字段,找不到抛出 NoSuchFieldException 异常

10. Field 类中和字段有关的方法有哪些

  • public String getName():获取字段的名称

  • public boolean isAccessible():判断当前程序是否有该字段的访问权限

  • public void setAccessible(boolean flag)flag 设为 true 表示忽略 Java 的访问检查机制,以允许读写非 public 的字段

    • 对于 private 字段,直接调用 get()/set() 会抛出 IllegalAccessException 异常,应该先调用 setAccessible(true) 以关闭 Java 的检查机制
  • public Object get(Object obj):获取指定对象 obj 中该字段的值

  • public void set(Object obj, Object value):将指定对象 obj 中该字段的值设为 value

    • get()/set() 方法中,对于静态变量obj 被忽略,可以为 null
    • 如果字段值为基本类型get()/set() 会自动在基本类型与对应的包装类型间进行转换
  • public int getModifiers():返回字段的修饰符

  • public Class<?> getType():返回字段的类型

  • 基本类型操作字段

    • public void setBoolean(Object obj, boolean z);
    • public boolean getBoolean(Object obj);
    • public void setDouble(Object obj, double d);
    • public double getDouble(Object obj);
  • 查询字段的注解信息

    • public <T extends Annotation> T getAnnotation(Class<T> annotationClass);
    • public Annotation[] getDeclaredAnnotations();

11. Field 类的 getModifiers() 方法 Demo

  • Demo

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //getModifiers() 方法返回的是一个 int,可以通过 Modifier 类的静态方法进行解读
    //假定 Student 类有如下字段
    public static final int MAX_NAME_LEN = 255;

    //可以这样查看该字段的修饰符
    Field f = Student.class.getField("MAX_NAME_LEN");
    int mod = f.getModifiers();
    System.out.println(Modifier.toString(mod));
    System.out.println("isPublic: " + Modifier.isPublic(mod));
    System.out.println("isStatic: " + Modifier.isStatic(mod));
    System.out.println("isFinal: " + Modifier.isFinal(mod));
    System.out.println("isVolatile: " + Modifier.isVolatile(mod));
  • 输出

    1
    2
    3
    4
    5
    public static final
    isPublic: true
    isStatic: true
    isFinal: true
    isVolatile: false

12. Class 中获取方法信息的方法有

  • 类中定义的静态和实例方法都被称为方法,用类 Method 表示
    • public Method[] getMethods():返回所有的 public 方法,包括其父类的,如果没有方法,返回空数组
    • public Method[] getDeclaredMethods():返回本类声明的所有方法,包括非 public 的,但不包括父类的
    • public Method getMethod(String name, Class<?> ... parameterTypes):返回本类或父类中指定名称和参数类型的 public 方法,找不到的话抛出 NoSuchMethodException 异常
    • public Method getDeclaredMethod(String name, Class<?>... parameterTypes):返回本类中声明的指定名称和参数类型的方法,找不到抛出 NoSuchMethodException 异常

13. Method 的基本方法有

  • public String getName():获取方法的名称
  • public void setAccesible(boolean flag)flag 设为 true 表示忽略 Java 的访问检查机制,以允许调用非 public 的方法
  • public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException:在指定对象 obj 上调用 Method 代表的方法,传递的参数列表为 args
    • 如果 Method静态方法obj 被忽略,可以为 nullargs 可以为 null,也可以为一个空的数组,方法调用的返回值被包装为 Object 返回
    • 如方法调用抛出异常,异常被包装为 InvocationTargetException 异常重新抛出,可以通过 getCause() 方法得到原异常
  • Method 还有很多方法,可以获取其修饰符参数返回值注解等信息

14. Class 中有关创建对象和构造方法的方法有

  • public T newInstance() throws InstantiationException, IllegalAccessException

    • 创建对象,会调用类的默认构造方法(即无参 public 构造方法),如果类没有该构造方法,会抛出 InstantiationException 异常

    • Demo

      1
      2
      Map<String, Integer> map = HashMap.class.newInstance();
      map.put("hello", 123);
    • newInstance() 只能使用默认构造方法

  • Class 还有一些方法,可以获取所有的构造方法

    • public Constructor<?>[] getConstructors():获取所有的 public 构造方法,返回值可能为长度为 0 的空数组
    • public Constructor<?>[] getDeclaredConstructors():获取所有的构造方法,包括非 public
    • public Constructor<T> getConstructor(Class<?>... parameterType):获取指定参数类型的 public 构造方法,没找到抛出 NoSuchMethodException 异常
    • public Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes):获取指定参数类型的构造方法,包括非 public 的,没找到抛出 NoSuchMethodException 异常
  • Constructor 表示构造方法,通过它可以创建对象,方法为:public T newInstance(Object... initargs) throws InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException

    • Demo

      1
      2
      Constructor<StringBuilder> constructor = StringBuilder.class.getConstructor(new Class[] {int.class});
      StringBuilder sb = constrcutor.newInstance(100);
  • 除了创建对象,Constructor 类还有很多方法,可以获取关于构造方法的很多信息,包括参数、修饰符、注解等

15. Class 类中关于类型检查和转换的方法有

  • instanceof 关键字的作用:判断变量指向的实际对象类型

  • instanceof 后面的类型是在代码中确定的,如果要检查的类型是动态的,可以使用 Class 类的本地方法:public native boolean isInstance(Object obj)

  • 下面两段代码的输出是相同的

    1
    2
    3
    if(list instanceof ArrayList) {
    System.out.println("array list");
    }
    1
    2
    3
    4
    5

    Class cls = Class.forName("java.util.ArrayList");
    if(cls.isInstance(list)) {
    System.out.println("array list");
    }
  • 之前在强制类型转换时,强转的类型是在写代码的时候就知道的,如果是动态的,可以使用 Class 类的 cast() 方法:public T cast(Object obj)

  • isInstance()/cast() 描述的都是对象和类之间的关系。Class 还有一个方法,可以判断 Class 之间的关系public native boolean isAssignableFrom(Class<?> cls):检测参数类型 cls 能否赋给当前 Class 类型的变量

    • Object.class.isAssignableFrom(String.class); //true
    • String.class.isAssignableFrom(String.class); //true
    • List.class.isAssignableFrom(ArrayList.class); //true

16. 对于 Class 的类型信息,下面说法:Class 代表的类型既可以是普通的类,也可以是内部类,还可以是基本类型、数组等。是否正确

  • 正确

17. 对于一个给定的 Class 对象,它到底是什么类型呢

  • public native boolean isArray():是否是数组
  • public native boolean isPrimitive():是否是基本类型
  • public native boolean isInterface():是否是接口
  • public boolean isEnum():是否是枚举
  • public boolean isAnnotation():是否是注解
  • public boolean isAnonymousClass():是否是匿名内部类
  • public boolean isMemberClass():是否是成员类,成员类定义在方法外,不是匿名类
  • public boolean isLocalClass():是否是本地类,本地类定义在方法内,不是匿名类

18. Class 类中有关类的声明信息的方法有

  • public native int getModifier():获取修饰符,返回值可通过 Modifier 类进行解读
  • public native Class<? super T> getSuperClass():获取父类,如果为 Object,父类为 null
  • public native Class<?>[] getInterfaces():对于类,为自己声明实现的所有接口;对于接口,为直接扩展的接口,不包括通过父类继承的
  • public Annotation[] getDeclaredAnnotations():自己声明的注解
  • public Annotation[] getAnnotations()所有的注解,包括继承得到的
  • public <A extends Annotation> A getAnnotation(Class<A> annotationClass)获取指定类型的注解,包括继承得到的
  • public boolean isAnnotationPresent(Class<? extends Annotation> annotationClass)检查指定类型的注解,包括继承得到的

19. Class 类中关于类的加载的方法有

  • 方法

    • public static Class<?> forName(String className)
    • public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
  • 分析

    • ClassLoader 表示类加载器

    • initialize 表示加载后,是否执行类的初始化代码(如 static 语句块)

    • 第一个方法中没有传这些参数,相当于调用:Class.forName(className, true, currentLoader)currentLoader 表示加载当前类的 ClassLoader

    • 这里 classNameClass.getName() 的返回值是一致的。比如,对于 String 数组

      1
      2
      3
      String name = "java.lang.String;";
      Class cls = Class.forName(name);
      System.out.println(cls == String[].class);
  • 需要注意的是,基本类型不支持 forName() 方法。即,如下写法:Class.forName("int"); 会抛出 ClassNotFoundException 异常

20. 接上题,如何根据原始类型的字符串构造 Class 对象呢

  • 可以对 Class.forName() 进行一下包装,比如

    1
    2
    3
    4
    5
    6
    7
    public static Class<?> forName(String className) throws ClassNotFoundException {
    if("int".equals(className)) {
    return int.class;
    }
    //其他基本类型
    return Class.forName(className);
    }
  • Java 9 还有一个 forName() 方法,用于加载指定模块中指定名称的类:public static Class<?> forName(Module module, String name)

    • 参数 module 表示模块,是 Java 9 引入的类,当找不到类的时候,它不会抛出异常,而是返回 null,它也不会执行类的初始化

21. Class 类中有关反射与数组的方法有

  • 对于数组类型,有一个专门的方法,可以获取它的元素类型public native Class<?> getComponentType()

    • 比如

      1
      2
      String[] arr = new String[]{};
      System.out.println(arr.getClass().getComponentType());
    • 输出为:class java.lang.String

  • java.lang.reflect 包中有一个针对数组的专门的类 Array (注意不是 java.util 中的 Arrays),提供了对于数组的一些反射支持,以便于统一处理多种类型的数组,主要方法有

    • public static Object newInstance(Class<?> componentType, int length):创建指定元素类型、指定长度的数组
    • public static Object newInstance(Class<?> componentType, int... dimensions):创建多维数组
    • public static native Object get(Object array, int index):获取数组 array 指定的索引位置 index 处的值
    • public static native void set(Object array, int index, Object value):修改数组 array 指定的索引位置 index 处的值为 value
    • public static native int getLength(Object array):返回数组的长度

22. 在 Array 类中,数组是用 Object 而非 Object[] 表示的,为什么

  • 这是为了方便处理多种类型的数组int[]String[] 都不能与 Object[] 相互转换,但可以与 Object 相互转换,比如

    1
    2
    int[] intArr = (int[]) Array.newInstance(int.class, 10);
    String[] strArr =(String[]) Array.newInstance(String.class, 10);
  • 除了以 Object 类型操作数组元素外,Array 也支持以各种基本类型操作数组元素,如

    • public static native double getDouble(Object array, int index);
    • public static native void setDouble(Object array, int index, double d);
    • public static native void setLong(Object array, int index, long l);
    • public static native long getLong(Object array, int index);

23. Class 类中有关反射与枚举的方法有

  • public T[] getEnumConstants():获取所有的枚举常量
-------------------- 本文结束感谢您的阅读 --------------------