Reputation: 14699
Forgive me if this question is primarily opinion based, but I have the feeling that it is not and there is a good reason for the choice. So, here's an example. Sorry, it's really long, but super simple:
Interface:
public interface Shape
{
double area ();
}
Implementing class 1:
import static java.lang.Math.PI;
public class Circle implements Shape
{
private double radius;
public Circle(double radius)
{
this.radius = radius;
}
public double area()
{
return PI*radius*radius;
}
}
Implementing class 2:
public class Square implements Shape
{
private double size;
public Square(double sideLength)
{
size = sideLength;
}
public double area()
{
return size*size;
}
}
Driver:
Shape[] shapes = new Shape[]{new Circle (5.3), new Square (2.4)};
System.out.println(shapes[0].area()); //prints 88.247...
System.out.println(shapes[1].area()); //prints 5.76
This works since .area()
is overridden by Circle
and Square
. Now, here's where my question truly begins. Let's say that the driver has these methods:
public static void whatIs(Shape s)
{
System.out.println("Shape");
}
public static void whatIs(Circle s)
{
System.out.println("Circle");
}
public static void whatIs(Square s)
{
System.out.println("Square");
}
If we call:
whatIs(shapes[0]); //prints "Shape"
whatIs(shapes[1]); //prints "Shape"
This happens because Java interprets the objects as Shape
s and not Circle
and Square
. Of course we can get the desired results through:
if (shapes[0] instanceof Circle)
{
whatIs((Circle) shapes[0]); //prints "Circle"
}
if (shapes[1] instanceof Square)
{
whatIs((Square) shapes[1]); //prints "Square"
}
Now that we have a background my question is:
What reasons contributed to the compiler/language design such that whatIs(shapes[0]);
will print "Shape?" As in, why can the Java compiler accurately distinguish between overridden methods for related objects, but not overloaded methods? More specifically, if the only methods that the driver has access to are:
public static void whatIs(Circle s)
{
System.out.println("Circle");
}
public static void whatIs(Square s)
{
System.out.println("Square");
}
and we attempt to call,
whatIs(shapes[0]);
whatIs(shapes[1]);
we will get two errors (one for Square
and one for Circle
) indicating that:
- method Driver.whatIs(Square) is not applicable
- actual argument Shape cannot be converted to Square by method invocation conversion
So, again, now that we've gotten to the nitty-gritty, why can Java not handle a situation like this? As in, is this done due to efficiency concerns, is it just not possible due to the some design decisions, is this a bad practice for some reason, etc?
Upvotes: 7
Views: 1061
Reputation: 63945
Why can the Java compiler accurately distinguish between overridden methods for related objects, but not overloaded methods?
It can't.
It checks strictly by the type it can see & guarantee. If your code is shapes[0].area()
it will check that Shape
has an area
method and will compile it to "call area() on that object". The concrete Object that exists at runtime is now guaranteed to have that method. Which version from which class is actually used is dynamically resolved at runtime.
Calling overloaded methods works the same. Compiler sees a Shape
and compiles that into "call whatis() in the basic Shape version". If you wanted to change that (and even allow having no basic Shape
version) you would need to be able to determine the type at compile time.
But it is AFAIK impossible to create a compiler that can determine the type that an object will have at runtime at that point. Think for example:
final Shape[] shapes = new Shape[] { new Circle(5.3), new Square(2.4) };
new Thread() {
public void run() {
shapes[0] = new Square(1.5);
}
}.start();
whatIs(shapes[0]);
You must execute that code to find out.
The compiler could auto generate code like
if (shapes[0] instanceof Circle)
{
whatIs((Circle) shapes[0]); //prints "Circle"
}
for you to achieve dynamic method invocation at runtime but it does not. I don't know the reason but it would be neat to have sometimes. Although instanceof
is often a sign for bad class design - you should not look from the outside for differences, let the class behave differently so the outside does not need to know.
Upvotes: 5
Reputation: 111
Well, as a stupid answer, you could get the whatIs function to work fine THIS way (without any type checking)
class Shape{
public abstract String whatIs();
}
class Square{
public String whatIs(){ return "Square"; }
}
class Circle{
public String whatIs(){ return "Circle"; }
}
And then call them like this
Shape square = new Square();
Shape circle = new Circle();
System.out.println(square.whatIs()) //prints 'square'
System.out.println(circle.whatIs()) //prints 'circle
Not at all the answer to the question you asked... But I couldn't resist.
Upvotes: 1
Reputation: 121599
Q: Why can the Java compiler accurately distinguish between overridden methods for related objects, but not overloaded methods ... why can Java not handle a situation like this?
A: You've got the question backwards.
Java ALLOWS you to distinguish between "overloading" and "overriding".
It doesn't try to second-guess what you mean, it gives you a choice to use one or the other.
Upvotes: 1
Reputation: 178243
Java, with object-oriented features, supports polymorphism, so calling area
will call the area
method of the specific instance, whatever it is. This is determined at runtime.
However, this polymorphism is not supported with overloaded methods. The Java Language Specification, Section 8.4.9 covers this:
When a method is invoked (§15.12), the number of actual arguments (and any explicit type arguments) and the compile-time types of the arguments are used, at compile time, to determine the signature of the method that will be invoked (§15.12.2). If the method that is to be invoked is an instance method, the actual method to be invoked will be determined at run time, using dynamic method lookup (§15.12.4).
That is, with overloaded methods, the method is chosen at compile time, using the compile time types of the variables, not at runtime like with polymorphism.
Upvotes: 5
Reputation: 13315
The dispatch to one of the whatIs
methods is decided by the compiler at compile time. The call to one of the area
methods is decided at runtime, based on the actual class of the object that is referenced.
Upvotes: 2