views:

620

answers:

6

I want to use

Class.getMethod(String name, Class... parameterTypes)

to find the method I need to invoke with the given parameters, but apparently as described in Bug 6176992 Java doesn't include autoboxing there. So if my reflected class has a method with a (String, int) signature you still get a NoSuchMethodException with a {String.class, Integer.class} array as a paremeter.

Is there any sollution for this? The only way I could think of to call getMethod() with every permutation of primitive and non primitive types which I don't really want to do.

Edit: To make it more clear: I am well aware of the primitive type classes, but I don't see how they could help to solve my problem. My parameterTypes array comes from somewhere and I know that it will only return non primitive types. I can not assume that the interface will only be declared with primitive types and that's exactly my problem:

public interface TestInterface()
{
    public void doTest(Integer i1, int i2, double d3, Double d);
}

Class<?>[] classes = { Integer.class, Integer.class, Double.class, Double.class }
// Due to autoboxing I should become the doTest method here, but it doesn't work
TestInterface.class.getMethod("doTest", classes);
+1  A: 

Integer.class represents the Integer object type. Integer.TYPE represents the int primitive type. Does that work?

Sean Owen
Well that's exactly the problem. getClass().getMethod(String name, Class... parameterTypes) doesn't seem to care about the whole autoboxing thing so there currently doesn't seem to be a way to find a method where parameters might become (un)boxed. I know neither the method signature nor how the Class array parameter for getMethod will look like.
Daff
One would ask why you are calling a method where you don't know the parameters... if you describe what you are doing a bit more perhaps a better solution will present itself.
TofuBeer
Guess you are right, I updated my question. Calling a method where I don't know the parameters is part of a generic dispatching mechanism.
Daff
+1  A: 

Yeah you need to use Integer.TYPE or (equivalently) int.class.

Update: "My parameterTypes array comes from somewhere and I know that it will only return non primitive types." Well, then that's this "somewhere"'s problem. If they don't give you the proper signature of the method they want, then how are you going to find it? What if there are two overloaded methods, that differ only in one of them takes a primitive and the other one takes the wrapper class? Which one does it choose then? I mean, if you really have no choice then I guess you could just loop through all the methods, and look for one with the right name, and manually check all the parameter types for whether they are correct, or are the primitive equivalents.

newacct
I updated my question, maybe it makes it a little clearer why the primitive type classes don't help me.
Daff
+2  A: 

reflection method look up is not as sophisticated as compiler's. I understand why you need something like that. suppose at runtime you have a method name, and an array of objects as arguments, and you want reflection to give you the exact method based on types of the arguments, but it's more complicated than that. for example:

void f(Integer i){..}
void f(int i){...}

when the argument is type Integer, which one can you choose? An even trickier one:

void f(Integer i, List l){...}
void f(Object o, LinkedList l){...}

compiler has a set of rules to choose the "most specific method" based on static information; if it can't determine it will alert you right away.

you have to simulate the compiler and write an algorithm to find out the "most specific method". (oh, and with consideration of auto-boxing, almost forgot that!)

irreputable
+1  A: 

The only answer at the moment is to write code to simulate the Java compiler's type promotion rules, an reflectively pick the most appropriate method. Autoboxing and unboxing are just examples of the type promotions the compiler knows about ...

Why don't the Java reflective APIs already do this? I can think of a number of reasons.

  • Before Java 1.5, the getMethod class and friends did not understand how to do (for example) promotion of int to float. If there wasn't a need pre-1.5, why now?

  • Adding this kind of stuff will make reflective method calling even slower.

  • Autoboxing and unboxing can be confusing with non-reflective method invocation. Adding reflection will only add more confusion.

  • The runtime implementation of the promotion rules will add a class of new runtime error cases that need to be mapped to exceptions and diagnosed by user code. These ones will be particularly tricky. For example, it would have to deal with the reflective equivalent of an ambiguous method call.

  • The overarching requirement for backwards compatibility means that Sun would have to implement this as new methods. They cannot change the behaviors of the current methods because that would potentially break thousands of customers' existing applications.

