反射是 java 一个强大的机制,通过反射,我们可以动态地创建类型的实例,将类型绑定到现有对象,或从现有对象中获取类型,然后调用类型的方法或访问其字段和属性。本节主要介绍反射机制中用到的类对象。
理解类对象
我们之前在 JVM 中定义了 Zclass 类,用来表示一个 class 文件在 JVM 中的抽象(注意该 Zclass 和 java/lang/Class 中的做区分,前者是在 JVM 中,后者是在 java 应用程序中的类)。在构造 Zclass 对象时,我们是传入了一个 ClassFile 的对象,然后通过 ClassFile 对象的各个字段,构造了 Zclass 对象。同一个 class 文件会产生唯一的一个 Zclass 对象(前提是用同一个类加载器加载的 class 文件,如果是不同的类加载器,则另当别论,本文暂时不讨论多个类加载器加载同一个 class 文件的情况,所以暂且认为同一个 class 文件和 Zclass 对象是一一对应的)。
1 2 3 4 5 6 7 8 9 10
| public Zclass(ClassFile classFile) { accessFlags = classFile.getAccessFlags(); thisClassName = classFile.getClassName(); superClassName = classFile.getSuperClassName(); interfaceNames = classFile.getInterfaceNames(); runtimeConstantPool = new RuntimeConstantPool(this, classFile.getConstantPool()); fileds = Zfield.makeFields(this, classFile.getFields()); methods = Zmethod.makeMethods(this, classFile.getMethods()); sourceFile = classFile.getSourceFile(); }
|
使用 Zclass 就可以描述一个类,其实我们在 java 中定义一个类,无非就是在定义两类东西,一个是该类中有哪些变量,另一个是该类中有哪些方法。这些都可以在 Zclass 中反映出来。
我们在 JVM 中定义的 Zobject(Zobject 表示的是在 JVM 中一个对象的,要注意其和在 java 应用中 Object 的区别) 中就有一个字段,其指向该对象对应的 Zclass 对象。这个很容易理解,因为 Zobject 中只保存了该对象的成员变量的值,如果要执行该类的方法,还是要去对应的 Zclass 中的 Zmethod 中获取对应的字节码。方法的字节码都是一样的,并不针对单独的对象,同一个类创建的对象,调用其方法时的字节码都是一样的,只不过传入的参数不同而已。因此为了节省 JVM 的内存,没有必要为每个 Zobject 中保存方法的字节码。
这里只是打通了 Zobject 到 Zclass 的单向通道,其实 Zclass 中也需要一个字段指向一个具体的 Zobject,注意该 Zobject 并不是由 Zclass 创建的实例对象,而是 Zclass 所代表的 Class 的类对象。
我们拿熟悉的 String 类举例子,每个字符串 eg:”hello”都是 String 的一个实例对象,对应 JVM 中的 Zobject。然而 String 所代表的类,也有一个实例对象,该对象的获取方式有两种:
- String.class
- “hello”.getClass()
这两种方法获取到的都是同一个对象。
这也解释了为什么 JVM 中每个 Zclass 中都需要一个字段指向该类的类对象(String.class),而不是类的实例对象 “hello”
接下来我们就在 Zclass 中添加这个字段,让其指向该类的类对象。
1 2 3 4 5
| public class Zclass { ... Zobject jObject; ... }
|
定义了该字段,那么该字段具体在上面时候赋值呢?回想一下 Zclass 是何时被创建的,是通过类加载器加载 class 文件是生成的,于是我们也就很自然的想到类对象需要紧跟着在 Zclass 生成之后生成。
实现类对象
前面也提到了,为类对象赋值是在 class 文件生成之后,那么我们再次修改类加载器,是其完成类对象的构造。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| public Zclass loadClass(String name) { if (map.containsKey(name)) { return map.get(name); }
Zclass clazz; if (name.charAt(0) == '[') { clazz = loadArrayClass(name); } else { clazz = loadNonArrayClass(name); }
Zclass jlClassClass = map.get("java/lang/Class"); if (jlClassClass != null) { clazz.jObject = jlClassClass.newObject(); clazz.jObject.extra = clazz; } return clazz; }
|
在产生 Zclass 之后,获取java/lang/Class
的 Zclass——jlClassClass,在利用 jlClassClass 创建 Zobject,此 Zobject 就是之前加载的 Zclass 的类对象,通过对 Zclass 的 jObject 的赋值,那么加载到方法区的类就都有类对象了。
这里还有个问题:类也是对象,而对象又是类的实例,那么在 JVM 中,究竟是先有类还是先有对象呢?
在 JVM 中,我们手动触发的是加载java/lang/Class
,但是在加载该类的时候,会先加载其父类 java/lang/Object
,以及 Class 实现的接口类。那么此时 JVM 的方法区中已经有了这几个类的 Zclass 了,接下来再为这几个类创建类变量,其逻辑在下面的 loadBasicClasses 方法中,该方法是在 ZclassLoader 的构造方法中调用的,也就是说 JVM 启动时就触发这些基本类的加载,因为这些类太重要了,所有其它的子类都需要这些类。
1 2 3 4 5 6 7 8 9 10 11 12 13
| private void loadBasicClasses() { Zclass jlClassClass = loadClass("java/lang/Class"); for (Map.Entry<String, Zclass> entry : map.entrySet()) { Zclass jClass = entry.getValue(); if (jClass.jObject == null) { jClass.jObject = jlClassClass.newObject(); jClass.jObject.extra = jClass; } } }
|
基本类型的类
通过前面的介绍,已经完成了类与类对象的关联,但是 void 和基本类型也有对应的类对象,但其只能通过字面值来访问,eg:
就像之前的数组类一样,基本类型的类并没有 class 文件,其也是由 JVM 在运行期间生成的。那么我们就在 JVM 开启时,也手动创建基本类型的类,并将基本类型的类加载到方法区。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| private void loadPrimitiveClasses() { for (Map.Entry<String, String> entry : ClassNameHelper.primitiveTypes.entrySet()) { String className = entry.getKey(); loadPrimitiveClass(className); } }
private void loadPrimitiveClass(String className) { Zclass clazz = new Zclass(AccessFlag.ACC_PUBLIC, className, this, true, null, new Zclass[]{}); clazz.jObject = map.get("java/lang/Class").newObject(); clazz.jObject.extra = clazz; map.put(className, clazz); }
public static HashMap<String, String> primitiveTypes;
static { primitiveTypes = new HashMap<String, String>(); primitiveTypes.put("void", "V"); primitiveTypes.put("boolean", "Z"); primitiveTypes.put("byte", "B"); primitiveTypes.put("short", "S"); primitiveTypes.put("int", "I"); primitiveTypes.put("long", "J"); primitiveTypes.put("char", "C"); primitiveTypes.put("float", "F"); primitiveTypes.put("double", "D"); }
|
对于非基本类型的类对象,其获取是通过 ldc 指令获取到的,
1 2 3 4 5 6 7
| static void fClazz() { Class c = String.class; } //对应的字节码: 0 ldc #4 <java/lang/String> 2 astore_0 3 return
|
回想一下,之前介绍的 ldc 指令的功能,是用来加载基本类型,字符串字面值的,所以现在需要扩展 ldc 指令,根据 ldc 操作数得到的运行时常量池中的常量,如果是 ClassRef 类型的,那么就将其 Zclass 的 jObject 对象放到操作数栈
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| public class LDC extends Index8Instruction { @Override public void execute(Zframe frame) { OperandStack operandStack = frame.getOperandStack(); Zclass clazz = frame.getMethod().getClazz(); RuntimeConstantInfo runtimeConstant = clazz.getRuntimeConstantPool().getRuntimeConstant(index); switch (runtimeConstant.getType()) { case ConstantInfo.CONSTANT_Integer: operandStack.pushInt((Integer) runtimeConstant.getValue()); break; ...... case ConstantInfo.CONSTANT_String: Zobject internedStr = StringPool.jString(clazz.getLoader(), (String) runtimeConstant.getValue()); operandStack.pushRef(internedStr); break; case ConstantInfo.CONSTANT_Class: ClassRef classRef = (ClassRef) runtimeConstant.getValue(); Zobject jObject = classRef.resolvedClass().getjObject(); operandStack.pushRef(jObject); break; default: break; } } }
|
同时,基本类型的类对象,在 java 代码中看起来是通过字面量获取的,但是通过查看其编译后的 class 文件,发现并不是由 ldc 加载到内存的,而是通过 getstatic 指令。因为每个基本类型都有对应的包装类,包装类中都有一个静态常量:TYPE,该字段中保存的就是基本类型的类,我们查看 Integer.java 类
1 2 3
| public final class Integer extends Number implements Comparable<Integer> { public static final Class<Integer> TYPE = (Class<Integer>) Class.getPrimitiveClass("int"); }
|
而 getPrimitiveClass 方法是一个 native 的方法,关于 native 的方法的实现,请参考下一节的讲解。
本文作者:
Zachaxy
版权声明:
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。