Chapter 10

Exercise 10.1

Yes, the number of foxes change.

Exercise 10.2

Yes, it changes each time simulateOneStep is invoked.

Sometimes the number increase and other times it decreases. It probably simulates the births and deaths of foxes.

Exercise 10.3

No.

Exercise 10.4

The numbers of foxes and rabbits goes up and down.

Exercise 10.5

No, it is not an identical simulation.

Yes, it is similar patterns.

Exercise 10.6

No, it doesn't look like the foxes or rabbits ever die out completely. Why doesn't that happen then? Well, it seems like the parameters of the simulation has been tuned to make a balance between the number of foxes and rabbits.

Exercise 10.9

Making the breeding probability of the rabbits much lower has the result that the foxes die out quickly because they don't have enough food. Increasing the breeding probability of the rabbits make them spread faster.

 

Exercise 10.11

Increasing the maximum age for foxes doesn't seem to have a big impact on the number of foxes.

Exercise 10.12

Yes, in some configurations the foxes or the rabbits disappear completely.

Exercise 10.13

Yes the size does affect the likelihood of species surviving. With a small field (10x10) this is easy to observe. Sometimes the foxes die out, and at other times the rabbits die out.

Exercise 10.14

The modified findFood() method:

 
/**
* Tell the fox to look for rabbits adjacent to its current location.
* @param field The field in which it must look.
* @param location Where in the field it is located.
* @return Where the last piece of food was found, or null if it wasn't.
*/
private Location findFood(Field field, Location location)
{
Iterator adjacentLocations =
field.adjacentLocations(location);
Location where = null;
while(adjacentLocations.hasNext()) {
where = (Location) adjacentLocations.next();
Object animal = field.getObjectAt(where);
if(animal instanceof Rabbit) {
Rabbit rabbit = (Rabbit) animal;
if(rabbit.isAlive()) {
rabbit.setEaten();
foodLevel = RABBIT_FOOD_VALUE;

}
}
}
return where;
}

It does not seem to have a big impact.

Exercise 10.15

To implement the suggested changes we need a field with maximum food value:

    private static final int MAX_FOOD_VALUE = 20; 

And the modified method:

    private Location findFood(Field field, Location location)
{
Iterator adjacentLocations =
field.adjacentLocations(location);
Location where = null;
while(adjacentLocations.hasNext()) {
where = (Location) adjacentLocations.next();
Object animal = field.getObjectAt(where);
if(animal instanceof Rabbit) {
Rabbit rabbit = (Rabbit) animal;
if(rabbit.isAlive()) {
rabbit.setEaten();
foodLevel = foodLevel + RABBIT_FOOD_VALUE;
if(foodLevel > MAX_FOOD_VALUE) {
foodLevel = MAX_FOOD_VALUE;
}

}
}
}
return where;
}

Depending on the maximum food value, the foxes seem to survive longer.

Exercise 10.16

No, it doesn't seem to have a catastrophic effect.

Exercise 10.17

Yes, after a while it seems to behave like the original version.

The relative size of the initial populations doesn't have a big impact on the outcome of the simulation.

Exercise 10.18

The effect on the fox population is that they all die out after a while when there is too many rabbits.

No, the constraint is not placed upon newly born rabbits.

Exercise 10.19

No.

Exercise 10.20

The similar class fields are:

    private static final int BREEDING_AGE = 5;
    private static final int MAX_AGE = 50;.
    private static final double BREEDING_PROBABILITY = 0.15;
    private static final int MAX_LITTER_SIZE = 5;
    private static final Random rand = new Random(); 

The fox has an additional class field:

    private static final int RABBIT_FOOD_VALUE = 4;

The similar instance fields are:

    private int age;
private boolean alive;
private Location location;

The fox has an additional instance field:

    private int foodLevel;

The constructors are similar, except that the Fox constructor also initialises its foodLevel.

The similar methods are:

    private void incrementAge() //except for the values of the static fields
    private int breed() //except for the values of the static fields
    private boolean canBreed() //except for the values of the static fields
    public boolean isAlive()
    public void setLocation(int row, int col)
    public void setLocation(Location location)

The Rabbit class has these methods that the Fox class doesn't have:

    public void run(Field updatedField, List newRabbits)
    public void setEaten()

And the Fox class has these methods that the Rabbit class doesn't have:

    public void hunt(Field currentField, Field updatedField, List newFoxes)
    private void incrementHunger()
    private Location findFood(Field field, Location location)

Exercise 10.21

The truly identical methods are:

    public boolean isAlive()
    public void setLocation(int row, int col)
    public void setLocation(Location location) 

