19-Soft-B17 Data Visualization · December 2014
Nivaar worked solution (AI-drafted; not reviewed by a licensed engineer)
Question text not reproduced: the examination questions are © Engineers and Geoscientists BC. Open the official past paper (linked at the top of this page) to read the question, then follow the worked solution below.
Taking Java as the example language: every object inherits a default equals(Object o) method from the root Object class, and by default that method is defined as this == o — that is, reference (identity) equality. Two distinct objects that hold exactly the same field values are, by default, considered unequal unless the class author overrides equals() to compare state instead of identity. This mirrors the general OO default: without programmer intervention, "equal" means "the same object in memory," not "objects that look the same." A class must explicitly opt into value (structural) equality by overriding equals() to compare the objects' relevant fields, and (in Java specifically) must also override the paired hashCode() method so that equal objects still produce equal hash codes — otherwise the object silently breaks when used as a key in a hash-based collection such as HashMap or HashSet, since those collections consult hashCode() to find the bucket before ever calling equals().
Deciding what "equal" should mean for a class requires several considerations. First, which fields participate: transient or purely derived state (a cached computation, an internal lock object) usually should not affect equality, while fields that define the object's identity/value should. Second, shallow vs. deep comparison: if a field is itself a reference type (e.g., a list or another object), the equality method must decide whether two containers are equal only when they hold the identical inner objects (shallow) or when their inner objects are themselves equal by value (deep) — getting this wrong is a common source of surprising bugs when equality is used with mutable collections. Third, mutability: if a field used in equals() can change after the object has been placed in a hash-based collection, the object's hash bucket becomes stale and lookups silently fail; classes intended for use as hash keys are therefore usually made immutable (or exclude mutable fields from equality). Fourth, whether equality should be type-strict (an instance of a subclass can never equal an instance of the base class, even with identical fields) or based on a shared interface — type-strict is the usual safe default because it avoids violating the symmetry property below when a subclass adds fields.
A correct equality method must satisfy the standard mathematical properties of an equivalence relation, which the Java contract states explicitly: reflexive (x.equals(x) is always true), symmetric (x.equals(y) iff y.equals(x)), transitive (if x.equals(y) and y.equals(z) then x.equals(z)), consistent (repeated calls return the same result provided no relevant field changes), and non-null (x.equals(null) must return false rather than throw). A common way symmetry is accidentally violated is a subclass that overrides equals() to add its own fields but still returns true when compared against a base-class instance that lacks those fields — the base instance's own (unmodified) equals() would then disagree with the subclass's, breaking the relation.