CSE 331: Software Design and Implementation, Autumn 2026
Your job is to deliver code that you know works.
24HelpQueue q1 = new FasterQueue();
25q1.join("ada");
26q1.join("bob");
27q1.join("eve");
28q1.next();
29
30// A LinkedQueue: bob and eve join.
31HelpQueue q2 = new LinkedQueue();
32q2.join("bob");
33q2.join("eve");35// Who is waiting in each?
36System.out.println("q1.waiting(): " + q1.waiting());
37System.out.println("q2.waiting(): " + q2.waiting());
38
39// Are the two queues the same?
40System.out.println("q1 == q2: " + (q1 == q2));
41System.out.println("q1.equals(q2): " + q1.equals(q2));q1 is a FasterQueue, q2 a LinkedQueue: both hold bob, eve$ java Queues q1.waiting(): [bob, eve] q2.waiting(): [bob, eve] q1 == q2: false q1.equals(q2): false
==: two new expressions, two objects. Not our questionequals printed false too. What did it compare?equals comparedpublic boolean equals(Object obj) {
return (this == obj);
}HelpQueue specifies no equals; neither class declares oneObject's, the class every class extendsObject's equals is ==: the one check Object can makeequals should mean for a help queueDefinition: equality for an abstract data type
Two objects of an abstract data type are equal when they stand for the
same abstract value.
equals is the method that decides it. Like every method of the type, its
specification is about the abstract state, the value an object stands for, and
its body works on the fields.
Object cannot know what a class stands for== is its equalsObject's specification of equalsx.equals(x) is truex.equals(y) if and only if y.equals(x)x.equals(y) and y.equals(z) give x.equals(z)equals uses changesx.equals(null) is falseObject: every equals has to keep these= on numbersequals asks for the same abstract valueDuration: one class, one number66public static void main(String[] args) {
67 Duration d1 = new Duration(1, 39);
68 Duration d2 = new Duration(0, 99);
69 Object o = d2;
70 Set<Duration> set = new HashSet<Duration>();
71 set.add(d1);
72 System.out.println("d1 == d2: " + (d1 == d2));
73 System.out.println("d1.equals(d2): " + d1.equals(d2));
74 System.out.println("d1.equals(o): " + d1.equals(o));
75 System.out.println("set.contains(d2): " + set.contains(d2));
76}equals yet$ java Duration d1 == d2: false d1.equals(d2): false d1.equals(o): false set.contains(d2): false
== and Object's equals: one object? No, twoo is d2 under a second name: the same questionHashSet decides with equals, and with hashCode, later/** @return whether d is the same length as this */
public boolean equals(Duration d) {
return seconds() == d.seconds();
}$ java Duration d1 == d2: false d1.equals(d2): true d1.equals(o): false set.contains(d2): false
d1.equals(d2) says true: lengths comparedo is d2, and d1.equals(o) says false!d2. The third line is the clue$ java Duration d1 == d2: false d1.equals(d2): true d1.equals(o): false set.contains(d2): false
equals(Duration d), and no other equals declaredd1.equals(d2) printed true, and d1.equals(o) printed falseo is a second name for d2, declared an Objectequals ran in each call, and why?$ javac Duration.java Duration.java:46: error: method does not override or implement a method from a supertype @Override ^ 1 error
equals(Duration) is a second equals: an overloadd2 is a Duration: ours. o is an Object: Object'sHashSet calls equals(Object), so ours never ran52/** @return whether o is a Duration of the same length as this */
53@Override
54public boolean equals(Object o) {
55 if (o instanceof Duration d) {
56 return seconds() == d.seconds();
57 }
58 return false;
59}$ java Duration d1 == d2: false d1.equals(d2): true d1.equals(o): true set.contains(d2): false
instanceof Duration d: null fails it; d is o, as a Durationseconds(), not the fields: an int that stands in for the abstract valueequalsHashSet files each element by its hash code, hashCode()contains looks only in the argument's bucket: d1 and d2 differObject's documentation: equal objects must have equal hash codes
61@Override
62public int hashCode() {
63 return seconds();
64}$ java Duration d1 == d2: false d1.equals(d2): true d1.equals(o): true set.contains(d2): true
equals and hashCode togetherRule: equals and hashCode together
Write equals(Object) to compare abstract values, and write hashCode from
the same abstract value, in the same class.
An equals whose parameter is anything but an Object is an overload, which
@Override catches. An equals without a hashCode is a mistake that
javac accepts: the program runs, and a HashSet loses track of things.
Duration has no subclass so far. Let's give it oneNanoDuration, as a subclass/** @return whether o is a NanoDuration of the same length as this */
@Override
public boolean equals(Object o) {
if (o instanceof NanoDuration n) {
return nanos() == n.nanos();
}
return false;
}equals written from its valued is 5:10, nd is 5:10 and 15 ns: predict both lines$ java NanoDuration d.equals(nd): true nd.equals(d): false
/** @return whether o is a NanoDuration of the same length as this */
@Override
public boolean equals(Object o) {
if (o instanceof NanoDuration n) {
return nanos() == n.nanos();
}
return false;
}NanoDuration extends Duration, with this equalsd.equals(nd) printed true, and nd.equals(d) printed falseObject's promises is broken, and who made it?$ java NanoDuration d.equals(nd): true nd.equals(d): false
Duration's asks instanceof Duration: nd is one, same secondsNanoDuration's asks instanceof NanoDuration: d is notObject promised, is broken/**
* @return whether o is a NanoDuration of the same length as this,
* or a Duration of the same length in whole seconds
*/
@Override
public boolean equals(Object o) {
if (o instanceof NanoDuration n) {
return nanos() == n.nanos();
}
return super.equals(o);
}$ java NanoDuration d.equals(nd1): true nd1.equals(d): true d.equals(nd2): true nd1.equals(nd2): false
nd1 is 5:10 and 15 ns, d is 5:10, nd2 is 5:10 and 30 nsDuration stands for a span of whole seconds: d is 5:10NanoDuration stands for a span of nanoseconds: nd1, nd2d and nd1 were the same valued and nd2 are too: Duration's equals goes by whole secondsnd1 and nd2 are, by transitivity. They differ by 15 nsequals keeps all five promises while it does both:nd1 equal to d, and tells nd1 from nd2extends DurationDuration47/** @return whether o is a NanoDuration of the same length as this */
48@Override
49public boolean equals(Object o) {
50 if (o instanceof NanoDuration n) {
51 return nanos() == n.nanos();
52 }
53 return false;
54}$ java NanoDuration d.equals(nd): false nd.equals(d): false
Duration in a fieldDuration's equals stays as it was, as it usually mustgetClass(), an object's exact class, would keep it. We do not use itList's documentation: equal lists "contain the same elements in the same order"
81/**
82 * @return whether o is a HelpQueue with the same students
83 * waiting, in the same order
84 */
85boolean equals(Object o);
86
87/** @return the hash code of the list waiting returns */
88int hashCode();HelpQueue's specification, for every class156@Override
157public boolean equals(Object o) {
158 if (o instanceof HelpQueue q) {
159 return waiting().equals(q.waiting());
160 }
161 return false;
162}
163
164@Override
165public int hashCode() {
166 return waiting().hashCode();
167}instanceof HelpQueue: the specification says any HelpQueuewaiting() stands in for the abstract value; List compares it in orderFasterQueue's two methods are the same, line for line36 // Does each say it equals the other?
37 System.out.println("q1.equals(q2): " + q1.equals(q2));
38 System.out.println("q2.equals(q1): " + q2.equals(q1));
39
40 // Does a set holding q2 find q1?
41 Set<HelpQueue> set = new HashSet<HelpQueue>();
42 set.add(q2);
43 System.out.println("set.contains(q1): " + set.contains(q1));
44
45 // Then dan joins q2. Are the two still equal, and does the
46 // set still find q2?
47 q2.join("dan");
48 System.out.println("q1.equals(q2): " + q1.equals(q2));
49 System.out.println("set.contains(q2): " + set.contains(q2));
50}$ java EqualQueues q1.equals(q2): true q2.equals(q1): true set.contains(q1): true q1.equals(q2): false set.contains(q2): false
q1$ java EqualQueues q1.equals(q2): true q2.equals(q1): false
Object's methods. javac accepts itHashSet can now be wrongq2.join("dan"): q1.equals(q2) is false, rightlyset.contains(q2) is false, and q2 is the set's only element!add filed q2 under the hash code of bob, evecontains looks under the hash code of bob, eve, dan: nothingSet's documentation: "Great care must be exercised if mutable objects are used as set elements."
equals from the abstract value costs this on a class that changesDuration and String never change: a set never loses track of oneStringBuilder changes, and keeps Object's equalsList took neither way, and every set of lists carries the warningList's choice, so Set's warning applies to itHashSet must not change while it is thereequals and hashCode together, from that valueequals(Object) with @Override: an overload is another method