WHAT YOU WILL MAKE
Making an enemy family EnemyFamily
All three monsters are injured, but each has its own attack method. You will put the common rules into the parent class, let the subclasses only write their own differences, and then check whether there are any wrong branches in the family tree. Prerequisites: classes, objects, construction methods and encapsulation. Today we are moving the duplicate biological codes into the same family tree.
Before you start: It is recommended to complete Lessons 17–20 first
Enemy
| Subclass | Superclass | Relationship |
|---|---|---|
| Zombie | Enemy | is an enemy |
| Skeleton | Enemy | is an enemy |
| Creeper | Enemy | is an enemy |
01 / 15
Before starting: predict first, then let the program answer
This chapter uses JDK 21 and does not require the preview function. The story is borrowed from the block world, and the example runs on the ordinary Java console; if the values and rules are course models, they will be clearly stated.
Each common example is a complete independent file, saved according to the file name marked on the box. Place each example in a different folder to avoid duplicate names of auxiliary classes. When the complete project is divided into sections, they are connected sequentially in the same file.
Terminal command: replace the Chinese placeholder with the actual file name and class name
javac -encoding UTF-8 ExampleFile.java
java ExampleClass- Prediction: Write the expected output first, or draw out the changes in references and data.
- Verification: Check line by line after running, and then only change one condition.
- Explanation: Explain whether the results come from Java rules, course contracts, or input data.
02 / 15
Inheritance first answers whether it is a
Zombies, skeletons, and creepers all have names, lives, and injury behaviors. Copy these common codes three times, and you have to chase three monsters when fixing bugs.
Inheritance allows subclasses to reuse the status and behavior of the parent class, but the premise is that the subclass is indeed a type of parent class.
Enemy
| Subclass | Superclass | Relationship |
|---|---|---|
| Zombie | Enemy | is an enemy |
| Skeleton | Enemy | is an enemy |
| Creeper | Enemy | is an enemy |
Conditions for passing this lesson
- Understand the family tree of parent and child classes.
- Let the subclass constructor call super.
- Use @Override to override methods with the same name and parameters.
- Prepare normal, borderline, and error tests for three types of enemies.
03 / 15
The parent class saves common parts
The parent class is not a large glove box. Only members that are needed by all enemies and have the same meaning are suitable for inclusion in the Enemy.
- nameOnly one copy of the name is saved
- healthPrivate life field
- takeDamageUnified injury rules
L21Example02.java · Complete independent example
class Enemy {
protected final String name;
private int health;
Enemy(String name, int health) {
this.name = name == null || name.isBlank()
? "Unnamed" : name.strip();
this.health = Math.max(1, Math.min(40, health));
}
public void takeDamage(int amount) {
if (amount > 0) health = Math.max(0, health - amount);
}
public int health() { return health; }
public String attack() { return name + " Normal attack 1"; }
public String status() { return name + " Life=" + health; }
}
public class L21Example02 {
public static void main(String[] args) {
Enemy e = new Enemy("Guard", 20);
e.takeDamage(7);
System.out.println(e.status());
}
}Output
Guard Life=1304 / 15
extends to the family tree
extends Enemy means Skeleton is a type of Enemy. It can add its own fields and methods, and can also override the behavior of the parent class.
- extendsA statement is a relationship
- superInitialize parent class part
- attackSubclasses can override behavior
L21Example03.java · Complete independent example
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
}
class Skeleton extends Enemy {
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
public class L21Example03 {
public static void main(String[] args) {
Skeleton e = new Skeleton("Xiaobai");
System.out.println(e.attack());
System.out.println(e instanceof Enemy);
}
}Output
Xiaobai Archery
true05 / 15
super completes parent class initialization
When creating a Skeleton, Java must first initialize its Enemy part. super(...) gives the name and life to the parent class constructor.
- Parent class constructorComplete the common state first
- Subclass constructorRe-initialize exclusive fields
- Matching parametersSuper() cannot be used by air
L21Example04.java · Complete independent example
class Enemy {
protected final String name;
private int health;
Enemy(String name, int health) {
this.name = name == null || name.isBlank()
? "Unnamed" : name.strip();
this.health = Math.max(1, Math.min(40, health));
}
public void takeDamage(int amount) {
if (amount > 0) health = Math.max(0, health - amount);
}
public int health() { return health; }
public String attack() { return name + " Normal attack 1"; }
public String status() { return name + " Life=" + health; }
}
class Skeleton extends Enemy {
private int arrows;
Skeleton(String name) { this(name, 12); }
Skeleton(String name, int arrows) {
super(name, 20);
this.arrows = Math.max(0, Math.min(64, arrows));
}
public int arrows() { return arrows; }
@Override public String attack() { return name + " Archery 6"; }
}
public class L21Example04 {
public static void main(String[] args) {
Skeleton e = new Skeleton("Xiaobai", 99);
System.out.println(e.status());
System.out.println(e.arrows());
}
}Output
Xiaobai Life=20
64BadSuper.java · Intentional error example
class Enemy { Enemy(String name) { } }
class Skeleton extends Enemy { Skeleton() { } }
public class BadSuper { public static void main(String[] args) { } }06 / 15
Override rewrite attack behavior
The parent class provides general attacks, and the subclasses can be rewritten according to their own fighting methods. When attack is called on a subclass object, the subclass version is executed.
- Method signatureName and parameters must match
- @OverrideCompiler check rewrite
- Actual objectDetermine instance method version
L21Example05.java · Complete independent example
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
}
class Skeleton extends Enemy {
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
public class L21Example05 {
public static void main(String[] args) {
Enemy e = new Skeleton("Xiaobai");
System.out.println(e.attack());
}
}Output
Xiaobai ArcheryBadOverride.java · Intentional error example
class Enemy { public String attack() { return "Normal attack"; } }
class Skeleton extends Enemy {
@Override public String attak() { return "Archery"; }
}
public class BadOverride { public static void main(String[] args) { } }07 / 15
Three kinds of enemies and three kinds of attacks
All three types of enemies inherit Enemy, but have different attack text and damage values. Differences remain in the subclass, and common life rules remain in the parent class.
Enemy
| Subclass | Superclass | Relationship |
|---|---|---|
| Zombie | Enemy | Swing 4 |
| Skeleton | Enemy | Archery 6 |
| Creeper | Enemy | Explosion 9 |
L21Example06.java · Paragraph 1/2, sequentially connected to the same file
class Enemy {
protected final String name;
private int health;
Enemy(String name, int health) {
this.name = name == null || name.isBlank()
? "Unnamed" : name.strip();
this.health = Math.max(1, Math.min(40, health));
}
public void takeDamage(int amount) {
if (amount > 0) health = Math.max(0, health - amount);
}
public int health() { return health; }
public String attack() { return name + " Normal attack 1"; }
public String status() { return name + " Life=" + health; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name, 22); }
@Override public String attack() { return name + " Swing 4"; }
}
class Skeleton extends Enemy {
private int arrows;
Skeleton(String name) { this(name, 12); }
Skeleton(String name, int arrows) {
super(name, 20);
this.arrows = Math.max(0, Math.min(64, arrows));
}
public int arrows() { return arrows; }
@Override public String attack() { return name + " Archery 6"; }L21Example06.java · Paragraph 2/2, sequentially connected to the same file
}
class Creeper extends Enemy {
Creeper(String name) { super(name, 18); }
@Override public String attack() { return name + " Explosion 9"; }
}
public class L21Example06 {
public static void main(String[] args) {
System.out.println(new Zombie("Carrion").attack());
System.out.println(new Skeleton("Xiaobai").attack());
System.out.println(new Creeper("Mung beans").attack());
}
}Output
Carrion Swing 4
Xiaobai Archery 6
Mung beans Explosion 908 / 15
super method reuses parent class behavior
Sometimes the subclass does not completely replace the behavior of the parent class, but reuses it first and then adds its own effect.
- super.attack()Call parent class version
- Supplementary effectAdd knockback after original result
- attack()Unconditional self-calling will be recursive
L21Example07.java · Complete independent example
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
}
class ArmoredZombie extends Enemy {
ArmoredZombie(String name) { super(name); }
@Override public String attack() {
return super.attack() + " + repel";
}
}
public class L21Example07 {
public static void main(String[] args) {
System.out.println(new ArmoredZombie("Iron armor").attack());
}
}Output
Iron armor Normal attack + repel09 / 15
Access rights guard the boundaries
Subclasses are not the master key to parent classes. The private fields of the parent class are still not directly accessible, and the rules should be maintained through the methods provided by the parent class.
- private healthIndependent subclasses cannot be accessed directly
- public methodProvide controlled operation externally
- protected nameAllow subclasses to read names
BadHealth.java · Intentional error example
class Enemy {
private int health = 20;
public int health() { return health; }
}
class Skeleton extends Enemy {
void cheat() { health = 999; }
}
public class BadHealth { public static void main(String[] args) { } }| Members | Suggested permissions | Reason |
|---|---|---|
| health | private | It is forbidden to bypass the rules of life |
| name | protected or private | Select according to expansion needs |
| takeDamage | In package or public | Provide controlled behavior |
| attack | In package or public | Allow overwriting and calling |
10 / 15
How to choose inheritance and combination
Inherited expression type relationships, a combined expression object owns another object. Choosing the wrong relationship can make the family tree grow strange branches.
- Skeleton is EnemyInherit connection type
- Skeleton has BowField expression possesses
- Bow is not EnemyDon’t pick the wrong family tree
| Problem | The answer is yes | Selection |
|---|---|---|
| Is Skeleton an Enemy? | Yes | Inheritance |
| Is Skeleton Bow? | No | Do not inherit |
| Does Skeleton have Bow? | Yes | Combination |
| Is the challenge Enemy? | Usually. | Inheritable |
L21Example09.java · Complete independent example
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
}
class Bow { String shoot() { return "Draw the bow"; } }
class Skeleton extends Enemy {
private final Bow bow = new Bow();
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " " + bow.shoot(); }
}
public class L21Example09 {
public static void main(String[] args) {
Skeleton e = new Skeleton("Xiaobai");
System.out.println(e.attack());
}
}Output
Xiaobai Draw the bow11 / 15
Three types of test family rules
Inheritance testing must not only check the differences between subclasses, but also confirm that the common rules of the parent class have not been violated.
- Normal damageLife is reduced on request
- Negative damageKeep the original value according to the contract
- Extreme damageThe minimum life is 0
| Scene | Operation | Expected |
|---|---|---|
| Normal | Skeleton attack | Output Archery 6 |
| Normal | Zombie injured 5 | Life reduced by 5 |
| Border | Suffering huge damage | Life stops at 0 |
| Error | Negative damage | Life remains unchanged |
| Return | Three subclasses status | The format is consistent |
L21Example10.java · Complete independent example
class Enemy {
protected final String name;
private int health;
Enemy(String name, int health) {
this.name = name == null || name.isBlank()
? "Unnamed" : name.strip();
this.health = Math.max(1, Math.min(40, health));
}
public void takeDamage(int amount) {
if (amount > 0) health = Math.max(0, health - amount);
}
public int health() { return health; }
public String attack() { return name + " Normal attack 1"; }
public String status() { return name + " Life=" + health; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name, 22); }
@Override public String attack() { return name + " Swing 4"; }
}
public class L21Example10 {
public static void main(String[] args) {
Enemy e = new Zombie("Carrion");
e.takeDamage(-5);
System.out.println(e.health());
e.takeDamage(Integer.MAX_VALUE);
System.out.println(e.health());
}
}Output
22
012 / 15
Challenge Blueprint Enemy Family
The challenge program is called EnemyFamily. The parent class saves the name and life, and the three subclasses override attack. The complete program also demonstrates that the parent class injury rules are valid for all child classes.
- Parent classSave common state
- Three subcategoriesImplement attacks separately
- Call programTesting Differences and Common Rules
Acceptance sequence
- Create zombies, skeletons and creepers.
- Three different attacks are output in sequence.
- Let the skeleton be injured by 7 points and output the status.
- Inflict negative damage to the creeper, and its status must remain unchanged.
- Confirm that all three subclasses are Enemy.
13 / 15
Challenge Realizing the Enemy Family
Create new EnemyFamily.java. This program only uses standard Java 21 and can be compiled and run directly.
- EnemyFamily.javaThe auxiliary class and the main class are the same file
- One action at a timeCheck Injury and Status
- Inheritance evidenceinstanceof is true
EnemyFamily.java · Section 1/2, sequentially connected to the same file
class Enemy {
protected final String name;
private int health;
Enemy(String name, int health) {
this.name = name == null || name.isBlank()
? "Unnamed" : name.strip();
this.health = Math.max(1, Math.min(40, health));
}
public void takeDamage(int amount) {
if (amount > 0) health = Math.max(0, health - amount);
}
public int health() { return health; }
public String attack() { return name + " Normal attack 1"; }
public String status() { return name + " Life=" + health; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name, 22); }
@Override public String attack() { return name + " Swing 4"; }
}
class Skeleton extends Enemy {
private int arrows;
Skeleton(String name) { this(name, 12); }
Skeleton(String name, int arrows) {
super(name, 20);
this.arrows = Math.max(0, Math.min(64, arrows));
}
public int arrows() { return arrows; }
@Override public String attack() { return name + " Archery 6"; }EnemyFamily.java · Section 2/2, sequentially connected to the same file
}
class Creeper extends Enemy {
Creeper(String name) { super(name, 18); }
@Override public String attack() { return name + " Explosion 9"; }
}
public class EnemyFamily {
public static void main(String[] args) {
Zombie zombie = new Zombie("Captain Carrion");
Skeleton skeleton = new Skeleton("Xiaobai");
Creeper creeper = new Creeper("Mung beans");
System.out.println(zombie.attack());
System.out.println(skeleton.attack());
System.out.println(creeper.attack());
skeleton.takeDamage(7);
creeper.takeDamage(-3);
System.out.println(skeleton.status());
System.out.println(creeper.status());
System.out.println(skeleton instanceof Enemy);
}
}Output
Captain Carrion Swing 4
Xiaobai Archery 6
Mung beans Explosion 9
Xiaobai Life=13
Mung beans Life=18
true14 / 15
Expansion tasks and clearance review
The assignment must draw a class diagram arrow and write a sentence to prove that each inheritance relationship is one.
- Class diagramThe arrow points from the subclass to the parent class
- Test tableNormal, Boundary, Error
- Bug recordsWrite down the reasons and minimal fixes
Third gear operation
- Wooden pickaxe: Create two subclasses, Zombie and Skeleton, and rewrite attack respectively.
- Iron pickaxe: Add move to the parent class, and the subclass only rewrites the truly different movement methods.
- Diamond Pickaxe: Design a two-stage challenge. In the second stage, super.attack is reused and special effects are added.
| Self-evaluation | Can | Still need to practice |
|---|---|---|
| I can determine the inheritance relationship | □ | □ |
| I would use super | □ | □ |
| I will rewrite the method correctly | □ | □ |
15 / 15
Own acceptance record
Beautiful output is the starting point, and being able to explain boundaries is considered mastery. Select one case each from normal, borderline and error, write down your expectations before running, and leave evidence after running.
| Category | The operation you selected | Check key points |
|---|---|---|
| Normal | A common input or a successful action | Is the result consistent line by line? |
| Border | Range endpoints and adjacent values | Is there one more step or one less thing? |
| Error | Illegal quantity, missing object or bad data | Whether it is explicitly rejected and whether the status is maintained |
- Bug records: triggering conditions, actual phenomena, causes, and minimum repairs.
- Illustrated record: describe the starting point, end point and meaning of each arrow.
- Adaptation record: only change one rule, and modify expectations and tests at the same time.
TAKE IT WITH YOU
Take the lesson offline
The complete lesson, code, diagrams and exercises, in your preferred language.