WHAT YOU WILL MAKE
Making Mixed Battle Waves BattleWave
The commander only shouts attack once, zombie swipes, skeleton shoots arrows, and spider bites. Follow the reference to the real object, and you'll see how Java selects the correct method and completes the mixing wave. Front-end backpack: Enemy family, extends, super and method rewriting in Lesson 21. Let the entire wave of monsters listen to the same command today.
Before you start: It is recommended to complete Lesson 21 first
- #1 · Skeleton objectattack() returns archery description
| Variable | Refers to |
|---|---|
| Enemy enemy | #1 |
01 / 13
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 / 13
The parent class reference accommodates the subclass object
Enemy enemy = new Skeleton(...) The left side shows that the program uses it through the Enemy contract, and the right side determines that the Skeleton is actually created.
- #1 · Skeleton objectattack() returns archery description
| Variable | Refers to |
|---|---|
| Enemy enemy | #1 |
L22Example01.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 {
public String reload() { return name + " Supplementary arrows"; }
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
}
public class L22Example01 {
public static void main(String[] args) {
Enemy a = new Zombie("Carrion");
Enemy b = new Skeleton("Xiaobai");
System.out.println(a.attack());
System.out.println(b.attack());
}
}Output
Carrion Swing
Xiaobai Archery03 / 13
Dynamic dispatch selection actual method
Java uses dynamic dispatch for instance methods. The compiler first confirms that there is an attack in the Enemy contract, and then executes the rewritten version of the actual subclass at runtime.
- #1 · Skeleton objectattack() returns archery description
| Variable | Refers to |
|---|---|
| Enemy enemy | #1 |
L22Example02.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 {
public String reload() { return name + " Supplementary arrows"; }
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
}
public class L22Example02 {
public static void main(String[] args) {
Enemy e = new Skeleton("Xiaobai");
System.out.println(e.attack());
e = new Zombie("Carrion");
System.out.println(e.attack());
}
}Output
Xiaobai Archery
Carrion Swing04 / 13
Mixed waves under parent class number assembly
If the array element type is written as Enemy, all Enemy subclasses can be placed in the same wave.
- Enemy[]Save references of the same family
- Array orderDetermine the order of appearance
- Dynamic dispatchDecide on attack method
L22Example03.java · Section 1/2, sequentially connected to the same file
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
public int damage() { return 1; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
@Override public int damage() { return 4; }
}
class Skeleton extends Enemy {
Skeleton(String name) { super(name); }
public String reload() { return name + " Supplementary arrows"; }
@Override public String attack() { return name + " Archery"; }
@Override public int damage() { return 6; }
}
class Spider extends Enemy {
Spider(String name) { super(name); }
@Override public String attack() { return name + " Bite"; }
@Override public int damage() { return 5; }
}
public class L22Example03 {
static void runWave(Enemy[] wave) {
if (wave == null) return;
for (Enemy enemy : wave)
if (enemy != null)
System.out.println(enemy.attack() + " Damage=" + enemy.damage());L22Example03.java · Paragraph 2/2, sequentially connected to the same file
}
static long totalDamage(Enemy[] wave) {
if (wave == null) return 0;
long total = 0;
for (Enemy enemy : wave)
if (enemy != null) total += enemy.damage();
return total;
}
public static void main(String[] args) {
Enemy[] wave = {new Zombie("Carrion"), new Skeleton("Xiaobai"),
new Spider("Spider")};
runWave(wave);
}
}Output
Carrion Swing Damage=4
Xiaobai Archery Damage=6
Spider Bite Damage=505 / 13
Method parameters use parent class contract
If the method only requires the ability of Enemy, the parameters will be written as Enemy, and there is no need to repeat a method for each subclass.
- Parameter EnemyRely only on common capabilities
- null elementThis lesson is explicitly skipped
- Empty arrayTotal damage is 0
L22Example04.java · Section 1/2, sequentially connected to the same file
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
public int damage() { return 1; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
@Override public int damage() { return 4; }
}
class Skeleton extends Enemy {
Skeleton(String name) { super(name); }
public String reload() { return name + " Supplementary arrows"; }
@Override public String attack() { return name + " Archery"; }
@Override public int damage() { return 6; }
}
class Spider extends Enemy {
Spider(String name) { super(name); }
@Override public String attack() { return name + " Bite"; }
@Override public int damage() { return 5; }
}
public class L22Example04 {
static void runWave(Enemy[] wave) {
if (wave == null) return;
for (Enemy enemy : wave)
if (enemy != null)
System.out.println(enemy.attack() + " Damage=" + enemy.damage());L22Example04.java · Paragraph 2/2, sequentially connected to the same file
}
static long totalDamage(Enemy[] wave) {
if (wave == null) return 0;
long total = 0;
for (Enemy enemy : wave)
if (enemy != null) total += enemy.damage();
return total;
}
public static void main(String[] args) {
runWave(new Enemy[]{null, new Skeleton("Xiaobai")});
runWave(null);
System.out.println(totalDamage(new Enemy[0]));
}
}Output
Xiaobai Archery Damage=6
006 / 13
The parent class reference only looks at the contract entry
Even if the actual object is a Skeleton, an Enemy reference can only directly call members declared in the Enemy.
- attack / damageAlready in the parent class contract
- reloadOnly unique to skeletons
- Compilation checkParent class reference cannot directly add arrows
BadReload.java · Intentional error example
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
}
class Skeleton extends Enemy {
public String reload() { return name + " Supplementary arrows"; }
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
}
public class BadReload {
public static void main(String[] args) {
Enemy e = new Skeleton("Xiaobai");
System.out.println(e.reload());
}
}07 / 13
Downward transformation should be limited and safe
Downcast treats Enemy as a more specific Skeleton. Use it only if you really need the subclass-specific capabilities, and you must check them first.
- instanceof checkIs it indeed Skeleton?
- Mode variable sArrows can be replenished after successful matching
- Forced transformationIf there is no match, an exception will be thrown.
L22Example06.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 {
public String reload() { return name + " Supplementary arrows"; }
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
}
public class L22Example06 {
public static void main(String[] args) {
Enemy e = new Skeleton("Xiaobai");
if (e instanceof Skeleton s) System.out.println(s.reload());
e = new Zombie("Carrion");
System.out.println(e instanceof Skeleton);
}
}Output
Xiaobai Supplementary arrows
falseBadCast.java · Intentional error example
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
}
class Skeleton extends Enemy {
public String reload() { return name + " Supplementary arrows"; }
Skeleton(String name) { super(name); }
@Override public String attack() { return name + " Archery"; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
}
public class BadCast {
public static void main(String[] args) {
Enemy e = new Zombie("Carrion");
Skeleton s = (Skeleton) e;
System.out.println(s.reload());
}
}08 / 13
Let objects replace type branches
If the main program determines the attack method according to the type, each branch will be modified when adding new enemies. Overriding makes the object responsible for its own behavior.
- Join WitchNew class implementation attack
- Old cycleNo type branch added
- Extended verificationProve compatibility with output
L22Example07.java · Section 1/2, sequentially connected to the same file
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
public int damage() { return 1; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
@Override public int damage() { return 4; }
}
class Skeleton extends Enemy {
Skeleton(String name) { super(name); }
public String reload() { return name + " Supplementary arrows"; }
@Override public String attack() { return name + " Archery"; }
@Override public int damage() { return 6; }
}
class Spider extends Enemy {
Spider(String name) { super(name); }
@Override public String attack() { return name + " Bite"; }
@Override public int damage() { return 5; }
}
class Witch extends Enemy {
Witch(String name) { super(name); }
@Override public String attack() { return name + " Throw potion"; }
@Override public int damage() { return 7; }
}
public class L22Example07 {L22Example07.java · Paragraph 2/2, sequentially connected to the same file
static void runWave(Enemy[] wave) {
if (wave == null) return;
for (Enemy enemy : wave)
if (enemy != null)
System.out.println(enemy.attack() + " Damage=" + enemy.damage());
}
static long totalDamage(Enemy[] wave) {
if (wave == null) return 0;
long total = 0;
for (Enemy enemy : wave)
if (enemy != null) total += enemy.damage();
return total;
}
public static void main(String[] args) {
runWave(new Enemy[]{new Witch("Purple robe"), new Spider("Spider")});
}
}Output
Purple robe Throw potion Damage=7
Spider Bite Damage=509 / 13
wave simulator test bench
The wave program checks the order, each attack, total damage, and incorrect input.
- Sequential testingAppearance item by item in array
- Border testingEmpty arrays and null
- Total damageAdd the damage of each object
| Scene | Enter | Expected |
|---|---|---|
| Normal | Three kinds of enemies | Output three attacks in order |
| Border | Empty array | No output, total damage 0 |
| Error | Array contains null | Skip without crashing |
| Extension | Join Witch | The old cycle does not need to be modified |
| Return | Total damage | Equal to the sum of damage of each object |
10 / 13
Challenge Blueprint Unification Wave
The challenge program is called BattleWave. The three subclasses return attack description and damage respectively, and the two general methods are responsible for playing waves and counting total damage.
- runWavePlay each attack
- totalDamageOnly summarize numerical values
- Enemy ContractTwo methods share the same entrance
Acceptance conditions
- The same Enemy array holds three subclasses.
- instanceof does not appear in runWave.
- Each object outputs its own attack.
- totalDamage returns 15.
- Empty waves return 0, and null elements are safely skipped.
11 / 13
Challenges Realizing Combat Waves
Create a new BattleWave.java and run it in the Java 21 console.
- Three kinds of objects4, 6, 5 points of damage
- null slotExplicitly skip
- long total numberGet 15
BattleWave.java · Section 1/2, sequentially connected to the same file
class Enemy {
protected final String name;
Enemy(String name) { this.name = name; }
public String attack() { return name + " Normal attack"; }
public int damage() { return 1; }
}
class Zombie extends Enemy {
Zombie(String name) { super(name); }
@Override public String attack() { return name + " Swing"; }
@Override public int damage() { return 4; }
}
class Skeleton extends Enemy {
Skeleton(String name) { super(name); }
public String reload() { return name + " Supplementary arrows"; }
@Override public String attack() { return name + " Archery"; }
@Override public int damage() { return 6; }
}
class Spider extends Enemy {
Spider(String name) { super(name); }
@Override public String attack() { return name + " Bite"; }
@Override public int damage() { return 5; }
}
public class BattleWave {
static void runWave(Enemy[] wave) {
if (wave == null) return;
for (Enemy enemy : wave)
if (enemy != null)
System.out.println(enemy.attack() + " Damage=" + enemy.damage());BattleWave.java · Paragraph 2/2, sequentially connected to the same file
}
static long totalDamage(Enemy[] wave) {
if (wave == null) return 0;
long total = 0;
for (Enemy enemy : wave)
if (enemy != null) total += enemy.damage();
return total;
}
public static void main(String[] args) {
Enemy[] wave = {new Zombie("Carrion"), new Skeleton("Xiaobai"),
new Spider("Spider"), null};
runWave(wave);
System.out.println("Total damage=" + totalDamage(wave));
System.out.println("Empty wave times=" + totalDamage(new Enemy[0]));
}
}Output
Carrion Swing Damage=4
Xiaobai Archery Damage=6
Spider Bite Damage=5
Total damage=15
Empty wave times=012 / 13
Expansion tasks and clearance review
Assignments must indicate the declared type and actual object type of each variable.
- Standard declaration typeFor example Enemy
- mark actual typeFor example Spider
- Standard call resultBite comes from rewrite method
Third gear operation
- Wooden pickaxe: Use the Enemy array to uniformly output three attacks.
- Iron Pickaxe: Add loot method to Enemy, and use polymorphic unified settlement to drop.
- Diamond Pickaxe: Added Healer to allow the same takeTurn contract to attack as well as heal, and indicate whether an interface should be used instead.
| Self-evaluation | Can | Still need to practice |
|---|---|---|
| I understand the parent class reference | □ | □ |
| I can explain dynamic dispatch | □ | □ |
| I would avoid type branches | □ | □ |
13 / 13
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.