Relevant Reading Material

Chapters from How to Design Classes (HtDC)

  • Chapters 24, 25, 26, 27, 28

Circular Data

Let’s look at a simplified representation of books and their authors. We will first restrict our design so that a book has one author and an author has one book.

studentcd2

And here is the code for the classes

Author.java
package simpleauthor;

import java.util.Objects;

public class Author {

  private String first;
  private String last;
  private Book book;

  public Author(String first, String last, Book book) {
    this.first = first;
    this.last = last;
    this.book = book;
  }


  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Author author = (Author) other;
    return Objects.equals(first, author.first)
        && Objects.equals(last, author.last)
        && Objects.equals(book, author.book);
  }

  @Override
  public int hashCode() {
    return Objects.hash(first, last, book);
  }

  /**
   * Getter for property 'first'.
   *
   * @return Value for property 'first'.
   */
  public String getFirst() {
    return first;
  }

  /**
   * Setter for property 'first'.
   *
   * @param first Value to set for property 'first'.
   */
  public void setFirst(String first) {
    this.first = first;
  }

  /**
   * Getter for property 'last'.
   *
   * @return Value for property 'last'.
   */
  public String getLast() {
    return last;
  }

  /**
   * Setter for property 'last'.
   *
   * @param last Value to set for property 'last'.
   */
  public void setLast(String last) {
    this.last = last;
  }

  /**
   * Getter for property 'book'.
   *
   * @return Value for property 'book'.
   */
  public Book getBook() {
    return book;
  }

  /**
   * Setter for property 'book'.
   *
   * @param book Value to set for property 'book'.
   */
  public void setBook(Book book) {
    this.book = book;
  }
}
Book.java
package simpleauthor;

import java.util.Objects;

public class Book {

  private String title;
  private Author author;

  public Book(String title, Author author) {
    this.title = title;
    this.author = author;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Book book = (Book) other;
    return Objects.equals(title, book.title)
        && Objects.equals(author, book.author);
  }

  @Override
  public int hashCode() {
    return Objects.hash(title, author);
  }

  /**
   * Getter for property 'title'.
   *
   * @return Value for property 'title'.
   */
  public String getTitle() {

    return title;
  }

  /**
   * Setter for property 'title'.
   *
   * @param title Value to set for property 'title'.
   */
  public void setTitle(String title) {
    this.title = title;
  }

  /**
   * Getter for property 'author'.
   *
   * @return Value for property 'author'.
   */
  public Author getAuthor() {
    return author;
  }

  /**
   * Setter for property 'author'.
   *
   * @param author Value to set for property 'author'.
   */
  public void setAuthor(Author author) {
    this.author = author;
  }
}
AuthorTest.java
package simpleauthor;

import org.junit.Assert;
import org.junit.Test;

public class AuthorTest {

  private Author a1;
  private Author a2;
  private Author a3;
  private Book b1;
  private Book b2;
  private Book b3;


  @org.junit.Before
  public void setUp() throws Exception {
    this.a1 = new Author("A", "B", null);
    this.a2 = new Author("A", "B", null);
    this.a3 = new Author("X", "Y", null);

    this.b1 = new Book("B1", a1);
    this.a1.setBook(b1);

    this.b2 = new Book("B1", a2);
    this.a2.setBook(b2);

    this.b3 = new Book("Q", a3);
    this.a3.setBook(b3);
  }

  @Test
  public void testEquals() throws Exception {
    Assert.assertTrue(this.a1.equals(a2));
  }
}

Running our test results in a StackOverflowException.

Walk over stack creation on the board.

Break the cycle

Object instance for our Test
Figure 1. Object instance for our Test

What want to check that

  1. author1.first is the same as author2.first

  2. author1.last is the same as author2.last

  3. book1.title is the same as book2.title

  4. author1.book points to book1 and author2.book points to book2. The same instances (or objects) we already checked their titles.

  5. book1.author points to author1 and book2.author points to author2. The same instances (or objects) we already checked their first and last fields.

Author.java
package simpleauthornoloop;

import java.util.Objects;

public class Author {

  private String first;
  private String last;
  private Book book;

