Relevant Reading Material

Code Style

We will follow the Google Style Guide. The lab will have a section on how to configure your IDE.

Recursive Data

Recall Racket Lists

Using an itemization, structs and a self reference.

                       +-------------+
                      \/             |
;; A List of Number (LoN) is one of  |
;; - empty                           |
;; - (cons Number LoN) --------------+



;;lon-size: LoN -> Number

           +---------------+
          \/               |
(define (lon-size alon)    |
 (cond                     |
    [(empty? alon) 0]      |
    [(cons? alon) (+ 1 (lon-size (rest alon)))]))

Now in Java

Same idea, itemization, classes and a self reference

racketlist

Adding a size() operation will require the following modifications

  1. Add abstract method Integer size() on List

  2. Implement size() in each of the subclasses Empty and Cons

Here is the updated uml diagram

listsize

Lets also add the following methods

  1. a method called List add(Integer num) that adds num to the beginning of the list.

  2. a method called Integer last() that returns the last element of the list

  3. a method called List addToEnd(Integer element) that adds the given element to the end of the list

  4. a method called Integer elementAt(Integer index) that returns the element at index index

Exceptions

An exception is an event that occurs during execution of a program and disrupts the normal flow of execution.

Diagram of call stack and what exceptions do.

Three kinds of exceptions

  1. Checked Exceptions capture events that a well-written application must anticipate and recover from.

    • opening a file using the file name provided by the user and cannot find that file

    • opening a connection to a server whose address was provided to the program by an external entity

  2. Errors capture exceptional conditions external to the application that the application cannot anticipate or recover from.

    • error reading from the disk

    • error reading from the connection to the database

  3. Runtime Exception captures events that are exceptional and internal to the application. The application typically cannot anticipate them or recover from

    • passing in the incorrect values for the arguments to a function

    • logical errors; going over the length of a list

Java provides special syntax to mark code that could throw an exception as well as catching an exception.

try {
    // try to run these expressions/statements that may throw an exception
} catch (ExceptionType ext) {
    // catch an exception of type ExceptionType, bind it to the variable ext
    // perform actions to recover
} catch (ExceptionType2 ext2) {
    // catch an exception of type ExceptionType2, bind it to the variable ext2
    // perform actions to recover
} catch (IOException | SQLException ex) {
    // catching multiple exceptions for which we deal with in the same
    // way
}

Syntax to declare that a method can throw an exception (of any kind)

 public Integer method() throws RuntimeException, ArgumentException {
 }

Java’s exception hierarchy

javaexceptions

Lets create an exception and add it to the method Integer elementAt(Integer index).

Testing methods that throw exceptions in JUnit

JUnit can be used to check your methods even under conditions that throw exceptions. To tell JUnit that you expect an exception to the thrown we add to the @Test annotation the expected exception

@Test(expected=MyException.class)
public void testElementAt() {
    //call the method in a way that causes an the expected exception to be thrown
}

Abstract Data Types (ADT)s

After adding all these methods we should obtain a class diagram that looks like

listadt

There is a pattern here!

  • An Abstract Data Type (ADT) refers to a model that describes data by specifying the operations that we can perform on them.

  • A Data Structure refers to the concrete implementation (the details).

  • Users care about the ADT.

  • Developers care about the data structure (it’s memory consumption, speed, correctness) and how the inner workings of the data structure satisfy the ADT.

This is why you will often here about the public interface and the private interface of a data.

The List ADT

We are going to use (()) to denote an empty list. To show a non-empty list we will place the elements of the list inside the parenthesis separated by commas.

Operation Specification Comments

Empty(): List

Empty() = (())

Creates an empty list

isEmpty() : Boolean

(()).isEmpty() = true
((1,2,...)).isEmpty() = false
  • Calling isEmpty() on an empty list returns true

  • Calling isEmpty() on a non-empty list returns false

add(Integer ele) : List

(()).add(1) = ((1))
((1,2,...)).add(0) = ((0,1,2,...))
  • Calling add(e) on an empty list returns a one-element list with the element e

  • Calling add(e) on a non-empty list returns the original list with e pre-pended

contains(Integer ele) : Boolean

