Java 11 Cookbook
上QQ阅读APP看书,第一时间看更新

How it works...

Let's write a test program:

public static void main(String... arg) {
double timeSec = 10.0;
int engineHorsePower = 246;
int vehicleWeightPounds = 4000;
Vehicle vehicle = new Car(4, vehicleWeightPounds, engineHorsePower);
System.out.println("Passengers count=" +
((Car)vehicle).getPassengersCount());
System.out.println("Car speed (" + timeSec + " sec) = " +
vehicle.getSpeedMph(timeSec) + " mph");
vehicle = new Truck(3300, vehicleWeightPounds, engineHorsePower);
System.out.println("Payload=" +
((Truck)vehicle).getPayload() + " pounds");
System.out.println("Truck speed (" + timeSec + " sec) = " +
vehicle.getSpeedMph(timeSec) + " mph");
}

Notice that the vehicle reference of the Vehicle type points to the object of the Car subclass and later to the object of the Truck subclass. This is possible thanks to polymorphism, according to which an object has a type of every class in its line of inheritance, including all the interfaces.

If you need to invoke a method that exists only in the subclass, you have to cast such a reference to the subclass type, as was done in the previous example.

The results of the preceding code are as follows:

We should not be surprised to see the same speed calculated for both the car and the truck because the same weight and engine power are used to calculate the speed of each. But, intuitively, we feel that a heavily loaded truck should not be able to reach the same speed as a car in the same period of time. To verify this, we need to include the total weight of the car (with the passengers and their luggage) and that of the truck (with the payload) in the calculations of the speed. One way to do this is to override the getSpeedMph(double timeSec) method of the Vehicle base class in each of the subclasses. 

We can add the getSpeedMph(double timeSec) method to the Car class, which will override the method with the same signature in the base class. This method will use car-specific weight calculation:

public double getSpeedMph(double timeSec) {
int weight = this.weightPounds + this.passengersCount * 250;
double v = 2.0 * this.horsePower * 746 * timeSec * 32.17 / weight;
return Math.round(Math.sqrt(v) * 0.68);
}

In the preceding code, we have assumed that a passenger with luggage weighs 250 pounds total on average.

Similarly, we can add the getSpeedMph(double timeSec) method to the Truck class: 

public double getSpeedMph(double timeSec) {
int weight = this.weightPounds + this.payload;
double v = 2.0 * this.horsePower * 746 * timeSec * 32.17 / weight;
return Math.round(Math.sqrt(v) * 0.68);
}

The results of these modifications (if we run the same test class) will be as follows:

The results confirm our intuition—a fully loaded car or truck does not reach the same speed as an empty one.

The new methods in the subclasses override getSpeedMph(double timeSec) of the Vehicle base class, although we access it via the base class reference:

Vehicle vehicle =  new Car(4, vehicleWeightPounds, engineHorsePower);
System.out.println("Car speed (" + timeSec + " sec) = " +
vehicle.getSpeedMph(timeSec) + " mph");

The overridden method is dynamically bound, which means that the context of the method invocation is determined by the type of the actual object being referred to. Since, in our example, the reference vehicle points to an object of the Car subclass, the vehicle.getSpeedMph(double timeSec) construct invokes the method of the subclass, not the method of the base class.

There is obvious code redundancy in the two new methods, which we can refactor by creating a method in the Vehicle base class and then use it in each of the subclasses:

protected double getSpeedMph(double timeSec, int weightPounds) {
double v = 2.0 * this.horsePower * 746 *
timeSec * 32.17 / weightPounds;
return Math.round(Math.sqrt(v) * 0.68);
}

Since this method is used by subclasses only, it can be protected and thus, accessible only to the subclasses.

Now, we can change the getSpeedMph(double timeSec) method in the Car class, as follows:

public double getSpeedMph(double timeSec) {
int weightPounds = this.weightPounds + this.passengersCount * 250;
return getSpeedMph(timeSec, weightPounds);
}

In the preceding code, there was no need to use the super keyword while calling the getSpeedMph(timeSec, weightPounds) method because a method with such a signature exists only in the Vehicle base class, and there is no ambiguity about it. 

Similar changes can be made in the getSpeedMph(double timeSec) method of the Truck class:

public double getSpeedMph(double timeSec) {
int weightPounds = this.weightPounds + this.payload;
return getSpeedMph(timeSec, weightPounds);
}

Now, we need to modify the test class by adding casting, otherwise there will be a runtime error because the getSpeedMph(double timeSec) method does not exist in the Vehicle base class:

