【问题标题】:Unboxing and Reflection in Java?Java中的拆箱和反射?
【发布时间】:2011-03-13 10:00:08
【问题描述】:

我正在编写的模型中有一个带有签名public void setFoo(int newFoo) 的方法。我正在使用以下代码从我的控制器中调用它:

protected void setModelProperty(String propertyName, Object newValue) {

    for (AbstractModel model: registeredModels) {
        try {
            Method method = model.getClass().
                getMethod("set"+propertyName, new Class[] {
                                                  newValue.getClass()
                                              }
                         );
            method.invoke(model, newValue);

        } catch (Exception ex) {
            //  Handle exception.
            System.err.println(ex.toString());
        }
    }
}

controller.setModelProperty("Foo",5); 这样调用这个方法会导致抛出异常:java.lang.NoSuchMethodException: foo.bar.models.FooModel.setFoo(java.lang.Integer)——看起来int 被装箱为Integer,这与setFoo 的签名不匹配。

有什么方法可以说服这个反射代码将5(或我传入的任何int)作为整数传递,而无需装箱?还是我必须在我的模型中创建public void setFoo(Integer newFoo) 并明确取消装箱,然后调用原始的setFoo?

【问题讨论】:

  • 这很奇怪。 Method.invoke() 的 Javadocs 说各个参数会自动解包以匹配原始形式参数,所以看起来这应该不是问题。我在这台机器上没有编译器,否则我会做一些测试。
  • @mmyers 但是 newValue 是一个装箱的 Integer,因此 getMethod 将在 Method.invoke(...) 被调用之前尝试并返回签名为 setFoo(Integer) 的(不存在的)方法。
  • @MHarris:就可以了。我看到了getMethod调用,但是因为不寻常的格式,我认为它是一个自我实现的方法,而不是标准的Class方法。

标签: java reflection


【解决方案1】:

Outoboxing 将完成,但您正在寻找错误的方法。参数类型数组不正确。对于 int,类型将是 Integer.TYPE 而不是 Integer.class。

无论参数类型如何,请尝试以下方法使其正常工作(outoboxing 会这样做):

protected void setModelProperty(String propertyName, Object newValue) {

    for (AbstractModel model: registeredModels) {
        try {
            Method method = findSetter(model.getClass(), propertyName);
            method.invoke(model, newValue);

        } catch (Exception ex) {
            //  Handle exception.
            System.err.println(ex.toString());
        }
    }
}

private Method findSetter(Class<?> type, String propertyName) {
    for (Method methos : type.getMethods()) {
        if (method.getName().equalsIgnoreCase("set" + propertyName) && method.getParameterTypes().length == 1) {
            return method;
        }
    }
    throw new NoSuchMethodError("No setter for " + propertyName);
}

【讨论】:

    【解决方案2】:

    问题不在于调用,而在于获取方法,因为您使用的 Integer.class 与 int.class 不同。

    如果第一次调用失败,您可以通过第二次调用 get 方法来修复它:

    protected void setModelProperty(String propertyName, Object newValue) {
    
        for (AbstractModel model: registeredModels) {
            Method method = null;
            try {
                method = model.getClass().
                     getMethod("set"+propertyName, new Class[] {
                                                      newValue.getClass()
                                                   }
                             );
            } catch (Exception ex) {
                try {
                    if (canBoxPrimitive(newValue.getClass()) {
                        method = model.getClass().
                               getMethod("set"+propertyName, new Class[] {
                                                      primitiveClass(newValue.getClass())
                                                   }
                    }
                }
                catch (Exception ex) {
                    // handle it
                }
            }
    
            method.invoke(model, newValue);
       }
    }
    

    函数编码选项:boolean canBoxPrimitive(Class)Class primitiveClass(Class)你可以看这里:Dynamically find the class that represents a primitive Java type

    【讨论】:

      【解决方案3】:

      有什么办法可以说服 反射代码通过 5(或 无论我传入什么 int) 作为整数, 没有拳击?

      当您的方法签名显示Object newValue 时不是这样,因为int 永远不可能是Object。保持方法通用的一种方法是让调用者显式传递类型,即:

      protected void setModelProperty(String propertyName, Object newValue, Class type) {
      

      或者,您可以测试newValue 的类型以查看它是否是原始包装器,在这种情况下查找该方法的原始版本和包装版本。但是,当用户传入null 时,这将不起作用。实际上,这种情况下该方法根本行不通……

      【讨论】:

      • 这样你可以重载方法来接收2个参数,就像你正在做的那样,如果它是一个普通的对象,那就是调用setModelProperty(propertyName, newValue, newValue.getClass())。但是,如果您知道该值必须是原始类型,则可以使用参数String, int 创建一个方法,该方法调用setModelProperty(propertyName, Object newValue, Integer.TYPE)。不过,您需要为每个要使用的原语使用一个。
      【解决方案4】:

      newValue 在调用 setModelProperty() 时被装箱为整数。这是唯一可以调用它的方法; 'int' 不是一个实例对象。 newValue.getClass() 返回“Integer”,因此对 getMethod() 的调用失败。

      如果您想在此处使用原语,则需要特殊版本的 setModelProperty。或者,您可以编写一个方法 setFoo(Integer)。

      或者更一般地说,你可以这样写:

      if (newValue.getClass()==Integer.class) {
        // code to look for a method with an int argument
      }
      

      【讨论】:

        【解决方案5】:

        您可以将 setModelProperty 专门用于您希望使用的任何原语:

        protected void setModelProperty(String propertyName, int newValue) { /* ... */ }
        

        或者,您可以在 newValue 上使用 instanceof 来检查盒装原语:

        Class[] classes;
        if (newValue instanceof Integer) {
          classes = new Class[] { int.class };
        } else if (newValue instanceof Double) {
          /* etc...*/
        } else {
          classes = new Class[] {newValue.getClass() };
        }
        

        或者最后,如果您有 setFoo 的来源,您可以将其更改为采用盒装 Integer 而不是 int - 开销通常可以忽略不计。

        【讨论】:

          【解决方案6】:

          你试过了吗

          Integer.valueOf(5) 将 5 作为整数而不是 int 传递?

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2013-05-27
            • 1970-01-01
            • 2022-06-16
            • 2018-06-20
            • 2011-09-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多