请原谅我,如果这个问题主要是基于意见的,但我觉得它不是,并且有充分的理由选择。所以,这是一个例子。对不起,它真的很长,但非常简单:
接口:
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
而不是Circle
和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]);
我们会收到两个错误(一个用于Square
,一个用于Circle
),表示:
- 方法Driver.whatIs(Square)不适用
- 实际参数Shape无法通过方法调用转换转换为Square
所以,再说一遍,既然我们已经了解了这个细节,为什么Java无法处理这样的情况呢?如果由于效率问题而完成,是否由于某些设计决定而无法实现,这是不是出于某种原因的不良做法等等?
答案 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
根本不是你问的问题的答案......但是我无法抗拒。