← Java tutorials

LESSON 21 / 40 · FREE TUTORIAL

The Mob Family Tree

Inheritance, super and method rewriting

90–110 minutesThis chapter uses JDK 21 and does not require the preview function.Face Object Progress

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

Inheritance: The hollow triangle points to the parent class

Enemy

SubclassSuperclassRelationship
ZombieEnemyis an enemy
SkeletonEnemyis an enemy
CreeperEnemyis 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.

Inheritance: The hollow triangle points to the parent class

Enemy

SubclassSuperclassRelationship
ZombieEnemyis an enemy
SkeletonEnemyis an enemy
CreeperEnemyis 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.

Only one copy of common fields and methods is maintained
  1. nameOnly one copy of the name is saved
  2. healthPrivate life field
  3. 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=13

04 / 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.

Subclasses automatically have inheritable members
  1. extendsA statement is a relationship
  2. superInitialize parent class part
  3. 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
true

05 / 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.

When a subclass is born, the parent class part is built first
  1. Parent class constructorComplete the common state first
  2. Subclass constructorRe-initialize exclusive fields
  3. 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
64

BadSuper.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.

The same method signature is replaced with a subclass version
  1. Method signatureName and parameters must match
  2. @OverrideCompiler check rewrite
  3. 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 Archery

BadOverride.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.

Inheritance: The hollow triangle points to the parent class

Enemy

SubclassSuperclassRelationship
ZombieEnemySwing 4
SkeletonEnemyArchery 6
CreeperEnemyExplosion 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 9

08 / 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.

First execute the parent class version and then add the subclass effect
  1. super.attack()Call parent class version
  2. Supplementary effectAdd knockback after original result
  3. 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 + repel

09 / 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.

The subclass can be expanded but the parent class safe cannot be dismantled
  1. private healthIndependent subclasses cannot be accessed directly
  2. public methodProvide controlled operation externally
  3. 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) { } }
Acceptance comparison table
MembersSuggested permissionsReason
healthprivateIt is forbidden to bypass the rules of life
nameprotected or privateSelect according to expansion needs
takeDamageIn package or publicProvide controlled behavior
attackIn package or publicAllow 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.

It is a kind of inheritance and there is a kind of combination
  1. Skeleton is EnemyInherit connection type
  2. Skeleton has BowField expression possesses
  3. Bow is not EnemyDon’t pick the wrong family tree
Acceptance comparison table
ProblemThe answer is yesSelection
Is Skeleton an Enemy?YesInheritance
Is Skeleton Bow?NoDo not inherit
Does Skeleton have Bow?YesCombination
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 bow

11 / 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.

Structure, attack, and injury all require checking boundaries.
  1. Normal damageLife is reduced on request
  2. Negative damageKeep the original value according to the contract
  3. Extreme damageThe minimum life is 0
Acceptance comparison table
SceneOperationExpected
NormalSkeleton attackOutput Archery 6
NormalZombie injured 5Life reduced by 5
BorderSuffering huge damageLife stops at 0
ErrorNegative damageLife remains unchanged
ReturnThree subclasses statusThe 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
0

12 / 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.

Common status of parent class management and attack differences of sub-class management
  1. Parent classSave common state
  2. Three subcategoriesImplement attacks separately
  3. 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.

Complete program verification super rewriting and parent class rules
  1. EnemyFamily.javaThe auxiliary class and the main class are the same file
  2. One action at a timeCheck Injury and Status
  3. 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
true

14 / 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.

The third-level task continues to expand the family tree
  1. Class diagramThe arrow points from the subclass to the parent class
  2. Test tableNormal, Boundary, Error
  3. 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.
Acceptance comparison table
Self-evaluationCanStill 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.

Leave a basis for judgment
CategoryThe operation you selectedCheck key points
NormalA common input or a successful actionIs the result consistent line by line?
BorderRange endpoints and adjacent valuesIs there one more step or one less thing?
ErrorIllegal quantity, missing object or bad dataWhether 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.