In the previous lesson we looked at lists and set.
Using the Pen class in ZooAdministrator
The zoo administrator will keep a record of each pen in the zoo, being one for each of the animal types. In the ZooAdministrator class define these Pen instance variables:
private Pen lionPen, monkeyPen, penguinPen;
You will now define a private helper method that "acquires" an animal into a pen:
private void acquireAnimal(Animal animal, Pen pen) {
pen.add(animal);
animal.setPen(pen);
}
Whenever you have a method that adds to a collection (as above) it is often a good idea to provide a way of reversing the process:
private void relinquishAnimal(Animal animal) {
Pen currentPen = animal.getPen();
currentPen.remove(animal);
animal.setPen(null);
}
You will now define a private helper method that simulates the acquisition of a few lions by placing them in the lion's pen:
private void acquireLions() {
acquireAnimal(new Lion("Leo", Gender.MALE, 2), lionPen);
acquireAnimal(new Lion("Mr Fuzz", Gender.MALE, 2), lionPen);
acquireAnimal(new Lion("Bazooka", Gender.FEMALE, 5), lionPen);
}
You can now define two similar methods for the other pens:
private void acquireMonkeys() {
acquireAnimal(new Monkey("Bonzo", Gender.MALE, 5), monkeyPen);
acquireAnimal(new Monkey("Norti", Gender.FEMALE, 5), monkeyPen);
acquireAnimal(new Monkey("Hairball", Gender.MALE, 4), monkeyPen);
}
private void acquirePenguins() {
acquireAnimal(new Penguin("Sammy", Gender.FEMALE, 1), penguinPen);
acquireAnimal(new Penguin("Oswald", Gender.MALE, 2), penguinPen);
acquireAnimal(new Penguin("Percy", Gender.MALE), penguinPen);
acquireAnimal(new Penguin("Petra", Gender.FEMALE, 2), penguinPen);
}
Now define a createExamplePens() method which instantiates the three Pen instance variables and assigns the relevant animals to them:
private void createExamplePens() {
lionPen = new Pen("Lion Lane");
acquireLions();
monkeyPen = new Pen("Monkey Mews");
acquireMonkeys();
penguinPen = new Pen("Penguin Parade");
acquirePenguins();
}
You can now invoke the createExamplePens() method at the end of the constructor:
public ZooAdministrator() {
createExampleZooKeepers();
createExampleVisitors();
createExamplePens();
}
You will now define a public method to simulate feeding time at the zoo. This could be written to iterate over each collection separately, but instead you will combine the three collections into one and then iterate over that:
public void feedingTime() {
// Collect all the animals
Collection<Animal> animals = new HashSet<>();
animals.addAll(lionPen.getAnimals());
animals.addAll(monkeyPen.getAnimals());
animals.addAll(penguinPen.getAnimals());
// Feed them one at a time
for (Animal anAnimal : animals) {
System.out.println(anAnimal.getName() +
" is eating a " + anAnimal.favouriteFood());
}
}
- In the method body, a new empty collection called
animalsis created - The
addAll()method is invoked on animals to add the objects within each of the pens - The
animalscollection is then iterated over. Note that because theanimalscollection is unsorted you cannot predict the sequence in which the objects will be processed
To see the results of the feedingTime() method you can now invoke it upon the admin object inside the main() method of Experiments:
ZooAdministrator admin = new ZooAdministrator(); // methods to be invoked on admin will go here... admin.feedingTime();
If you want the animals to be sorted into their natural order, then you can pass the collection into a new TreeSet (which will be sorted). In feedingTime() insert the following statement after the last addAll() statement and before the for-each loop:
Collection<Animal> sortedAnimals = new TreeSet<>(animals);
- Previously, when instantiating a collection class, you have used empty brackets so that an empty collection is created. Above, however, you can see that it is possible to pass another collection inside the brackets so that the new collection will contain all of the objects in the passed collection. This technique enables you to pass in an unsorted collection and turn it into a sorted collection
To see the sorted results ensure you change the for-each loop to use sortedAnimals instead of animals:
for (Animal anAnimal : sortedAnimals) {
To sort in a different sequence than the natural ordering you can utilise the same Comparator object that was used when sorting arrays, although for a collection it is a two-step process.
Firstly, you need to instantiate an empty TreeSet passing the Comparator object as its argument. This tells the TreeSet how it should sequence any objects subsequently added into it:
Collection<Animal> sortedAnimals = new TreeSet<>(new Animal.SortByAgeName());
Secondly, you can add all of the objects within the unsorted collection into the new sorted collection in one go though its addAll() method:
sortedAnimals.addAll(animals);
You can now iterate over sortedAnimals and they will be sorted into the order specified by the Comparator object, which in this case is age and name.
There is one final consideration that needs to be addressed. You will recall the discussion of privacy leaks in Section 4, and the Pen class currently leaks the collection of animals through the getAnimals() method. You can see this if you insert the following statements in, for example, the constructor of ZooAdministrator:
Collection<Animal> monkeys = monkeyPen.getAnimals();
monkeys.add(new Monkey("Invader", Gender.MALE, 9));
You have now managed to modify the internal contents of the Pen object without going through Pen's add() method, which is supposed to be the only way animals should be added to a pen. You saw in Section 8 the copy constructor technique when returning an instance variable, and that would be a valid way of preventing the privacy leak. You could change the getAnimals() method in Pen as follows to return a new collection which contains the objects of the current collection:
public Collection<Animal> getAnimals() {
return new HashSet<Animal>(animals);
}
Now, if the client object adds or removed objects from the returned collection it will not affect the collection inside the Pen object. There is an alternative technique you can use with collections which will also prevent client objects from adding or removing at all. This uses a static utility method of the Collections class (note the plural name) called unmodifiableCollection(), which returns a read-only version of the collection specified in its argument. Change the getAnimals() method again:
public Collection<Animal> getAnimals() {
return Collections.unmodifiableCollection(animals);
}
You will now find that if a client object (such as ZooAdministrator) tries to add or remove objects from the collection returned from getAnimals() then although the program will compile you will receive an UnsupportedOperationException at run-time. If you added the statements to the end of the constructor to see the effects of the privacy leak, then please delete them.
In the next lesson we will look at maps.
Comments