CSE 331: Software Design and Implementation, Autumn 2026
Your job is to deliver code that you know works.
$ javac Calculator.java $ java Calculator > 1 2 + . 3 > q
., pops the top number and prints it$ java Calculator > 42 > 1 3 10 17 > p [42 1 3 10 17] > + p [42 1 3 27] > - p [42 1 -24] > . -24
3 minus 27, not 27 minus 3: second from the top goes on the left
q, or at the end of the input41while (true) {
42 System.out.print("> ");
43 if (!in.hasNextLine()) {
44 break;
45 }
46 String line = in.nextLine();
47 String[] tokens = line.split("\\s+");
48 for (String token : tokens) {
49 if (token.equals("")) {
50 continue;
51 } else if (token.equals("q")) {
52 return;$ java Calculator > 1 + error: need two numbers to add > p [1] > x error: don't understand 'x' > 2 + p [3] > q
The failed + left the stack alone
Sum stopped at its first mistake: a message, exit status 1main must learn of every operation that could not be done1 +1 *: does it go the same way? Why might it not?indexOf on a missing value1 + reaches add as a request it cannot carry outmain talks to the user, CalcStack computes*): multiply requires 2 <= size().): pop returns null+): add returns falset): top returns an empty OptionalInt/): divide throws one-): subtract throws one$ java Calculator > . error: stack is empty > t error: stack is empty > + error: need two numbers to add > - error: need two numbers to subtract ⋮
⋮ > * error: need two numbers to multiply > / error: need two numbers to divide > 1 0 / error: cannot divide by zero > p [1 0] > q
176/**
177 * @requires 2 <= size()
178 * @modifies this
179 * @effects pops the top two numbers and pushes their product
180 */
181public void multiply() {
182 int b = pop(); int a = pop();
183 push(a * b);
184}multiply checks nothing82} else if (token.equals("*")) {
83 if (stack.size() < 2) {
84 System.out.println(
85 "error: need two numbers to multiply");
86 } else {
87 stack.multiply();
88 }main; a precondition inside125/**
126 * @modifies this
127 * @effects removes the top number from the stack, if the stack is
128 * non-empty
129 * @return the number removed, or null if the stack was empty
130 */
131public Integer pop() {
132 if (items.size() == 0) { return null; }
133 int last = items.remove(items.size() - 1);
134 return last;
135}null says no number, as Lecture 2's -1 did; main checks on line 57
-1 for an empty stack's top? The top may be -1int or boolean, holds a plain valuenull, which refers to nothingInteger: the ints, plus nullint to Integer always works; Integer to int fails on null146/**
147 * @modifies this
148 * @effects if the stack has at least two numbers, pops the top
149 * two and pushes their sum; otherwise leaves the stack
150 * unchanged
151 * @return true if the top two numbers were replaced with their
152 * sum, or false if the stack had fewer than two numbers
153 */
154public boolean add() {
155 if (items.size() < 2) { return false; }
156 int b = pop(); int a = pop();
157 push(a + b);
158 return true;
159}137/**
138 * @return an OptionalInt holding the top number of the stack, or
139 * an empty OptionalInt if the stack is empty
140 */
141public OptionalInt top() {
142 if (items.size() == 0) { return OptionalInt.empty(); }
143 return OptionalInt.of(items.get(items.size() - 1));
144}OptionalInt holds one int, or is empty62} else if (token.equals("t")) {
63 OptionalInt top = stack.top();
64 if (top.isEmpty()) {
65 System.out.println("error: stack is empty");
66 } else {
67 System.out.println(top.getAsInt());
68 }Line 64 asks isEmpty; only then does line 67 ask getAsInt
196public void divide() {
197 if (items.size() < 2) {
198 throw new TooFewOperandsException(
199 "need two numbers to divide");
200 }
201 int b = pop(); int a = pop();
202 if (b == 0) {
203 push(a); push(b);
204 throw new DivideByZeroException("cannot divide by zero");
205 }
206 push(a / b);
207}divide's exceptions89} else if (token.equals("/")) {
90 try {
91 stack.divide();
92 } catch (TooFewOperandsException e) {
93 System.out.println(
94 "error: " + e.getMessage());
95 } catch (DivideByZeroException e) {
96 System.out.println(
97 "error: " + e.getMessage());
98 }throw ends the method at oncetry block runs, and that is allcatch: its block runs, not a crashif on "did the block throw?", with catch as the else161/**
162 * @modifies this
163 * @effects if the stack has at least two numbers, pops the top
164 * two (first and second) and pushes second - first; otherwise
165 * does nothing
166 * @throws StackException if the stack had fewer than two numbers
167 */
168public void subtract() throws StackException {
169 if (items.size() < 2) {
170 throw new StackException("need two numbers to subtract");
171 }
172 int b = pop(); int a = pop();
173 push(a - b);
174}main's checks69} else if (token.equals("+")) {
70 boolean ok = stack.add();
71 if (!ok) {
72 System.out.println(
73 "error: need two numbers to add");
74 }Only the call on line 70 is left: main never looks at ok
$ java Calculator > 1 + > p [1]
add left the stack as it was: to the user, + added 0main ignored the flag, so nobody told the user82} else if (token.equals("*")) {
83 if (stack.size() < 2) {
84 System.out.println(
85 "error: need two numbers to multiply");
86 } else {
87 stack.multiply();
88 }Only the call on line 87 is left: main calls multiply without checking
$ java Calculator > 1 * Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "CalcStack.pop()" is null at CalcStack.multiply(Calculator.java:182) at Calculator.main(Calculator.java:87)
pop returned null, stored in the int a55} else if (token.equals(".")) {
56 Integer top = stack.pop();56 int top = stack.pop();
57 if (top == null) {
58 System.out.println("error: stack is empty");
59 } else {
60 System.out.println(top);
61 }No check, and pop's answer in an int: does it compile?
$ java Calculator > . Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "CalcStack.pop()" is null at Calculator.main(Calculator.java:56)
null into an int: the same intValue crash as multiply's, now in mainInteger kept, . prints null and the run goes on89} else if (token.equals("/")) {
90 try {
91 stack.divide();
92 } catch (TooFewOperandsException e) {
93 System.out.println(
94 "error: " + e.getMessage());
95 } catch (DivideByZeroException e) {
96 System.out.println(
97 "error: " + e.getMessage());
98 }Only line 91 is left: no try around stack.divide()
$ java Calculator > 1 / Exception in thread "main" TooFewOperandsException: need two numbers to divide at CalcStack.divide(Calculator.java:198) at Calculator.main(Calculator.java:91)
divide's own message, and the line that threw62} else if (token.equals("t")) {
63 OptionalInt top = stack.top();
64 if (top.isEmpty()) {
65 System.out.println("error: stack is empty");
66 } else {
67 System.out.println(top.getAsInt());
68 }Line 67 asks getAsInt with no isEmpty first: does it compile?
$ java Calculator > t Exception in thread "main" java.util.NoSuchElementException: No value present at java.base/java.util.OptionalInt.getAsInt(OptionalInt.java:130) at Calculator.main(Calculator.java:67)
int: nextint62} else if (token.equals("t")) {
63 OptionalInt top = stack.top();63 int top = stack.top();
64 if (top.isEmpty()) {
65 System.out.println("error: stack is empty");
66 } else {
67 System.out.println(top.getAsInt());67 System.out.println(top);
68 }No check, and top's answer in an int: does it compile?
int, the run$ javac Calculator.java Calculator.java:63: error: incompatible types: OptionalInt cannot be converted to int int top = stack.top(); ^ 1 error
getAsInt, a reminder there may be nonenull or a crash, a trace, a crashint was caught before a run, by the compiler222class StackException extends Exception {
223 public StackException(String message) {
224 super(message);
225 }
226}228class TooFewOperandsException extends RuntimeException {
229 public TooFewOperandsException(String message) {
230 super(message);
231 }
232}RuntimeExceptionthrows clause; the client may catch it or notException, not RuntimeExceptionthrows StackException (line 168)FileNotFoundException, which Lecture 3's Sum caught, is checked75} else if (token.equals("-")) {
76 try {
77 stack.subtract();
78 } catch (StackException e) {
79 System.out.println(
80 "error: " + e.getMessage());
81 }Only line 77 is left: no try around stack.subtract()
$ javac Calculator.java Calculator.java:77: error: unreported exception StackException; must be caught or declared to be thrown stack.subtract(); ^ 1 error
main declares it too: next38public static void main(String[] args) {38public static void main(String[] args) throws StackException {75} else if (token.equals("-")) {
76 try {
77 stack.subtract();
78 } catch (StackException e) {
79 System.out.println(
80 "error: " + e.getMessage());
81 }No try, and main declares the exception too: does it compile?
$ java Calculator > 1 - Exception in thread "main" StackException: need two numbers to subtract at CalcStack.subtract(Calculator.java:170) at Calculator.main(Calculator.java:77)
main passed the exception on, as its clause saysmain, so nothing catches it: a stack traceNullPointerException. prints null, or an int stops the programint, it does not compilemain catches StackException, or declares itCalcStack says no six different ways, one per operationCalcStack, and why?main and subtracttry or a clausemainif, a try, or a try everywhereJoshua Bloch, Effective Java, 2nd ed. (2008), item 58.
add could treat a missing number as 0undefinedundefined + 1 is NaN, and NaN + 1 is NaN: it travels onNaN is a special value; unchecked, it travels on toonull, returned where the specification does not say so$ java Calculator > 2147483647 1 + p [-2147483648]
int, so a + b wraps aroundadd promised their sum: on this run it breaks its specificationadd does then, so Java's arithmetic decidedSum?pmain keeps the conversation: the prompt, the tokens, the messagesCalcStack keeps the state and the arithmeticadd needs no keyboardadd, check the flag and the stackmain@throws clause17@Test
18void divideOnOneNumberThrows() {
19 CalcStack stack = new CalcStack();
20 stack.push(1);
21 assertThrows(TooFewOperandsException.class,
22 () -> stack.divide());
23 assertEquals("[1]", stack.toString());
24}Your job is to deliver code that you know works.