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.
And here is the code for the classes
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;
}
}
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;
}
}
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
What want to check that
-
author1.first
is the same asauthor2.first
-
author1.last
is the same asauthor2.last
-
book1.title
is the same asbook2.title
-
author1.book
points tobook1
andauthor2.book
points tobook2
. The same instances (or objects) we already checked their titles. -
book1.author
points toauthor1
andbook2.author
points toauthor2
. The same instances (or objects) we already checked their first and last fields.
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;
}
}
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;
}
}
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
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;
}
}
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;
}
}
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;
}
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{}";
}
}
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
+ '}';
}
}
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;
}
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{}";
}
}
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.
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
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
.
-
if the list is empty, then replace
null
with a newCell
that contains the newly added value and thenext
field ofCell
is null -
if the list is non-empty,
-
then create a new
Cell
that contains the newly added value and -
the
next
field points to the currenthead
-
update
head
to point to the newly createdCell
size
We need to count the number of elements
-
if the list is empty, then 0
-
if the list is not empty,
-
iterate over each
Cell
object and stop when wenext
points tonull
isEmpty
If head
points to null
then we are empty, else we are not.
contains
-
iterate over each
Cell
and check the value inside theCell
-
if the value is equal to the argument,
true
-
if the value is not equal tot he argument keep going
-
if we hit
null
in our iteration, returnfalse
getFirst
-
if we are empty, then error
-
else from
head
grab the value of the firstCell
and return it
getLast
-
if we are empty, then error
-
else iterate over each
Cell
until we find theCell
whosenext
field points tonull
, return the value inside thatCell
object.
addAtIndex
-
if the index provided is outside the bounds of the list, then error
-
if the index is 0 then getFirst
-
iterate down
index
elements, we will refer to theCell
we have iterated to ascurrent
and -
create a new
Cell
-
add the argument as the value of the new
Cell
-
set
next
for the newCell
to thecurrent.next
-
make
current.next
point to our newly createdCell
.
remove
-
if the list is empty, we are done
-
else iterate over each element and
-
if the current list element is equal to the argument then
-
grab the previous list elements
-
point previous list elements
next
field tocurrent.next
removeAtIndex
-
if the index provided is out of bounds, then error
-
else iterate over
index
element, we will refer to theCell
we have iterated to ascurrent
and -
grab the previous list element from
current
-
point previous list elements
next
field tocurrent.next
Design our classes
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 +
'}';
}
}
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
-
For each add operation we will increment our element counter.
-
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.
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.,
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
-
before the execution of a
public
method and -
immediately after the execution of a
public
method.
We are allowed to temporarily break the object invariant
-
during the execution of a
public
method; -
before the execution of a
private
method -
during the execution of a
private
method -
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.