Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Compiler interpretation of overriding vs overloading

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 Shapes 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?

like image 451
Steve P. Avatar asked Aug 09 '13 17:08

Steve P.


2 Answers

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.

like image 182
zapl Avatar answered Oct 01 '22 20:10

zapl


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.

like image 37
rgettman Avatar answered Oct 01 '22 19:10

rgettman