压缩与重载的编译器解释

Ste*_* P. 7 java overriding overloading

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

接口:

public interface Shape
{
    double area ();
}
Run Code Online (Sandbox Code Playgroud)

实施第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;
    }
}
Run Code Online (Sandbox Code Playgroud)

实施第2课:

public class Square implements Shape
{
    private double size;

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

    public double area()
    {
        return size*size;
    }   
}
Run Code Online (Sandbox Code Playgroud)

司机:

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
Run Code Online (Sandbox Code Playgroud)

这是因为.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");
}
Run Code Online (Sandbox Code Playgroud)

如果我们打电话:

whatIs(shapes[0]); //prints "Shape"
whatIs(shapes[1]); //prints "Shape"
Run Code Online (Sandbox Code Playgroud)

发生这种情况是因为Java将对象解释为Shapes而不是CircleSquare.当然,我们可以通过以下方式获得预期结果:

if (shapes[0] instanceof Circle)
{
    whatIs((Circle) shapes[0]); //prints "Circle"
}
if (shapes[1] instanceof Square)
{
    whatIs((Square) shapes[1]); //prints "Square"
}
Run Code Online (Sandbox Code Playgroud)

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

public static void whatIs(Circle s)
{
    System.out.println("Circle");
}
public static void whatIs(Square s)
{
    System.out.println("Square");
}
Run Code Online (Sandbox Code Playgroud)

我们试图打电话,

whatIs(shapes[0]);
whatIs(shapes[1]);
Run Code Online (Sandbox Code Playgroud)

我们将得到两个错误(一个用于Square,一个用于Circle),表明:

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

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

rge*_*man 5

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

但是,重载方法不支持此多态性.在Java语言规范,第8.4.9涵盖这样的:

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

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


zap*_*apl 5

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

它不能。

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

调用重载方法的工作原理相同。编译器看到 aShape并将其编译为“在基本形状版本中调用 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]);
Run Code Online (Sandbox Code Playgroud)

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

编译器可以自动生成代码,如

if (shapes[0] instanceof Circle)
{
    whatIs((Circle) shapes[0]); //prints "Circle"
}
Run Code Online (Sandbox Code Playgroud)

为您在运行时实现动态方法调用,但它没有。我不知道原因,但有时会很好。虽然instanceof这通常是糟糕的类设计的标志——你不应该从外部寻找差异,让类的行为不同,这样外部就不需要知道。