Why Array and Integer constructor are ambigious - java

I have wrote this program in eclipse and eclipse is complaining that constructor is ambigious. I am not sure why java compiler calling them ambiguous.
public class Ambigious {
public Ambigious(float[] a){
System.out.println("array constructor");
}
public Ambigious(Integer a){
System.out.println("Integer constructor");
}
public static void main(String[] args) {
new Ambigious(null);
}
}
but this is not
public class Ambigious {
public Ambigious(Object a){
System.out.println("object constructor");
}
public Ambigious(float[] a){
System.out.println("array constructor");
}
public static void main(String[] args) {
new Ambigious(null);
}
}

Problem 1:
Both float[] and Integer are Objects so null can be applied to both constructors.
Since there isn't any best suited type compiler can't decide which one should be used.
Problem 2:
In case of Object and Integer types the most suited one is chosen. For instance if you would pass new Integer(1) as argument then both constructors could be applied because Integer is also Object, but compiler binds this invocation with code from constructor with most precise argument types.
Similarly with null, since it can be used as Object and Integer compiler assumes that the more precise type would be Integer.
To solve this kind of problems you can specify which type null should represent by casting
new Ambigious((Integer)null);
You can also try changing one of argument types to non-Object like
public Ambigious(int a){//primitive type
instead of
public Ambigious(Integer a){

As the exception raised states, reference to Ambigious is ambiguous, both constructor Ambigious(float[]) in Ambigious and constructor Ambigious(Integer) in Ambigious match.
This means that both constructors can take the value you passed, which is null.

Related

Primitive vararg parameters in method overloading

Primitives are at it again, breaking rules, I learned before. Well not technically primitive but composed of them.
I learned that whenever there's no method more specific than rest, compile time error occurs as it happens here.
public static void caller(){
z5(); // Error. Neither Integer, nor String is more specific
z5(null); // Error for the same reason
}
public static void z5(Integer...integers){
System.out.println("Integer z5 called");
}
public static void z5(String...strings){
System.out.println("String z5 called");
}
Now comes primitives into the picture.
public static void caller(){
z1(null); // Error cuz [I, [J, [F all are subclass of Object.
z1(); // SURPRISINGLY works and calls the int one. WHY?
}
public static void z1(int...integers){
System.out.println("int z1 called");
}
public static void z1(long...longs){
System.out.println("long z1 called");
}
public static void z1(float...floats){
System.out.println("float z1 called");
}
Expected compile time errors occurs here.
public static void caller(){
z1(null); // Error
z1(); // Error
}
public static void z1(int...integers){
System.out.println("int z1 called");
}
public static void z1(boolean...bools){
System.out.println("bool z1 called");
}
Now my question is, int[], float[], or any array of primitives are not primitive types then Why are they treated differently than other reference types?
--UPDATE--
#john16384 You don't think I read your "Possible duplicate" Varargs in method overloading in Java
Top answer there says You cannot combine var-args, with either widening or boxing. Besides I forgot to mention, OP's code posted there, works fine on my jdk 7.
What exactly is going on which works for (int...is) & (float...fs) but not for (Integer...is) & (Float...fs) and not for (int...is) & (boolean...bool)
Quote from the JLS about varargs invocations when multiple methods are applicable:
15.12.2.5. Choosing the Most Specific Method
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.
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 error. In cases such as an
explicitly typed lambda expression argument (§15.27.1) or a variable
arity invocation (§15.12.2.4), some flexibility is allowed to adapt
one signature to the other.
The important part here is how methods are defined to be more specific. It basically says that int... is more specific than long... because any values you could pass to the first method could also be passed to the second method.
This will also apply to the case where you pass no arguments. int... will be the most specific (it will even see byte... as more specific!).
public static void main(String[] args) {
bla();
}
private static void bla(long... x) {}
private static void bla(int... x) {}
private static void bla(short... x) {}
private static void bla(byte... x) {} // <-- calls this one
The reason you get an error when also creating an overload boolean... is that it now is ambigious which one to call, and the compiler stops before getting to the point where it has to pick the most specific method.

Having an method argument as 'Object' type

Suppose I have the following method:-
public static void abhay(Object a){
....
}
and I use it somewhere like this:-
public static void main(String[] args){
...
Kaal b = new Kaal();
abhay(b);
...
}
where Kaal is a class, obviously.
Now, what will be the class type(object type at compile time) of a inside the method abhay? In other words, what will be the class of a, in the eyes of compiler, when inside method abhay?
Also, how above is different than below?
(given that we only change the definition of abhay):-
public static void abhay(Kaal a){
....
}
"Java is always 'call by value'. Instead of the object, only the reference to the object, is passed as an argument, where this reference, is, the value."
If the above fact has any relevance to the answer to this question, please try to explain in context of above fact.
The compiler only cares about the signature of the method it is currently in.
It can't know and doesn't care about how that method is invoked.
So, to the compiler sees that abhay() has declared a to be an Object; and that is what the compiler knows about a.
Of course, at runtime, a call like b instanceof Kaal will result to true.
And just for the record: this has nothing to do with call-by-value or call-by-reference.
The answer is straight forward. Given the following method signature:
public static void abhay(Object a)
and you invoke it by abhay(new Kaal());
It is like saying:
Object a = new Kaal(); //only able to access members of Object
Because you told Java to handle it as an Object, it will be treated as an Object. To access members of Kaal:
((Kaal)a).kaalMethod();
This is just a matter of Java complying to the data type given in the parameter list. It has nothing to do with "pass by value". Further more, both instances Kaal and Object are objects. You are not even dealing with primitives here where the value are referring to the actual content of the primitives.
Take Kaal as a parameter and you can access Kaal methods
public static void abhay(Kaal a){
a.getSomeKaalMethod(); // works
....
}
Take Object as a parameter, and pass a Kaal class, and you can't access Kaal methods or fields
public static void abhay(Object a){
a.getSomeKaalMethod(); // doesnt work
....
}
Unless you cast the object to Kaal
public static void abhay(Object a){
Kaal k = (Kaal)a;
k.getSomeKaalMethod(); // works
....
}
You might want to check if is instanceof Kaal other wise I dont see why you will take a Object instead of the class
public static void abhay(Object a){
if(a instanceof Kaal){
Kaal k = (Kaal)a;
k.getSomeKaalMethod(); // works
}
}

Java Generics - calling specific methods from generic-typed ones

I try to dive deeply into the Java Generics and I've come across a problem described by the following sample code.
public static void test(Object o) {
System.out.println("Hello Object!");
}
public static void test(Integer i) {
System.out.println("Hello Integer!");
}
public static <T> void test(Collection<T> col) {
for (T item : col) {
System.out.println(item.getClass().getSimpleName());
test(item);
}
}
public static void main (String[] args) throws java.lang.Exception
{
Collection<Integer> ints = new ArrayList<>();
ints.add(1);
test(ints);
}
The output of the sample is
Integer
Hello Object!
All the types are obviously known at compile time. As far as I understand, Java holds only a single compiled copy of each method (unlike C++) and because no other constraints are given about the parameter T, it's forced to call the generic Object implementation.
My question is - is there any way to call the "Hello Integer" method for integers without overloading the test method for Collection<Integer> and without using runtime type checking?
It cannot be done. Due to type erasure type Collection<T> will be resolved to type Collection<Object>. Erasure of Generic Methods
You can observe the same behavior without generics.
Object o = new Integer(5);
test(o); // will output "Hello Object!"
Overloaded methods are resolved using compile-time rather than run-time type information. See JLS: https://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.4.9
No, you can't do what you ask for, for the reasons you gave. Type erasure means that the overload is resolved once for all type variables, and it has to resolve to Object.

What is the type on null as a method argument?

So here it is this example
public static void main(String[] args) {
new Stuff(null);
new Stuff("a");
new Stuff(1);
}
and class Stuff is defined as follow
public class Stuff {
Stuff(Object o){
System.out.println("object");
}
Stuff(String s){
System.out.println("string");
}
}
The output is
string
string
object
How does Java tell the null is a String? If I change Stuff to
public class Stuff {
Stuff(String s){
System.out.println("string");
}
Stuff(Integer o){
System.out.println("Integer");
}
}
I get compilation error for Stuff(null):
The constructore Stuff(String) is ambigous.
Again, why does Java "decide" null is a String?
The compiler first lists all applicable methods. In your case, both are applicable.
It then tries to find a method which is more specific than the other(s).
In your first example, String is a subclass of Object and is therefore more specific.
In your second example, both methods are applicable (String and Integer) but neither is more specific than the other (String is not a subclass of Integer which is not a subclass of String). So there is an ambiguity, hence the compiler error.
The full algorithm to determine which method should be chosen is defined in the JLS.
Because String is more specific then Object. Java always tries to use more specific match when figuring out which constructor or method to use.

Not able to understand the inheritance and overriding/overloading in java

I have problem understanding the below code(commented against the line number)
class Base {
void m1(Object o) {
}
void m2(String o) {
}
}
public class Overloading extends Base {
void m1(String s) {
}
void m2(Object o) {
}
public static void main(String[] args) {
Object o = new Object();
Base base1 = new Base();
base1.m1("");//**why this works perfect**
Base base = new Overloading();
base.m2(o);// **why compile time error** - The method m2(String) in the type Base is not applicable for the arguments (Object)
Compiler always resolves the method invocation based on the declared type of the reference you invoke it on.
When you invoke the method:
base1.m1("");
compiler looks for the method signature in declared type of base1, which is Base in this case. The matching method in Base is:
void m1(Object o) { }
Since parameter Object can accept a String argument, the invocation is valid. You can pass a subclass object to a superclass reference.
Now, with 2nd invocation:
base.m2(o);
again the declared type of base is Base. And the matching method in Base class is:
void m2(String o) { }
Since you cannot pass an Object reference where a String is accepted. The compiler gives you compiler error. There is no implicit narrowing conversion.
You can understand it more clearly with a simple assignment:
Object ob = new Integer(3);
String str = ob; // This shouldn't be valid
Java doesn't perform implicit narrowing conversion. The assignment from obj to str shouldn't be valid, because else you would get a ClassCastException at runtime.
At the line base.m2(o), the compiler doesn't know that base is an Overloading -- it only knows that it is a Base. Why has it forgotten? Because you told it to.
You told the compiler to treat base as a Base, by declaring it with Base base = ....
So, as per your instructions, the compiler will treat base as a Base without knowing anything about any subclass of Base it might or might not extend, and it (correctly) points out that base might not support m2 on an arbitrary Object.

Categories

Resources