Lab 4
Lab 4 Description
Understanding Equality
Note: This material is covered in pages 321 - 330 in the textbook. Read it carefully at home.
Our object is to define a method that will determine whether a given Account is the same as this account. We may need such a method to find a desired account within the Bank database that ahs a list of all accounts.
Of course, now that we have the abstract class it would be easy to compare just account number and the name on the account, but we want to make sure that all the customer’s data matches what we have on file exactly, including balances, interest rates, etc.
The starter files:
The code that shows why using instanceof fails ExamplesBadInstanceOf.java.
The code that illustrates the implementation of equality comparison using safe casting ExamplesCoffeeSafeCasting.java.
A more extensive example that illustrates the implementation of equality comparison using safe casting ExamplesShapesSafeCasts.java.
The code that illustrates the implementation of equality comparison using double dispatch ExamplesDoubleDispatch.java.
A more extensive example that illustrates the implementation of equality comparison using double dispatch ExamplesShapesDoubleDispatch.java.
Why instanceof does not work
Read the code in the file ExamplesBadInstanceOf.java. Examine the examples (run the code when you have the chance to do so) and make sure you understand why this method fails.
There is a more detailed discussion of this in the second edition of Joshua Bloch’s Effective Java.
You may also want to read the explanation of the subtleties of defining equality in Java by Martin Odersky, Lex Spoon, and Bill Venners at How to Write an Equality Method in Java
Safe Casting
We will design a method boolean same(Acct that) using the technique Equality by safe casting. The code in the files ExamplesCoffeeSafeCasting.java and ExamplesShapesSafeCasting.java illustrates this technique.
Note: we do not mention the method equals until the students are ready to also understand and implement the hashCode method.
Begin by designing the method same for the Acct classes.
Make examples that compare all kinds of accounts: of the same kind (e.g., Checking vs. Checking) and of different kinds (e.g., Savings vs. Checking). For the accounts of the same kind you should test both true and false cases. Comparing different kinds of accounts should always produce false.
Now that you have sufficient examples, follow with the design of the same method in one of the concrete account classes (for example the Checking class). Write the template and think of what data and methods are available.
You will need two different helper methods: one that determines whether the given account is a Checking or Savings account, and one that converts this account into the desired type. Design the methods isChecking, and isSavings that determine whether this account is a checking or savings account, respectively.
Design the methods toChecking and toSavings that convert this account into a checking/savings account, respectively. Header and purpose for the checking account case:
// Convert this Account into a Checking |
abstract Checking toChecking(); |
In the Checking class the body will be just |
|
// Produce a checking account from this account |
Checking toChecking(){ |
return this; |
} |
while the others should throw a RuntimeException with the appropriate message.
Now we can define the body of the same method in the Checking class:
// Is the given Account the same as this? |
boolean same(Account that){ |
if(that.isChecking()){ |
return this.sameChecking(that.toChecking()); |
}else{ |
return false; |
} |
} |
We still need the method sameChecking but this only needs to be defined within Checking and can be defined with private (or protected) visibility.
Complete the design of the same methods (including sameChecking and sameSavings) for the other classes. Then add the class PrimeChecking that extends Checking and make sure all test (and the new ones) pass.
Double dispatch
Here is another version that also works. The code in the files ExamplesDoubleDispatch.java and ExamplesShapesDoubleDispatch.java illustrates this technique.
It requires us to add a new method to the abstract class for each subclass of Acct.
For our IShape hierarchy, the methods were:
// Is that Circle the same as this Shape? |
public boolean sameCirc(Circ that); |
|
// Is that Rect the same as this Shape? |
public boolean sameRect(Rect that); |
|
// Is that Combo the same as this Shape? |
public boolean sameCombo(Combo that); |
This technique is popular in object-oriented languages like Java and is known as double dispatch.
Study the examples, and implement yoour own version for the Acct class and its subclasses. Then add the class PrimeChecking that extends Checking and make sure all test (and the new ones) pass.
Instanceof flaw eplained
The method same can be incorrectly written with two features of the Java language: the instanceof operator and casting. In the Checking class this style method would look like the following:
// Is the given Account the same as this Checking? |
boolean same(Account that){ |
if(that instanceof Checking){ |
return this.sameChecking((Checking)that); |
}else{ |
return false; |
} |
} |
However, this version introduces bugs!
The issue is that any class that later extends Checking, say a PremiumChecking class, will also be considered a Checking instance by the instanceof operator. (The correct name for this operator should be can-be-cast-to.
If we implement a similar same method in PremiumChecking:
// Is the given Account the same as this PremiumChecking? |
boolean same(Account that){ |
if(that instanceof PremiumChecking){ |
return this.samePremChecking((PremiumChecking)that); |
}else{ |
return false; |
} |
} |
Now only PremiumChecking objects can be the same as other PremiumChecking instances.
But Checking instances can be the same as PremiumChecking instances!!
These kinds of bugs can cause serious problems. This issue is also illustrated in the example file ExamplesBadInstanceOf.java.