The java. util package contains a series of important collection classes. This article will start with analyzing the source code, thoroughly studying the internal structure of a collection class, and traversing the source code implementation of the iteration mode of the set.
Next, we will briefly discuss a Root Interface Collection, analyze a abstract class AbstractList and its corresponding Iterator interface, and carefully study the implementation principle of the iteration submode.
The source code version discussed in this article is JDK 1.4.2, because JDK 1.5 uses a lot of generic code in java. util. to simplify the problem, we still discuss the code of version 1.4.
Collection
The Collection interface is the root type of all Collection classes. One of its main interface methods is:
Boolean add (Object c)
The add () method adds a new element. Note that this method returns a boolean value, but the returned value does not indicate that the value is successfully added. Read the doc carefully and you can see that Collection rules: If a Collection refuses to add this element, an exception must be thrown for any reason. The returned value indicates whether the content of the set has changed after the add () method is executed (that is, whether the number and position of the element have changed), which is implemented by a specific class. That is, if a method error occurs, an exception is always thrown. The returned value only indicates whether the content of the Collection changes after the method is executed.
Similar:
Boolean addAll (Collection c );
Boolean remove (Object o );
Boolean removeAll (Collection c );
Boolean remainAll (Collection c );
The Object [] toArray () method is very simple. Convert the set into an array and return it. The Object [] toArray (Object [] a) method is a bit complicated. First, the returned Object [] Still converts all elements of the set into an array, but the type is the same as the type of parameter a, for example, run:
String [] o = (String []) c. toArray (new String [0]);
The actual o type is String [].
Second, if the size of parameter a does not fit all the elements in the Set, a new array is returned. If the size of parameter a can hold all the elements of the set, the returned result is a, but the content of a is filled with the elements of the set. In particular, if the size of a is more than the number of elements in the Set, all parts after a are set to null.
The last and most important method is iterator (), which returns an Iterator (iterator) to traverse all elements of the set.
Traverse a set in Iterator Mode
The Iterator mode is a standard access method used to traverse collection classes. It can abstract the access logic from a collection class of the same type to avoid exposing the internal structure of the set to the client.
For example, if the Iterator is not used, the index is used to traverse an array:
For (int I = 0; I
To access a linked list, you must use the while loop:
While (e = e. next ())! = Null) {... e. data ()...}
The clients of the above two methods must know the internal structure of the set in advance. The access code and the set itself are tightly coupled and the access logic cannot be separated from the Collection class and client code, each set corresponds to a Traversal method, and client code cannot be reused.
What's even more frightening is that if you need to replace ArrayList with a future list, all the original client code must be rewritten.
To solve the above problem, the Iterator mode always uses the same logic to traverse the set:
For (Iterator it = c. iterater (); it. hasNext ();){...}
The mystery is that the client does not maintain the "Pointer" of the traversal set. All internal states (such as the current element location and whether there is any next element) are maintained by Iterator, this Iterator is generated by the collection class through the factory method, so it knows how to traverse the entire set.
The client never directly deals with the collection class. It always controls the Iterator and sends the "forward", "backward", "Get current element" command to it to indirectly traverse the entire set.
First, let's take a look at the definition of the java. util. Iterator interface:
Public interface Iterator {
Boolean hasNext ();
Object next ();
Void remove ();
}
The traversal can be completed by relying on the first two methods. The typical code is as follows:
For (Iterator it = c. iterator (); it. hasNext ();){
Object o = it. next ();
// O operations...
}
In JDK1.5, the above Code is also simplified in syntax:
// Type is a specific Type, such as String.
For (Type t: c ){
// T operation...
}
The Iterator types returned by each collection class may be different. Array may return ArrayIterator, Set may return SetIterator, and Tree may return TreeIterator, but they all implement the Iterator interface. Therefore, the client does not care about which Iterator it is. It only needs to obtain this Iterator interface, which is the power of object-oriented.
Iterator source code analysis
Let's take a look at how AbstracyList creates an Iterator. First, AbstractList defines an inner class ):
Private class Itr implements Iterator {
...
}
The iterator () method is defined as follows:
Public Iterator iterator (){
Return new Itr ();
}
Therefore, the client does not know the real type of Iterator obtained through iterator it = a. Iterator.
Now we are concerned about how the private Itr class traverses javasactlist:
Private class Itr implements Iterator {
Int cursor = 0;
Int lastRet =-1;
Int expectedModCount = modCount;
}
The Itr class traverses three int variables (and an implicit AbstractList reference). The cursor is the position of the element in the next () call and calls next () for the first time () returns the element with an index of 0. LastRet records the position of the last cursor, so it is always 1 less than cursor.
The number of elements in the variable cursor and set determines hasNext ():
Public boolean hasNext (){
Return cursor! = Size ();
}
The method next () returns the element whose index is cursor, and then modifies the values of cursor and lastRet:
Public Object next (){
CheckForComodification ();
Try {
Object next = get (cursor );
LastRet = cursor ++;
Return next;
} Catch (IndexOutOfBoundsException e ){
CheckForComodification ();
Throw new NoSuchElementException ();
}
}
ExpectedModCount indicates the expected modCount value, which is used to determine whether the combination has been modified during the traversal process. AbstractList contains a modCount variable whose initial value is 0. When the set is modified once (the add, remove, and other methods are called), modCount is added to 1. Therefore, if modCount remains unchanged, the set content is not modified.
When itr is initialized, expectedmodcount is used to record the modcount variable of the set. It then detects the value of modcount where necessary:
Final void checkforcomodification (){
If (modcount! = Expectedmodcount)
Throw new concurrentmodificationexception ();
}
If modcount is different from the value of the initial record in expectedmodecount, it indicates that the set content has been modified and concurrentmodificationexception is thrown.
This concurrentmodificationexception is runtimeexception and should not be captured on the client. If this exception occurs, it indicates that there is a problem with programming code. You should check the code carefully instead of ignoring it in catch.
However, it is no problem to call the remove () method of iterator to delete the current element, because the expectedmodcount and modcount values are automatically synchronized in this method:
Public void remove (){
...
Abstractlist. This. Remove (lastret );
...
// After the remove () method of the set is called, The expectedmodcount is reset:
Expectedmodcount = modcount;
...
}
To ensure the smooth completion of the traversal process, you must ensure that the content of the set is not changed during the traversal process (except for the remove () method of Iterator). Therefore, to ensure reliable traversal, you can only use this set in one thread or synchronize the traversal code in multiple threads.
Finally, a complete example is provided:
Collection c = new ArrayList ();
C. add ("abc ");
C. add ("xyz ");
For (Iterator it = c. iterator (); it. hasNext ();){
String s = (String) it. next ();
System. out. println (s );
}
If you replace the ArrayList of the first line of code with a struct list or Vector, the remaining code can be compiled without changing the line, and the function remains unchanged. This is the principle of abstract programming: the minimum dependence on a specific class.