public static void main(String... arg) {
double timeSec = 10.0;
int engineHorsePower = 246;
int vehicleWeightPounds = 4000;
Vehicle vehicle = new Car(4, vehicleWeightPounds,
engineHorsePower);
System.out.println("Passengers count=" +
((Car)vehicle).getPassengersCount());
System.out.println("Car speed (" + timeSec + " sec) = " +
((Car)vehicle).getSpeedMph(timeSec) + " mph");
vehicle = new Truck(3300, vehicleWeightPounds, engineHorsePower);
System.out.println("Payload=" +
((Truck)vehicle).getPayload() + " pounds");
System.out.println("Truck speed (" + timeSec + " sec) = " +
((Truck)vehicle).getSpeedMph(timeSec) + " mph");
}
}

As you may have expected, the test class produces the same values:

To simplify the test code, we can drop casting and write the following instead:

public static void main(String... arg) {
double timeSec = 10.0;
int engineHorsePower = 246;
int vehicleWeightPounds = 4000;
Car car = new Car(4, vehicleWeightPounds, engineHorsePower);
System.out.println("Passengers count=" + car.getPassengersCount());
System.out.println("Car speed (" + timeSec + " sec) = " +
car.getSpeedMph(timeSec) + " mph");
Truck truck =
new Truck(3300, vehicleWeightPounds, engineHorsePower);
System.out.println("Payload=" + truck.getPayload() + " pounds");
System.out.println("Truck speed (" + timeSec + " sec) = " +
truck.getSpeedMph(timeSec) + " mph");
}

The speed values produced by this code remain the same.

Yet, there is an even simpler way to achieve the same effect. We can add the getMaxWeightPounds() method to the base class and each of the subclasses. The Car class will now look as follows:

public class Car extends Vehicle {
private int passengersCount, weightPounds;
public Car(int passengersCount, int weightPounds, int horsePower){
super(weightPounds, horsePower);
this.passengersCount = passengersCount;
this.weightPounds = weightPounds;
}
public int getPassengersCount() {
return this.passengersCount;
}
public int getMaxWeightPounds() {
return this.weightPounds + this.passengersCount * 250;
}
}

And here's what the new version of the Truck class looks like now: 

public class Truck extends Vehicle {
private int payload, weightPounds;
public Truck(int payloadPounds, int weightPounds, int horsePower) {
super(weightPounds, horsePower);
this.payload = payloadPounds;
this.weightPounds = weightPounds;
}
public int getPayload() { return this.payload; }
public int getMaxWeightPounds() {
return this.weightPounds + this.payload;
}
}

We also need to add the getMaxWeightPounds() method to the base class so that it can be used for the speed calculations:

public abstract class Vehicle {
private int weightPounds, horsePower;
public Vehicle(int weightPounds, int horsePower) {
this.weightPounds = weightPounds;
this.horsePower = horsePower;
}
public abstract int getMaxWeightPounds();
public double getSpeedMph(double timeSec){
double v = 2.0 * this.horsePower * 746 *
timeSec * 32.17 / getMaxWeightPounds();
return Math.round(Math.sqrt(v) * 0.68);
}
}

Adding an abstract method, getMaxWeightPounds(), to the Vehicle class makes the class abstract. This has a positive side effect—it enforces the implementation of the getMaxWeightPounds() method in each subclass. Otherwise, a subclass cannot be instantiated and has to be declared abstract too.

The test class remains the same and produces the same results:

But, to be honest, we did it just to demonstrate one possible way of using an abstract method and class. In fact, an even simpler solution would be to pass the maximum weight as a parameter into the constructor of the Vehicle base class. The resulting classes will look like this:

public class Car extends Vehicle {
private int passengersCount;
public Car(int passengersCount, int weightPounds, int horsepower){
super(weightPounds + passengersCount * 250, horsePower);
this.passengersCount = passengersCount;
}
public int getPassengersCount() {
return this.passengersCount; }
}

We added the weight of the passengers to the value we pass to the constructor of the superclass; this is the only change in this subclass. Here is a similar change in the Truck class:

public class Truck extends Vehicle {
private int payload;
public Truck(int payloadPounds, int weightPounds, int horsePower) {
super(weightPounds + payloadPounds, horsePower);
this.payload = payloadPounds;
}
public int getPayload() { return this.payload; }
}

The Vehicle base class remains the same as the original one:

public class Vehicle {
private int weightPounds, horsePower;
public Vehicle(int weightPounds, int horsePower) {
this.weightPounds = weightPounds;
this.horsePower = horsePower;
}
public double getSpeedMph(double timeSec){
double v = 2.0 * this.horsePower * 746;
v = v * timeSec * 32.174 / this.weightPounds;
return Math.round(Math.sqrt(v) * 0.68);
}
}

The test class does not change and produces the same results:

This last versionpassing the maximum weight to the constructor of the base classwill now be the starting point for further code demonstrations.