When you play a role-playing game, you work together as a team with other people. This could be with other players in the same room in real life or simulated characters in a computer game. A party is a collection, a group of people who have something in common.
In Java, we can create collections using arrays or ArrayLists. These collections can be of everything, and we can use them to create collections of things that we define ourselves using classes. Java collections aren’t limited to things that are just in the core language—they can be instances of objects that we create in our own custom classes.
Here is an example, in our program, we can create a collection of
characters in our game, called
party:
import java.util.ArrayList;
public static ArrayList party;
public static void main(String[] args) {
party = new ArrayList();
party.add(new Mage("Jaana"));
party.add(new Mage("Antos"));
}
}
In this example, we now have a collection called party, and it contains two instances of the Mage class
. Party on! Well—not quite.
When we create an
ArrayList without generics, we are typing the collection to
Object, meaning that anything can be added to it, including a number, string, or any object in Java. It also means that we can’t access the unique methods that are part of the
Mage class, like
showStats()
. So if we try to add this line:
party.get(0).showStats();
we get an error, because it can’t find that method in the class that defines the collection: the Object class.
But, we can fix this. Using generics, we can type the
ArrayList collection to the
Mage type, then we can access the methods of
Mage. We can update our code to read like this:
import java.util.ArrayList;
public static ArrayList<Mage> party;
public static void main(String[] args) {
party = new ArrayList<Mage>();
party.add(new Mage("Jaana"));
party.add(new Mage("Antos"));
}
}
Now we can use the methods that are part of the
Mage class
, and we can even use a
for...each loop to go through each of the objects in the collection:
import java.util.ArrayList;
public static ArrayList<Mage> party;
public static void main(String[] args) {
party = new ArrayList<Mage>();
party.add(new Mage("Jaana"));
party.add(new Mage("Antos"));
for (Mage pc : party)
pc.showStats();
}
}
When I run this, I can get the output for each of the two
Mage class
instances that are now in the collection:
A new player character has been created!
A new mage named Jaana has been created!
A new player character has been created!
A new mage named Antos has been created!
Here are the stats for, Jaana
----------------------------------
Jaana, a mage:
Strength: 13
Intelligence: 21
Agility: 10
Wisdom: 14
Hit Points: 13 / 13
Mana: 49 / 49
Here are the stats for, Antos
----------------------------------
Antos, a mage:
Strength: 9
Intelligence: 20
Agility: 13
Wisdom: 15
Hit Points: 9 / 9
Mana: 50 / 50
Process finished with exit code 0
But we still have a problem. What about other classes? If I want to create a
Fighter and add them to the collection, I can’t:
party.add(new Fighter("Sentri"));
The following code gives me an error, because the ArrayList
collection is specifically typed to a Mage. So how do we fix this?
We need to think back to how we built the
Mage and
Fighter and other potential classes. We built them to extend the
PlayerCharacter class
, so, with that in mind, what are
Mage and
Fighter? They are
PlayerCharacters! We can use the superclass as a tool to type any collection or object to accept any of the subclasses that extend it. We will also need to update our
for...each loop, because it will need to look for
PlayerCharacters
in the collection, not a unique class like the
Mage class:
import java.util.ArrayList;
public static ArrayList<PlayerCharacter> party;
public static void main(String[] args) {
party = new ArrayList<PlayerCharacter>();
party.add(new Mage("Jaana"));
party.add(new Mage("Antos"));
party.add(new Fighter("Sentri"));
for (PlayerCharacter pc : party)
pc.showStats();
}
}
Now we can support multiple character types in our collection by typing it to the superclass. The collection can now hold the Mage and Fighter instances because they extend the PlayerCharacter superclass.
There is one important thing to note though. The showStats() method
that we access in the for...each loop works, because it is located in the superclass. If we didn’t have that method in PlayerCharacter, we wouldn’t be able to use it, so if you know that you will have subclasses that have a method and will need to type collections and objects to a superclass, it is helpful to create empty methods so you can work with them.
But, we have one more issue. The collection can now support any class that extends
PlayerCharacter—but that includes the
PlayerCharacter class itself. We have designed
PlayerCharacter to serve as a foundation to build our
Mage,
Fighter, and other classes from. We don’t want to be able to create instances of
PlayerCharacter:
import java.util.ArrayList;
public static ArrayList<PlayerCharacter> party;
public static void main(String[] args) {
party = new ArrayList<PlayerCharacter>();
party.add(new Mage("Jaana"));
party.add(new Mage("Antos"));
party.add(new Fighter("Sentri"));
party.add(new PlayerCharacter());
for (PlayerCharacter pc : party)
pc.showStats();
}
}
In this example, we can create an instance of
PlayerCharacter, which we never intended, and as a result, we get some strange output at the end when we run our program:
A new player character has been created!
A new mage named Jaana has been created!
A new player character has been created!
A new mage named Antos has been created!
A new player character has been created!
A new fighter named Sentri has been created!
A new player character has been created!
Here are the stats for, Jaana
----------------------------------
Jaana, a mage:
Strength: 12
Intelligence: 17
Agility: 10
Wisdom: 12
Hit Points: 12 / 12
Mana: 41 / 41
Here are the stats for, Antos
----------------------------------
Antos, a mage:
Strength: 9
Intelligence: 16
Agility: 13
Wisdom: 12
Hit Points: 9 / 9
Mana: 40 / 40
----------------------------------
Sentri, a fighter:
Strength: 18
Intelligence: 9
Agility: 9
Constitution: 12
Hit Points: 60 / 60
Mana: 0 / 0
Here are the stats for, null
Process finished with exit code 0
We need to find a way to have the
PlayerCharacter class
, but prevent our program from allowing us to create instances of it. The answer is to make it an abstract class. The
abstract clause
is added to the first line of a class definition:
public abstract 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);
}
}
With the class now defined as abstract, we can extend it to build subclasses, but we can’t create instances of it on its own:
party.add(new PlayerCharacter());
So this line of code now generates an error, stating that it is abstract, and can’t be instantiated. We still can type collections and other objects to it though—which is perfectly legal—because we aren’t creating new instances of the PlayerCharacter class; we are simply allowing its subclasses to work with it.