CSE 331�Software Design & Implementation
Autumn 2026
Section 5 - Exceptions+Subtyping
Administrivia
HW3 due Friday 11/6 @ 11:59 PM !!
Midterm next Friday 11/13 during lecture !!!!
- Mock exam session: Tuesday 11/10, 4:30 - 6:20 PM
- Midterm review session: Thursday 11/12, 4:30 - 6:20 PM
- Both in CSE2 G01
Java Subtypes
Inheritance: Copy fields & methods from a superclass to a subclass
Java Subclassing: B extends A
Java Subtyping: B extends A or B implements A
class Pony {...}
class Unicorn extends Pony {...}
class RainbowDash extends Pony {...}
class Rarity extends Unicorn {...}
Pony
RainbowDash
Unicorn
Rarity
Behavioral Subtyping
Class B is a behavioral subtype of A if:
Crucially, Java cannot enforce behavioral subtyping!
Liskov Substitution Principle (LSP): When B is a behavioral subtype of A, one can use B wherever A is expected
void magic(Unicorn x) {...}
magic(new Rarity()) Safe!
magic(new Twilight ()) Unsafe!
Pony
RainbowDash
Unicorn
Rarity
Subtyping: Behavioral vs Java
| Behavioral Subtype | Not Behavioral Subtype |
Java Subtype | SAFE | Permitted by compiler but UNSAFE |
Not Java Subtype | Not permitted by compiler | Not permitted by compiler |
Task 1: A Square and a Rectangle Walk Into a bar()...
Consider the following classes below to complete Task 1 on the worksheet. Justify your answers using the definition of behavioral subtyping.
public class Rectangle {
private int width;
private int height;
public int getWidth() { ... }
public int getHeight() { ... }
// @effects this.width = w
public void setWidth(int w) { ... }
// @effects this.height = h
public void setHeight(int h) { ... }
}
public class ImmutableRectangle {
private int width;
private int height;
public int getWidth() { ... }
public int getHeight() { ... }
}
public class Square {
private int side;
public int getWidth() { ... }
public int getHeight() { ... }
// @effects this.side = w
public void setWidth(int w) { ... }
// @effects this.side = h
public void setHeight(int h) { ... }
}
public class ImmutableSquare {
private int side;
public int getWidth() { ... }
public int getHeight() { ... }
}
Task 1: A Square and a Rectangle Walk Into a bar()...
Comparison | Y / N | Why? |
Is Square a behavioral subtype of Rectangle? | | |
Is Rectangle a behavioral subtype of ImmutableRectangle? | | |
Is ImmutableSquare a behavioral subtype of ImmutableRectangle | | |
Is ImmutableRectangle a behavioral subtype of Rectangle? | | |
Task 1: A Square and a Rectangle Walk Into a bar()...
Comparison | Y / N | Why? |
Is Square a behavioral subtype of Rectangle? | No |
|
Is Rectangle a behavioral subtype of ImmutableRectangle? | | |
Is ImmutableSquare a behavioral subtype of ImmutableRectangle | | |
Is ImmutableRectangle a behavioral subtype of Rectangle? | | |
Task 1: A Square and a Rectangle Walk Into a bar()...
Comparison | Y / N | Why? |
Is Square a behavioral subtype of Rectangle? | No |
|
Is Rectangle a behavioral subtype of ImmutableRectangle? | Yes |
|
Is ImmutableSquare a behavioral subtype of ImmutableRectangle | | |
Is ImmutableRectangle a behavioral subtype of Rectangle? | | |
Task 1: A Square and a Rectangle Walk Into a bar()...
Comparison | Y / N | Why? |
Is Square a behavioral subtype of Rectangle? | No |
|
Is Rectangle a behavioral subtype of ImmutableRectangle? | Yes |
|
Is ImmutableSquare a behavioral subtype of ImmutableRectangle | Yes |
|
Is ImmutableRectangle a behavioral subtype of Rectangle? | | |
Task 1: A Square and a Rectangle Walk Into a bar()...
Comparison | Y / N | Why? |
Is Square a behavioral subtype of Rectangle? | No |
|
Is Rectangle a behavioral subtype of ImmutableRectangle? | Yes |
|
Is ImmutableSquare a behavioral subtype of ImmutableRectangle | Yes |
|
Is ImmutableRectangle a behavioral subtype of Rectangle? | No |
|
Subtypes Example from Lecture
RuntimeException
Exceptions from Calculator.java:
CalcException
TooFewOperandsException
StackEmptyException
DivideByZeroException
Exception
NumberFormatException
IllegalArgumentException
Task 2: Try and Catch Me
Recall the Calculator.java class from lecture, with the following commands:
/*
* Commands:
* a number push it onto the stack
* + pop two numbers, push their sum
* - pop two numbers, push (second - first)
* * pop two numbers, push their product
* / pop two numbers, push (second / first), int div
* p print the whole stack
* . pop the top number and print it
* t print the top number without removing it
* q quit
*/
Task 2: Try and Catch Me
What if we added a new command?
/*
* Commands:
* a number push it onto the stack
* + pop two numbers, push their sum
* - pop two numbers, push (second - first)
* * pop two numbers, push their product
* / pop two numbers, push (second / first), int div
* p print the whole stack
* . pop the top number and print it
* t print the top number without removing it
* s swaps the top two numbers
* q quit
*/
Task 2: Try and Catch Me
a) Assume stack starts empty. What happens when we run 6 s p?
public static void main(String[] args) {
⋮
try {
step(stack, token);
} catch (CalcStack.CalcException e) {
System.out.println("error: " + e.getMessage());
}
⋮
}
static void step(CalcStack stack, String token) {
⋮� } else if (token.equals("s")) {� int a = stack.pop();� int b = stack.pop();� stack.push(a);� stack.push(b);� }
⋮�}
Task 2: Try and Catch Me
a) Assume stack starts empty. What happens when we run 6 s p?
Task 2: Try and Catch Me
b) Which method spec was violated by which method?
static void step(CalcStack stack, String token) {
⋮� } else if (token.equals("s")) {� int a = stack.pop();� int b = stack.pop();� stack.push(a);� stack.push(b);� }
⋮�}
Task 2: Try and Catch Me
b) Which method spec was violated by which method?
/**
* Carries out one command.
* @modifies stack, System.out
* @effects carries out the command token stands for, as the class
* comment lists them, printing what the command prints
* @throws CalcStack.CalcException if the command cannot be carried
* out, with a message for the user, and leaves the stack unchanged
*/
Task 2: Try and Catch Me
c) How could you repair the swap behavior?
static void step(CalcStack stack, String token) {
⋮
} else if (token.equals("s")) {� int a = stack.pop();� int b = stack.pop();� stack.push(a);� stack.push(b);� }
⋮�}
Task 2: Try and Catch Me
c) How could you repair the swap behavior?
static void step(CalcStack stack, String token) {
⋮
} else if (token.equals("s")) {
if (stack.size() < 2) {
throw new CalcStack.TooFewOperandsException(
"need two numbers to swap");
}
int a = stack.pop();
int b = stack.pop();
stack.push(a);
stack.push(b);
}
⋮�}
Task 3: Don’t Take it Parse-onally
a) Why does parse catch one exception only to throw another?
What does this accomplish and how does it affect main?
Calculator.parse()
static int parse(String token) {
try {
return Integer.parseInt(token);
} catch (NumberFormatException e) {
throw new CalcStack.CalcException(
"don't understand '" + token + "'");
}
}
Try-catch block in Calculator. main()
try {
step(stack, token);
} catch (CalcStack.CalcException e) {
System.out.println("error: " + e.getMessage());
}
Task 3: Don’t Take it Parse-onally
a) Why does parse catch one exception only to throw another?
What does this accomplish and how does it affect main?
Task 3: Don’t Take it Parse-onally
b) What if parse did not catch? What would happen in main?
Consider if we ran abc.
Modified Calculator.parse()
static int parse(String token) {
return Integer.parseInt(token);
}
Unchanged try-catch block in Calculator. main()
try {
step(stack, token);
} catch (CalcStack.CalcException e) {
System.out.println("error: " + e.getMessage());
}
Task 3: Don’t Take it Parse-onally
b) What if parse did not catch? What would happen in main?
Consider if we ran abc.
Modified Calculator.parse()
static int parse(String token) {
return Integer.parseInt(token);
}
Task 3: Don’t Take it Parse-onally
b) What if parse did not catch? What would happen in main?
Consider if we ran abc.
Modified Calculator.parse()
static int parse(String token) {
return Integer.parseInt(token);
}
Task 3: Don’t Take it Parse-onally
b) What if parse did not catch? What would happen in main?
Consider if we ran abc.
Calculator.step()
static void step(CalcStack stack, String token) {
} else {
stack.push(parse(token));
}
}
Task 3: Don’t Take it Parse-onally
b) What if parse did not catch? What would happen in main?
Consider if we ran abc.
Try-catch block in Calculator. main()
try {
step(stack, token);
} catch (CalcStack.CalcException e) {
System.out.println("error: " + e.getMessage());
}
Task 3: Don’t Take it Parse-onally
b) What if parse did not catch? What would happen in main?
Consider if we ran abc.
Try-catch block in Calculator. main()
try {
step(stack, token);
} catch (CalcStack.CalcException e) {
System.out.println("error: " + e.getMessage());
}
The program terminates with uncaught exception and stack trace!
Task 3: Don’t Take it Parse-onally
c) What if we broadened what was caught in main?
Proposed replacement try-catch block in Calculator. main()
try {
step(stack, token);
} catch (Exception e) {
System.out.println("error: " + e.getMessage());
}
Task 3: Don’t Take it Parse-onally
c) What if we broadened what was caught in main?
Proposed replacement try-catch block in Calculator. main()
try {
step(stack, token);
} catch (Exception e) {
System.out.println("error: " + e.getMessage());
}
Pros:
Cons:
Task 3: Don’t Take it Parse-onally
c) What if we broadened what was caught in main?
Original try-catch block in Calculator. main()
try {
step(stack, token);
} catch (CalcStack.CalcException e) {
System.out.println("error: " + e.getMessage());
}
Note about tasks 4 & 5
Task 4: Testing Coverage Review
a) If we write a test that never reaches a for/while loop conditional, does this count as zero loop iterations?
Task 4: Testing Coverage Review
a) If we write a test that never reaches a for/while loop conditional, does this count as zero loop iterations?
No.
Zero loop iteration coverage means we have to reach the loop conditional but never go inside the body.
Task 4: Testing Coverage Review
b) What if the spec does not allow inputs such that we can write a test that covers 0 iterations of the loop?
Task 4: Testing Coverage Review
b) What if the spec does not allow inputs such that we can write a test that covers 0 iterations of the loop?
We only test on inputs allowed by the spec, so this is acceptable.
Task 4: Testing Coverage Review
/**
* Replaces duplicate elements in an array with 0.
*
* @requires arr != null and every element in arr is positive.
* @modifies arr
* @effects For every index i, if arr[i] originally appeared at
* an earlier index, then arr[i] is replaced with 0. Otherwise,
* arr[i] remains unchanged. For either case, the array's
* length does not change.
*/
public static void removeDuplicates(int[] arr) {
for (int i = 0; i < arr.length; i++) {
boolean duplicate = false;
for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
duplicate = true;
break;
}
}
if (duplicate) {
arr[i] = 0;
}
}
}
Task 4: Testing Coverage Review
public static void removeDuplicates(int[] arr) {
for (int i = 0; i < arr.length; i++) {
boolean duplicate = false;
for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
duplicate = true;
break;
}
}
if (duplicate) {
arr[i] = 0;
}
}
}
@Test
public void emptyArray() {
int[] arr = {};
removeDuplicates(arr);
assertArrayEquals(new int[]{}, arr);
}
Task 4: Testing Coverage Review
public static void removeDuplicates(int[] arr) {
for (int i = 0; i < arr.length; i++) { // 0 iterations
boolean duplicate = false;
for (int j = 0; j < i; j++) {
if (arr[i] == arr[j]) {
duplicate = true;
break;
}
}
if (duplicate) {
arr[i] = 0;
}
}
}
@Test
public void emptyArray() {
int[] arr = {};
removeDuplicates(arr);
assertArrayEquals(new int[]{}, arr);
}
Task 4: Testing Coverage Review
public static void removeDuplicates(int[] arr) {
for (int i = 0; i < arr.length; i++) { // 0,1 iterations
boolean duplicate = false;
for (int j = 0; j < i; j++) { // 0 iterations
if (arr[i] == arr[j]) {
duplicate = true;
break;
}
}
if (duplicate) {
arr[i] = 0;
}
// implicit else
}
}
@Test
public void singleElement() {
int[] arr = {7};
removeDuplicates(arr);
assertArrayEquals(new int[]{7}, arr);
}
Task 4: Testing Coverage Review
@Test
public void repeatedDuplicates() {
int[] arr = {4, 4, 4, 2, 2};
ArrayUtils.removeDuplicates(arr);
assertArrayEquals(new int[]{4, 0, 0, 2, 0}, arr);
}
public static void removeDuplicates(int[] arr) {
for (int i = 0; i < arr.length; i++) { // 0,1 iterations
boolean duplicate = false;
for (int j = 0; j < i; j++) { // 0 iterations
if (arr[i] == arr[j]) {
duplicate = true;
break;
}
}
if (duplicate) {
arr[i] = 0;
}
// implicit else
}
}
Task 4: Testing Coverage Review
@Test
public void repeatedDuplicates() {
int[] arr = {4, 4, 4, 2, 2};
ArrayUtils.removeDuplicates(arr);
assertArrayEquals(new int[]{4, 0, 0, 2, 0}, arr);
}
public static void removeDuplicates(int[] arr) {
for (int i = 0; i < arr.length; i++) { // 0, 1, 2+ iterations
boolean duplicate = false;
for (int j = 0; j < i; j++) { // 0, 1, 2+ iterations
if (arr[i] == arr[j]) {
duplicate = true;
break;
}
// implicit else
}
if (duplicate) {
arr[i] = 0;
}
// implicit else
}
}
Task 5: Proofs, Proofs, and More Proofs
{{P: exp > 0 and base > 1 and temp = base^(exp - 1) }}
{{Q: base * temp > 1}}
Task 5: Proofs, Proofs, and More Proofs
{{P: exp > 0 and base > 1 and temp = base^(exp - 1) }}
{{Q: base * temp > 1}}
base * temp = base * base^(exp - 1) since temp = base^(exp - 1)
Task 5: Proofs, Proofs, and More Proofs
{{P: exp > 0 and base > 1 and temp = base^(exp - 1) }}
{{Q: base * temp > 1}}
base * temp = base * base^(exp - 1) since temp = base^(exp - 1)
= base ^ exp
Task 5: Proofs, Proofs, and More Proofs
{{P: exp > 0 and base > 1 and temp = base^(exp - 1) }}
{{Q: base * temp > 1}}
base * temp = base * base^(exp - 1) since temp = base^(exp - 1)
= base ^ exp
> base ^ 0 since exp > 0
Task 5: Proofs, Proofs, and More Proofs
{{P: exp > 0 and base > 1 and temp = base^(exp - 1) }}
{{Q: base * temp > 1}}
base * temp = base * base^(exp - 1) since temp = base^(exp - 1)
= base ^ exp
> base ^ 0 since base > 1 and exp > 0
= 1
Ensure that you are not skipping the substitution of base > 1. Notice that if base < 0, and if exp is an odd number, base * temp can be < 0.
Task 5: Proofs, Proofs, and More Proofs
b) Explain how P implies Q
{{ P: 0 <= count_0 <= capacity and requested >= 0 and
0 <= added <= requested and this.count = count_0 + added
and this.count <= capacity and remaining = requested - added
and (remaining = 0 or this.count = capacity) }}
{{Q: added = min(requested, capacity - count_0)
and this.count <= capacity }}
Task 5: Proofs, Proofs, and More Proofs
b) Explain how P implies Q
{{ P: 0 <= count_0 <= capacity and requested >= 0 and
0 <= added <= requested and this.count = count_0 + added
and this.count <= capacity and remaining = requested - added
and (remaining = 0 or this.count = capacity) }}
{{Q: added = min(requested, capacity - count_0)
and this.count <= capacity }}
From P, we know that either remaining = 0 or this.count = capacity. We consider both cases.
Task 5: Proofs, Proofs, and More Proofs
b) Explain how P implies Q
{{ P: 0 <= count_0 <= capacity and requested >= 0 and
0 <= added <= requested and this.count = count_0 + added
and this.count <= capacity and remaining = requested - added
and (remaining = 0 or this.count = capacity) }}
{{Q: added = min(requested, capacity - count_0)
and this.count <= capacity }}
Case 1: remaining = 0
Since remaining = requested - added, we know that added = requested. From P, we also know that this.count = count_0 + added and this.count <= capacity, which gives added <= capacity - count_0. Substituting added = requested, we get requested <= capacity - count_0. Therefore, requested is the smaller of the two values, so added = min(requested, capacity - count_0).
Task 5: Proofs, Proofs, and More Proofs
b) Explain how P implies Q
{{ P: 0 <= count_0 <= capacity and requested >= 0 and
0 <= added <= requested and this.count = count_0 + added
and this.count <= capacity and remaining = requested - added
and (remaining = 0 or this.count = capacity) }}
{{Q: added = min(requested, capacity - count_0)
and this.count <= capacity }}
Case 2: this.count = capacity
Since this.count = count_0 + added, substituting gives capacity = count_0 + added. Subtracting count_0 from both sides gives added = capacity - count_0. From P, we also know that added <= requested, meaning capacity - count_0 <= requested. Thus, added = min(requested, capacity - count_0).
Task 5: Proofs, Proofs, and More Proofs
b) Explain how P implies Q
{{ P: 0 <= count_0 <= capacity and requested >= 0 and
0 <= added <= requested and this.count = count_0 + added
and this.count <= capacity and remaining = requested - added
and (remaining = 0 or this.count = capacity) }}
{{Q: added = min(requested, capacity - count_0)
and this.count <= capacity }}
Finally, this.count <= capacity is already given in P. Therefore, both conditions in Q hold, proving that P implies Q.
Please turn in your half-sheets
Reminder:�Midterm next Friday 11/13 during lecture !!!!
- Mock exam session: Tuesday 11/10, 4:30 - 6:20 PM
- Midterm review session: Thursday 11/12, 4:30 - 6:20 PM
- Both in CSE2 G01