Exercise 10.22

In this case, no. Because it is most likely that we would want to change the value in the future to values different for the Fox and the Rabbit.

In general it depends on the actual field. If it can truly be considered a field which will always have the same value for both the Rabbit and the Fox, it would make sense to treat methods that use that field as identical. In this case that would be the methods: canBreed() and breed(). But in this case it would make more sense to keep them as separate methods, because it is most likely just a coincidence that the values are equal.

Exercise 10.23

A good way to test it would be to build a set of unit tests that tests the part of the program that is likely to be affected by the changes. This could be done by creating a JUnit test class for Fox and Rabbit.

After each small change to the program we should run the unit tests to see if the program still behaves as expected.

Running the program is also a good way to test this program, because it is easy to spot serious errors from the visual output of the program.

Exercise 10.24

See the project foxes-and-rabbits-v2 from the CD.

Exercise 10.25

We have avoided code duplication.

The classes Fox and Rabbit are smaller.

If we want to add new animals in the future, we already have some functionality available in the animal class.

Exercise 10.26

We can't treat the Animals as Objects because the object does not define the act() method that we need to use.

Exercise 10.27

Yes, a class must be declared abstract if it has abstract methods.

Exercise 10.28

Yes, a class can be declared as abstract even though it does not contain any abstract methods.

Exercise 10.29

If you want to prohibit instantiation of a class you could declare it abstract.

If you know that the class will never be instantiated, declaring it abstract would help other programmers in understanding the code

Exercise 10.30

AbstractCollection, AbstractSet, AbstractMap, AbstractList, AbstractSequentialList.

You can see that a class is abstract in the documentation. The first line below the heading says something like:

  public abstract class AbstractCollection 

To see which concrete classes extend them, take a look at the class diagram in the solution to exercise 8.14.

Exercise 10.31

Yes, you can see that a method is abstract in the API documentation. For instance, in the left column of the Method Summary.

We need to know this if we want to extend an abstract class with a concrete class. This tells us which methods that we as a minimum must implement.

Exercise 10.32

None of the other classes deals specifically with the Fox and Animal classes.

Exercise 10.33

Because we use abstract methods in the Animal class which are overridden in the two subclasses Rabbit and Fox. To understand this behaviour it is necessary to understand method overriding.

Exercise 10.34

Download: foxes-and-rabbits-10.34.zip

Exercise 10.35

The canBreed methods was not moved because they used different values of the static field BREEDING_AGE.

If the methods are moved to the Animal class it wont compile because it does not have access to the static field BREEDING_AGE. If we just want to make it compile, we can move the BREEDING_AGE from the Fox or Rabbit class to the Animal class. However, this will not allow us to set different breeding ages for foxes and rabbits. This is because fields can not be overridden.

To be able to set different breeding ages for the Rabbits and the Foxes we could instead create and abstract method in the Animal class called getBreedingAge(). In the canBreed() method we then call this method to obtain the breeding age. The getBreedingAge() method should then be overridden in Fox and Rabbit to return the correct value.

This is discussed in section 10.4.

Exercise 10.36

We need to put in the definition of the getBreedingAge in the Animal class:


 protected abstract int getBreedingAge(); 

An implementation of this can be downloaded in the next exercise.

Exercise 10.37

Download: foxes-and-rabbits-10.37.zip

Exercise 10.38

Yes, the breed() can be moved to the Animal class. We then also have to create methods to access the two static fields: BREEDING_PROBABILITY and MAX_LITTER_SIZE. Just as we did in the two previous exercises.

Exercise 10.40

The changes we have made to the Animal, Rabbit and Fox classes did not require us to modify other classes except the Simulator class (disregarding exercise 10.34 where we created the PopulationGenerator). This tells us that the original program had a low degree of coupling and good encapsulation.

Exercise 10.41

Download: foxes-and-rabbits-10.41.zip

Exercise 10.42

Need to add the import statement of java.util.List to the Actor class - but that is all.

Of course, we also need to specify that Animal extends Actor.

Furthermore, we should update the field to use Actors instead of Animals.

Exercise 10.43

Yes it compiles and runs.

Exercise 10.44

The fields are static and public fields . Interfaces only allow public static final fields.

Exercise 10.45

There are three errors in this program:

- The field THRESHOLD is declared private which is not allowed.
- It is not allowed to have constructors in interfaces.
- It has an implementation of getThreshold() - no implementations allowed in interfaces.

Exercise 10.46

Download: foxes-and-rabbits-10.46.zip

The number of hunters varies. This is because the newly born animals just get placed into the field without checking whether an actor already occupies that field.

