12

I have a class with two methods like this:

public class Dummy{
  public void doIt(String arg1, File arg2, Writer... ctx){
    // Do something very important...
  }

  public void doIt(String arg1, Writer... ctx){
    // Do something else...
  }

  public static void main(String[] args){
    new Dummy().doIt("Test", null);
  }
}

I would expect, that the compiler would give an error, because the method call is ambiguous. Instead the second method is called.

In our case the ambigous methods are generated from database methods and the varargs where added later. Now we don't know how to avoid problems with method calls like in the example.

Does anybody else have this problem and an idea how to solve it?

ThiesR
  • 121
  • 4
  • 9
    If you want to use the second method, use `new Dummy().doIt("Test", null, null);`. Java will give precedence to the most appropriate method and since varargs are optional in their entirety (no parameters is a valid varargs call), the former is more specific. If you want to know the exact rules to determine the most-appropriate method then you'll have to look in the JLS. – Jeroen Vannevel Feb 07 '14 at 17:11
  • Thanks for your comment! The problem is not how to call the second method. This could be done by casting the null argument. The problem is how to find method calls like this, where the first method should be called (and was before we changed our code generator), but the second one is called. – ThiesR Feb 07 '14 at 17:20
  • Sorry. Yes the second method is called, but we want to call the first method. – ThiesR Feb 07 '14 at 17:21
  • 2
    @user3284827: What compiler are you using? Using JDK 7, I'm getting a warning about the call already. To call the first method, just cast the null to `File`. – Jon Skeet Feb 07 '14 at 17:23
  • 1
    [JLS § 15.12](http://docs.oracle.com/javase/specs/jls/se7/html/jls-15.html#jls-15.12) is the authority on this, but it's defeatingly vast and incomprehensible. – Boann Feb 07 '14 at 17:24
  • Same here: `warning: non-varargs call of varargs method with inexact argument type for last parameter;` – assylias Feb 07 '14 at 17:24
  • Do you really need to overload the methods. Should be no issue to give them each another name when you generate them anyway. – Ingo Feb 07 '14 at 17:26
  • 1
    The answer is probably in JLS 15.12.2.5, which says that the _most specific_ method is chosen if this is applicable; later on it discusses how it determines whether one method is more specific than another for variable-arity methods. I don't yet understand how it works, though. It's pretty formidable. But I suspect the answer is in this section. – ajb Feb 07 '14 at 17:27
  • possible duplicate of [Why doesn't autoboxing overrule varargs when using method overloading in Java 7?](http://stackoverflow.com/questions/7689386/why-doesnt-autoboxing-overrule-varargs-when-using-method-overloading-in-java-7) – Makoto Feb 07 '14 at 17:32
  • possible duplicate of [Java: overloaded method resolution and varargs -- confusing example](http://stackoverflow.com/questions/6032901/java-overloaded-method-resolution-and-varargs-confusing-example) – jmj Feb 07 '14 at 17:36
  • 1
    Do the methods do such different things? When you say they are generated from database methods, it makes me think of a persistence framework. If the goal is to insert or update a row, then wouldn't both method result in a value for the first parameter and the remaining ones unspecified? Or, to put it another way, can you make semantics identical in this case so that it doesn't matter which method the compiler selects? – David Conrad Feb 07 '14 at 20:12

3 Answers3

2

I'm giving the answer according to this:

The problem is how to find method calls like this, where the first method should be called (and was before we changed our code generator), but the second one is called.

First, as it is already has been pointed out, Java compiler gives warnings about such method usages. It looks like this:

com/stack/undsprlbl/varargs/Main.java:10: warning: non-varargs call of varargs method with inexact argument type for last parameter;

and could be easily grep-ed from javac output.

Second, you may consider writing some self-test for your code along these lines:

    Class cl = Ambiguity.class;
    Method[] methods = cl.getDeclaredMethods();
    Set<String> varargsMethods = new HashSet<String>();
    for (Method method : methods) {
        Class c[] = method.getParameterTypes();
        if(c.length > 0)
        {
            Class last = c[c.length - 1];
            if(last.isArray())
            {
                if(varargsMethods.contains(method.getName()))
                    System.out.println("Method " + cl.getName() + "#"+ method.getName() + " looks suspicious.");
                else
                    varargsMethods.add(method.getName());
            }
        }
    }

with understanding that you ought to iterate over all your classes instead of direct mentioning. This answer seems a way to go --- take all packages in the app and check them.

At this point you'll have two lists:

  1. List of ambiguous varargs method usages

  2. List of ambiguous varargs methods.

By crossing those two you can figure out where you probably will have problems.

Next, I would suggest checking out the version of the code before the second method was added and finding all the usages of the first one. Those clearly have to be disambiguated in favour of the first method in HEAD version. This way, I suppose, there would be quite finite number of those calls left.

Community
  • 1
  • 1
Undespairable
  • 203
  • 1
  • 11
0

The answer to your specific question is to do what others have said and call doIt("someString", null, null) to make the method call more specific.

That being said, the REAL answer is to just rewrite the methods to take a Collection as a argument. Using varargs tends to cause more problems than it solves.

allTwentyQuestions
  • 1,160
  • 7
  • 11
0

I would suggest creating an unambiguous wrapper class to prevent the accidental misuse of the generated class.

Example Wrapper:

public class NicerDummy{
    Dummy dummy;
    public NicerDummy(){
        dummy = new Dummy(); 
    }

    public void doSomething(String arg1, Writer... ctx){
        dummy.doIt(arg1, Writer);
    }

    public void doSomethingElse(String arg1, File arg2, Writer... ctx){
        dummy.doIt(arg1, arg2, ctx);
    }
}
rock-fall
  • 438
  • 2
  • 11