Class, Extend Thyself!
Rich Winnie
Principal Content Publishing Manager @ Microsoft Learn | Instructor, Author
This is a continuation on my series of articles on object-oriented programming. The first article, discusses how you can use role-playing game characters as a metaphor for object classes, the second, discusses how party-systems and roles introduce polymorphism and class hierarchy, and the third discusses how to create Java classes using a role-playing game character sheet as a model.
With our basic class created for the mage, we can look at other characters in our game and see how they compare to each other. We can see from their character sheets that they each have different skills and rules that govern how these skills are populated with values at the time of instantiation and construction.
Comparing Our Two Classes
The Mage class defines strength, intelligence, agility and wisdom as the main fields, and then uses a set of calculations to create their values and determine hit points and mana:
public class Mage {
// Name
public String name;
// Skill attributes
public int strength;
public int intelligence;
public int agility;
public int wisdom;
// Health and magic
public int maxHitPoints;
public int hitPoints;
public int maxMana;
public int mana;
Mage(String newName) {
// Constructor
name = newName;
strength = 7;
intelligence = 15;
agility = 8;
wisdom = 10;
strength += (int) (Math.random() * 6 + 1);
intelligence += (int) (Math.random() * 6 + 1);
agility += (int) (Math.random() * 6 + 1);
wisdom += (int) (Math.random() * 6 + 1);
maxHitPoints = hitPoints = strength;
maxMana = mana = intelligence + (wisdom * 2);
System.out.println("A new mage named " + name + " has been created!");
}
public void showStats() {
System.out.println("----------------------------------");
System.out.println(name + ", a mage:");
System.out.println(" Strength: " + strength);
System.out.println("Intelligence: " + intelligence);
System.out.println(" Agility: " + agility);
System.out.println(" Wisdom: " + wisdom);
System.out.println(" Hit Points: " + hitPoints + " / " + maxHitPoints);
System.out.println(" Mana: " + mana + " / " + maxMana);
System.out.println();
}
}
Now, if we look at the Fighter class, we have a different set of skills that are used. The Fighter class defines strength, intelligence, agility and constitution instead of wisdom. The calculations to determine their initial values and the amount of hit points and mana are also different:
public class Fighter {
// Name
public String name;
// Skill attributes
public int strength;
public int intelligence;
public int agility;
public int constitution;
// Health and magic
public int maxHitPoints;
public int hitPoints;
public int maxMana;
public int mana;
Fighter(String newName) {
// Constructor
name = newName;
strength = 15;
intelligence = 7;
agility = 8;
constitution = 10;
strength += (int) (Math.random() * 6 + 1);
intelligence += (int) (Math.random() * 6 + 1);
agility += (int) (Math.random() * 6 + 1);
constitution += (int) (Math.random() * 6 + 1);
maxHitPoints = hitPoints = (strength * 2) + (constitution * 2);
maxMana = mana = 0;
System.out.println("A new fighter named " + name + " has been created!");
}
public void showStats() {
System.out.println("----------------------------------");
System.out.println(name + ", a fighter:");
System.out.println(" Strength: " + strength);
System.out.println("Intelligence: " + intelligence);
System.out.println(" Agility: " + agility);
System.out.println("Constitution: " + constitution);
System.out.println(" Hit Points: " + hitPoints + " / " + maxHitPoints);
System.out.println(" Mana: " + mana + " / " + maxMana);
System.out.println();
}
}
When you look at these two, there is a lot that is in common between them. They both contain a name, strength, intelligence, agility, hit points and mana fields. They also both have a showStats() method that displays the instance's attribute values.
Creating superclasses
When you create classes, you can take things that are common for multiple classes and create superclasses. It works like this. If we create a class that has everything that is in common, we can then build what is unique for a Mage and a Fighter on top of what is common for both of them. When you create classes in this way, you are able to build on top of and include or extend that contents of these classes.
So, if we had a new class called PlayerCharacter, we can define that to include everything that is in common between Fighter and Mage:
public class PlayerCharacter {
// Name
public String name;
// Skill attributes
public int strength;
public int intelligence;
public int agility;
// Health and magic
public int maxHitPoints;
public int hitPoints;
public int maxMana;
public int mana;
PlayerCharacter() {
System.out.println("A new player character has been created!");
}
public void showStats() {
}
}
As you can see, only the things that are common across Mage and Fighter are included here. We define strength, intelligence, agility, hit points and mana fields, and we have a constructor for the PlayerCharacter class, and we have a method for showStats() which doesn't do anything.
Now we can use this class as the foundation for other classes, including Mage and Fighter. We can strip out the items that are already in the PlayerCharacter class in each of these two classes. First we can do that with the Mage class by removing the definitions for name, strength, intelligence, agility, hit points and mana:
public class Mage {
// Skill attributes
public int wisdom;
Mage(String newName) {
// Constructor
name = newName;
strength = 7;
intelligence = 15;
agility = 8;
wisdom = 10;
strength += (int) (Math.random() * 6 + 1);
intelligence += (int) (Math.random() * 6 + 1);
agility += (int) (Math.random() * 6 + 1);
wisdom += (int) (Math.random() * 6 + 1);
maxHitPoints = hitPoints = strength;
maxMana = mana = intelligence + (wisdom * 2);
System.out.println("A new mage named " + name + " has been created!");
}
public void showStats() {
System.out.println("----------------------------------");
System.out.println(name + ", a mage:");
System.out.println(" Strength: " + strength);
System.out.println("Intelligence: " + intelligence);
System.out.println(" Agility: " + agility);
System.out.println(" Wisdom: " + wisdom);
System.out.println(" Hit Points: " + hitPoints + " / " + maxHitPoints);
System.out.println(" Mana: " + mana + " / " + maxMana);
System.out.println();
}
}
When you do this though in an IDE, you will get errors appear, because Java doesn't know that you are building the Mage class on top of the PlayerCharacter class:
We need to specifically define that we are building the Mage class on top of the PlayerCharacter class. We do that by changing the definition of the class on the first line.
When you build on top of another class, you are extending it, so we need to change the first line to read:
public class Mage extends PlayerCharacter {
When you do that, all of the errors that we previously saw are resolved and go away, because everything that is part of the PlayerCharacter class is now included in the Mage class:
Let's go to our Fighter class now. Let's change the class definition at the top to include the PlayerCharacter class by changing the first line to read:
public class Fighter extends PlayerCharacter {
Now we can remove the items that are already in the PlayerCharacter class:
public class Fighter extends PlayerCharacter {
// Skill attributes
public int constitution;
Fighter(String newName) {
// Constructor
name = newName;
strength = 15;
intelligence = 7;
agility = 8;
constitution = 10;
strength += (int) (Math.random() * 6 + 1);
intelligence += (int) (Math.random() * 6 + 1);
agility += (int) (Math.random() * 6 + 1);
constitution += (int) (Math.random() * 6 + 1);
maxHitPoints = hitPoints = (strength * 2) + (constitution * 2);
maxMana = mana = 0;
System.out.println("A new fighter named " + name + " has been created!");
}
public void showStats() {
System.out.println("----------------------------------");
System.out.println(name + ", a fighter:");
System.out.println(" Strength: " + strength);
System.out.println("Intelligence: " + intelligence);
System.out.println(" Agility: " + agility);
System.out.println("Constitution: " + constitution);
System.out.println(" Hit Points: " + hitPoints + " / " + maxHitPoints);
System.out.println(" Mana: " + mana + " / " + maxMana);
System.out.println();
}
}
Everything looks good, so let's go to our Main class and update our main() method to create two instances of the Mage and Fighter class:
public class Main {
public static void main(String[] args) {
Mage myMage = new Mage("Francisco");
myMage.showStats();
Mage myOtherMage = new Mage("Jaana");
myOtherMage.showStats();
Fighter myFighter = new Fighter("Dupre");
myFighter.showStats();
Fighter myOtherFighter = new Fighter("Sentri");
myOtherFighter.showStats();
}
}
Now let's run and see what the output is:
A new player character has been created!
A new mage named Francisco has been created!
----------------------------------
Francisco, a mage:
Strength: 12
Intelligence: 17
Agility: 9
Wisdom: 16
Hit Points: 12 / 12
Mana: 49 / 49
A new player character has been created!
A new mage named Jaana has been created!
----------------------------------
Jaana, a mage:
Strength: 12
Intelligence: 16
Agility: 11
Wisdom: 15
Hit Points: 12 / 12
Mana: 46 / 46
A new player character has been created!
A new fighter named Dupre has been created!
----------------------------------
Dupre, a fighter:
Strength: 18
Intelligence: 9
Agility: 12
Constitution: 12
Hit Points: 60 / 60
Mana: 0 / 0
A new player character has been created!
A new fighter named Sentri has been created!
----------------------------------
Sentri, a fighter:
Strength: 18
Intelligence: 12
Agility: 13
Constitution: 11
Hit Points: 58 / 58
Mana: 0 / 0
Process finished with exit code 0
You'll see that everything is looking good and everything is almost identical to what we did before, with one main difference. You'll see a new line "A new player character has been created!" at the top of each section when a new instance is constructed.
That line is located in our PlayerCharacter class constructor. When we create a class that is built on top of another class, it will run the contents of the base class constructor and then the code in the class it is built on top of.
But if we try to do that with the showStats() method, the same behavior doesn't happen. For example, change the PlayerCharacter showStats() method to add a new statement to the output console:
public class PlayerCharacter {
// Name
public String name;
// Skill attributes
public int strength;
public int intelligence;
public int agility;
// Health and magic
public int maxHitPoints;
public int hitPoints;
public int maxMana;
public int mana;
PlayerCharacter() {
System.out.println("A new player character has been created!");
}
public void showStats() {
System.out.println("Here are the stats for, " + name);
}
}
If you run the program again, the code in the PlayerCharacter showStats() method is never displayed. When you build on top of a class and want to run code from a method it is built on top of, you need to specifically state you want to run it. A constructor will automatically run the code in the base class without you needing to do anything.
To call the code in a base class' matching method, you need to use the super statement. Let's update the Mage class to see how this works:
public class Mage extends PlayerCharacter {
// Skill attributes
public int wisdom;
Mage(String newName) {
// Constructor
name = newName;
strength = 7;
intelligence = 15;
agility = 8;
wisdom = 10;
strength += (int) (Math.random() * 6 + 1);
intelligence += (int) (Math.random() * 6 + 1);
agility += (int) (Math.random() * 6 + 1);
wisdom += (int) (Math.random() * 6 + 1);
maxHitPoints = hitPoints = strength;
maxMana = mana = intelligence + (wisdom * 2);
System.out.println("A new mage named " + name + " has been created!");
}
public void showStats() {
super.showStats();
System.out.println("----------------------------------");
System.out.println(name + ", a mage:");
System.out.println(" Strength: " + strength);
System.out.println("Intelligence: " + intelligence);
System.out.println(" Agility: " + agility);
System.out.println(" Wisdom: " + wisdom);
System.out.println(" Hit Points: " + hitPoints + " / " + maxHitPoints);
System.out.println(" Mana: " + mana + " / " + maxMana);
System.out.println();
}
}
You'll see that the showStats() method has a call at the beginning:
super.showStats();
This will go to the class that this class extends and execute the method. If we run this program, we can see how this works:
A new player character has been created!
A new mage named Francisco has been created!
Here are the stats for, Francisco
----------------------------------
Francisco, a mage:
Strength: 13
Intelligence: 19
Agility: 12
Wisdom: 16
Hit Points: 13 / 13
Mana: 51 / 51
A new player character has been created!
A new mage named Jaana has been created!
Here are the stats for, Jaana
----------------------------------
Jaana, a mage:
Strength: 9
Intelligence: 18
Agility: 12
Wisdom: 15
Hit Points: 9 / 9
Mana: 48 / 48
A new player character has been created!
A new fighter named Dupre has been created!
----------------------------------
Dupre, a fighter:
Strength: 18
Intelligence: 8
Agility: 13
Constitution: 16
Hit Points: 68 / 68
Mana: 0 / 0
A new player character has been created!
A new fighter named Sentri has been created!
----------------------------------
Sentri, a fighter:
Strength: 18
Intelligence: 9
Agility: 9
Constitution: 13
Hit Points: 62 / 62
Mana: 0 / 0
Process finished with exit code 0
If you notice, only the mages that are created display the "Here are the stats for…" line. That is because we haven't added the super.showStats() call to the Fighter class, only to the Mage class.
When you create classes like this that extend other classes, you are creating a class hierarchy. The base class is called the super class. The class that you create that extends the super class is called the sub class.
So in our example, PlayerCharacter is the super class. Mage and Fighter are sub classes of the PlayerCharacter super class.
When we use the super statement, we are asking the super class to do something, in this case, the PlayerCharacter class.
You can create as many sub classes of a super class as you want, and you can continue to extend classes as far as you need to.
Additional Information
For more information on how to extend classes and build sub classes, check out this video from my course, "Computer Science Principles: Java Lab":