Only a few changes is necessary in the other classes:
- A new method needs to be introduced in the Field class: getRandomLocation()
- The populate method needs to be updated to also create hunters.

Exercise 10.47

ArrayList:
                         ensureCapacity
                         removeRange
                         trimToSize
LinkedList:
                         addFirst
                         addLast
                         getFirst
                         getLast
                         removeFirst
                         removeLast

The reason that these methods are not defined in the List interface is that the methods are not common to all lists, but are specific for that type of list implementation.

Exercise 10.48

The following interfaces are mentioned:
- List
- Comparator
- Comparable

Exercise 10.49

There is an error in this exercise. It should have said Comparable instead of Comparator!

An implementation of a class implementing the Comparable interface:

/**
* A class representing some kind of coffee.
*/
public class Coffee implements Comparable
{
// The strength of the coffee
private int strength;
/**
* Create a new coffee with the given strength
*/
public Coffee(int strength)
{
this.strength = strength;
}
public int compareTo(Object o)
{
int otherStrength = ((Coffee) o).strength;
if (strength > otherStrength) {
return 1;
}
else if (strength < otherStrength) {
return -1;
}
else {
return 0;
}
}
public boolean equals(Object o)
{
if (o instanceof Coffee) {
return ((Coffee) o).strength == strength;
}
else {
return false;
}
}
public String toString()
{
return "" + strength;
}
}

And a class to test that it is sorted correctly:

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
public class Test
{
public void testComparable()
{
List coffees = new ArrayList();
coffees.add(new Coffee(10));
coffees.add(new Coffee(2));
coffees.add(new Coffee(10));
coffees.add(new Coffee(20));
coffees.add(new Coffee(5));
Collections.sort(coffees);
Iterator iter = coffees.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}

Exercise 10.50

Download: foxes-and-rabbits-10.50.zip

Exercise 10.51

Download: foxes-and-rabbits-10.51.zip

Exercise 10.52

In the program below, we have created a list of views that all get updated when required.

This has been done in a way that allows any number of views to be added. To avoid making a lot of changes to the Simulator class, we have created a new kind of view (named MultiView) that can delegate the method calls to several other views.

Download: foxes-and-rabbits-10.52.zip

Exercise 10.53

a)
Yes, an abstract class can have concrete methods. That is just the way it is - if it couldn't have concrete methods it would almost be the same as a Java interface.

b)
No, a concrete class can not have abstract methods. If it could, it would not be possible to instantiate an object of that class. What implementation should be run when you try to invoke an abstract method that is not implemented?

c)
Yes, you can have an abstract class without any abstract methods. See exercise 10.29.

Exercise 10.54

All the types could be interfaces. Because G and X both are super classes for U (legal: g=u,x=u), and do not have any relationship between them (illegal: g=x, x=g), at least one of G or X must be an interface.

Exercise 10.55

Several possible hierarchies could be created. This is one example:

Exercise 10.56

First it is good to create a test class that prints out the time it takes to run simulations of different lengths. This could look like this:

public class TimingTest
{
Simulator test = new Simulator();
public void runAllTests() {
timeTest(1);
timeTest(200);
timeTest(200);
timeTest(200);
timeTest(200);
timeTest(200);
timeTest(200);
timeTest(200);
}
public void timeTest(int steps) {
System.out.println(" Testing with " + steps + " steps...");
long t1 = System.currentTimeMillis();
test.simulate(steps);
long t2 = System.currentTimeMillis();
System.out.println(" total time: " + (t2-t1));
System.out.println(" average step time: " + (t2-t1)/steps);
}
}

This outputs:

  Testing with 1 steps...
total time: 435
average step time: 435
Testing with 200 steps...
total time: 30504
average step time: 152
Testing with 200 steps...
total time: 33025
average step time: 165
Testing with 200 steps...
total time: 38070
average step time: 190
Testing with 200 steps...
total time: 43603
average step time: 218
Testing with 200 steps...
total time: 51817
average step time: 259
Testing with 200 steps...
total time: 58078
average step time: 290
Testing with 200 steps...
total time: 63480
average step time: 317

This indicates that something is not as it should be. One possible source of the problem could be that objects are created and not destroyed again. This would fill up the memory and might lead to increased computation time.

To find the source of the problem we should read through the code and watch for creation of objects and make sure that they are removed when no longer needed. An object is removed (or at least a candidate for removal) when there is no reference to it.

We start by looking at the simulate(int steps) method. This method doesn't create any objects but it calls the method simulateOneStep() so we take a look at that method.

