In the previous lesson you learnt how to debug a class.
Collections and maps
The Java Collections Framework provides a set of classes and interfaces that let you manage groups of related objects in a more powerful way than arrays.
In this section you will learn:
- How the collections framework is structured
- How to use lists for ordered groups of objects
- How to use sets for unordered groups of objects
- How to use maps for keyed-pair groups of objects
The collections framework
In Section 6 you created the Pen class that defined an array to store a group of Animal objects. While arrays are efficient, you saw a particular downside in that after you have declared the number of elements the array will contain you cannot change that amount thereafter. The capacity of ten chosen for each pen in the zoo was completely arbitrary. While it may be sufficient to store the number of lions, the penguin pen is likely to need to be able to store several dozen.
Rather than having to guess a likely maximum and then have many null elements, a far more flexible approach is to make use of Java's collections framework. This incorporates several related classes and interfaces that enable you to store groups of objects, and which can dynamically expand or contract in size as required.
The root of the framework is an interface called Collection. If you look at the API for this interface you will see several methods, the most frequently used being:
add()- adds an object to the collection;remove()- removes an object from the collection;size()- returns the number of objects currently in the collection.
The collection framework can be shown diagrammatically as in the following figure. Note that all the items shown are Java interfaces, and there are corresponding classes that implement the interfaces and which you actually use to hold the collection data:
The main restriction of the collection classes is that they can only contain object references: primitives are not allowed. There is a simple workaround for this, however, since you can use the wrapper objects (Integer instead of int, for example), as described in Section 2.
The collections framework classes and interfaces are in the java.util package, so you will need to import this into the classes which make use of them.
Lists
The type of collection which most closely resembles an array is List, which contains several objects in the order in which they were added to the list. List is in fact another interface; the most used class that implements it is ArrayList. Here is an example of using ArrayList:
// Create an empty list of words
Collection<String> words = new ArrayList<String>(); // statement 1
// Add some words to the list
words.add("To"); // statement 2
words.add("be"); // statement 3
words.add("or"); // statement 4
words.add("not"); // statement 5
words.add("to"); // statement 6
words.add("be"); // statement 7
words.add("that"); // statement 8
words.add("is"); // statement 9
words.add("the"); // statement 10
words.add("question"); // statement 11
// Output the words
for (String s : words) { // statement 12
System.out.println(s); // statement 13
}
System.out.println("The list contains " + // statement 14
words.size() + " words");
- Statement 1 declares a
CollectionofStringobjects calledwords, and then instantiates anArrayListofStringobjects (theArrayListclass implements theListinterface, which in turn extends theCollectioninterface). Note that the syntax includes both the formal type parameter in angle-brackets and then empty round brackets to indicate a call to the constructor ofArrayList - Statements 2 through 11 add a series of
Stringobjects to the list - Statement 12 initiates a
for-eachloop over the words - Statement 13 sends the current word in the loop to output
- Statement 14 reports how many words are in the list
To be or not to be that is the question The list contains 10 words
You will notice that lists store the objects in the order they were added, and that they can contain duplicate items, for example the word "be".
If you need to access a specific object in a list by its position, you can use the get() method passing the required index position. However, get() is defined in the List interface and not the Collection interface, so you would need to modify the declaration as follows:
List<String> words = new ArrayList<String>();
You will now be able to invoke the get() method:
String firstWord = words.get(0); String secondWord = words.get(1); String lastWord = words.get(words.size() - 1);
- Like arrays, lists are indexed such that the first element has index zero, the second element has index 1, etc.
- The index number of the last element can be calculated by subtracting one from the number of elements in the list
Since Java 7, it's unnecessary to include the type when instantiating a collection since it is inferred from the declaration. This means the declaration and instantiation of words can be as follows:
List<String> words = new ArrayList<>();
Note the empty angle-brackets that follow ArrayList.
You can also omit the types from the other collection types to be explained in subsequent sections, and we will do so for consistency.
Sets
A Set differs from a List in the following ways:
- Sets do not allow duplicate objects to be added. An object is considered to be a duplicate if its
equals()method returnstruefor any object already in the set - The order in which objects are added to the set is not maintained unless you use a sorted set
- This means there is no
get()method available to sets - The
HashSetclass maintains a set of objects in no particular order - The
TreeSetclass maintains a set of objects sorted into their natural order, as defined by thecompareTo()method. Alternatively, you can specify an different ordering using aComparatorobject
Replace the following statement:
List<String> words = new ArrayList<>();
With this:
Collection<String> words = new HashSet<>();
The remainder of the code does not need to change. Your output may look like this:
to not that is or To question the be The list contains 9 words
- It is possible that your words will be listed in a different order to that shown above, and even that you may get a different order if you run it again
- There are only nine words in the set since the word "be" was duplicated and so was not added twice. Note that "To" is considered different to "to" due to the differing capitalisation
If you want to ensure the objects are iterated using their natural ordering, specify a TreeSet rather than a HashSet. Because the set contains String objects, the natural ordering will be alphabetical:
Collection<String> words = new TreeSet<>();
The output should now be as follows:
To be is not or question that the to The list contains 9 words
- Note that uppercase characters are sorted before lowercase, hence "To" is listed before "be"
Be aware that the HashSet class has better performance that TreeSet since it does not need to take steps to keep the objects in a particular order, so it is recommended that you only use TreeSet when you actually require the objects to be sorted.
Converting the Pen class to use a collection
You have seen that the collection classes offer greater flexibility than arrays when needing to store groups of objects. In terms of the Pen class that currently uses an array that can hold up to ten animals, the first decision that needs to be made is whether to use instead a List or a Set. To help answer this you can address the following questions:
- Does it make sense for the same animal to exist in the collection more than once?
- Does the sequence in which animals are added into the pen have any significance?
- Does it matter what sequence animals are retrieved from the pen?
It seems clear that that the answer to question 1 is that each individual animal can only exist once inside a pen. For question 2, there is no obvious significance to the order in which animals will be placed inside the pen, and likewise for question 3, there is no need to have to store them in any particular order. The conclusion, therefore, is that Set would be more appropriate than List, and that the Set does not need to be sorted. The best implementation class to achieve this would be HashSet. You are therefore now ready to start modifying the Pen class.
Because collections do not have an upper size limit you no longer need to specify an arbitrary capacity[1]. Remove the following statement from Pen:
public static final int CAPACITY = 10;
The instance variable animals is currently declared to be an array of Animal objects, but it should now be a Collection of Animal objects. Change the declaration statement so it reads as follows:
private Collection<Animal> animals;
You no longer need the instance variable to hold the next element index since collections will manage this internally. Remove the following declaration:
private int nextElementIndex;
Change the constructor to instantiate the animals collection as a new unsorted set:
public Pen(String name) {
this.name = name;
animals = new HashSet<>();
}
The add() method now simply needs to forward to the add() method of the collection object:
public void add(Animal animal) {
animals.add(animal);
}
It is generally a good idea when providing an add() method to also provide a remove() method. Insert the new method below so that an Animal object can be removed from the pen:
public void remove(Animal animal) {
animals.remove(animal);
}
The getAnimals() method currently returns an array, but it should instead return a collection. Change the method signature and the body to return the whole collection:
public Collection<Animal> getAnimals() {
return animals;
}
The getCount() method needs to be changed to return the size of the collection (i.e., the number of objects it currently contains):
public int getCount() {
return animals.size();
}
It will prove useful later on for each animal to "know" what pen it is in. Add the following instance variable inside the Animal class:
private Pen pen;
Define a getter and setter method for the pen field:
public Pen getPen() {
return pen;
}
public void setPen(Pen pen) {
this.pen = pen;
}
It will be useful for the natural ordering of a Pen object to be alphabetically by the pen's name, so implement the Comparable interface:
public class Pen implements Comparable<Pen> {
And provide the required compareTo() method code:
@Override
public int compareTo(Pen otherPen) {
// Sort alphabetically by name
int result = getName().compareTo(otherPen.getName());
if (result != 0) return result;
/*
* If reached here names are the same.
* So that method is consistent with equals() will now
* sort on hash code.
*/
return hashCode() - otherPen.hashCode();
}
In the next lesson we will make use of the Pen class in ZooAdministrator.
Comments