压缩与重载的编译器解释

时间:2013-08-09 17:15:24

标签: java overloading override

请原谅我,如果这个问题主要是基于意见的,但我觉得它不是,并且有充分的理由选择。所以,这是一个例子。对不起,它真的很长,但非常简单:

接口:

public interface Shape
{
    double area ();
}

实施第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;
    }
}

实施第2课:

public class Square implements Shape
{
    private double size;

    public Square(double sideLength)
    {
        size = sideLength;
    }

    public double area()
    {
        return size*size;
    }   
}

驱动:

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

这可行,因为.area()Circle会覆盖Square。现在,我的问题真正开始了。让我们说司机有这些方法:

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");
}

如果我们致电:

whatIs(shapes[0]); //prints "Shape"
whatIs(shapes[1]); //prints "Shape"

这是因为Java将对象解释为Shape而不是CircleSquare。当然,我们可以通过以下方式获得预期的结果:

if (shapes[0] instanceof Circle)
{
    whatIs((Circle) shapes[0]); //prints "Circle"
}
if (shapes[1] instanceof Square)
{
    whatIs((Square) shapes[1]); //prints "Square"
}

现在我们有了一个背景,我的问题是:
什么原因促成了编译器/语言设计,使得whatIs(shapes[0]);将打印"形状?"同样,为什么Java编译器能够准确区分相关对象的重写方法,而不是重载方法?更具体地说,如果驱动程序可以访问的唯一方法是:

public static void whatIs(Circle s)
{
    System.out.println("Circle");
}
public static void whatIs(Square s)
{
    System.out.println("Square");
}

我们试图打电话,

whatIs(shapes[0]);
whatIs(shapes[1]);

我们会收到两个错误(一个用于Square,一个用于Circle),表示:

  
      
  • 方法Driver.whatIs(Square)不适用   
        
    • 实际参数Shape无法通过方法调用转换转换为Square
    •   
  •   

所以,再说一遍,既然我们已经了解了这个细节,为什么Java无法处理这样的情况呢?如果由于效率问题而完成,是否由于某些设计决定而无法实现,这是不是出于某种原因的不良做法等等?

5 个答案:

答案 0 :(得分:5)

具有面向对象功能的Java支持多态,因此调用area将调用特定实例的area方法,无论它是什么。这是在运行时确定的。

但是,重载方法不支持此多态性。 Java Language Specification, Section 8.4.9涵盖了这一点:

  

调用方法时(第15.12节),实际参数的数量(和   任何显式类型参数)和编译时类型   在编译时使用参数来确定签名   将被调用的方法(§15.12.2)。如果方法是   invoked是一个实例方法,要调用的实际方法是   使用动态方法查找(第15.12.4节)在运行时确定。

也就是说,使用重载方法,在编译时选择使用变量的编译时类型的方法,而不是像运行时那样使用多态。

答案 1 :(得分:4)

  

为什么Java编译器能够准确区分相关对象的重写方法,而不是重载方法?

它不能。

严格按照可以看到的类型进行检查&保证。如果您的代码为shapes[0].area(),则会检查Shape是否有area方法,并将其编译为“该对象上的调用区域()”。现在,运行时存在的具体Object保证具有该方法。实际使用哪个类的版本在运行时动态解析。

调用重载方法的工作原理相同。编译器看到Shape并将其编译为“基本Shape版本中的call whatis()”。如果你想改变它(甚至允许没有基本的Shape版本),你需要能够在编译时确定类型。

但AFAIK无法创建一个编译器,可以确定对象在运行时将具有的类型。想想例如:

    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]);

您必须执行该代码才能找到答案。

编译器可以自动生成像

这样的代码
if (shapes[0] instanceof Circle)
{
    whatIs((Circle) shapes[0]); //prints "Circle"
}

您可以在运行时实现动态方法调用,但事实并非如此。我不知道原因,但有时会很好。虽然instanceof通常是糟糕的课堂设计的标志 - 你不应该从外面看差异,让课堂表现不同,所以外面不需要知道。

答案 2 :(得分:2)

whatIs方法之一的调度由编译器在编译时决定。根据引用的对象的实际类,在运行时决定对area方法之一的调用。

答案 3 :(得分:1)

问:为什么Java编译器能够准确地区分相关对象的重写方法,而不是重载方法...为什么Java无法处理这样的情况?

答:你有倒退的问题。

Java 允许区分“重载”和“覆盖”。

它不会试图猜测你的意思,它可以让你选择使用其中一种。

答案 4 :(得分:1)

好吧,作为一个愚蠢的答案,你可以通过这种方式使whatIs函数正常工作(没有任何类型检查)

class Shape{
  public abstract String whatIs();
}

class Square{
  public String whatIs(){ return "Square"; }
}
class Circle{
  public String whatIs(){ return "Circle"; }
}

然后像这样打电话给他们

Shape square = new Square();
Shape circle = new Circle();

System.out.println(square.whatIs()) //prints 'square'
System.out.println(circle.whatIs()) //prints 'circle

根本不是你问的问题的答案......但是我无法抗拒。