1 of 53

CSE 331�Software Design & Implementation

​

Autumn 2026

Section 5 - Exceptions+Subtyping

2 of 53

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

​

3 of 53

Java Subtypes

Inheritance: Copy fields & methods from a superclass to a subclass

Java Subclassing: B extends A

  • Reusing or extending implementations

Java Subtyping: B extends A or B implements A

  • Type compatibility and allowed substitution

​

​

  • Ex: Every Unicorn is a Pony

class Pony {...}

class Unicorn extends Pony {...}

class RainbowDash extends Pony {...}

class Rarity extends Unicorn {...}

​

Pony

RainbowDash

Unicorn

Rarity

4 of 53

Behavioral Subtyping

Class B is a behavioral subtype of A if:

  • B has at least all the methods of A
  • Each method of B has a spec at least as strong as A’s methods

​

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

​

  • Ex: Suppose
    • Rarity is a behavioral subtype AND a Java subtype of Unicorn
    • Twilight is a Java-but-not-behavioral subtype of Unicorn

​

void magic(Unicorn x) {...}

magic(new Rarity()) Safe!

magic(new Twilight ()) Unsafe!

​

Pony

RainbowDash

Unicorn

Rarity

5 of 53

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

6 of 53

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() { ... }

}

7 of 53

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?

​

​

8 of 53

Task 1: A Square and a Rectangle Walk Into a bar()...

Comparison

Y / N

Why?

Is Square a behavioral subtype of Rectangle?

No

  • setWidth and setHeight have different specifications.
  • A client expecting a Rectangle can change width and height independently.

Is Rectangle a behavioral subtype of ImmutableRectangle?

​

​

Is ImmutableSquare a behavioral subtype of ImmutableRectangle

​

​

Is ImmutableRectangle a behavioral subtype of Rectangle?

​

​

9 of 53

Task 1: A Square and a Rectangle Walk Into a bar()...

Comparison

Y / N

Why?

Is Square a behavioral subtype of Rectangle?

No

  • setWidth and setHeight have different specifications.
  • A client expecting a Rectangle can change width and height independently.

Is Rectangle a behavioral subtype of ImmutableRectangle?

Yes

  • Rectangle has all of the observer methods, with compatible specifications.
  • The extra mutators do not affect substitutability.

​

Is ImmutableSquare a behavioral subtype of ImmutableRectangle

​

​

Is ImmutableRectangle a behavioral subtype of Rectangle?

​

​

10 of 53

Task 1: A Square and a Rectangle Walk Into a bar()...

Comparison

Y / N

Why?

Is Square a behavioral subtype of Rectangle?

No

  • setWidth and setHeight have different specifications.
  • A client expecting a Rectangle can change width and height independently.

Is Rectangle a behavioral subtype of ImmutableRectangle?

Yes

  • Rectangle has all of the observer methods, with compatible specifications.
  • The extra mutators do not affect substitutability.

​

Is ImmutableSquare a behavioral subtype of ImmutableRectangle

Yes

  • Same observer methods and compatible behavior.
  • Every immutable square can be used wherever an immutable rectangle is expected.

​

Is ImmutableRectangle a behavioral subtype of Rectangle?

​

​

11 of 53

Task 1: A Square and a Rectangle Walk Into a bar()...

Comparison

Y / N

Why?

Is Square a behavioral subtype of Rectangle?

No

  • setWidth and setHeight have different specifications.
  • A client expecting a Rectangle can change width and height independently.

Is Rectangle a behavioral subtype of ImmutableRectangle?

Yes

  • Rectangle has all of the observer methods, with compatible specifications.
  • The extra mutators do not affect substitutability.

​

Is ImmutableSquare a behavioral subtype of ImmutableRectangle

Yes

  • Same observer methods and compatible behavior.
  • Every immutable square can be used wherever an immutable rectangle is expected.

​

Is ImmutableRectangle a behavioral subtype of Rectangle?

No

  • Missing setWidth and setHeight.
  • It does not provide every method in Rectangle's specification.

12 of 53

Subtypes Example from Lecture

RuntimeException

Exceptions from Calculator.java:

CalcException

TooFewOperandsException

StackEmptyException