simulateOneStep does not create any objects, but it does do some suspicious stuff with the two collections it is using. After it has run through all the act() methods of the objects in the animal list it adds all the newAnimals to the animals, but it never removes the dead animals. This might be done someplace else, so we should check if this is actually the source of the problem. This can be done by inserting a print statement after the line that adds all the newAnimals to the animals list. This print statement should print out the size of the animal list like this:

System.out.println(animals.size());
                    

Running the simulation for 100 steps prints out a range of numbers in increasing order. The first number is around 500 and then it increases until it ends at around 30000. This means that there should be 30000 animals in the simulation! But the field is only 50x50 which equals 2500 locations. So 30000 is a bit too many.

To fix the problem we should remove the dead animals from the list. The first time we know an animal is dead, is in the setDead() method in the Rabbit or Fox class. But these classes know nothing about our list of animals.

The only class that knows about the animal list is the Simulator class. So this is where it should be fixed. We want to clean it up in each step so it must be in simulateOneStep() that we fix it. If we check if animal is dead before calling the act() method on each animal we could also avoid the isAlive() check in the act method of both Rabbit and Fox.

The improved loop in simulateOneStep:

        // let all animals act
for(Iterator iter = animals.iterator(); iter.hasNext(); ) {
Animal animal = (Animal)iter.next();
if(animal.isAlive()) {
animal.act(field, updatedField, newAnimals);
}
else {
iter.remove();
}
}

Running the application again and watching the numbers printed to the console shows that it has improved. Before the size of the list after 100 steps was around 30000 and now it is down below 3000 and it doesn't only increase - sometimes it decreases as well.

(Actually we notice another error here, because it sometimes goes above 2500 items in the list, which shouldn't be possible with only 2500 locations. This is because newly born animals can be placed on top of other animals.)

We might have fixed the bug now. But to be sure, we should run the timing test we did in the beginning. The new results are:

  Testing with 1 steps...
total time: 161
average step time: 161
Testing with 200 steps...
total time: 24309
average step time: 121
Testing with 200 steps...
total time: 22751
average step time: 113
Testing with 200 steps...
total time: 22898
average step time: 114
Testing with 200 steps...
total time: 22670
average step time: 113
Testing with 200 steps...
total time: 22392
average step time: 111
Testing with 200 steps...
total time: 20970
average step time: 104

This looks much better - the bug is squashed!

 

Exercise 10.57

The reason for the Adapter classes are only convenience for the programmer. If the programmer knows that he/she is actually only going to use a few of the methods from interface it is still necessary to write an implementation for all the methods in the interface. If the Adapter class is used instead, the programmer can just override the few methods that are needed.

A common use of adapter classes is for instance the MouseAdapter. The MouseListener interface contains 5 methods to listen for mouse events. If you only want to listen for events when the mouse is clicked you would have to write something like this with the interface:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; public class ClickPrinter implements MouseListener
{
public void mouseClicked(MouseEvent e)
{
System.out.println("Mouse clicked");
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
}

And with the MouseAdapter it would look like this:

import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
public class Main extends MouseAdapter
{
public void mouseClicked(MouseEvent e)
{
System.out.println("Mouse clicked");
}
}

 

Exercise 10.58

We need to give the TreeSet someway of knowing how to sort the elements. This can be done in two ways. Either we let Person implement the Comparable interface or we create a new class which extends Comparator and knows how to compare Persons. In exercise 10.49 we did something similar where we used the Comparable interface. So we will try it out with the Comparator this time.

The Person class:

public class Person
{
private int age;
public Person(int age)
{
this.age = age;
}
public int getAge() {
return age;
}
public boolean equals(Object other)
{
if(other instanceof Person) {
return this.age == ((Person) other).age;
}
else {
return false;
}
}
public String toString()
{
return "" + age;
}
}

The PersonComparator:

import java.util.Comparator;
public class PersonComparator implements Comparator
{
public int compare(Object o1, Object o2) {
Person p1 = (Person) o1;
Person p2 = (Person) o2;
if(p1.getAge() < p2.getAge()) {
return -1;
}
else if(p1.getAge() == p2.getAge()) {
return 0;
}
else { //(p1.getAge() > p2.getAge())
return 1;
}
}
}

A test class:

import java.util.Set;
import java.util.TreeSet;
import java.util.Iterator;
public class Test
{
public static void runTest() {
Set persons = new TreeSet(new PersonComparator());
persons.add(new Person(32));
persons.add(new Person(17));
persons.add(new Person(13));
persons.add(new Person(35));
persons.add(new Person(27));
Iterator iter = persons.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}