Skip to main content

CSE 331: Section 5 — Exceptions + Subtyping

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

Consider the following classes.

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 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 ImmutableRectangle {
  private int width;
  private int height;

  public int getWidth() { ... }
  public int getHeight() { ... }
}
public class ImmutableSquare {
  private int side;

  public int getWidth() { ... }
  public int getHeight() { ... }
}

For each comparison below, answer Yes or No and briefly justify your answer.

Comparison Yes / No
Is Square a behavioral subtype of Rectangle?
Why or why not?
Is Rectangle a behavioral subtype of ImmutableRectangle?
Why or why not?
Is ImmutableSquare a behavioral subtype of ImmutableRectangle?
Why or why not?
Is ImmutableRectangle a behavioral subtype of Rectangle?
Why or why not?

Task 2 - Try and Catch Me

What if we added a new command?

/*
 *   s          swaps the top two numbers
 */
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);
  }
  ⋮
}
  1. Assume stack starts empty. What happens when we run 6 s p?

  2. Which method spec was violated by which method?

  3. How could you repair the swap behavior?

Task 3 - Don’t Take It Parse-onally

  1. Why does parse catch one exception only to throw another? What does this accomplish and how does it affect main?

  2. What if parse did not catch? What would happen in main? Consider if we ran abc.

    static int parse(String token) {
        return Integer.parseInt(token);
    }
    
  3. What if we broadened what was caught in main?

    try {
        step(stack, token);
    } catch (Exception e) {
        System.out.println("error: " + e.getMessage());
    }
    

Task 4 - Testing Coverage Review

  1. If we write a test that never reaches a for/while loop conditional, does this count as zero loop iterations?

  2. What if the spec does not allow inputs such that we can write a test that covers 0 iterations of the loop?

  3. Consider the following method:

    /**
     * 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;
            }
        }
    }
    

    Use the testing heuristics from lecture to write a test suite for the following method:

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

Calculator Reference

/*
 * 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
 */
import java.util.Scanner;

public static void main(String[] args) {
    Scanner in = new Scanner(System.in);
    CalcStack stack = new ListStack();
    while (true) {
        System.out.print("> ");
        if (!in.hasNextLine()) {
            break;
        }
        String line = in.nextLine();
        for (String token : line.split("\\s+")) {
            if (token.equals("")) {
                continue;
            } else if (token.equals("q")) {
                return;
            }
            try {
                step(stack, token);
            } catch (CalcStack.CalcException e) {
                System.out.println("error: " + e.getMessage());
            }
        }
    }
}
/**
 * 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
 */
static void step(CalcStack stack, String token) {
    if (token.equals("p")) {
        System.out.println(stack.numbers());
    } else if (token.equals(".")) {
        System.out.println(stack.pop());
    } else if (token.equals("t")) {
        System.out.println(stack.top());
    } else if (token.equals("+")) {
        stack.add();
    } else if (token.equals("-")) {
        stack.subtract();
    } else if (token.equals("*")) {
        stack.multiply();
    } else if (token.equals("/")) {
        stack.divide();
    } else {
        stack.push(parse(token));
    }
}
/**
 * @return the integer that token spells
 * @throws CalcStack.CalcException if token does not spell an
 *     integer
 */
static int parse(String token) {
    try {
        return Integer.parseInt(token);
    } catch (NumberFormatException e) {
        throw new CalcStack.CalcException(
            "don't understand '" + token + "'");
    }
}
public interface CalcStack {
    /**
     * @modifies this
     * @effects pushes value onto the top of the stack
     */
    void push(int value);

    /**
     * @modifies this
     * @effects removes the top number from the stack
     * @return the number removed
     * @throws StackEmptyException if the stack is empty, and leaves
     *     the stack unchanged
     */
    int pop();

    /**
     * A command the calculator cannot carry out.
     */
    public class CalcException extends RuntimeException {
        public CalcException(String message) {
            super(message);
        }
    }

    /** A pop on an empty stack. */
    public class StackEmptyException extends CalcException {
        public StackEmptyException(String message) {
            super(message);
        }
    }

    /** An operation on a stack with fewer than two numbers. */
    public class TooFewOperandsException extends CalcException {
        public TooFewOperandsException(String message) {
            super(message);
        }
    }

    /** A divide whose divisor, the top number, is zero. */
    public class DivideByZeroException extends CalcException {
        public DivideByZeroException(String message) {
            super(message);
        }
    }
}