1. Summary

  • Code Style and IDE configuration

  • Java Equality

  • Itemizations

2. Code Style

We will be using the Google Java Style giude. From now on all code that you write for this class must follow the Google Java Style guide.

2.1. Configuring your IDE

Thankfully, there are saved IDE configurations that enforce most but not all coding rules specified in the Google Java Style guide.

  1. Download the saved configuration file for IntelliJ intellij-java-google-style.xml

  2. Open Preferences.

    1. On Windows go to File → Settings on the main menu.

    2. On Mac go to IntelliJ → Preferences on the main menu.

  3. Navigate to Editor → Code Style. Select Code Style but do not expand the item.. In the right hand side pane at the top you will see a button with the title Manage. Click it to open the Code Style Schemes option window.

  4. In the Code Style Schemes option window click the Import button that will open the Import Form selection widnow.

  5. In the Import Form Selection window select IntelliJIDEA code style XML and a file explorer window will for you to select the file you wish to import.

  6. Select the file you just downloaded named intellij-java-google-style.xml

IntelliJ will try and format your code while you type. However, when editing a file at different locations in the file IntelliJ might not indent properly.

To force IntelliJ to re-indent all your code select Code → Reformat Code from the main IntelliJ menu.

For Eclipse users these instructions might work.

3. Equality in Java

Java provides two mechanisms for checking values for equality.

  1. ==, the double equals is used to check

    1. equality between primitive types

    2. "memory equality", check whether the two references point to the same object in memory

  2. equals() is a method defined in the class Object that is inherited to all classes. The JVM expects developers to override the equals() method in order to define the notion of equality between objects of classes that they define.

Overriding the equals() method however imposes extra restrictions. These restrictions are spelled out in the equals() method documentation.

  1. reflexive - for a non-null reference value x, x.equals(x) returns true

  2. symmetric - for non-null reference values x and y, x.equals(y) returns true if and only if y.equals(x) returns true

  3. transitive - for non-null reference values x, y, and z,

    1. if x.equals(y) returns true and

    2. y.equals(z) returns true, then

    3. x.equals(z) must return true

  4. consistent - for non-null references x, y, multiple invocations of x.equals(y) should return the same result provided the data inside x and y has not been altered. [1]

  5. for any non-null reference value x x.equals(null) returns false

  6. the code needs to override hashCode as well in order to uphold the hashCode() method’s contract. The contract for hashCode() is spelled out in hashCode() 's documentation.

Here are the conditions for the hashCode() method’s contract

  1. for a non-null reference value x multiple invocations of x.hashCode() must return the same value provided the data inside x has not been altered. [1]

  2. for any two non-null reference values x, y

    1. if x.equals(y) returns true then

    2. x.hashCode() and y.hashCode() must return the same result

  3. for any two non-null reference values x, y

    1. if x.equals(y) returns false then

    2. x.hashCode() and y.hashCode() must return different/distinct results.

Let’s look at an example using our Posn class from the lecture notes.

Posn.java
/**
 * Repersents a cartesian coordinate.
 *
 */
public class Posn {

    private Integer x;
    private Integer y;

    public Posn(Integer x, Integer y) {
        this.x = x;
        this.y = y;
    }

    /**
     * Getter for property 'x'.
     *
     * @return Value for property 'x'.
     */
    public Integer getX() {
        return this.x;
    }

    /**
     * Getter for property 'y'.
     *
     * @return Value for property 'y'.
     */
    public Integer getY() {
        return this.y;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {  (1)
        if (this == o) return true;    (2)
        if (o == null || getClass() != o.getClass()) return false; (3)

        Posn posn = (Posn) o; (4)

        if (this.x != null ? !this.x.equals(posn.x) : posn.x != null) return false; (5)
        return this.y != null ? this.y.equals(posn.y) : posn.y == null; (6)

    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        int result = this.x != null ? this.x.hashCode() : 0;  (7)
        result = 31 * result + (this.y != null ? this.y.hashCode() : 0); (8)
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "Posn{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

The strategy used to check for equality is recursive; we check each field in turn and since a field can be a reference type, we need to call its equals() method. There are many different implementations of equals(), we will go over this specific one here so that we have one example of how we could write an equals() method.

The strategy used for the implementation of hashCode() is also recursive; we check each field and gets its hash code, we then compose all field hash codes together along with a prime number to get this objects hash code.

1 Observe that the compile-time type for the argument o is Object not Posn.
2 We first check whether the argument passed is in fact the same exact object in memory as this object using ==.
3 We then check that if the argument o is null or the runtime-type of o [2] is not the same as the runtime-type of this then the two values cannot be equal.
4 If the runtime-types are the same then we cast--force the compile-time type of a variable to a new compile-time type-- o to be a Posn and give it a new name posn.
5 Now we check each field for equality, first x.
6 and then y. The ?: is the Java if-expression; an if statement that returns a value. The test is the code before ?, if that expression returns true then we evaluate the expression found between ? and :. If the test expression returns false then we evaluate the expression found after the :.
7 If a field, x in this case, is null then return 0 else grab its hashCode.
8 repeat to get the hash code for y, add the field hashCode s together and multiply by 31. [3]
Practise Exercises
  • 1) Create a test class PosnTest and write tests for getX() and getY()

  • 2) In your test class PosnTest add tests for equals()

