3. Code Style
We will be using the Google Java Style guide. From now on all code that you write for this class must follow the Google Java Style guide.
3.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.
-
Download the saved configuration file for IntelliJ intellij-java-google-style.xml
-
Right click on the link in the preceding line and select
Save Link As…
-
-
Open Preferences.
-
On Windows window to File → Settings on the main menu.
-
On Mac go to IntelliJ → Preferences on the main menu.
-
-
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. -
In the
Code Style Schemes
option window click the Import button, that will open theImport Form Selection
window. -
In the
Import Form Selection
window selectIntelliJIDEA code style XML
and a file explorer window will pop up for you to select the file you wish to import. -
Select the file you just downloaded named
intellij-java-google-style.xml
. This is the file from step 1.
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. Read the IntelliJ documentation on code formatting for more options.
For Eclipse users these instructions might work.
4. Equality in Java
Java provides two mechanisms for checking equality between values.
-
==
, the double equals is used to check-
equality between primitive types
-
"memory equality", check whether the two references point to the same object in memory
-
-
equals()
is a method defined in the classObject
that is inherited to all classes. The JVM expects developers to override theequals()
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.
-
reflexive - for a non-null reference value
x
,x.equals(x)
returnstrue
-
symmetric - for non-null reference values
x
andy
,x.equals(y)
returnstrue
if and only ify.equals(x)
returnstrue
-
transitive - for non-null reference values
x
,y
, andz
,-
if
x.equals(y)
returnstrue
and -
y.equals(z)
returnstrue
, then -
x.equals(z)
must returntrue
-
-
consistent - for non-null references
x
,y
, multiple invocations ofx.equals(y)
should return the same result provided the data insidex
andy
has not been altered. [1] -
for any non-null reference value
x
x.equals(null)
returnsfalse
-
the code needs to override
hashCode
as well in order to uphold thehashCode()
method’s contract. The contract forhashCode()
is spelled out inhashCode()
's documentation.
Here are the conditions for the hashCode()
method’s contract
-
for a non-null reference value
x
multiple invocations ofx.hashCode()
must return the same value provided the data insidex
has not been altered. [1] -
for any two non-null reference values
x
,y
-
if
x.equals(y)
returnstrue
then -
x.hashCode()
andy.hashCode()
must return the same result
-
-
for any two non-null reference values
x
,y
-
if
x.equals(y)
returnsfalse
then -
it is prefered but not required that
x.hashCode()
andy.hashCode()
return different/distinct results.
-
Let’s look at an example using our Posn
class from the lecture notes.
/**
* Represents 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 use each field and get its hash code, we then compose all field hash codes together along with a prime number to get this
object’s 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 change 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 hash code by calling hashCode() on that object. |
8 | repeat to get the hash code for y , add the field hash code’s together and multiply by 31 . [3] |
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.
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() .
|
For the rest of the lab we will use the code for our Shape
example from our lecture with all signatures in Shape
commented out.
5. Itemizations
For the rest of this lab we will be working with the shape example that we discussed in our last lecture
The clients of our shape library would like the following extensions.
-
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 inclusive, 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. -
All shapes must be able to return their RGB code when called to do so.
-
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.
-
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.
6. 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
-
the location of the pin,
-
the triangle’s base and
-
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
-
it’s base
-
it’s height
-
it’s pin
-
it’s hypotenuse
We also expect the right angle triangle to behave correctly for each shape operation that we have already discussed and are implemented for the existing shapes (i.e., circle, rectangle and square).
7. Adding more behaviour
The clients would like to replace one shape for another if the shape to be replaced and the existing shape have the same area.
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.