Linked List II
The public
and private
keywords
- private method or field: invisible & inaccessible to other classes.
- Why use it?
- To provide protection from other classes
- To ensure that you can improve the implementation of a class without causing other classes that depend on it to fail
Example: EvilTamperer tries to get around the error checking code of the Date class by fiddling with the internals of a Date object.
public class Date { | public class EvilTamperer {
private int day; | public void tamper() {
private int month; | Date d = new Date(1, 1, 2006);
|
private void setMonth(int m) { | d.day = 100; // Foiled!!
month = m; | d.setMonth(0); // Foiled again!!
} | }
| }
public Date(int month, int day) { |
[Implementation with |
error-checking code here.] |
}
}
However, javac won't compile EvilTamperer.
Important definitions
- The interface of a class = a set of prototypes for public methods + descriptions of the method's behaviors
- An Abstract Data Type (ADT) is a class that has a well-defined interface, but its implementation details are firmly hidden from other classes. (so that you can change implementation without jeopardizing the programs that depend on it)
- An invariant is a fact about a data structure that is always true. (e.g. A Data object always stores a valid date.) Enforced by allowing access only through method calls.
- When to use private / getters, setters? Depending on whether there are invariants that need to be enforced.
- Not all classes are ADTs! Some classes are data storage units; no invariants; fields can be public.
The SList ADT
Third advantage: it enforces 2 invariants.
- "size" is always correct.
- A list is never circularly linked; there is always a tail node whose "next" reference is null.
Both goals accomplished by making sure that only SList methods can change the lists. SList ensures that
- The fields of SList (head & size) are "private"
- No method of SList returns an SListNode
Doubly-linked lists
Inserting/deleting at front of list is easy, but at the end of list takes a long time.
class DListNode { | class DList {
Object item; | private DListNode head;
DListNode next; | private DListNode tail;
DListNode prev; | }
} |
------------- ------------- -------------
| item| | item| | item|
head | -----| | -----| | -----| tail
----- |----- | 4 || |----- | 1 || |----- | 8 || -----
| . +->|| X | -----|<-----++-. | -----|<-----++-. | -----|<-+-. |
----- |----- -----| |----- -----| |----- -----| -----
|prev | .-++----->|prev | .-++----->|prev | X ||
| -----| | -----| | -----|
| next| | next| | next|
------------- ------------- -------------
Insert & delete items at both ends in constant running time.
Remove the tail node if there are at lease two items.
tail.prev.next = null;
tail = tail.prev;
Need a special case for a DList with no items and also a special case for a DList with one item.
Another implementation
Sentinel - A special node that does not represent an item.
class DList {
private DListNode head;
private int size;
}
// no null pointer in the prev/next field, making less special cases.
sentinel
------------- -----
| item|<---+-. |
--------------->| -----| -----
| |prev | X || head
| |----- -----|
| || .-+------+-----------------
| |----- -----| |
| ---------+------+-. || |
| | | next-----|<---------------+-----
| | ------------- | |
| v v |
---+--------- ------------- ------------- |
| | item| | item| | item| |
| | -----| | -----| | -----| |
|--+-- | 4 || |----- | 1 || |----- | 8 || |
|| . | -----|<-----++-. | -----|<-----++-. | -----| |
|----- -----| |----- -----| |----- -----| |
|prev | .-++----->|prev | .-++----->|prev | .-++---
| -----| | -----| | -----|
| next| | next| | next|
------------- ------------- -------------
DList invariants (with sentinel)
- For any DList,
d.head != null
. - For any DListNode,
x.prev != null
. - For any DListNode,
x.next != null
. - For any DListNode, if
x.next == y
, theny.prev == x
. - For any DListNode, if
x.prev == y
, theny.next == x
. - A DList "size" variable is # of DListNodes. (not counting sentinel, accessible from sentinel by sequence of
next
access)
Empty DList: Sentinel's prev & next fields points to itself.
Remove the last item from a DList.
public void removeBack(){
if (head.prev != head) {
head.prev = head.prev.prev;
head.prev.next = head;
size--;
}
}
Debugging - Don't need to check the invariants in the implementation of the class. One good way to debug a program is to figure out what are the invariants are, write code to check the invariants, and check them when you're debugging. (write code that runs in the debug mode)