0

我正在阅读官方的 Oracle 教程,其中介绍了多态性的概念,并以 3 个类的类层次结构为例;自行车是超类,MountainBike 和 RoadBike 是 2 个子类。

它显示了 2 个子类如何通过声明不同版本的方式覆盖在 Bicycle 中声明的方法“printDescription”。

最后,在最后,本教程提到 Java 虚拟机 (JVM) 为每个变量中引用的对象调用适当的方法。

但是,关于多态性的教程没有提到“抽象”类和方法的概念。除非将 Bicycle 中的 printDescription() 声明为“抽象”,否则如何实现运行时多态性?我的意思是,给定这个例子,编译器基于什么提示决定不在编译时将方法调用绑定到引用类型,并认为它应该留给 JVM 在运行时处理?

下面是使用的示例:

public class Bicycle {
    public int cadence;
    public int gear;
    public int speed;

    public Bicycle(int startCadence, int startSpeed, int startGear) {
      gear = startGear;
      cadence = startCadence;
      speed = startSpeed;
    }

    public void setCadence(int newValue) {
      cadence = newValue;
    }

    public void setGear(int newValue) {
      gear = newValue;
    }

    public void applyBrake(int decrement) {
      speed -= decrement;
    }

    public void speedUp(int increment) {
      speed += increment;
    }

    public void printDescription(){
        System.out.println("\nBike is " + "in gear " + this.gear
         + " with a cadence of " + this.cadence +
         " and travelling at a speed of " + this.speed + ". ");
    }

}

public class MountainBike extends Bicycle {
  private String suspension;

  public MountainBike(
           int startCadence,
           int startSpeed,
           int startGear,
           String suspensionType){
    super(startCadence,
          startSpeed,
          startGear);
    this.setSuspension(suspensionType);
  }

  public String getSuspension(){
    return this.suspension;
  }

  public void setSuspension(String suspensionType) {
    this.suspension = suspensionType;
  }

  public void printDescription() {
    super.printDescription();
    System.out.println("The " + "MountainBike has a" +
        getSuspension() + " suspension.");
  }

}

public class RoadBike extends Bicycle{

  private int tireWidth;

  public RoadBike(int startCadence,
                int startSpeed,
                int startGear,
                int newTireWidth){
    super(startCadence,
          startSpeed,
          startGear);
    this.setTireWidth(newTireWidth);
  }

  public int getTireWidth(){
    return this.tireWidth;
  }

  public void setTireWidth(int newTireWidth){
    this.tireWidth = newTireWidth;
  }

  public void printDescription(){
    super.printDescription();
    System.out.println("The RoadBike"
        " has " + getTireWidth() +
        " MM tires.");
  }
}


public class TestBikes {
    public static void main(String[] args){
        Bicycle bike01, bike02, bike03;

      bike01 = new Bicycle(20, 10, 1);
      bike02 = new MountainBike(20, 10, 5, "Dual");
      bike03 = new RoadBike(40, 20, 8, 23);

      bike01.printDescription();
      bike02.printDescription();
      bike03.printDescription();
      }
}
4

6 回答 6

4

除非将 Bicycle 中的 printDescription() 声明为“抽象”,否则如何实现运行时多态性?

为什么你会认为抽象类会改变任何东西?抽象类做两件主要的事情

  1. 允许程序员声明一个自身不能被实例化的类,强制子类化,并且
  2. 允许程序员通过声明方法抽象来强制子类提供方法的实现。

请注意,第 2 点并不意味着除非在基类上将方法声明为抽象,否则多态将不起作用。相反,它为开发人员提供了强制子类提供实现的机会,这在不涉及任何抽象使用的子类化场景中是不需要的。

就是这样。换句话说,抽象的概念补充了 Java 的多态性——它是一种语言特性,但与 Java 在运行时调用方法时使用的动态调度没有任何关系。任何时候在实例上调用方法时,都会使用运行时实例的类型来确定要使用的方法实现。

于 2012-10-03T13:02:50.090 回答
2

在 Java 中,所有方法都在运行时绑定(这就是您可以在 C++ 中声明一个虚拟方法)。

所以JVM总是可以正确地分派方法。

实际上,Java 中的方法绑定永远不可能是静态的,因为您总是在处理对对象的引用(不能在堆栈上分配对象,如 C++)。这实际上强制 JVM 始终检查对象引用的运行时类型。

于 2012-10-03T13:02:50.777 回答
2

virtual是许多语言中的关键字,表示“此方法可以被子类覆盖”。Java 没有该关键字,而是所有非静态成员方法都是虚拟的并且可以覆盖

abstract与 virtual 相同,只是它告诉编译器基类没有该方法的定义。如果基类没有执行有用的功能,它有时很有用,但它绝不需要能够覆盖基类方法。

在您的情况下, printDescription 方法对基类有一个有用的定义,因此无需将其声明为抽象的。默认情况下它是虚拟的,因此可以被子类覆盖,因此不需要关键字来表明这一点。

于 2012-10-03T13:03:24.273 回答
1

无需声明该方法抽象。在运行时多态中,根据基类引用指向的类实例调用适当的派生类方法。

考虑以下示例:-

class A {
    public void doSomething() {

    }
}

class B extends A {
    public void doSomething() {
         System.out.println("In B")
    }
}

public class Test {
    public static void main(String args[]) {
          A obj = new B();   // Base class reference and derived class object.

          obj.doSomething();  // Calls derived class B's method and prints `In B`
    }
}

引用您阅读的声明:-

本教程提到 Java 虚拟机 (JVM) 为每个变量中引用的对象调用适当的方法。

为了证明上述陈述的合理性,请参见上面的示例。在那里调用您的 B 类方法,因为您的基类引用obj指向派生类B's实例。

始终在编译时检查指向对象的引用类型,而在运行时检查该引用指向的对象类型。

因此,将在运行时决定调用哪个方法. 无论您的基类方法abstract是否是都会调用适当的派生类方法。

于 2012-10-03T13:01:56.693 回答
0

这不是 C++。在 Java 中,您总是知道每个实例的真实类,因此,当printDescription()被调用时,将使用该类的定义。但是,您只能使用对实例的引用中可用的方法(因此,如果您getMPH()在类中定义一个方法RoadBike并使用变量处理该类的实例,Bike如果您打算使用它,编译器将出错) .

于 2012-10-03T13:04:53.350 回答
0

我认为这段代码:

bike01 = new Bicycle(20, 10, 1);       
bike02 = new MountainBike(20, 10, 5, "Dual");       
bike03 = new RoadBike(40, 20, 8, 23);        
bike01.printDescription();       
bike02.printDescription();       
bike03.printDescription(); 

不是运行时多态性的最佳示例,因为即使在编译时所有事实(要调用的方法)都是已知的。但是,如果您将其更改为:

Random r = new Random();

if(r.nextInt(2)%2==0)
{
    bike01 = new Bicycle(20, 10, 1)
}
else
{
    bike01 = new MountainBike(20, 10, 5, "Dual");
}

// only at run-time the right function to call is known

bike01.printDescription();

...

于 2012-10-03T13:14:49.447 回答