7

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

界面:

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()它被Circleand覆盖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 将对象解释为Shapes 而不是Circleand Square。当然我们可以通过以下方式得到想要的结果:

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

我们将得到两个错误(一个 forSquare和一个 for Circle),表明:

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

那么,既然我们已经了解了细节,为什么 Java 不能处理这样的情况呢?例如,这样做是出于效率问题吗,是否由于某些设计决策而无法实现,出于某种原因,这是一种不好的做法,等等?

4

5 回答 5

5

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

它不能。

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

调用重载方法的工作方式相同。编译器看到 aShape并将其编译为“在基本 Shape 版本中调用 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通常是糟糕的类设计的标志 - 你不应该从外部寻找差异,让类表现不同,这样外部就不需要知道。

于 2013-08-09T17:58:13.873 回答
5

Java具有面向对象的特性,支持多态,所以调用area会调用area具体实例的方法,不管它是什么。这是在运行时确定的。

但是,重载方法不支持这种多态性。Java 语言规范,第8.4.9 节涵盖了这一点:

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

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

于 2013-08-09T17:23:03.817 回答
2

对其中一种方法的分派whatIs由编译器在编译时决定。对其中一种area方法的调用是在运行时根据所引用对象的实际类决定的。

于 2013-08-09T17:22:57.043 回答
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

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

于 2013-08-09T17:51:27.810 回答
1

问:为什么Java编译器可以准确区分相关对象的重载方法,但不能准确区分重载方法……为什么Java不能处理这种情况?

答:你的问题倒过来了。

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

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

于 2013-08-09T17:24:44.450 回答