  public Author(String first, String last, Book book) {
    this.first = first;
    this.last = last;
    this.book = book;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Author author = (Author) other;

    return Objects.equals(first, author.first)
        && Objects.equals(last, author.last)
        && book.equalsBook(this, author.book, author);
  }

  public boolean equalsAuthor(Book thisBook, Author other, Book checkedBook){
    if (this == other) return true;
    if (other == null) return false;
    if (this.book != thisBook) return false;
    if (other.book != checkedBook) return false;
    return Objects.equals(first, other.first)
        && Objects.equals(last, other.last);
  }


  @Override
  public int hashCode() {
    return Objects.hash(first, last, book);
  }

  /**
   * Getter for property 'first'.
   *
   * @return Value for property 'first'.
   */
  public String getFirst() {
    return first;
  }

  /**
   * Setter for property 'first'.
   *
   * @param first Value to set for property 'first'.
   */
  public void setFirst(String first) {
    this.first = first;
  }

  /**
   * Getter for property 'last'.
   *
   * @return Value for property 'last'.
   */
  public String getLast() {
    return last;
  }

  /**
   * Setter for property 'last'.
   *
   * @param last Value to set for property 'last'.
   */
  public void setLast(String last) {
    this.last = last;
  }

  /**
   * Getter for property 'book'.
   *
   * @return Value for property 'book'.
   */
  public Book getBook() {
    return book;
  }

  /**
   * Setter for property 'book'.
   *
   * @param book Value to set for property 'book'.
   */
  public void setBook(Book book) {
    this.book = book;
  }
}
Book.java
package simpleauthornoloop;

import java.util.Objects;

public class Book {

  private String title;
  private Author author;

  public Book(String title, Author author) {
    this.title = title;
    this.author = author;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Book book = (Book) other;
    return Objects.equals(title, book.title)
        && author.equalsAuthor(this, book.author, book);
  }

  public boolean equalsBook(Author thisAuthor, Book other, Author checkedAuthor) {
    if (this == other) return true;
    if (other == null) return false;
    if (this.author != thisAuthor) return false;
    if (other.author != checkedAuthor) return false;
    return Objects.equals(title, other.title);
  }

  @Override
  public int hashCode() {
    return Objects.hash(title, author);
  }

  /**
   * Getter for property 'title'.
   *
   * @return Value for property 'title'.
   */
  public String getTitle() {

    return title;
  }

  /**
   * Setter for property 'title'.
   *
   * @param title Value to set for property 'title'.
   */
  public void setTitle(String title) {
    this.title = title;
  }

  /**
   * Getter for property 'author'.
   *
   * @return Value for property 'author'.
   */
  public Author getAuthor() {
    return author;
  }

  /**
   * Setter for property 'author'.
   *
   * @param author Value to set for property 'author'.
   */
  public void setAuthor(Author author) {
    this.author = author;
  }
}
AuthorTest.java
package simpleauthornoloop;

import org.junit.Assert;
import org.junit.Test;

public class AuthorTest {

  private Author a1;
  private Author a2;
  private Author a3;
  private Book b1;
  private Book b2;
  private Book b3;


  @org.junit.Before
  public void setUp() throws Exception {
    this.a1 = new Author("A", "B", null);
    this.a2 = new Author("A", "B", null);
    this.a3 = new Author("X", "Y", null);

    this.b1 = new Book("B1", a1);
    this.a1.setBook(b1);

    this.b2 = new Book("B1", a2);
    this.a2.setBook(b2);

    this.b3 = new Book("Q", a3);
    this.a3.setBook(b3);
  }

  @Test
  public void testEquals() throws Exception {
    Assert.assertTrue(this.a1.equals(a2));
  }
}

Let’s remove the restriction and allow for multiple authors and multiple books

atuhorsbooks
Author.java
package authorlistbooklist;

import java.util.Objects;

public class Author {

  private String first;
  private String last;
  private BookList book;

