Problem overview
A shop ships parcels in three ways. Every option answers the same two questions: what is it called, and what does it cost for a parcel of a given weight? The answers differ. You write three classes: Shipping (the standard option) and two subclasses, Express and Economy, that inherit from it and override both of its methods.
| Class | name() | cost(weight) |
|---|---|---|
Shipping | "Standard" | 5 + weight |
Express | "Express" | 10 + 2 * weight |
Economy | "Economy" | 3 + weight / 2 |
For a 4 kilogram parcel the costs are 9, 18 and 5. The lesson's tests create an option for each input line and print name(): cost, so the first example prints Standard: 9, Express: 18 and Economy: 5.
How inheritance and overriding work
In Java, class Express extends Shipping makes Express a kind of Shipping. It gets the parent's methods for free, and it can replace them by declaring a method with the same name and parameters. Marking the replacement with @Override asks the compiler to check that the parent really has such a method. If you misspell the name or change a parameter type, the compiler reports an error instead of quietly creating a new, unrelated method.
Java decides which version to run from the object, not from the variable. A variable of type Shipping can hold an Express, and calling cost on it still runs the express version:
Shipping s = new Express();
System.out.println(s.name() + " " + s.cost(3));
This prints:
Express 16
That is why the code that uses your classes can hold every option in the same kind of variable and call the same two methods: each object answers with its own version.
Step by step
- Write
Shipping.nameso it returns"Standard". - Write
Shipping.costso it returns5 + weight: a base price plus 1 per kilogram. - Override
nameinExpresswith the same header, marked@Override, returning"Express". - Override
costinExpressto return10 + 2 * weight. Without it, an express parcel would get the parent's price. - Override
nameinEconomyto return"Economy". - Override
costinEconomyto return3 + weight / 2.
Each subclass changes only itself. Overriding in Express does not affect Economy or Shipping.
The solution in Java
class Shipping {
public String name() {
return "Standard";
}
public int cost(int weight) {
return 5 + weight;
}
}
class Express extends Shipping {
@Override
public String name() {
return "Express";
}
@Override
public int cost(int weight) {
return 10 + 2 * weight;
}
}
class Economy extends Shipping {
@Override
public String name() {
return "Economy";
}
@Override
public int cost(int weight) {
return 3 + weight / 2;
}
}
Nothing else is needed: no constructors, because none of the classes keeps any data, and no fields. Each cost is a plain formula of the weight.
Integer division in Economy
Both weight and 2 are int, so weight / 2 drops the remainder instead of producing a fraction. For a weight of 7, weight / 2 is 3, so the cost is 6. For a weight of 1 it is 0, so the cost is 3.
Using the three classes together
The array below has the type Shipping[], yet it holds one object of each class. The loop never asks which class an element belongs to. It calls name() and cost(7), and every object runs its own version:
Shipping[] options = { new Shipping(), new Express(), new Economy() };
for (Shipping option : options) {
System.out.println(option.name() + ": " + option.cost(7));
}
Standard: 12
Express: 24
Economy: 6
This is the practical reason for overriding. The alternative is one class with an if chain on a "kind" field, and every new shipping option would mean editing that chain. With subclasses, a new option is a new class, and the code that loops over parcels stays the same. Because Express and Economy are also Shipping objects, they fit anywhere a Shipping is expected.
Inherited, overridden, or extended
A subclass that overrides nothing simply behaves like its parent. A subclass can also override a method and still reuse the parent's version through super. In this example, Pickup keeps both methods as they are, and GiftWrapped adds 2 to the parent's price:
class Pickup extends Shipping {
}
class GiftWrapped extends Shipping {
@Override
public int cost(int weight) {
return super.cost(weight) + 2;
}
}
For a weight of 4, a Pickup object reports Standard and costs 9, while a GiftWrapped object reports Standard and costs 11. The lesson's Express and Economy don't use super, because their prices don't build on the standard one.
Edge cases
- Weight 0. Only the base prices are left: 5, 10 and 3.
- Odd weights in Economy. The half is rounded down, so weights 6 and 7 cost the same:
3 + 3 = 6. - Large weights. The weight is at most 1000, so the largest result,
10 + 2 * 1000, is 2010 and fits easily in anint.
Testing
Trying every option on a few weights gives:
| Weight | Standard | Express | Economy |
|---|---|---|---|
| 0 | 5 | 10 | 3 |
| 1 | 6 | 12 | 3 |
| 4 | 9 | 18 | 5 |
| 7 | 12 | 24 | 6 |
Common mistakes
- Forgetting to override
cost. IfExpressonly overridesname, express parcels print the right name with the standard price. - Changing the header. An override needs the same name and parameter list.
cost(double weight)would be a different method, and@Overrideturns that slip into a compile error. - Dividing after multiplying in Economy. The rule is
3 + weight / 2, with the division applied to the weight alone and then added to 3. - Misspelling the names. The returned text is printed as it is, so
"express"in lower case does not match"Express". - Making the methods
privateorstatic. An override must be callable through the parent type, so keeppublicand don't addstatic. A narrower access level is a compile error, because a subclass may not make a method less visible than the parent's.
