In the previous lesson you learnt about the Object class:


Class to model a zookeeper

The zoo will of course require a number of zookeepers to help look after the animals, so you will now develop a ZooKeeper class. Just as you did with the Animal class, you need to think about the attributes and methods that each zookeeper object will require, and you can assume that this results in the following list:

Attributes

For each zookeeper you need to store their name, their address, their email address, and their annual salary.

Methods

For each zookeeper you need methods that get and set their name, home address, email and salary. You will also override the toString() method to provide a user-friendly summary of this state.

Using NetBeans, create a new class in the virtualzoo.core package called ZooKeeper, and declare its instance variables: 

package virtualzoo.core;

public class ZooKeeper {
    
    private String name;
    private String address;
    private String email;
    private double salary;
    
}
 

As shown above, the String class has been specified as the type to use for name, address and email, and the primitive double type for the salary (on the assumption that a salary could potentially include decimal places). But are these the best types to use?

You may recall from a previous section that double is not the best choice for storing or doing calculations for monetary values. The problem can be demonstrated with the code below:

// Demonstrate problem with floating-point arithmetic
double valueA = 2.0;					// statement 1
double valueB = 1.1;					// statement 2
double difference = valueA – valueB;			// statement 3
System.out.println("difference = " + difference);	// statement 4
 
  • Statement 1 creates a double variable named valueA containing value 2.0
  • Statement 2 creates a double variable named valueB containing value 1.1
  • Statement 3 subtracts valueB from valueA and stores the answer in a variable named difference
  • Statement 4 sends the difference variable value to the Output window

Instead of resulting in an answer of 0.9, you will see 0.8999999999999999. This is not a bug with Java. It is instead an artefact of the way the double type is implemented as a floating-point value, and the same issue exists with the float primitive type.

The solution is to store financial value fields as type BigDecimal. This is a Java supplied class that exists within the package java.math, and it allows you to specify the exact number of decimal places required and what rounding rules should apply. A short demonstration follows (you will need to import java.math.* if you want to try this):

// Calculation with BigDecimal
BigDecimal valueX = new BigDecimal("2.0");	 // statement 1
BigDecimal valueY = new BigDecimal("1.1");	 // statement 2
BigDecimal result = valueX.subtract(valueY);	 // statement 3
System.out.println("Difference = " + result);  // statement 4
 
  • Statement 1 instantiates a BigDecimal object called valueX with the String[1] value 2.0. The String is converted internally to a numeric value
  • Statement 2 instantiates a BigDecimal object called valueY with the String value 1.1
  • Statement 3 performs the subtraction and stores the result in a new BigDecimal object called result
  • Statement 4 sends the result reference to the Output window

The setScale() method can be used to specify the number of decimal places and the rounding rule to apply.

[1]It is also possible to pass a double to the BigDecimal constructor, but this is not recommended since it may result in similar problems as previously demonstrated. When you need to specify a specific value you should pass it as a String.
// Using BigDecimal with setScale()
BigDecimal valueZ = new BigDecimal("123.456");
valueZ = valueZ.setScale(2, RoundingMode.HALF_UP);
System.out.println("valueZ = " + valueZ);
 
  • The first argument passed to setScale() specifies the number of decimal places required and the second argument the rounding rule to apply. RoundingMode.HALF_UP means round to the nearest neighbour unless it is exactly halfway, in which case it will round up. This is generally the most suitable mode to use for financial calculations, although the alternative RoundingMode.HALF_EVEN rounds toward the even neighbour when exactly half way may be preferred in some cases[1]
  • Note that BigDecimal is, like String, an immutable class, and therefore the setScale() method returns a new BigDecimal object rather than modifying its own state. This is why its returned reference is assigned back to the variable valueZ

The output from the above should be:

[1]This is known as "Bankers rounding".
valueZ = 123.46 

Make sure that package java.math.* is imported in the class ZooKeeper and then change the instance variable type for salary:

package virtualzoo.core;

import java.math.*;

public class ZooKeeper {
    
    private String name;
    private String address;
    private String email;
    private BigDecimal salary;
    
}
 

NetBeans contains a useful facility to assist with imports. Delete the above import statement and notice that an error glyph appears to the left of the private BigDecimal salary; statement. If you hover over this it will state that it cannot find the the BigDecimal symbol, so click with your mouse and select from the resulting pop-up the option to Add import for java.math.BigDecimal. The import statement will be added for you. An alternative approach, especially useful if you have several such imports needed, is to right-click somewhere in the source code editor and select Fix Imports from the pop-up menu. You will note, however, that it imports the specific class rather than using the wild card import.

You will now define a constructor that requires arguments for all four attributes:

package virtualzoo.core;

import java.math.BigDecimal;

public class ZooKeeper {
    
    private String name;
    private String address;
    private String email;
    private BigDecimal salary;

    public ZooKeeper(String myName, String myAddress, String myEmail,
                                    BigDecimal mySalary) {
        name = myName;
        address = myAddress;
        email = myEmail;
        salary = mySalary;
    }

}
 

You need to create setter and getter methods for each of these attributes:

package virtualzoo.core;

import java.math.BigDecimal;

public class ZooKeeper {
    
    private String name;
    private String address;
    private String email;
    private BigDecimal salary;

    public ZooKeeper(String myName, String myAddress, String myEmail,
                                    BigDecimal mySalary) {
        name = myName;
        address = myAddress;
        email = myEmail;
        salary = mySalary;
    }

    public String getName() {
        return name;
    }

    public void setName(String myName) {
        name = myName;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String myAddress) {
        address = myAddress;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String myEmail) {
        email = myEmail;
    }

    public BigDecimal getSalary() {
        return salary;
    }

    public void setSalary(BigDecimal mySalary) {
        salary = mySalary;
    }
    
}
 

And finally override the toString() method to return the zoo keeper's name:

… rest of code omitted …

@Override
public String toString() {
    return getName();
}
 

You can test the toString() method (and the constructor) by specifying the following statements in VirtualZoo:

ZooKeeper alice = new ZooKeeper("Alice Smith", "Some City",
                "alice@example.com", new BigDecimal("20000"));
System.out.println(alice);
 

Note that for the fourth argument (the salary) a new BigDecimal object is instantiated itself using a String argument. You must create a BigDecimal object because that is the type required on the signature of the ZooKeeper constructor. Running the above should result in the following output:

Alice Smith 

In the next lesson you will learn about the keywords this and super.

Next lesson: 4.4 Keywords this and super