An abstract class and 2 subclasses, worked
3 small files, written from their header comments: an abstract parent and 2 classes that extend it.
The 3 header comments
| File | Header comment |
|---|---|
Plant.java | There will never be an object that is only a Plant. A Plant has a name and a number of days between waterings, and nothing outside Plant may change them. The constructor may be called only by subclasses and by classes in the same package. isIndoor() has no body here. toString() returns the name, then ", water every ", then the number of days, then " days". |
Cactus.java | A Cactus is a Plant. It is built with no arguments. Its name is cactus, it is watered every 14 days, and it lives outdoors. |
Fern.java | A Fern is a Plant. It is built from a name. It is watered every 3 days and lives indoors. Its toString() returns what Plant's returns, followed by ", keep out of direct sun". |
Read each comment phrase by phrase. Each phrase turns into 1 piece of Java.
| Phrase | Java |
|---|---|
| never an object that is only a Plant | public abstract class Plant |
| nothing outside Plant may change them | private fields |
| called only by subclasses and by classes in the same package | a protected constructor |
| has no body here | public abstract boolean isIndoor(); |
| is a Plant | extends Plant |
| built with no arguments; its name is cactus | a constructor with no parameters that calls super("cactus", 14) |
| what Plant's returns, followed by | super.toString() + ... |
The 3 files
public abstract class Plant
{
private String name;
private int waterDays;
protected Plant(String name, int waterDays)
{
this.name = name;
this.waterDays = waterDays;
}
public String getName()
{
return this.name;
}
public abstract boolean isIndoor();
@Override
public String toString()
{
return this.name + ", water every " + this.waterDays + " days";
}
}
public class Cactus extends Plant
{
public Cactus()
{
super("cactus", 14);
}
@Override
public boolean isIndoor()
{
return false;
}
}
public class Fern extends Plant
{
public Fern(String name)
{
super(name, 3);
}
@Override
public boolean isIndoor()
{
return true;
}
@Override
public String toString()
{
return super.toString() + ", keep out of direct sun";
}
}
Trace it once
Plant p1 = new Cactus();
Plant p2 = new Fern("Boston fern");
System.out.println(p1);
System.out.println(p2);
System.out.println(p1.isIndoor());
System.out.println(p2.isIndoor());
System.out.println(p2.getName());
| Line | Body that runs | Prints |
|---|---|---|
println(p1) | Plant's toString (Cactus has none) | cactus, water every 14 days |
println(p2) | Fern's toString, which calls Plant's | Boston fern, water every 3 days, keep out of direct sun |
p1.isIndoor() | Cactus's | false |
p2.isIndoor() | Fern's | true |
p2.getName() | Plant's | Boston fern |
What goes wrong, and what you see
| Mistake | What happens |
|---|---|
abstract left off Plant's class line | Plant is not abstract and does not override abstract method isIndoor() in Plant |
Fern's constructor writes this.name = name; in place of the super call | name has private access in Plant, plus the missing-super error in the next row |
Fern's constructor has no super call | constructor Plant in class Plant cannot be applied to given types, with required: String,int and found: no arguments |
super(3, name), arguments in the wrong order | incompatible types: int cannot be converted to String |
Cactus has no isIndoor | Cactus is not abstract and does not override abstract method isIndoor() in Plant |
Fern's toString calls this.toString() in place of super.toString() | compiles, then stops with StackOverflowError the first time a fern is printed |
a driver writes new Plant("ivy", 5) | Plant is abstract; cannot be instantiated |
When 2 parameters have the same type, the wrong order compiles and prints the 2 values swapped. Check every super(...) call against the parameter order of the constructor it calls.
Drill
4 questions. Work each one on paper before you tap.
Level-up check
1. Predict the output. Plant p = new Fern("maidenhair"); then System.out.println(p); What prints?
The variable's type is Plant, and the object is a Fern, so Fern's toString runs. It calls Plant's with super.toString() and adds its own part. The first choice is what prints when Fern has no toString. The third is Object's toString, which runs only when no class in the chain overrides it.
2. Spot the bug. Fern's constructor is written as below. What happens when you compile?
public Fern(String name)
{
super(3, name);
}
Java matches arguments to parameters by position. Plant's first parameter is a String, and 3 is an int, so the call is rejected. Java never reorders arguments by type. The first choice is what happens when both parameters have the same type, which is why the order needs checking by eye.
3. The missing body. Someone deletes isIndoor from Cactus and compiles all 3 files. What happens?
A class you can build must supply a body for every abstract method it inherits. The error is reported on Cactus, the class that still owes the body, even though the method was declared in Plant. Nothing about it waits until the program runs.
4. Who may call the constructor. Plant and Cactus are in a package named garden. A class in another package, which imports garden.* and does not extend Plant, writes new Cactus(). A second line writes new Plant("ivy", 5). Which lines compile?
Cactus's constructor is public, so anyone may call it, and inside it Cactus calls the protected constructor as a subclass, which is allowed. new Plant(...) fails for 2 reasons: Plant is abstract, and the outside class is neither a subclass nor in the same package.
Write it out
Not scored. Type it from memory, then compare. The box is scratch space and is not saved.
Write Orchid from this comment. An Orchid is a Plant. It is built from a name. It is watered every 7 days and lives indoors. Its toString() returns what Plant's returns, followed by ", mist the leaves".
public class Orchid extends Plant
{
public Orchid(String name)
{
super(name, 7);
}
@Override
public boolean isIndoor()
{
return true;
}
@Override
public String toString()
{
return super.toString() + ", mist the leaves";
}
}
new Orchid("moth orchid") prints moth orchid, water every 7 days, mist the leaves. Check 3 things: super(...) is the first statement, its arguments follow Plant's parameter order, and both methods carry @Override.
Your checklist
Tick these when they are true.
Could you write this cold?
Blank file, no notes, 4 hours on a clock, and only the Java API documentation open. Say yes only when that is true. You can take it back at any time.
- You change only the lines between
//startMethodand//endMethod, and you keep each method header as given. - A private helper method or field of your own between the markers is fine.
- No new imports.
The official site's Basic Strategies page says: You may receive points for compiling code but you WON’T receive points for code that does not compile.
The Inheritance problem in each practice APE uses these moves on its own classes.
If this one is shaky
Further reading
- Object.toString (Java SE 19 & JDK 19). The method a class replaces when it writes its own
toString. - Override (Java SE 19 & JDK 19). The annotation that turns a misspelled method name into a compile error.
- Inheritance (The Java Tutorials). What a subclass inherits and what it does not.
- Abstract Methods and Classes (The Java Tutorials). Why the class line needs the word once a method has it.
- Using the Keyword super (The Java Tutorials). The constructor call and the method call.
- Controlling Access to Members of a Class (The Java Tutorials). The table that says who can reach a protected constructor.
- Polymorphism (The Java Tutorials). Why a parent-type variable runs the child's method.
- Inheritance (object-oriented programming) (Wikipedia). The idea across languages.
- Dynamic dispatch (Wikipedia). The mechanism that picks which body runs.