19

We have a class LogManager in our Java project which looks like this:

public class LogManager {

    public void log(Level logLevel, Object... args) {
        // do something
    }

    public void log(Level logLevel, int value, Object... args) {
        // do something else
    }
}

When compiling the project with OpenJDK 6 under Debian everyting works fine. When using OpenJDK 7 the build (done with ant) produces the following errors and the build fails:

[javac] /…/LogManager.java:123: error: reference to log is ambiguous,
                      both method log(Level,Object...) in LogManager
                      and method log(Level,int,Object...) in LogManager match
[javac]       log(logLevel, 1, logMessage);
[javac]       ^
[javac] /…/SomeOtherClass.java:123: error: reference to log is ambiguous,
                      both method log(Level,Object...) in LogManager
                      and method log(Level,int,Object...) in LogManager match
[javac]       logger.log(logLevel, 1, logMessage);
[javac]             ^

As long as the 1 is not autoboxed, the method call should be unambiguous as 1 is an int and cannot be upcast to Object. So why doesn't autoboxing overrule varargs here?

Eclipse (installed using the tar.gz from eclipse.org) compiles it no matter if OpenJDK 6 is installed or not.

Thank's a lot for your help!

Edit:

The compiler gets the option source="1.6" and target="1.6" in both cases. The Eclipse compiling note is just meant as a comment.

Michael
  • 1,502
  • 19
  • 29

3 Answers3

17

I guess it's related to bug #6886431, which seems to be fixed in OpenJDK 7 as well.

The problem is that JLS 15.12.2.5 Choosing the Most Specific Method says that one method is more specific than another one when types of formal parameters of the former are subtypes of formal parameters of the latter.

Since int is not a subtype of Object, neither of your methods is the most specific, thus your invocation is ambiguous.

However, the following workaround is possible, because Integer is a subtype of Object:

public void log(Level logLevel, Object... args) { ... }
public void log(Level logLevel, Integer value, Object... args) { ... } 
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • I tried using Integer instead of int and it worked (with both OpenJDK 6 and 7). But with int and a call log(logLevel, 1, logMessage1) my method signature should be more specific as only upcasts and no autoboxing is needed. So I can see that it works this way, but I don't really understand why. I would expect the error with your signatures instead of mine. – Michael Oct 07 '11 at 16:17
  • +1 as I didn't bother to reread other answers before I came to the same conclusion in a comment. – Edwin Buck Oct 07 '11 at 16:38
2

Eclipse uses it's own compiler, so what Eclipse does eventually follows what the SUN / Oracle provided compilers does; however, sometimes (like in this case) there are differences.

This could "go either way" and probably in Java 6, the issue wasn't addressed in detail. Since Java has a strong requirement to reduce the number of "ambiguous" meanings in its environment (to enforce same behavior across many platforms), I'd imagine that they tightened up (or directly specified) the decided behavior in the 7 release.

You just got caught on the "wrong" side of new specification clarification. Sorry, but I think you'll be writing a bit of this

public void log(Level logLevel, Object... args) {
    if (args != null && args[0] instanceof Integer) {
      // do something else
    } else {
      // do something
    }
}

into your new solution.

Edwin Buck
  • 69,361
  • 7
  • 100
  • 138
  • 1
    Using your solution would work to compile the project, but destroy the beauty of method overwriting :( – Michael Oct 07 '11 at 15:45
  • @Michael: You could also try if declaring a method `log(Level level, int i)` that calls `log(level, i, new Object[]{})` helps the compiler - I don't have a Java 7 environment at hand right now to try. (Also, method overloading isn't beautiful, precisely because it makes method resolution needlessly complicated causing these kinds of edge cases; at least compared to named and optional parameters which would cover your use case well.) – millimoose Oct 07 '11 at 16:06
  • This would just lead to an unused method as we never need the method log without any further arguments, i.e., the log messages. Clearly I meant method overloading instead of overwriting. I didn't like it 6 years ago, but now I can see its beauty. And I don't think that it's needlessly complicated as it can be defined very clearly. From my point of view this is a bug of the OpenJDK 7 compiler;) – Michael Oct 07 '11 at 16:23
  • @Michael, not a bug in the compiler; but, perhaps a bug in the language. When promoting built-in types to Object types, there's a conceptual conflict. Have you tried `log(Level, Integer, Object ...)`? That might use autoboxing to your favor, without quickly conflicting with the `Integer is a Object` which leads to the binding with `log(Level, Object ...)`. Personally, I understand the built-in type compromise, but I prefer languages where even constants are Objects. Why not `int x = 4; System.out.println(x.toString())`? – Edwin Buck Oct 07 '11 at 16:37
  • int x is not a constant. There are no real constants in Java (just `final` types). Having `int`s as non objects has its benefits, as they are more efficient. Allowing something like `x.toString()` would be restricted to very few functions and the benefit for the programmer wouldn't be so big. I by the way prefer languages from the functional programming world:) – Michael Oct 07 '11 at 21:56
0

This is skating way closer to the edge than is prudent. Unless you can find clear language in the spec as to behavior, I'd avoid anything ambiguous like this.

Even if it is in the spec, readers of your code won't have done the language lawyering to know, so you'll need to comment it to explain, and they may or may not read the comment. They may not even consider one of the alternatives - just see one overload that fits, and run with that. An accident waiting to happen.

Ed Staub
  • 15,480
  • 3
  • 61
  • 91
  • No one yet had problems understanding this part of the code. Maybe because it is a project of a computer science chair used only by master and Ph.D. students:) But after all, I thought, that Java considers autoboxing + upcasting more expensive than only upcasting. – Michael Oct 07 '11 at 16:31