11. Testing; equals()
equals()
Every class has an equals()
method. Default: same as ==
. Many classes override equals() to compare the content of two objects.
In the following example, "i1 == i2" is false, but "i1.equals(i2)" is true. "i2 == i3" and "i2.equals(i3)" are both true.
--- ------- --- ------- ---
i1 |.+--->| 7 | i2 |.+--->| 7 |<---+.| i3
--- ------- --- ------- ---
IMPORTANT: r1.equals(r2) throws a run-time exception if r1 is null.
There are at least four different degrees of equality.
- Reference equality,
==
. - Shallow structural equality: two objects are "equals" if all their fields are
==
. For example, two SLists whose "size" fields are equal and whose "head" fields point to the same SListNode. - Deep structural equality: two objects are "equals" if all their fields are "equals". For example, two SLists that represent the same sequence of items (though the SListNodes may be different).
- Logical equality. Two examples:
- Two "Set" objects are "equals" if they contain the same elements, even if the underlying lists store the elements in different orders.
- The Fractions 1/3 and 2/6 are "equals", even though their numerators and denominators are all different.
Any of these four levels can be implemented depending on what seems appropriate. Let's write an equals() method for SLists that tests for deep structural equality.
public class SList {
public boolean equals(Object other){ // why don't just use `SList other`?
if (!(other instanceof SList)) {
return false;
}
SList o = (SList) other;
if (size != o.size) {
return false;
}
SListNode n1 = head;
SListNode n2 = o.head;
while (n1 != null){
if (!n1.item.equals(n2.item)) {
return false;
}
n1 = n1.next;
n2 = n2.next;
}
return true;
}
}
Here is why.
IMPORTANT: Overriding DOESN'T WORK if we change the signature of the original method, even just to change a parameter to a subclass. In the Object class, the signature is equals(Object), so in the code above, we must declare "other" to be an Object too. If we declare "other" to be an SList, the equals() method will compile but it will NOT override. That means the code
Object s = new SList();
s.equals(s);
will call Object.equals(), not SList.equals(). Dynamic method lookup won't care that s is an SList, because the equals() method above is not eligible to override Object.equals().
Therefore, if you want to override a method, make sure the signature is EXACTLY the same.
for each
loops
int[] array = {7, 12, 3, 8, 4, 9};
for (int i : array) { // i = 7 -> 12 -> 3 -> ...
System.out.print(i + " ");
}
Note that the type declaration must be in the for
statement.
TESTING
Write test code. Three types of testing:
- Modular testing.
- Integration testing.
- Result verification.
(1) Modular Testing
Testing each method and each class separately.
- Test drivers: call the code, check results.
- stubs: bits of code called by the code being tested.
(2) Integration Testing
Testing a set of methods/classes together.
- Define interface well.
- Learn to use a debugger.
(3) Result Verification
Testing results for correctness, and testing data structures to ensure they still satisfy their invariants.
- Data structure integrity checkers.
- Algorithm result checkers.
assertion: code that tests an invariant or a result.
assert x == 3;
assert list.size == list.countLength(): "wrong SList size: "+list.size;
Turn them on: java -ea
Turn them off: java -da
(greater speed, will not call assertions.)