  public Author(String first, String last, BookList book) {
    this.first = first;
    this.last = last;
    this.book = book;
  }


  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Author author = (Author) other;
    return Objects.equals(first, author.first)
        && Objects.equals(last, author.last)
        && Objects.equals(book, author.book);
  }

  public boolean equalsAuthor(Author other){
    if (this == other) return true;
    if (other == null) return false;
    return Objects.equals(first, other.first)
        && Objects.equals(last, other.last);
  }

  @Override
  public int hashCode() {
    return Objects.hash(first, last, book);
  }

  @Override
  public String toString() {
    return "Author{"
        + "first='" + first + '\''
        + ", last='" + last + '\''
        + ", book=" + book
        + '}';
  }

  /**
   * Getter for property 'first'.
   *
   * @return Value for property 'first'.
   */
  public String getFirst() {
    return first;
  }

  /**
   * Setter for property 'first'.
   *
   * @param first Value to set for property 'first'.
   */
  public void setFirst(String first) {
    this.first = first;
  }

  /**
   * Getter for property 'last'.
   *
   * @return Value for property 'last'.
   */
  public String getLast() {
    return last;
  }

  /**
   * Setter for property 'last'.
   *
   * @param last Value to set for property 'last'.
   */
  public void setLast(String last) {
    this.last = last;
  }

  /**
   * Getter for property 'book'.
   *
   * @return Value for property 'book'.
   */
  public BookList getBook() {
    return book;
  }

  /**
   * Setter for property 'book'.
   *
   * @param book Value to set for property 'book'.
   */
  public void setBook(BookList book) {
    this.book = book;
  }


}
Book.java
package authorlistbooklist;

import java.util.Objects;

public class Book {

  private String title;
  private AuthorList author;

  public Book(String title, AuthorList author) {
    this.title = title;
    this.author = author;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Book book = (Book) other;
    return Objects.equals(title, book.title)
        && Objects.equals(author, book.author);
  }

  public boolean equalsBook(Book other) {
    if (this == other) return true;
    if (other == null) return false;
    return Objects.equals(title, other.title);
  }

  @Override
  public int hashCode() {
    return Objects.hash(title, author);
  }

  @Override
  public String toString() {
    return "Book{"
        + "title='" + title + '\''
        + ", author=" + author
        + '}';
  }

  /**
   * Getter for property 'title'.
   *
   * @return Value for property 'title'.
   */
  public String getTitle() {

    return title;
  }

  /**
   * Setter for property 'title'.
   *
   * @param title Value to set for property 'title'.
   */
  public void setTitle(String title) {
    this.title = title;
  }

  /**
   * Getter for property 'author'.
   *
   * @return Value for property 'author'.
   */
  public AuthorList getAuthor() {
    return author;
  }

  /**
   * Setter for property 'author'.
   *
   * @param author Value to set for property 'author'.
   */
  public void setAuthor(AuthorList author) {
    this.author = author;
  }



}
AuthorList.java
package authorlistbooklist;

/**
 * Created by therapon on 6/12/16.
 */
public interface AuthorList extends List {

  static AuthorList create() {
    return new MtAuthorList();
  }
  AuthorList add(Author element);
  Boolean contains(Author element);
  AuthorList tail() throws AuthorListIllegalOperationException;
}
MtAuthorList.java
package authorlistbooklist;

/**
 * Created by therapon on 6/12/16.
 */
public class MtAuthorList implements AuthorList {

  @Override
  public AuthorList add(Author element) {
    return new ConsAuthorList(element, this);
  }

  @Override
  public Boolean contains(Author element) {
    return false;
  }

  @Override
  public AuthorList tail() throws AuthorListIllegalOperationException {
    throw new AuthorListIllegalOperationException("Called tail on empty list");
  }

  @Override
  public Integer size() {
    return 0;
  }

  @Override
  public Boolean isEmpty() {
    return true;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    else return true;
  }

  @Override
  public int hashCode() {
    return 42;
  }

  @Override
  public String toString() {
    return "MtAuthorList{}";
  }


}
ConsAuthorList.java
package authorlistbooklist;

import java.util.Objects;

/**
 * Created by therapon on 6/12/16.
 */
public class ConsAuthorList implements AuthorList {

  private Author first;
  private AuthorList rest;


  public ConsAuthorList(Author first, AuthorList rest) {
    this.first = first;
    this.rest = rest;
  }


  @Override
  public AuthorList add(Author element) {
    return new ConsAuthorList(element, this);
  }

  @Override
  public Boolean contains(Author element) {
    if (this.first.equals(element)) {
      return true;
    } else {
      return this.rest.contains(element);
    }
  }

  @Override
  public AuthorList tail() throws AuthorListIllegalOperationException {
    return this.rest;
  }

  @Override
  public Integer size() {
    return 1 + this.rest.size();
  }

  @Override
  public Boolean isEmpty() {
    return false;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    ConsAuthorList that = (ConsAuthorList) other;
    return Objects.equals(first, that.first)
        && Objects.equals(rest, that.rest);
  }

  @Override
  public int hashCode() {
    return Objects.hash(first, rest);
  }

  @Override
  public String toString() {
    return "ConsAuthorList{"
        + "first=" + first
        + ", rest=" + rest
        + '}';
  }


}
BookList.java
package authorlistbooklist;

/**
 * Created by therapon on 6/12/16.
 */
public interface BookList extends List {

  static BookList create() {
    return new MtBookList();
  }

  BookList add(Book element);

  Boolean contains(Book element);

  BookList tail() throws BookListIllegalOperationException;

}
MtBookList.java
package authorlistbooklist;

/**
 * Created by therapon on 6/12/16.
 */
public class MtBookList implements BookList {


  @Override
  public BookList add(Book element) {
    return new ConsBookList(element, this);
  }

  @Override
  public Boolean contains(Book element) {
    return false;
  }

  @Override
  public BookList tail() throws BookListIllegalOperationException {
    throw new BookListIllegalOperationException("Called tail on an empty list");
  }

  @Override
  public Integer size() {
    return 0;
  }

  @Override
  public Boolean isEmpty() {
    return true;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    else return true;
  }

  @Override
  public int hashCode() {
    return 41;
  }

  @Override
  public String toString() {
    return "MtBookList{}";
  }


}
ConsBookList.java
package authorlistbooklist;

import java.util.Objects;

/**
 * Created by therapon on 6/12/16.
 */
public class ConsBookList implements BookList {

  private Book first;
  private BookList rest;

  public ConsBookList(Book element, BookList rest) {
    this.first = element;
    this.rest = rest;
  }


  @Override
  public BookList add(Book element) {
    return new ConsBookList(element, this);
  }

  @Override
  public Boolean contains(Book element) {
    if (this.first.equals(element)) {
      return true;
    } else {
      return this.rest.contains(element);
    }
  }

  @Override
  public BookList tail() throws BookListIllegalOperationException {
    return this.rest;
  }

  @Override
  public Integer size() {
    return 1 + this.rest.size();
  }

  @Override
  public Boolean isEmpty() {
    return null;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    ConsBookList that = (ConsBookList) other;
    return Objects.equals(first, that.first)
        && Objects.equals(rest, that.rest);
  }

  @Override
  public int hashCode() {
    return Objects.hash(first, rest);
  }

  @Override
  public String toString() {
    return "ConsBookList{"
        + "first=" + first
        + ", rest=" + rest
        + '}';
  }


}
Go over possible definitions of equality using object diagrams.

Singly Linked Lists

Similar to our Cons lists but we are going to use mutation instead.

List.java
package sllist;

/**
 * Linked List interface.
 */
public interface List {

  /**
   * Create an empty list.
   *
   * @return  the empty list
   */
  static List create(){
    return new SLList();
  }

  /**
   * Add {@code element} as the first item in the list.
   *
   * @param element new element to be added to beggining (index 0) of the list.
   */
  void add(Integer element);

  /**
   * Return the number of elements currently in the list.
   *
   * @return total number of elements in the list
   */
  Integer size();

  /**
   * Check if the list is empty.
   *
   * @return true if this is an empty list, false otherwise.
   */
  Boolean isEmpty();

  /**
   * Check if {@code element} is already in the list.
   *
   * @param element the element we want to check
   * @return true if element is in the list, false otherwise
   */
  Boolean contains(Integer element);
  /**
   * Getter for property 'first'.
   *
   * @return Value for property 'first'.
   */
  Integer getFirst();
  /**
   * Getter for property 'last'.
   *
   * @return Value for property 'last'.
   */
  Integer getLast();

  /**
   * Add {@code element} at location {@code index} in the list.
   *
   * Where:
   *
   * <pre>
   *   size() >= index >=0
   * </pre>
   *
   * @param index location to which the element will be added in this list.
   * @param element the new element to add.
   */
  void addAtIndex(Integer index, Integer element);

  /**
   * Remove {@code element} from the list. If the element is not found, then
   * the list remains unchanged.
   *
   * @param element value to be deleted from the list
   */
  void remove(Integer element);

  /**
   * Remove the element found at {@code index} in the list.
   *
   * Where:
   *
   * <pre>
   *   size() > index >= 0
   * </pre>
   * @param index location in the list to be removed.
   */
  void removeAtIndex(Integer index);

}
Follow the Design Recipe!

Let’s make examples and walk through them

Empty singly linked list.
Figure 2. Empty singly linked list.
One element singly linked list.
Figure 3. One element singly linked list.
More than one element singly liked list.
Figure 4. More than one element singly liked list.

Let’s walk through want needs to happen for each operation.

Perform the steps on the whiteboard with using the object diagrams from our examples

add

So we are adding the new element at the start of the list on the side of head.

  1. if the list is empty, then replace null with a new Cell that contains the newly added value and the next field of Cell is null

  2. if the list is non-empty,

  3. then create a new Cell that contains the newly added value and

  4. the next field points to the current head

  5. update head to point to the newly created Cell

size

We need to count the number of elements

  1. if the list is empty, then 0

  2. if the list is not empty,

  3. iterate over each Cell object and stop when we next points to null

isEmpty

If head points to null then we are empty, else we are not.

contains

  1. iterate over each Cell and check the value inside the Cell

  2. if the value is equal to the argument, true

  3. if the value is not equal tot he argument keep going

  4. if we hit null in our iteration, return false

getFirst

  1. if we are empty, then error

  2. else from head grab the value of the first Cell and return it

getLast

  1. if we are empty, then error

  2. else iterate over each Cell until we find the Cell whose next field points to null, return the value inside that Cell object.

addAtIndex

  1. if the index provided is outside the bounds of the list, then error

  2. if the index is 0 then getFirst

  3. iterate down index elements, we will refer to the Cell we have iterated to as current and

  4. create a new Cell

  5. add the argument as the value of the new Cell

  6. set next for the new Cell to the current.next

  7. make current.next point to our newly created Cell.

remove

  1. if the list is empty, we are done

  2. else iterate over each element and

  3. if the current list element is equal to the argument then

  4. grab the previous list elements

  5. point previous list elements next field to current.next

removeAtIndex

  1. if the index provided is out of bounds, then error

  2. else iterate over index element, we will refer to the Cell we have iterated to as current and

  3. grab the previous list element from current

  4. point previous list elements next field to current.next

Design our classes

Class diagram for Singly Linked List.
Figure 5. Class diagram for Singly Linked List.
SLList.java
package sllist;


import java.util.Objects;

/**
 * Created by therapon on 6/12/16.
 */
public class SLList implements List {

  private Cell head;

  public SLList() {
    this.head = null;
  }

  @Override
  public void add(Integer element) {
    if (isEmpty()) {
      setHead(new Cell(element, null));
    } else {
      setHead(new Cell(element, getHead()));
    }
  }

  @Override
  public Integer size() {
    Integer count = 0;
    Cell current = getHead();

    while (current != null) {
      count++;
      current = current.getNext();
    }
    return count;
  }

  @Override
  public Boolean isEmpty() {
    return head == null;
  }

  @Override
  public Boolean contains(Integer element) {
    Cell current = getHead();
    while (current != null) {
      if (current.getVal().equals(element)) {
        return true;
      } else {
        current = current.getNext();
      }
    }
    return false;
  }

  @Override
  public Integer getFirst() {
    if (isEmpty()) {
      throw new ListIllegalOperationException("Called getFirst on empty list");
    }
    return getHead().getVal();
  }

  @Override
  public Integer getLast() {
    if (isEmpty()) {
      throw new ListIllegalOperationException("Attempted to get last element from an empty list");
    } else {
      Cell current = getHead();
      while (current.getNext() != null) {
        current = current.getNext();
      }
      return current.getVal();
    }
  }

  @Override
  public void addAtIndex(Integer index, Integer element) {
    if (index < 0 || index > this.size()) {
      throw new ListIllegalOperationException("Invalid index for list");
    }

    if (index == 0) {
      add(element);
    } else {

      Cell current = getHead();
      while (index > 1) {
        current = current.getNext();
        index = index - 1;
      }
      Cell newEntry = new Cell(element, current.getNext());
      current.setNext(newEntry);
    }
  }


  @Override
  public void remove(Integer element) {
    if (isEmpty()) return;
    else if (getHead().getVal().equals(element)) {
      setHead(getHead().getNext());
    } else {
      removeHelper(getHead(), getHead().getNext(), element);
    }
  }

  private void removeHelper(Cell previous, Cell current, Integer element) {
    if (current == null) return;
    if (current.getVal().equals(element)) {
      previous.setNext(current.getNext());
    } else {
      removeHelper(current, current.getNext(), element);
    }
  }

  @Override
  public void removeAtIndex(Integer index) {
    if (index < 0 || index > this.size() - 1) {
      throw new ListIllegalOperationException("Invalid index for list");
    }
    if (isEmpty()) return;
    else if (index == 0) {
      setHead(getHead().getNext());
    } else {
      removeAtIndexHelper(getHead(), getHead().getNext(), index - 1);
    }
  }

  private void removeAtIndexHelper(Cell previous, Cell current, Integer index) {
    while (index != 0) {
      index = index - 1;
      previous = current;
      current = current.getNext();
    }
    previous.setNext(current.getNext());
  }

  /**
   * Getter for property 'head'.
   *
   * @return Value for property 'head'.
   */

  public Cell getHead() {
    return head;
  }

  /**
   * Setter for property 'head'.
   *
   * @param head Value to set for property 'head'.
   */
  public void setHead(Cell head) {
    this.head = head;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    SLList slList = (SLList) other;
    return Objects.equals(head, slList.head);
  }

  @Override
  public int hashCode() {
    return Objects.hash(head);
  }

  @Override
  public String toString() {
    return "SLList{" +
        "head=" + head +
        '}';
  }


}
Cell.java
package sllist;

import java.util.Objects;

/**
 * Created by therapon on 6/12/16.
 */
class Cell {


  private Integer val;
  private Cell next;

  public Cell(){}
  public Cell(Integer val, Cell next) {
    this.val = val;
    this.next = next;
  }



  /**
   * Getter for property 'next'.
   *
   * @return Value for property 'next'.
   */
  public Cell getNext() {
    return next;
  }

  /**
   * Setter for property 'next'.
   *
   * @param next Value to set for property 'next'.
   */
  public void setNext(Cell next) {
    this.next = next;
  }


  /**
   * Getter for property 'val'.
   *
   * @return Value for property 'val'.
   */
  public Integer getVal() {
    return val;
  }

  /**
   * Setter for property 'val'.
   *
   * @param val Value to set for property 'val'.
   */
  public void setVal(Integer val) {
    this.val = val;
  }


  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    Cell cell = (Cell) other;
    return Objects.equals(val, cell.val)
        && Objects.equals(next, cell.next);
  }

  @Override
  public int hashCode() {
    return Objects.hash(val, next);
  }

  @Override
  public String toString() {
    return "Cell{" +
        "val=" + val +
        ", next=" + next +
        '}';
  }
}

Caching

Now that we know how to update fields, there is another way to implement size. We could keep a private field that acts as our element counter. Our code will initialize this field to 0 on creation of our list and then

  1. For each add operation we will increment our element counter.

  2. For each remove operation we will decrement our element counter.

In this implementation the method size will simply return the value of our element counter field.

SLList with element counter
package sllistcount;


import java.util.Objects;

/**
 * List implementation as a singly linked list.
 * <p>
 * Object invariant:
 * <pre>
 *   elementCount = |this|
 *
 * </pre>
 */
public class SLList implements List {

  private Cell head;
  private Integer elementCount;

  public SLList() {
    this.head = null;
    this.elementCount = 0;
  }