    • Make sure your tests validate the conditions set forth by the contract for equals() and hashCode()

Your IDE has the ability to automatically generate default implementations for equals() and hashCode(). The default implementations generated by your IDE are typically what you need. However, sometimes we will have to amend/write our own.

NOTE

Along with equals() and hashCode() methods, Java also defines a method on class Object called toString() that returns the representation of your object as a Java String.

To print the string representation of an object to the Console, or any other string value, pass an object as an argument to System.out.println(). Java automatically calls toString() on reference values passed to System.out.println(), e.g. inside the body of a test method in PosnTest writing

    System.out.println(new Posn(1,2));

prints

Posn{x=1, y=2}

in the Console window.

We will allow you to

  • generate equals(), hashCode() and toString() using your IDE when you deem it is appropriate

  • use

    • null

    • casting

    • primitive types

only for your implementations of equals() and hashCode() for now.

JUnit4 relies on equals() and hashCode() in your reference types for Assert.assertEquals(). The implementation of Assert.assertEquals() essentially calls equals() on your objects.

Once your classes properly override equals() and hashCode(), you can now use Assert.assertEquals() in your tests and pass reference types that you defined. You are no longer limited to only use JDK defined reference types in your Assert.assertEquals().
Practise Exercises
  • 3) Using the Shape example that we covered in our last lecture, add equals() and hashCode() methods to all concrete classes (i.e., non-abstract classes).

  • 4) Write tests for each of the concrete classes in Shape. Make sure you write methods for each method that is defined in your class whether it is a new method or an overriding method like equals().

4. Itemizations

For the rest of this lab we will be working with the shape example that we discussed in our last lecture

Practise Exercises
  • 5) Provide implementations and tests for your implementations for moveX() and moveY() methods.

The clients of our shape library would like the following extensions.

  1. All shapes should also have a color. The color is going to be given as an RGB number (8-bit notation). The RGB notation that we would like to use is made of of 3 numbers each number ranges from 0 to 255, e.g. The color white is represented as (255,255,255) the color black is represented as (0,0,0). You can use this website to play around with RGB codes and colors.

  2. All shapes must be able to return their RGB code when called to do so.

  3. Given a shape with an existing color, we should be able to provide a new color and obtain a new shape identical to the original but with its color updated to our new color.

  4. All shapes should also support a shade mode that is one of two states, shaded or unshaded. We should be able to get the current shade mode of a shape as well alter the mode in the same manner as in the case of color.

Practise Exercises
  • 6) Implement the preceding requirement. Make sure that you update any classes needed and add tests for each new feature added.

  • 7) Look closely at the implementation of moveX(Integer dx) and moveY(Integer dy) and see if there is duplicated code that could possibly be abstracted. If so make the appropriate code changes and ensure that your tests still succeed.

5. Adding new items in an Itemization

The clients are now requesting that our shape library deals with right angle triangles. The pin on a right angle triangle is on the corner of the triangle that has the right-angle. To create a triangle we will need to pass

  1. the location of the pin,

  2. the triangle’s base and

  3. the triangle’s height

We would like the library to calculate the hypotenuse of the right-angle triangle. Given a right angle triangle we should be able to obtain

  1. it’s base

  2. it’s height

  3. it’s pin

  4. it’s hypotenuse

We also expect the right angle triangle to behave correctly for each shape operation that we have already discussed and is implemented for the existing shapes (i.e., circle, rectangle and square).

Practise Exercises
  • 8) Provide a UML Class Diagram for your design of the preceding requirements.

  • 9) Implement the preceding requirements. Make sure that you update any classes needed and add tests for each new feature added.

6. Adding more behaviour

The clients would like to replace one shape for another if the shape to be replaced takes up the same area as the shape to be replaced with.

They would also like to be able to test if the area of one shape is greater than the area of another shape, as well as, test if the area of one shape is less than the area of another shape.

Practise Exercises
  • 10) Provide a UML Class Diagram for your design of the preceding requirements.

  • 11) Implement the preceding requirements. Make sure that you update any classes needed and add tests for each new feature added.


1. This condition will be more relevant once we cover mutation later in the semester.
2. getClass() is defined on all reference types and returns the value’s runtime-type.
3. It is impossible to have a computer generate a perfect hash function. Our hashing algorithm here can have collisions. The best we can do is to decrease the probability of a collision.