1. List Overview
The Collection interface we described earlier does not actually have a direct implementation class. List is a type of container, indicating the meaning of the List. If we do not know how much data is stored, we can use List to store data. For example, a scenario mentioned above. We want to save the current online user information of an application system. We can use a List for storage. The biggest feature of List is that it can automatically dynamically change the container size based on the inserted data volume. Next, let's take a look at some common methods of the List interface.
2. Common Methods
List is the meaning of a List. It is a Collection that inherits the Collection interface to define an ordered set that allows repeated items. This interface not only processes part of the list, but also adds location-oriented operations. List stores objects in the order they enter, without sorting or editing them. In addition to all the methods of the Collection interface, it also has some other methods.
Location-oriented operations include the ability to insert an element or Collection, and the ability to obtain, remove, or change an element. You can search for an element in the List from the header or tail of the List. If an element is found, the position of the report element is also located.
Void add (int index, Object element): add an Object element to the position index.
Boolean addAll (int index, Collection collection): Add all elements in the collection of the container after the index position
Object get (int index): retrieves the element at the position where the subscript is index.
Int indexOf (Object element): searches for the location where the Object element first appears in the List.
Int lastIndexOf (Object element): searches for the last position of an Object element in the List.
Object remove (int index): deletes the element at the index position.
Object set (int index, Object element): replaces the Object at the index position with the element and returns the old element.
First, let's look at the following table:
Brief Introduction
Implementation
Operation Features
Member requirements
List
Provides Random Access to members based on indexes.
ArrayList
Provides quick access to index-based members, which provides better support for adding and deleting tail members.
Objects whose members can subclass any Object
Shortlist
It provides better support for adding and deleting members at any position in the list, but poor support for access to indexed members.
Objects whose members can subclass any Object
There are two common List implementations in the "collection framework": ArrayList and sorted List. Which of the two lists is implemented depends on your specific needs. To support random access without inserting or removing elements at any position except the tail, ArrayList provides an optional set. However, if you want to frequently add and remove elements from the middle of the list, and as long as the elements in the list are accessed sequentially, the sorted list implementation is better.
Take ArrayList as an example. Let's look at a simple example:
In this example, we store the 12 months in the ArrayList, use a loop, and use the get () method to retrieve all the objects in the list.
The handler list adds some methods to process the elements at both ends of the List (only new methods are displayed ):
With these new methods, you can easily treat the consumer list as a stack, queue, or other endpoint-oriented data structure.
Let's look at another example of using queue list to implement a simple queue:
Import java. util .*;
Public class ListExample {
Public static void main (String args []) {
Queue list queue = new queue list ();
Queue. addFirst ("Bernadine ");
Queue. addFirst ("Elizabeth ");
Queue. addFirst ("Gene ");
Queue. addFirst ("Elizabeth ");
Queue. addFirst ("Clara ");
System. out. println (queue );
Queue. removeLast ();
Queue. removeLast ();
System. out. println (queue );
}
}
The running program generates the following output. Note that, unlike Set, List can be repeated.
[Clara, Elizabeth, Gene, Elizabeth, Bernadine]
[Clara, Elizabeth, Gene]
This program demonstrates the use of a specific List class. The first part is to create a List supported by ArrayList. After filling in the list, you can get the specific entries. In the example, the queue list part treats the queue list as a queue, adds items from the queue header, and removes them from the tail.
The List interface not only traverses the entire List in a location-friendly manner, but also can process the subset of the Set:
ListIterator listIterator (): returns a ListIterator Downloader. The default start position is 0.
ListIterator listIterator (int startIndex): returns a ListIterator downloader, starting from startIndex.
List subList (int fromIndex, int toIndex): returns a subList, where elements are stored as an element before fromIndex to toIndex.
When processing subList (), the elements in fromIndex are in the subList, while those in toIndex are not. This is important. The following for-loop test cases roughly reflect this point:
For (int I = fromIndex; I <toIndex; I ++ ){
// Process element at position I
}
In addition, we should also note that changes to the sublist (such as add (), remove (), and set () calls) also affect the underlying List.
ListIterator Interface
The ListIterator interface inherits the Iterator interface to support adding or modifying elements in the underlying set and bidirectional access.
The following source code demonstrates the reverse loop in the list. Note that the ListIterator is located after the end of the list (list. size (), because the subscript of the first element is 0.
List list = ...;
ListIterator iterator = list. listIterator (list. size ());
While (iterator. hasPrevious ()){
Object element = iterator. previous ();
// Process element
}
Normally, you do not need ListIterator to change the direction of elements in a traversal set-forward or backward. Although technically feasible, next () is called immediately after previous (), and the same element is returned. Reverse the order of calling next () and previous () with the same result.
Let's look at a List Example:
Import java. util .*;
Public class ListIteratorTest {
Public static void main (String [] args ){
List list = new ArrayList ();
List. add ("aaa ");
List. add ("bbb ");
List. add ("ccc ");
List. add ("ddd ");
System. out. println ("subscript 0 start:" + list. listIterator (0). next (); // next ()
System. out. println ("subscript 1 start:" + list. listIterator (1). next ());
System. out. println ("sub-List 1-3:" + list. subList (1, 3); // sub-List
ListIterator it = list. listIterator (); // starts from subscript 0 by default
// Implicit cursor attribute add operation, insert to the front of the current cursor
It. add ("sss ");
While (it. hasNext ()){
System. out. println ("next Index =" + it. nextIndex () + ", Object =" + it. next ());
}
// Set attributes
ListIterator it1 = list. listIterator ();
It1.next ();
It1.set ("ooo ");
ListIterator it2 = list. listIterator (list. size (); // subscript
While (it2.hasPrevious ()){
System. out. println ("previous Index =" + it2.previusindex () + ", Object =" + it2.previous ());
}
}
}
The execution result of the program is:
Subscript 0 start: aaa
Subscript 1 start: bbb
Sub-List 1-3: [bbb, ccc]
Next Index = 1, Object = aaa
Next Index = 2, Object = bbb
Next Index = 3, Object = ccc
Next Index = 4, Object = ddd
Previous Index = 4, Object = ddd
Previous Index = 3, Object = ccc
Previous Index = 2, Object = bbb
Previous Index = 1, Object = aaa
Previous Index = 0, Object = ooo
We need to explain the add () operation a little more. Adding an element will immediately add the new element to the front of the implicit cursor. Therefore, calling previous () after adding an element returns a new element, but calling next () does not work. The next element before the add operation is returned. Shows the display method of the following mark:
We have learned the basic usage of List. Next we will take a closer look at the implementation principle of List so that we can better understand the collection.
3. Implementation Principle
As mentioned above, Collection is implemented based on arrays. Next we will take the ArrayList as an example to briefly analyze the implementation method of the ArrayList list. First, let's take a look at its constructor.
The following table describes the APIS provided by SUN:
ArrayList () Constructs an empty list with an initial capacity of ten.
ArrayList (Collection c) Constructs a list containing the elements of the specified collection, in the order they are returned by the collection's iterator.
ArrayList (int initialCapacity) Constructs an empty list with the specified initial capacity.
The first constructor ArrayList () and the second constructor ArrayList (Collection c) follow the Collection interface documentation to provide two constructor functions, one with no parameter and the other with another Collection.
There are 3rd constructors:
ArrayList (int initialCapacity) is an important constructor implemented by ArrayList. Although we do not use ArrayList, a constructor calls this parameter: initialCapacity constructor. The parameter initialCapacity indicates the initial capacity of the ArrayList we constructed. If you call the default constructor, it indicates that the initialCapacity = 10 method is called by default to construct an ArrayList object.
To better understand the initialCapacity Parameter Concept, Let's first look at the implementation method of ArrayList in the source code provided by Sun. Let's take a look at its attributes:
ArrayList inherits AbstractList. Let's take a look at the attributes in ArrayList.
ArrayList contains two attributes:
Private transient ObjectelementData [];
Private int size;
Here, array: elementData [] is the core attribute of the List Implementation: array. We use this array to store the data in the set. Our initialization parameter is the length of the array during construction, that is, the length attribute of the array is the initialCapacity parameter.
Keys: transient indicates that the modified attribute is not part of the object's persistent State and will not be automatically serialized.
2nd attributes: size indicates the quantity of real data in the list.
Let's take a look at the ArrayList constructor to learn more about ArrayList Based on arrays.
From the source code, we can see that the default constructor calls a constructor with parameters:
PublicArrayList (int initialCapacity)
However, the initialCapacity parameter is 10.
We mainly look at the constructor ArrayList (int initialCapacity. You can see:
This. elementData = new Object [initialCapacity];
We use the initialCapacity parameter to create an Object array. All the data we store in this set Object is stored in this Object array.
Let's look at the source code of another constructor:
Here, we first look at the implementation form of the size () method. It returns the size attribute value. Then let's look at another constructor, public ArrayList (Collection c). The constructor stores the elements in another container object in the current List object.
We can see that, first, we set the length of the size attribute of the current List object by calling the size () method of another container object C.
Next, initialize the elementData array. The initial size is 1.1 times the size of the original container. Finally, we use the Object [] toArray (Object [] a) method in the container interface to store all objects in the current container into the new array elementData. This completes the creation of an ArrayList.
There may be a problem, that is, the ArrayList we created is implemented using an array, but once the length of the array is determined, it cannot be changed. When we add elements to the ArrayList object, there is no length limit. At this time, the elementData attribute in the ArrayList must have a mechanism for dynamic capacity expansion. The following code describes the extension mechanism:
This method is used to determine whether the current array needs to be resized and how much it should be resized. The attribute: modCount is inherited from the parent class. It indicates the number of resizing, clearing, and removing operations performed on the elementData array by the current object. This attribute is equivalent to an operation log number for the current List object. We mainly look at the following code implementation:
1. Obtain the oldCapacity of the current elementData attribute.
2. determine the size of the oldCapacity and minCapacity parameters to determine whether the scale-up is required.
If minCapacity is greater than oldCapacity, we can expand the current List object. The scale-up policy is: The one with a larger value between (oldCapacity * 3)/2 + 1 and minCapacity. Then, use the array COPY method to transfer the previously stored data to the new array object.
If minCapacity is not greater than oldCapacity, resizing is not performed.
Next let's take a look at how the ensureCapacity method is used:
Both add methods on are used to add elements to the List. Each time an element is added, we need to determine whether to resize the current array.
We mainly look at the public booleanadd (Object o) method. We can find that when adding an element to the container, we will first determine whether expansion is required. Because only one element is added, the size of the expansion is determined by the current size + 1. Then, add the newly added elements to the array elementData.
The second method publicboolean addAll (Collection c) is the same principle. Place the new element after the elementData array. At the same time, the size attribute of the current List object is changed.
Other methods in similar lists are also operated based on arrays. If you are interested, you can see more implementation methods in the source code.
Finally, let's look at how to determine whether an object already exists in the collection:
From the source code, we can see that the public boolean contains (Object elem) method is to call the public int indexOf (Object elem) method to determine whether an Object elem exists in the set. Let's take a look at the specific implementation of the indexOf method.
First, let's determine whether the elem object is null. If it is null, traverse the array elementData to return the first position with null.
If elem is not null, we traverse the array elementData and call the equals () method of the elem object to obtain the location of the First Equal element.
Here we can find that the ArrayList is used to determine whether an object is contained, and the equals () method implemented by each object is called. In the previous advanced features, we can know that to determine whether an instance object of a class is equal to another object, then we need to overwrite the public boolean equals (Object obj) method of the Object class. If this method is not overwritten, the Object's equals () method will be called for judgment. This is equivalent to comparing whether the memory application addresses of two objects are equal.
In the collection framework, not only lists, but all collection classes. If you need to determine whether an object is stored in it, the equals () method of the object is called for processing.
Author: pplsunny