简体   繁体   English

Java中的取消装箱和反射?

[英]Unboxing and Reflection in Java?

I have a method with the signature public void setFoo(int newFoo) in a model I'm writing. 我正在编写的模型中具有签名public void setFoo(int newFoo)的方法。 I'm using the following code to call it from within my controller: 我正在使用以下代码从我的控制器中调用它:

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());
        }
    }
}

Calling this method like controller.setModelProperty("Foo",5); 调用类似controller.setModelProperty("Foo",5); results in an exception being thrown: java.lang.NoSuchMethodException: foo.bar.models.FooModel.setFoo(java.lang.Integer) -- it looks like the int is being boxed as an Integer , which doesn't match the signature of setFoo. 导致引发异常: java.lang.NoSuchMethodException: foo.bar.models.FooModel.setFoo(java.lang.Integer) -看起来int被装箱为Integer ,与签名不匹配setFoo的

Is there any way to convince this reflection code to pass 5 (or whatever int I pass in) as an Integer, without the boxing? 有没有办法说服此反射代码将5 (或我传入的任何int)作为整数传递而无需装箱? Or do I have to create public void setFoo(Integer newFoo) in my model and unbox explicitly, then call the original setFoo? 还是我必须在模型中创建public void setFoo(Integer newFoo)并显式取消装箱,然后调用原始setFoo?

You could specialise your setModelProperty for any primitives you expect to be used with: 您可以将setModelProperty专用于希望与以下对象一起使用的任何原语:

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

Alternatively, you could use instanceof on newValue to check for boxed primitives: 另外,您可以在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() };
}

Or finally, if you have the source for setFoo, you could change it to take a boxed Integer instead of an int - the overhead is usually negligible. 或者最后,如果您有setFoo的源代码,则可以将其更改为采用盒装Integer而不是int-开销通常可以忽略不计。

Is there any way to convince this reflection code to pass 5 (or whatever int I pass in) as an Integer, without the boxing? 有没有办法说服此反射代码将5(或我传入的任何int)作为整数传递而无需装箱?

Not while your method signature says Object newValue , because an int can never be an Object . 在您的方法签名中说Object newValue ,因为int永远不可能是Object A way to keep the method generic would be to have callers pass in the type explicitly, ie: 保持方法通用的一种方法是让调用者显式传递类型,即:

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

Alternatively, you could test the type of newValue to see if it's a primitive wrapper and in that case look for both the primitive and the wrapped version of the method. 或者,您可以测试newValue的类型,以查看它是否是原始包装,然后在这种情况下查找方法的原始版本和包装版本。 However, that won't work when the user passes in a null . 但是,当用户传递null时,这将不起作用。 Actually, the method won't work at all in that case... 实际上,在这种情况下,该方法根本不起作用...

newValue is being boxed as an Integer when setModelProperty() is called. 调用setModelProperty()时,会将newValue装箱为Integer。 This is the only way it can be called; 这是可以调用的唯一方法。 'int' is not an instanceof Object. “ int”不是Object的实例。 newValue.getClass() returns "Integer" and thus the call to getMethod() fails. newValue.getClass()返回“ Integer”,因此对getMethod()的调用失败。

If you want to work with primitives here you'll need a special version of setModelProperty. 如果要在这里使用基元,则需要特殊版本的setModelProperty。 Alternatively you could write a method setFoo(Integer). 或者,您可以编写一个方法setFoo(Integer)。

Or more generally you could write: 或更笼统地说,您可以这样写:

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

Problem is not on invoking but on getting the method, as you are using Integer.class which is different from int.class. 问题不在于调用而是在于获取方法,因为您使用的是与int.class不同的Integer.class。

You can fix it by doing a second call to get method if the first one fails: 如果第一个失败,可以通过第二次调用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);
   }
}

For options to code the functions: boolean canBoxPrimitive(Class) and Class primitiveClass(Class) you can take a look here: Dynamically find the class that represents a primitive Java type 有关编码功能的选项: boolean canBoxPrimitive(Class)Class originalClass(Class) ,您可以在此处查看: 动态查找表示原始Java类型的类

Outoboxing will be done, but you are looking for the wrong method. 发件箱将完成,但是您正在寻找错误的方法。 The parameter type array is not correct. 参数类型数组不正确。 For int the type would be Integer.TYPE instead of Integer.class. 对于int,类型将是Integer.TYPE而不是Integer.class。

Try some like the following to get it working regardless of the parameer type (outoboxing will do it): 尝试使用以下类似方法使它不管参数类型如何都可以工作(发件箱可以做到):

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);
}

Did you try 你试过了吗

Integer.valueOf(5) to pass 5 along as an Integer instead of an int? Integer.valueOf(5)作为Integer而不是int传递5?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM