反射
静态强类型语言来说,基本上都由
任何语言编写代码的时候,可能都会遇到一种需求,就是希望看到某个类或者结构数据到底长什么样,比如获取类名称,字段名称,方法名称,这些信息都属于代码层面的信息了,也叫元信息。而操作元信息来编写代码的过程被称为元编程。由于任何语言的代码都会先交给编译器或者解释器,因此这个任务可以让编译器或者解释器来完成,比如java编译器,JVM, Python解释器,Go语言运行时环境这些。
在 Java 中实现元编程就靠反射机制了。这是 Java 虚拟机提供的一套接口。因为 Java 的代码编译成字节码后,都是交给 JVM 解释执行的,JVM 能够了解运行中的 Java 代码的一切信息。
inspect classes
java.lang.Class
类是一切类的封装和抽象,因此一切都是从这个类开始的,获得一个 Class 的实例对象有两种方式,一个是编译时,一个是运行时。
编译时的用法,其实就是你的这个类(字节码)在编译的时候就存在且可访问的。
Class myObjectClass = MyObject.class;
运行时的用法,就是你可能就不知道类名,或者这个类在编译的时候可能都还没有,必须在运行时才确定。
String className = 'myMapper'; Class klass = Class.forName(className);
在获取类对象 Class 以后,可以通过相关函数获取到类的各种元信息。包括 类名, 修饰符,包信息,超类,接口,构造函数,方法,字段,注解信息,范型信息, 模块信息(JAVA 9引入)等。
获取类里的范型信息
// return generic type Method method = Klass.class.getMethod("getPhones", null); Type returnType = method.getGenericReturnType(); if (returnType instanceof ParameterizedType) { ParameterizedType type = (ParameterizedType) returnType; Type[] typeArguments = type.getActualTypeArguments(); for (Type typeArgument : typeArguments) { Class typeArgClass = (Class) typeArgument; System.out.println("typeArgClass = " + typeArgClass); } } // argument generic type method = Klass.class.getMethod("setPhones", List.class); Type[] genericParameterTypes = method.getGenericParameterTypes(); for (Type genericParameterType : genericParameterTypes) { if (genericParameterType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericParameterType; Type[] parameterArgTypes = aType.getActualTypeArguments(); for (Type parameterArgType : parameterArgTypes) { Class parameterArgClass = (Class) parameterArgType; System.out.println("parameterArgClass = " + parameterArgClass); } } } // field generic type Field field = Klass.class.getField("phones"); Type genericFieldType = field.getGenericType(); if (genericFieldType instanceof ParameterizedType) { ParameterizedType aType = (ParameterizedType) genericFieldType; Type[] fieldArgTypes = aType.getActualTypeArguments(); for (Type fieldArgType : fieldArgTypes) { Class fieldArgClass = (Class) fieldArgType; System.out.println("fieldArgClass = " + fieldArgClass); } }
获取数组的类对象
{ /* * int[] intArray = (int[]) Array.newInstance(int.class, 3); * * Array.set(intArray, 0, 123); Array.set(intArray, 1, 456); Array.set(intArray, * 2, 789); * * intArray[0] = 1; * * System.out.println("intArray[0] = " + Array.get(intArray, 0)); * System.out.println("intArray[1] = " + Array.get(intArray, 1)); * System.out.println("intArray[2] = " + Array.get(intArray, 2)); */ // Class stringArrayClass = String[].class; // 运行时获取 int[] 数组,注意是 [I, I 在虚拟机中表示 int, Class intArray = Class.forName("[I"); Class stringArrayClass = Class.forName("[Ljava.lang.String;"); // 通用的获取任何元素类型数组类的方法 String theClassName = intArray.getName(); Class theClass = getClass(theClassName); Class typeArrayClass = Array.newInstance(theClass, 0).getClass(); System.out.println("is array: " + typeArrayClass.isArray()); // 获取数组元素类 ComponentType of an array Class stringArrayComponentType = stringArrayClass.getComponentType(); System.out.println(stringArrayComponentType); } public static Class getClass(String className) throws ClassNotFoundException { System.out.println("className: " + className); if ("int".equals(className)) return int.class; if ("long".equals(className)) return long.class; return Class.forName(className); }
class loading & reloading
Java 类加载机制遵循一个层级结构,一个创建一个类加载器必须告知它的父类加载器,子加载器加载一个类之前先会委托父加载器加载,只有父加载器无法加载时候,子加载器才进行加载。加载步骤:
1. 检查类是否已经加载。
2. 如果未被加载,委托父加载器加载。
3. 如果父加载器无法加载,则尝试自己加载。
动态类加载
public class MainClass { public static void main(String[] args){ ClassLoader classLoader = MainClass.class.getClassLoader(); try { Class aClass = classLoader.loadClass("com.jenkov.MyClass"); System.out.println("aClass.getName() = " + aClass.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
动态 类重加载
运行时加载一个类, 重新加载一个类必须实现自己的classloader, 因为Java 内置的类加载器加载一个类都会检查类是否已经加载过。
设计类重加载代码
Java中每一个类被加载后都通过全类名fully qualified name(包+类名) 和类加载实例来区分。同一个类,A加载器和B加载器加载出来的就是不一样的。
MyObject object = (MyObject) myClassReloadingFactory.newInstance("com.jenkov.MyObject");
如果以上代码每次使用不同的类加载器加载MyObject,就无法进行强制(cast)转换了. 因此,良好的设计应该采用接口或者超类来引用被加载的类的实例.
MyObjectInterface object = (MyObjectInterface) myClassReloadingFactory.newInstance("com.jenkov.MyObject"); MyObjectSuperclass object = (MyObjectSuperclass) myClassReloadingFactory.newInstance("com.jenkov.MyObject");
appendix
package advance.reflection; import java.util.List; interface Interface { void print(); } public class Klass implements Interface { String name; int id; public List<String> phones; public Klass() { this("tt", 77); } public Klass(String name, int id) { this.name = name; this.id = id; } public Klass(String name, int id, List<String> phones) { this.name = name; this.id = id; this.phones = phones; } public List<String> getPhones() { return this.phones; } public void setPhones(List<String> phones) { this.phones = phones; } @Override public void print() { System.out.println(id + ": " + name); } }