(()).contains(1) = false
((1,2,...)).contains(1) = true
((1,2,...)).contains(x) = ((2,...)).contains(x)
  • Calling contains(e) on an empty list returns false

  • Calling contains(e) on a non-empty list returns true if the element is in the list, false otherwise

size() : Integer

(()).size() = 0
((1,2,...)).size() = 1 + ((2,...)).size()
  • Calling size() on an empty list returns 0

  • Calling size() on a non-empty list returns the total number of element in the list

elementAt(Integer index) : Integer

(()).elementAt(n) = ERROR
((1,2,...)).elementAt(0) =  1
((1,2,...)).elementAt(n) =  ((2,...)).elementAt(n - 1)
  • Calling elementAt(n) on an empty list is an error

  • Calling elementAt(n) on a non-empty list returns the element at index n

tail() : List

(()).tail() = ERROR
((1,2,...)).tail() =  ((2,...))
  • Calling tail() on an empty list is an error

  • Calling tail() on a non-empty list returns the all but the first element in a list

The specification is a logical/mathematical description of what the operation does not how to do it.

  • The clients only cares about what each operation does.

  • The developer needs to

    1. understand what each operation should do

    2. design a program that implements the specification

Liskov substitutability principle

In our designs we have used the property that a subclass can be used in the place of the super class.

With ADTs we also need to ensure that the behaviour of our subclasses is compatible with the behaviour of our superclass.

Types as Sets

A Java class defines two aspects of a type

  1. data

  2. behaviour

With ADTs we are interested in behaviour. Informally, and for the purpose of this discussion, we will map Java Classes to a set of values. A value is in the set of a type iff the value is create using the class' constructor or a subclass' constructor.

We can use logical formulae to

  1. describe the conditions that we expect to hold true before calling a method. Conditions can refer

    1. the state of our object

    2. impose restrictions to the values passed as arguments

  2. describe the conditions that we expect to hold true as soon as the method returns. Conditions can refer

    1. to the return value

    2. the relations of the return value and the original values passed as argument and/or the state of the object when the method was called

These conditions are called contracts. . The formulae that describes the conditions we expect to hold true before calling a method is called a pre-condition. . The formulae that describes the conditions we expect to hold true immediately after the method returns is called a post-condition.

Operation Specification Contracts

Empty(): List

Empty() = (())

  1. pre-condition : true

  2. post-condition : (())

isEmpty() : Boolean

(()).isEmpty() = true
((1,2,...)).isEmpty() = false
  1. pre-condition : true

  2. post-condition :

result ->  this == (()) OR
!result -> this != (())

add(Integer ele) : List

(()).add(1) = ((1))
((1,2,...)).add(0) = ((0,1,2,...))
  1. pre-condition : true

  2. post-condition :

result.size() == old.size() + 1
AND
result.contains(ele)
AND
forall x( old.contains(x) &&  result.contains(x) )

contains(Integer ele) : Boolean

(()).contains(1) = false
((1,2,...)).contains(1) = true
((1,2,...)).contains(x) = ((2,...)).contains(x)
  1. pre-condition : true

  2. post-condition :

result ->
 (this.size() > 0  &&
  exists x (
   0 <= x <= this.size() &&
   this.elementAt(x).equals(ele)))

size() : Integer

(()).size() = 0
((1,2,...)).size() = 1 + ((2,...)).size()
  1. pre-condition : true

  2. post-condition :

result == |this|----

elementAt(Integer index) : Integer

(()).elementAt(n) = ERROR
((1,2,...)).elementAt(0) =  1
((1,2,...)).elementAt(n) =  ((2,...)).elementAt(n - 1)
  1. pre-condition : 0 ⇐ index ⇐ this.size()

  2. post-condition :

this.contains(result) AND
this.tail() n-1 .elementAt(0).equals(result)

tail() : List

(()).tail() = ERROR
((1,2,...)).tail() =  ((2,...))
  1. pre-condition : !this.isEmpty()

  2. post-condition :

result.size() + 1 == this.size() AND
result.add(this.elementAt(0)).equals(this)

Liskov’s substitution principle states that for two types T and S such that S is a subtype of T, we should be able to take existing client code that used T, and replacement with objects of type S without altering breaking the clients contracts (pre- and post-conditions).

So if we have

liskov

The logical implications that we need to ensure hold true are

  1. A.m preB.m pre and

  2. B.m postA.m post