One of the most standard abstract data types is that of a List.
A list is an ordered collection of items - ordered means that there is an order to the items in the list, that is, there is a first item, a second item, and so on.
The list is not necessarily "sorted".
Lists may contain duplicate values.
One can typically query the element at a particular index in the list, add elements to a list, remove elements from a list, and get the length of a list.
There are in fact multiple levels of abstraction.
The above description applies to pretty much anything a computer scientist would call a "list".
The ability to change the size of the list by adding or removing elements is the key thing that distinguishes lists from arrays (lists can be, and in many cases are, implemented using arrays).
But the description is at a very high level, and leaves out certain details that vary a lot between different libraries and programming languages, for example whether lists are mutable entities or the above abstract operations construct a new, slightly changed copy of the original list every time.
In Java, the typical choice is for things to be mutable, and so a standard list interface might look as follows:
public interface List {
int size();
Object getElement(int index);
void add(Object o);
void remove(Object o);
}
This interface describes the above basic operations.
Using void as the return type of the add and remove operations indicates that it is the particular list object itself that is changing when those methods are called.
A more "functional" version of this code would return a new List object for those methods.
There are multiple ways to implement this interface.
Here, we choose a linked list.
public class LinkedList implements List {
class ListNode {
Object value;
ListNode next;
ListNode(Object v, ListNode n) {
value = v;
next = n;
}
int size() {
if(this.next==null) {
return 1;
}
return 1 + this.next.size();
}
void add(Object element) {
if(next == null) {
next = new ListNode(element, null);
} else {
next.add(element);
}
}
ListNode remove(Object element) {
if(Objects.equals(value, element)) {
return next;
} else if(next!=null) {
next = next.remove(element);
}
return this;
}
}
ListNode head = null;
@Override
public int size() {
if (head == null) {
return 0;
} else {
return head.size();
}
}
/**
*
* @param index < size()
* @return
*/
@Override
public Object getElement(int index) {
try {
ListNode current = head;
while (index >= 0) {
index = index - 1;
current = current.next;
}
return current.value;
} catch(NullPointerException e) {
throw new IndexOutOfBoundsException();
}
}
@Override
public void add(Object o) {
if(head!=null) {
head.add(o);
} else {
head = new ListNode(o, null);
}
}
@Override
public void remove(Object o) {
if(head!=null) {
head=head.remove(o);
}
}
}
The above class implements our List interface. It uses an inner class to have a linked list implementation inside - the advantage for the user here is that they do not need to distinguish
the base case from the recursive case themselves anymore, the list implementation does this for them.
The methods in the class are implemented with a mix of recursion and iteration for demonstration purposes.
In general, one should use iteration here in Java.
One can now write code using just the List interface.
The only point at which the LinkedList class comes in is when the particular list object is constructed, though in that place, it could also be replaced with any other class implementing the List interface.