-->

Run-time Polymorphism in Java without “abstract”?

2019-01-29 07:57发布

问题:

I was going over the official Oracle tutorial where it introduces the idea of polymorphism with the example of a class hierarchy of 3 classes; Bicycle being the superclass, and MountainBike and RoadBike being 2 subclasses.

It shows how the 2 subclasses override a method "printDescription" declared in Bicycle, by declaring different versions of it.

And finally, toward the end, the tutorial mentions the Java Virtual Machine (JVM) calls the appropriate method for the object that is referred to in each variable.

But, nowhere does the tutorial on polymorphism mention the concept of "abstract" classes and methods. How is run-time polymorphism achieved unless printDescription() in Bicycle is declared "abstract"? I mean, given this example, based on what hints does the compiler decide not to bind method invocation to the reference type at compile time, and think that it should leave it for the JVM to deal with at run-time?

Below is the example used:

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

回答1:

How is run-time polymorphism achieved unless printDescription() in Bicycle is declared "abstract"?

Why would you think abstract classes would change anything? Abstract classes do 2 primary things

  1. Allow the programmer to declare a class that cannot itself be instantiated, forcing subclassing, and
  2. Allow the programmer to force subclasses to provide implementations of methods, by declaring the method abstract.

Note that point 2 does not imply that polymorphism won't work unless a method is declared abstract on the base class; rather, it provides the developer an opportunity to force a subclass to provide an implementation, which is not required in subclassing scenarios that don't involve any abstract usage.

That's it. In other words, the notion of abstract compliments Java's polymorphism -- it is a language feature, but doesn't have anything to do with the dynamic dispatch Java uses at runtime to invoke methods. Anytime a method is invoked on an instance, the type of the instance at runtime is used to determine which method implementation to use.



回答2:

virtual is a keyword in many languages that means "this method can be overridden by a subclass". Java does not have that keyword but instead all non static member methods are virtual and possible to override.

abstract is the same as virtual, except it tells the compiler that the base class does not have a definition for the method. It's useful at times if there is no useful function for the base class to perform, but it's by no means needed to be able to override the base class method.

In your case, the printDescription method has a useful definition for the base class, so there's no need to declare it abstract. It's virtual by default and therefor overridable by the subclass, so there is no need for a keyword to indicate that.



回答3:

In Java all methods are bind at run-time (that's what you can achieve in C++ declaring a method virtual).

So the JVM can always dispatch the method correctly.

Actually the method binding in Java could never be static, because you are always dealing with references to objects (can't allocate an object on the stack, like C++). This actually force the JVM to always check the run-time type of an object reference.



回答4:

There no need to declare that method abstract.. In runtime polymorphism, appropriate derived class method is invoked based on what class instance does the base class reference points to..

Consider the following example: -

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`
    }
}

To quote the statement that you read: -

the tutorial mentions the Java Virtual Machine (JVM) calls the appropriate method for the object that is referred to in each variable.

To justify the above statement, see the above example. There your B class method is invoked because your base class reference obj is pointing derived class B's instance..

Type of the reference pointing the object is always checked at compile time, whereas, the type of object pointed by that reference is checked at runtime..

So, the decision of which method will be invoked is made at runtime.. Regardless of the whether your base class method is abstract or not, appropriate derived class method is invoked..



回答5:

This is not C++. In Java, you always know the real class of each instance so, when printDescription() is invoked, the definition of that class is used. Though, you can only use the methods available in the references to the instance (so, if you define a method getMPH() in class RoadBike and you handle an instance of that class with a Bike variable, the compiler will thow an error if you intend to use it).



回答6:

I think this code:

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

is not the best example of run-time polymorphism because all facts (methods to call) are known even at compile time. But if you would change it to:

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

...