使用ASM Java库进行拆箱操作

8

我正在使用ASM Java库来替代一些反射操作。我生成了这个方法的主体:

void set(Object object, int fieldIndex, Object value);

使用这个生成的方法,我可以在运行时设置对象上的字段而不使用反射。它非常好用。然而,我发现它对于原始类型的字段无法正常工作。以下是我的set方法的相关部分:
for (int i = 0, n = cachedFields.length; i < n; i++) {
    mv.visitLabel(labels[i]);
    mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
    mv.visitVarInsn(ALOAD, 1);
    mv.visitTypeInsn(CHECKCAST, targetClassName);
    mv.visitVarInsn(ALOAD, 3);
    Field field = cachedFields[i].field;
    Type fieldType = Type.getType(field.getType());
    mv.visitFieldInsn(PUTFIELD, targetClassName, field.getName(), fieldType.getDescriptor());
    mv.visitInsn(RETURN);
}

此代码用于为select生成案例标签。对于对象,它的运行很好;但是对于原始数据类型,我会遇到以下错误:

期望在堆栈上找到浮点数

好的,这是有道理的。我需要自己完成拆箱操作。我实现了以下内容:

for (int i = 0, n = cachedFields.length; i < n; i++) {
    mv.visitLabel(labels[i]);
    mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
    mv.visitVarInsn(ALOAD, 1);
    mv.visitTypeInsn(CHECKCAST, targetClassName);
    mv.visitVarInsn(ALOAD, 3);

    Field field = cachedFields[i].field;
    Type fieldType = Type.getType(field.getType());
    switch (fieldType.getSort()) {
    case Type.BOOLEAN:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z");
        break;
    case Type.BYTE:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B");
        break;
    case Type.CHAR:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C");
        break;
    case Type.SHORT:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S");
        break;
    case Type.INT:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I");
        break;
    case Type.FLOAT:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F");
        break;
    case Type.LONG:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J");
        break;
    case Type.DOUBLE:
        mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
        mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D");
        break;
    case Type.ARRAY:
        mv.visitTypeInsn(CHECKCAST, fieldType.getDescriptor());
        break;
    case Type.OBJECT:
        mv.visitTypeInsn(CHECKCAST, fieldType.getInternalName());
        break;
    }

    mv.visitFieldInsn(PUTFIELD, targetClassName, field.getName(), fieldType.getDescriptor());
    mv.visitInsn(RETURN);
}

我已经追踪过了,针对适当的字段它确实进入了“case Type.FLOAT”,但我收到了以下错误消息:
期望在堆栈上找到对象/数组
这就是我的困境所在。我无论如何都无法弄清楚为什么取消装箱不起作用。 "ALOAD,3"将set方法的第三个参数放在堆栈上,应该是Float。你有什么想法吗?
我发现asm-commons库有一个GeneratorAdapter类,其中有一个unbox方法。但是,我真的不想为应该很简单的事情再包含另一个JAR文件。我查看了GeneratorAdapter源码,它正在做非常相似的事情。我尝试修改我的代码以使用GeneratorAdapter,只是为了看看它是否起作用,但发现转换并不容易。
2个回答

10

结果证明,上述拆箱过程是正常工作的。我之前写的代码在调用get方法时没有将结果装箱成Object类型,导致返回错误。这是我的问题,应该先编写简单测试!

如果有其他人需要,以下是正确的装箱代码:

Type fieldType = Type.getType(...);
switch (fieldType.getSort()) {
case Type.BOOLEAN:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
    break;
case Type.BYTE:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
    break;
case Type.CHAR:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;");
    break;
case Type.SHORT:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
    break;
case Type.INT:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
    break;
case Type.FLOAT:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;");
    break;
case Type.LONG:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
    break;
case Type.DOUBLE:
    mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
    break;
}

1
使用GeneratorAdapter比MethodVisitor更加简洁,并且具有unbox()方法,可以插入正确的invoke primitive.valueOf()方法调用。

谢谢,但我不想依赖于额外的asm-commons JAR来使用GeneratorAdapter。 - NateS

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接