DivideByZeroException

Exception

NumberFormatException

IllegalArgumentException

13 of 53

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

*/

14 of 53

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

*/

15 of 53

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);� }

⋮�}

16 of 53

Task 2: Try and Catch Me

a) Assume stack starts empty. What happens when we run 6 s p?

  1. 6 is pushed onto the stack
  2. s runs the swap implementation:
    1. a = 6, the first pop() returns 6 and removes it
    2. The second pop() throws a StackEmptyException
    3. Main catches the exception and prints it
  3. p prints the empty stack

17 of 53

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);� }

⋮�}

18 of 53

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

*/

  • pop() satisfies the spec, but step() violates it!
  • The stack changed, but the spec promised to leave it unchanged.
  • A called method, such as pop(), can satisfy its spec while its caller breaks its own.

19 of 53

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);� }

⋮�}

20 of 53

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);

}

⋮�}

21 of 53

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());

}

22 of 53

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?

​

  • Converts NumberFormatException into CalcException.
  • Translates parsing failures into user-friendly calculator errors.
  • main catches CalcException to handle expected errors uniformly.

23 of 53

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());

}

24 of 53

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);

}

  • Integer.parseInt("abc")throws NumberFormatException.

25 of 53

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);

}

  • Integer.parseInt("abc")throws NumberFormatException.
  • Calculator.parse() does not catch it.

26 of 53

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));

}

}

  • Integer.parseInt("abc")throws NumberFormatException.
  • Calculator.parse() does not catch it.
  • Calculator.step() does not catch it.

27 of 53

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());

}

  • Integer.parseInt("abc")throws NumberFormatException.
  • Calculator.parse() does not catch it.
  • Calculator.step() does not catch it.
  • The catch type in Calculator.main() does not match.

28 of 53

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());

}

  • Integer.parseInt("abc")throws NumberFormatException.
  • Calculator.parse() does not catch it.
  • Calculator.step() does not catch it.
  • The catch type in Calculator.main() does not match.

​

The program terminates with uncaught exception and stack trace!

29 of 53

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());

}

30 of 53

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:

  • NumberFormatException is a subtype of Exception.
  • Now our main would catch the exception from Integer.parseInt("abc").

​

Cons:

  • Unexpected exceptions could be printed as ordinary user errors.
  • This hides implementation bugs and allows unsafe program continuation.

31 of 53

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());

}

  • The original version of the try-catch block is what we want.
  • This allows us to separate intended user errors from actual bugs.

32 of 53

Note about tasks 4 & 5

  • Tasks 4 and 5 are extra practice.
  • If we have time, we can cover one of them.
    • You get to vote!

33 of 53

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?

34 of 53

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.

35 of 53

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?

36 of 53

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.

37 of 53

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;

}

}

}

38 of 53

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);

}

39 of 53

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);

}

  • 0 iterations for outer loop
  • inner loop never reached
    • not the same as 0 iterations

40 of 53

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);

}

  • 1 iteration in outer loop
  • 0 iterations in inner loop
  • implicit else branch in outer loop

41 of 53

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

}

}

42 of 53

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

}

}

  • 2+ loop outer
  • 1 & 2+ loop inner
  • inner if branch & statements
  • inner implicit else
  • outer if branch & statements

43 of 53

Task 5: Proofs, Proofs, and More Proofs

  1. Explain how P implies Q

{{P: exp > 0 and base > 1 and temp = base^(exp - 1) }}

​

{{Q: base * temp > 1}}

44 of 53

Task 5: Proofs, Proofs, and More Proofs

  1. Explain how P implies Q

{{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)

​

​

45 of 53

Task 5: Proofs, Proofs, and More Proofs

  1. Explain how P implies Q

{{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

​

46 of 53

Task 5: Proofs, Proofs, and More Proofs

  1. Explain how P implies Q

{{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

​

​

47 of 53

Task 5: Proofs, Proofs, and More Proofs

  1. Explain how P implies Q

{{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.

​

​

48 of 53

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 }}

​

​

​

​

​

49 of 53

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.

​

​

50 of 53

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).

51 of 53

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).

52 of 53

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.

​

​

​

53 of 53

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