There is one final point that relates specifically to the OP's use-case (as described). The OP says that his code does not know whether to expect (for example) a method with an int or Integer parameter on the target class. Suppose that the person who wrote the target class provided both overloads ... and the semantics are subtly (or unsubtly) different? No matter what you do, there will be situations where the OP's code picks the overload that the client doesn't expect. Bad.

IMO, it is better for the OP's code to impose some simple API rules that say when it is correct to use primitives versus wrappers.

Stephen C
Thanks, thinking about it, that makes indeed sense. Maybe I should just make the rule that the reflected classes can only contain wrapped types if they want to be called.
Daff
+4  A: 

As @Stephen C mentions, your only hope is to do the search yourself. All of his caveats hold but I'd argue a little flexibility would go a long way to covering most of the gotchas as long as the callers were aware of the caveats... versus making your callers always be painfully specific.

For code that actually does something like this you can look here: http://meta-jb.svn.sourceforge.net/viewvc/meta-jb/trunk/dev/src/main/java/org/progeeks/util/MethodIndex.java?revision=3811&amp;view=markup

The findMethod() call is the entry point but it delegates (after some caching, etc.) to this method:

private Method searchForMethod( String name, Class[] parms ) {
    Method[] methods = type.getMethods();
    for( int i = 0; i < methods.length; i++ ) {
        // Has to be named the same of course.
        if( !methods[i].getName().equals( name ) )
            continue;

        Class[] types = methods[i].getParameterTypes();

        // Does it have the same number of arguments that we're looking for.
        if( types.length != parms.length )
            continue;

        // Check for type compatibility
        if( InspectionUtils.areTypesCompatible( types, parms ) )
            return methods[i];
        }
    return null;
}

InspectionUtils.areTypesCompatible() takes two lists of types, normalizes their primitives, and then verifies that one is "assignable" to the other. So it will handle the case where you have an Integer and are trying to call a method that takes int as well as the case where you have a String and are trying to call a method that takes Object. It does not handle the case of having an int and calling a method that takes float. There has to be some specificity.

The one caveat is that the above method just searches in method order so if there are ambiguities then the selection is arbitrary. I've never encountered a real-world issue, so far in practice.

Here is the compatibility check for reference: public static boolean areTypesCompatible( Class[] targets, Class[] sources ) { if( targets.length != sources.length ) return false;

    for( int i = 0; i < targets.length; i++ ) {
        if( sources[i] == null )
            continue;

        if( !translateFromPrimitive( targets[i] ).isAssignableFrom( sources[i] ) )
            return false;
        }
    return( true );
}

The code is BSD and mine so the snippets are legal to use. If you decide you'd rather use this util package directly the most recent public release is here: https://meta-jb.svn.sourceforge.net/svnroot/meta-jb/trunk/dev/m2-repo/org/meta-jb/meta-jb-util/0.17.1/

And I only mention that because there hasn't been a bundled download in a long time since most of my active users are maven users. I seem to be more fond of writing code than cutting full releases. ;)

PSpeed
Thanks for the example source. I will try the code and hope that it won't be too slow. Hopefully it isn't error prone but if I will just go with a strict ruleset on what the reflected classes may of may not contain.
Daff
One downside of the this early return of matching results is if have two matching methods e.g. (String, int) and (String, Integer) Java would always take the same method, however it would be difficult to determine which method your search would return.
Peter Lawrey
That is the caveat I mentioned: "if there are ambiguities then the selection is arbitrary." In general it is consistent but I agree it's hard to predict. Furthermore, I'd argue that it is not clear-cut anyway. When you are making calls through reflection then you are always passing an "Integer" even if you started by looking for an "int" based method. The nice thing is that if there is only an "int" or an "Integer" you are covered either way. The cases where you have (String, int) and (String, Integer) are very rare... rarer still that one doesn't just call the other.
PSpeed
A: 

You could add some additional logic around your reflection call which tries converting your Integer.class (or whatever) into the corresponding primitive class, and then repeatedly looking up the method until you get a match. If you have Apache Commons Lang, then the wrapperToPrimitive method will do this conversation for you, but writing it yourself is trivial.

  1. Perform getMethod as usual
  2. If nothing found, then look for any parameter types which have corresponding primitives
  3. For each combination of those, perform another lookup until something sticks.

Not elegant, and for methods with a lot of primitive parameters, it might even be slow.

skaffman