Consider the following example code:
public class TestClass {
public void doSth(String str, String l, Object... objects) {
System.out.println("A");
}
public void doSth(String str, Object... objects) {
System.out.println("B");
}
}
When I now call new TestClass().doSth("foo", "bar") I get the expected result A. But if I change the method signature of the first method by chaging the parameter l to a primitive type:
public class TestClass {
public void doSth(String str, long l, Object... objects) {
System.out.println("A");
}
public void doSth(String str, Object... objects) {
System.out.println("B");
}
}
calling new TestClass().doSth("foo", 2L) will yield a reference to call ambiguous compile time error.
I thought about that one for some time now and also consulted this stackoverflow question, but I was unable to understand why this happens. In my opinion the doSth("foo", 2L) is more specific to the doSth(String string, long l, Object... obj) signature and should allow the compiler to also come to this conclusion.
In this case, auto-boxing is causing you grief. Ironically, before that you're correct - the "long" version would have easily been picked.
Basically the compiler knows that it can create a Long from your value which, of course, is an Object. So it is still confused as either the long or the Long version could be used. Is one "better" than the other? Maybe but it is a pretty fine line.
At this state, I can only report my observation, not the exact argumentation as to WHY Java behaves, like it does.
First, changing the methods to
void doSth(long l) {...}
void doSth(Object o) {...}
gets rid of the problem, i.e. doSth(2L); will yield the expected result.
Going one step further, changing the method parameter to varargs
void doSth(long... ls) {...}
void doSth(Object... os) {...}
together with the call doSth(2l); yields the same compilation error as reported by OP.
My suggestion at this stage is that encapusalting the parameter into an array, together with Autoboxing causes the havoc. My knowledge about the JLS is not firm enough to explain this properly.
Related
Clearly I've not enough knowledge of how overloading, autoboxing and variable arguments work.
So here's the program causing trouble whenever there's an involvement of primitive types.
public static void z(int a, Object...objects){
}
public static void z(Object...objects){
}
public static void main(String[] args) {
z(); // no error
z(4); // Compile time Error : Ambiguous
z2(); // No error
z2(true, "sadas"); // Error
// No problem with reference types
z3(); // No error. String one called
z3(5); // No error. Object one called
z4(); // Error
z4(4); // Error
z4("asdas"); // Working properly
}
public static void z2(boolean b, Object...objects){
}
public static void z2(Object...objects){
}
public static void z3(String...objects){
System.out.println("String one called");
}
public static void z3(Object...objects){
System.out.println("Object one called");
}
public static void z4(int...objects){
System.out.println("int z4 called");
}
public static void z4(Object...objects){
System.out.println("Object z4 called");
}
Can anybody explain why any of this is happening? I can happily use Integer, Boolean instead of int, boolean but would very much like to know internal working behind it.
A method call will not compile if the compiler cannot determine which of the overloaded method variants it should use.
Let's use z4 as an example:
The method call z4() fits the signature of both variants.
The method call z4(4) also fits the signature of both variants because the variable can be auto-boxed.
The method call z4("asdas") is not ambiguous, as String cannot be cast to int.
Update: the rules for resolving overloaded method calls are as follows:
The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.
...
The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the third phase.
...
The third phase (§15.12.2.4) allows overloading to be combined with variable arity methods, boxing, and unboxing.
If more than one variant is selected in the same phase, then the most specific one is chosen,* but in short z3(String...) is more specific than z3(Object...), while z4(int...) and z4(Object...) are equally specific.
*The rules for determining this most specific variant are somewhat complicated (see here)
Thats a fab question
Now consider
public static void z2(boolean b, Object... objects) {
}
public static void z2(Object... objects) {
}
Both methods have a vararg argument, which means they will both be considered on the third and final phase of overloading resolution.
Remember Object class is the mother of all objects in java
Well this explains one thing
The compiler can treat both as object which will result in object array again.
Now finally
void z2(boolean b, Object... objects)
public static void z2(Object... objects)
is treated as same function.
To check you can comment
z2(true);
z2(true, "sadas");
removing either of
public static void z2(boolean b, Object... objects) {
}
Or
public static void z2(Object... objects) {
}
This will work.
But
You have taken object inside parameter
If you are specific for example
public static void z2(boolean b, String... objects) {
}
public static void z2(String... objects) {
}
z2(); // No error
z2(true, "sadas"); // No error
This way it can be resolved
See below simple snippet:
public class GenericsOverloadingDistinguish<T> {
public void print1(T t) {
System.out.println("t");
}
public void print1(Integer i) {
System.out.println("integer");
}
}
public static void main(String[] args) {
new GenericsOverloadingDistinguish<Integer>().print1(new Integer(1));
}
This would cause an ambiguous method call and will not compile.
This is utterly confusing on the user of that class. It is not able to call neither print1(T t) nor print1(Integer i) simple because it unfortunately used Integer as the generic type.
I understand generics is compile-time and there is type erasure, but doesn't Java have something to prevent such errors?
What if the GenericsOverloadingDistinguish Class is given and can't be changed, and I just need to invoke print1(T t) with T being an Integer?
If the class is given, you're out of luck, there are no nice ways to resolve the problem.
How would you guess which method the designer expected to be called in this case? You simply can't. Now think of how the compiler could do the job?
What could've been done in the language is to not allow this kind of overloading at all if the type parameter can be given a conflicting value. Why it hasn't been done is a good question but difficult to answer. It was probably deemed too restrictive.
Anyway, if you absolutely must, you can work around this problem like this:
GenericsOverloadingDistinguish<Integer> t = new GenericsOverloadingDistinguish<Integer>();
((GenericsOverloadingDistinguish)t).print1((Object)new Integer(1)); //prints "t"
((GenericsOverloadingDistinguish)t).print1(new Integer(1)); //prints "integer"
This works, because the type erasure of print1(T) is print1(Object).
Needless to say, this is bug ugly, and you really shouldn't be using raw types, but this is the least messy way of dealing with a bad situation.
You can actually avoid this by doing:
public class GenericsOverloadingDistinguish<T> {
public void print1(T t) {
if (t instanceof Integer)
System.out.println("integer");
else System.out.println("t");
}
}
public static void main(String[] args) {
new GenericsOverloadingDistinguish<Integer>().print1(new Integer(1));
}
If your code is as-is and cannot be changed, then you're in a problem, since there is no clean (imo) way to distinguish if T or Integer overloaded method should be called.
I am just catching up with java 1.5, (yes i know its too early;) ) . while trying out few exercises on varargs , i just found something strange as below. the code compiles well and the varargs method is invoked only when i supply atleast one parameter. shouldn't this have been compiler error, a method and overloaded method with varargs. Or is there any specific usecase you may think, this scenario will
be useful
public class VarargsExample {
public static void main(String args[]) {
test1();
}
public static void test1(int... x) {
System.out.println("AssertionExample.test1(ARRAY METHOD)");
}
public static void test1() {
System.out.println("AssertionExample.test1(PARAM LESS)");
}
}
PS: tried to search this in SO, could not find similar one. pardon me if there is one already:)
Summary, thanks all for your quick responses. seems to be the normal methods are the one preferred. Same is the case when a single param method is present as below
public class VarargsExample{
public static void main( String args[] ){
test1();
test1(2);
}
public static void test1(int... x){
System.out.println("AssertionExample.test1(ARRAY METHOD)");
}
public static void test1(int x){
System.out.println("AssertionExample.test1(single param METHOD)");
}
public static void test1(){
System.out.println("AssertionExample.test1(PARAM LESS)");
}
}
First of call, the parameter-less overloading gets called because its signature is more specific than that of the overlauding with varargs. It is in general a very bad idea to have two overloaded methods which perform a completely different operation. So let's assume that the parameter-less method does the same thing as the varargs method when called without arguments, that is, the parameter-less method is a specialization of the varargs method.
Then a use-case is the following. Calling a varargs method always requires creating an array. Although, certainly at first, I wouldn't think about such minor optimizations too much, but it is an overhead which might, in some cases (for example in tight loop), be considerable enough. The parameter-less version of the method does not require creating an array, and additionally also may contain other optimizations for the specific case.
Sometimes, one sees more than one specializations, one with no arguments, one with one, one with two, and a general method. For example:
void doSomething() { ... }
void doSomething(String a1) { ... }
void doSomething(String a1, String a2) { ... }
void doSomething(String... as) { ... }
But I suggest to only do this in a late stage of development, if at all.
I came across this question in a quiz,
public class MoneyCalc {
public void method(Object o) {
System.out.println("Object Verion");
}
public void method(String s) {
System.out.println("String Version");
}
public static void main(String args[]) {
MoneyCalc question = new MoneyCalc();
question.method(null);
}
}
The output of this program is "String Version". But I was not able to understand why passing a null to an overloaded method chose the string version. Is null a String variable pointing to nothing ?
However when the code is changed to,
public class MoneyCalc {
public void method(StringBuffer sb) {
System.out.println("StringBuffer Verion");
}
public void method(String s) {
System.out.println("String Version");
}
public static void main(String args[]) {
MoneyCalc question = new MoneyCalc();
question.method(null);
}
}
it gives a compile error saying "The method method(StringBuffer) is ambiguous for the type MoneyCalc"
Is null a String variable pointing to nothing ?
A null reference can be converted to an expression of any class type. So in the case of String, this is fine:
String x = null;
The String overload here is chosen because the Java compiler picks the most specific overload, as per section 15.12.2.5 of the JLS. In particular:
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.
In your second case, both methods are still applicable, but neither String nor StringBuffer is more specific than the other, therefore neither method is more specific than the other, hence the compiler error.
Additionally, the JLS 3.10.7 also declares that "null" is a literal value of the "null type". Therefore there exists a type called "null".
Later, the JLS 4.1 states that there exists a null type of which is impossible to declare variables, but you can use it through the null literal only. Later it says:
The null reference can always undergo a widening reference conversion
to any reference type.
Why the compiler chooses to widen it to String might well be explained in Jon's answer.
You can assign a string to a null value so it is valid and the order for java and most programming languages is fit to the closest type and then to object.
To answer the question in the title: null is neither a String nor an Object, but a reference to either can be assigned to null.
I'm actually surprised this code even compiles. I tried something similar previously and I got a compiler error saying that the call was ambiguous.
However, in this case, it seems like the compiler is choosing the method which is lowest on the food chain. It's assuming that you want the least generic version of the method in order to help you out.
I'll have to see if I can dig up the example where I got a compiler error in this (seemingly) exact same scenario, though...]
EDIT: I see. In the version I made, I had two overloaded methods accepting a String and an Integer. In this scenario, there is no "most specific" parameter (as in Object and String), so it can't choose between them, unlike in your code.
Very cool question!
As String type is more specific than Object type. Let's say you add one more method that takes an Integer type.
public void method(Integer i) {
System.out.println("Integer Version");
}
Then you will get a compiler error saying that the call is ambiguous. As now we two equally specific methods with same precedence.
Java compiler gives most derived class type to assign null.
Here is the example to understand it :
class A{
public void methodA(){
System.out.println("Hello methodA");
}
}
class B extends A{
public void methodB(){
System.out.println("Hello methodB");
}
}
class C{
public void methodC(){
System.out.println("Hello methodC");
}
}
public class MyTest {
public static void fun(B Obj){
System.out.println("B Class.");
}
public static void fun(A Obj){
System.out.println("A Class.");
}
public static void main(String[] args) {
fun(null);
}
}
output: B Class.
on the other hand:
public class MyTest {
public static void fun(C Obj){
System.out.println("B Class.");
}
public static void fun(A Obj){
System.out.println("A Class.");
}
public static void main(String[] args) {
fun(null);
}
}
Result : The method fun(C) is ambiguous for the type MyTest
Hope it will help to understand this case better.
Source: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5
Concept: Most specific method
Explanation: If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen. Try casting the null on to specific type and the method you wish will be automatically called.
I would say neither. NULL is a state not a value. Check out this link for more info on this (the article applies to SQL, but I think it helps with your question as well).
I came across this question in a quiz,
public class MoneyCalc {
public void method(Object o) {
System.out.println("Object Verion");
}
public void method(String s) {
System.out.println("String Version");
}
public static void main(String args[]) {
MoneyCalc question = new MoneyCalc();
question.method(null);
}
}
The output of this program is "String Version". But I was not able to understand why passing a null to an overloaded method chose the string version. Is null a String variable pointing to nothing ?
However when the code is changed to,
public class MoneyCalc {
public void method(StringBuffer sb) {
System.out.println("StringBuffer Verion");
}
public void method(String s) {
System.out.println("String Version");
}
public static void main(String args[]) {
MoneyCalc question = new MoneyCalc();
question.method(null);
}
}
it gives a compile error saying "The method method(StringBuffer) is ambiguous for the type MoneyCalc"
Is null a String variable pointing to nothing ?
A null reference can be converted to an expression of any class type. So in the case of String, this is fine:
String x = null;
The String overload here is chosen because the Java compiler picks the most specific overload, as per section 15.12.2.5 of the JLS. In particular:
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time type error.
In your second case, both methods are still applicable, but neither String nor StringBuffer is more specific than the other, therefore neither method is more specific than the other, hence the compiler error.
Additionally, the JLS 3.10.7 also declares that "null" is a literal value of the "null type". Therefore there exists a type called "null".
Later, the JLS 4.1 states that there exists a null type of which is impossible to declare variables, but you can use it through the null literal only. Later it says:
The null reference can always undergo a widening reference conversion
to any reference type.
Why the compiler chooses to widen it to String might well be explained in Jon's answer.
You can assign a string to a null value so it is valid and the order for java and most programming languages is fit to the closest type and then to object.
To answer the question in the title: null is neither a String nor an Object, but a reference to either can be assigned to null.
I'm actually surprised this code even compiles. I tried something similar previously and I got a compiler error saying that the call was ambiguous.
However, in this case, it seems like the compiler is choosing the method which is lowest on the food chain. It's assuming that you want the least generic version of the method in order to help you out.
I'll have to see if I can dig up the example where I got a compiler error in this (seemingly) exact same scenario, though...]
EDIT: I see. In the version I made, I had two overloaded methods accepting a String and an Integer. In this scenario, there is no "most specific" parameter (as in Object and String), so it can't choose between them, unlike in your code.
Very cool question!
As String type is more specific than Object type. Let's say you add one more method that takes an Integer type.
public void method(Integer i) {
System.out.println("Integer Version");
}
Then you will get a compiler error saying that the call is ambiguous. As now we two equally specific methods with same precedence.
Java compiler gives most derived class type to assign null.
Here is the example to understand it :
class A{
public void methodA(){
System.out.println("Hello methodA");
}
}
class B extends A{
public void methodB(){
System.out.println("Hello methodB");
}
}
class C{
public void methodC(){
System.out.println("Hello methodC");
}
}
public class MyTest {
public static void fun(B Obj){
System.out.println("B Class.");
}
public static void fun(A Obj){
System.out.println("A Class.");
}
public static void main(String[] args) {
fun(null);
}
}
output: B Class.
on the other hand:
public class MyTest {
public static void fun(C Obj){
System.out.println("B Class.");
}
public static void fun(A Obj){
System.out.println("A Class.");
}
public static void main(String[] args) {
fun(null);
}
}
Result : The method fun(C) is ambiguous for the type MyTest
Hope it will help to understand this case better.
Source: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.12.2.5
Concept: Most specific method
Explanation: If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run-time method dispatch. The Java programming language uses the rule that the most specific method is chosen. Try casting the null on to specific type and the method you wish will be automatically called.
I would say neither. NULL is a state not a value. Check out this link for more info on this (the article applies to SQL, but I think it helps with your question as well).