  @Override
  public void add(Integer element) {
    if (isEmpty()) {
      setHead(new Cell(element, null));
    } else {
      setHead(new Cell(element, getHead()));
    }
    elementCount = elementCount + 1;
  }

  @Override
  public Integer size() {
    return elementCount;
  }

  @Override
  public Boolean isEmpty() {
    return head == null;
  }

  @Override
  public Boolean contains(Integer element) {
    Cell current = getHead();
    while (current != null) {
      if (current.getVal().equals(element)) {
        return true;
      } else {
        current = current.getNext();
      }
    }
    return false;
  }

  @Override
  public Integer getFirst() {
    if (isEmpty()) {
      throw new ListIllegalOperationException("Called getFirst on empty list");
    }
    return getHead().getVal();
  }

  @Override
  public Integer getLast() {
    if (isEmpty()) {
      throw new ListIllegalOperationException("Attempted to get last element from an empty list");
    } else {
      Cell current = getHead();
      while (current.getNext() != null) {
        current = current.getNext();
      }
      return current.getVal();
    }
  }

  @Override
  public void addAtIndex(Integer index, Integer element) {
    if (index < 0 || index > this.size()) {
      throw new ListIllegalOperationException("Invalid index for list");
    }

    if (index == 0) {
      add(element);

    } else {
      Cell current = getHead();
      while (index > 1) {
        current = current.getNext();
        index = index - 1;
      }
      Cell newEntry = new Cell(element, current.getNext());
      current.setNext(newEntry);
    }
    elementCount = elementCount + 1;
  }


  @Override
  public void remove(Integer element) {
    if (isEmpty()) return;
    else if (getHead().getVal().equals(element)) {
      setHead(getHead().getNext());
      elementCount = elementCount - 1;
    } else {
      removeHelper(getHead(), getHead().getNext(), element);
    }
  }

  private void removeHelper(Cell previous, Cell current, Integer element) {
    while (current != null) {
      if (current.getVal().equals(element)) {
        previous.setNext(current.getNext());
        elementCount = elementCount - 1;
      }
      previous = current;
      current = current.getNext();
    }
  }

  @Override
  public void removeAtIndex(Integer index) {
    if (index < 0 || index > this.size() - 1) {
      throw new ListIllegalOperationException("Invalid index for list");
    }
    if (isEmpty()) return;
    else if (index == 0) {
      setHead(getHead().getNext());
      elementCount = elementCount - 1;
    } else {
      removeAtIndexHelper(getHead(), getHead().getNext(), index - 1);
    }
  }

  private void removeAtIndexHelper(Cell previous, Cell current, Integer index) {
    while (index != 0) {
      index = index - 1;
      previous = current;
      current = current.getNext();
    }
    previous.setNext(current.getNext());
    elementCount = elementCount - 1;
  }

  /**
   * Getter for property 'head'.
   *
   * @return Value for property 'head'.
   */

  public Cell getHead() {
    return head;
  }

  /**
   * Setter for property 'head'.
   *
   * @param head Value to set for property 'head'.
   */
  public void setHead(Cell head) {
    this.head = head;
  }

  @Override
  public boolean equals(Object other) {
    if (this == other) return true;
    if (other == null || getClass() != other.getClass()) return false;
    SLList slList = (SLList) other;
    return Objects.equals(head, slList.head);
  }

  @Override
  public int hashCode() {
    return Objects.hash(head);
  }

  @Override
  public String toString() {
    return "SLList{" +
        "head=" + head +
        '}';
  }


}

Object invariants

Our implementation of size that relies on elementCounter imposes a condition on our objects. The condition is that at any given point in time during execution and for each object of type SLList the value stored in the field elementCount must be the number of elements currently in that list, i.e.,

\[ elementCount = |this|\]

These kinds of conditions that need to hold for each object and for the lifespan of the object are called object invariants. More specifically, our object invariants need to hold true

  1. before the execution of a public method and

  2. immediately after the execution of a public method.

We are allowed to temporarily break the object invariant

  1. during the execution of a public method;

  2. before the execution of a private method

  3. during the execution of a private method

  4. immediately after the execution of a private method

These are essentially the windows of time that our code will update our object’s state in order to make our object invariant hold true.