CSE 331: Software Design and Implementation, Autumn 2026
Your job is to deliver code that you know works.
ListQueue's join walks the list to check for the studentFastQueue keeps the students twice: a list for the order, and
a set12public class FastQueue implements HelpQueue {
13 // The students waiting, front first.
14 private List<String> queue;
15 // The same students, for a fast lookup.
16 private Set<String> members;Two fields hold the same students, so they have to agree
50public void join(String netid) {
52 if (members.contains(netid)) {
...
55 queue.add(netid);
56 members.add(netid);
58}60public String next() {
...
65 String front = queue.remove(0);
66 members.remove(front);
68 return front;
69}join and next must change the set too, not only the listjoin behaves as beforeAll of this rests on the set holding the same students as the list
next()60public String next() {
62 if (queue.isEmpty()) {
63 throw new NoSuchElementException("nobody is waiting");
64 }
65 String front = queue.remove(0);
66 members.remove(front);
68 return front;
69}How can a client exploit this bug to cause a specification violation?
next(): what goes wrong?60public String next() {
...
65 String front = queue.remove(0);
66 members.remove(front);
68 return front;4FastQueue queue = new FastQueue();
5queue.join("ada");
6queue.next();
7queue.join("ada");
8
9FastQueue other = new FastQueue();
10other.join("ada");
11other.join("bob");
12other.next();
13other.size();What
should join do here?
ada is not in the queue, so the specification says: add ada
What does it do?
It throws "ada is in the queue"
What
should size return here?
One student is waiting, so 1
What does it return?
2
Something is wrong, since the specification was violated
But which method's code is wrong?
45public int size() {
47 return members.size();
48}
50public void join(String netid) {
52 if (members.contains(netid)) {
...
55 queue.add(netid);
56 members.add(netid);
58}60public String next() {
...
65 String front = queue.remove(0);
66 members.remove(front);
68 return front;
69}4FastQueue queue = new FastQueue();
5queue.join("ada");
6queue.next();
7queue.join("ada");
8
9FastQueue other = new FastQueue();
10other.join("ada");
11other.join("bob");
12other.next();
13other.size();Is the bug here? It threw
Or here? It returned 2
Or here? A line is missing, but the client never
saw next break its specification
The specification cannot say, since
members is a private field: what was it meant to
hold?
Students who left too: join and
size are wrong. Only the queue: next
is wrong
Definition: representation invariant
A representation invariant (RI) of a class is a claim about its fields
that is true of every object of the class whenever no method of the
class is running.
The constructor makes it true. Every method may assume it when called
and must make it true again before it returns. An implementation
should document its representation invariant with a comment like
// RI: ... beside the fields.
Abstract invariant: the abstract state. RI: the concrete state, the fields
65 String front = queue.remove(0);
66 members.remove(front);
68 return front;queue means removing it from members toonext returns with the RI false: next is wrongjoin and size may assume the RI, and the client saw them misbehaveReasoning finds the mistake where it was made; a test would see it a call later
Definition: checkRep
A checkRep method of a class checks the class's representation invariant
in code: it reads the fields, computes each clause of the RI from
them, and throws an exception if any clause is false.
It is private, since it is the implementer's check and not part of
the specification, and it changes no field.
36private void checkRep() {
37 if (!members.equals(new HashSet<String>(queue))) {
38 throw new IllegalStateException("members is not the queue");
39 }
40 if (members.size() != queue.size()) {
41 throw new IllegalStateException("someone is in queue twice");
42 }
43}if checks one clause of the RI, and throws if it is falseWith
the buggy next from before, now where does it
fail?
65String front = queue.remove(0);
66members.remove(front);
67checkRep();
68return front;4FastQueue queue = new FastQueue();
5queue.join("ada");
6queue.next();
7queue.join("ada");next throws "members is not the
queue", from the check at its end
The failure is in next, on the call
that broke the RI, not a call later
Without the checks, join took the
blame
join and next do, is a
mutatorcheckRepcheckRep builds a set from the whole list, at every checkjoin is fast only once the checks are offjoin does the RI become false? When true again?50public void join(String netid) {
51 checkRep();
52 if (members.contains(netid)) {
53 throw new IllegalArgumentException(netid + " is in the queue");
54 }
55 queue.add(netid);
56 members.add(netid);
57 checkRep();
58}60public String next() {
...
65 String front = queue.remove(0);
66 members.remove(front);
68 return front;
69}queue.remove(0) moves every student after the first oneHow
could next be made fast?
join adds at the inbox's end; next takes from the outbox's endnext moves the inbox over in reverse13// The students who joined since the last refill, oldest first.
14private List<String> inbox;
15// The students ahead of them, with the front of the queue last.
16private List<String> outbox;
17// The same students, for a fast lookup.
18private Set<String> members;
19// RI: no NetID is in inbox or outbox twice, or in both, and
20// members holds exactly the NetIDs in inbox and outbox.The RI now names both lists
next's specification: removes the student at the frontinbox = [], outbox = [eve, bob], members = {bob, eve}Definition: abstraction function
The abstraction function (AF) of a class maps each value of
the fields that the representation invariant allows to the
abstract state it stands for.
The AF is a mathematical function, not code. An implementation
should document its abstraction function with a comment like
// AF(this) = ... beside the fields.
19// RI: no NetID is in inbox or outbox twice, or in both, and
20// members holds exactly the NetIDs in inbox and outbox.
21// AF(this) = the help queue of the students in outbox, last to
22// first, then the students in inbox, first to last.[], [eve, bob], {bob, eve}: the queue bob, eve[eve], [bob], {bob, eve}: the queue bob, evewaiting()86public List<String> waiting() {
87 checkRep();
88 List<String> list = new ArrayList<String>();
89 for (int i = outbox.size() - 1; i >= 0; i--) {89 list.addAll(outbox);
90 list.add(outbox.get(i));
91 }
92 list.addAll(inbox);
93 return list;
94}How can a client exploit this bug to cause a specification violation?
89 for (int i = outbox.size() - 1; i >= 0; i--) {89 list.addAll(outbox);
90 list.add(outbox.get(i));
91 }
92 list.addAll(inbox);5queue.join("ada");
6queue.join("bob");
7queue.join("eve");
8queue.next();
9queue.waiting();What
should waiting return here?
ada was served, so the specification says: bob, eve, front first
What does it return?
[eve, bob]
With two students the outbox holds one, so Wednesday's five tests all pass
The client's call refutes it; the AF says what it got wrong: the outbox is last to first
Rule: a method of an abstract data type is correct
A method with precondition P and postcondition Q, written about the
abstract state, is correct exactly when the Hoare triple
{{ RI and P(AF(this)) }}
the method's body
{{ RI and Q(AF(this)) }}
is valid and every call that begins with P true terminates,
where RI is the representation invariant and AF is
the abstraction function.
19// RI: no NetID is in inbox or outbox twice, or in both, and
20// members holds exactly the NetIDs in inbox and outbox.
21// AF(this) = the help queue of the students in outbox, last to
22// first, then the students in inbox, first to last.52public int size() {
{{ RI }}
54 return members.size();
{{ returns members.size() }}
{{ owed by the specification: the number of students waiting in AF(this) }}
{{ by the RI: members.size() = inbox.size() + outbox.size() }}
{{ by the AF: that is the number of students in AF(this) }}
55}The gap: the body counts a set, the specification counts a queue
The RI and the AF close it; no field changed, so
size is correct
next, by the rule: the argument stops at the missing linecheckRep at the start of a method, not only at
the end?waiting that returns its own list71public List<String> waiting() {
72 checkRep();
73 return new ArrayList<String>(queue);73 return queue;
74}49void waitingIsACopy() {
50 HelpQueue queue = emptyQueue();
51 queue.join("ada");
52 List<String> copy = queue.waiting();
53 copy.add("eve");The client's copy is the list in the
field queue
The client changes the field, and no method of
FastQueue ran
The check at waiting's start throws:
half sheet question 2
26/** @requires no NetID is in netids twice
27 * @effects makes a queue of the students given, front first */
28public FastQueue(List<String> netids) {
29 // A copy, so that the client cannot change the queue through
30 // the list it passed.
31 queue = new ArrayList<String>(netids);31 queue = netids;
32 members = new HashSet<String>(netids);
33 checkRep();
34}The constructor keeps a copy of the client's list
A buggy version keeps the client's own list. Why is that a bug? Coming up
31 queue = new ArrayList<String>(netids);31 queue = netids;11List<String> netids = new ArrayList<String>(List.of("ada"));
12FastQueue queue = new FastQueue(netids);
13netids.add("eve");
14assertEquals(List.of("ada"), queue.waiting());The queue's field queue is the
client's list
The client changes the field, and no method of
FastQueue ran
waiting's check throws "members is not
the queue"
31 queue = new ArrayList<String>(netids);31 queue = netids;31 queue = new ArrayList<String>(netids);11 List<String> netids = new ArrayList<String>(List.of("ada"));
12 FastQueue queue = new FastQueue(netids);
13 netids.add("eve");netids and the field queue name one list: aliasingFastQueue ranDefinition: representation exposure
Representation exposure is a reference, held by code outside a class, to a changeable object among the class's fields. Through it, that code can change the fields without calling any method of the class, so the representation invariant and every specification can be broken by code the class does not control. To prevent it, copy in a changeable object a client passes that becomes part of the fields, and copy out a changeable field that a method returns.
Copy in: line 31 of FastQueue.java. Copy out: its line 73
String can change a stringString field exposes nothingint holds a plain value: the client gets a copy of the numberList, a Set, an array: copy in, copy outJava 25's documentation of String: "Strings are constant; their
values cannot be changed after they are created"
fetch