The Java.util package contains a series of important collection classes. This article will start with the analysis of the source, in-depth study of the internal structure of a collection class, as well as traversing the collection of iterative mode of the source code to achieve insider. Let's start with a simple discussion of the root interface collection, then analyze an abstract class abstractlist and its corresponding iterator interface, and carefully study the implementation principle of the iterative sub pattern. The version of the source code 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, so we're still talking about the 1.4 version of the code. the root interface of the collection class 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, but the return value does not indicate whether the addition was successful or not. Carefully read Doc can see that collection stipulates that if a collection refuses to add this element, it must throw an exception for whatever reason. This return value represents the meaning of the Add () method after the execution of the contents of the collection has changed (that is, the element has no quantity, position, etc.), which is implemented by the specific class. That is, if the method fails, the exception is always thrown; the return value simply indicates that the content of the collection has changed since the method was executed. There are also similar: Boolean AddAll (Collection c); Boolean remove (Object O); Boolean RemoveAll (Collection c); Boolean remainall (Collection c); Object[] The ToArray () method is simple, converting the collection to an array of returns. Object[] ToArray (object[] A) method is a bit complicated, first of all, the return of the object[] is still an array of all the elements of the collection, but the type and parameter A are the same type, such as execution: string[] o = (string[]) C.toarray (new string[0)); The obtained o actual type is string[]. Second, if the size of parameter A does not fit all the elements of the collection, a new array will be returned. If the size of parameter a can hold all elements of the collection, the return is a, but the content of a is populated with the elements of the collection. In particular, if the size of a is more than the number of elements in the collection, the part after a is all set to NULL. The last and most important method is iterator (), which returns a iterator (iteration) that iterates through all the elements of the collection. traversing the collection with iterator mode The iterator pattern is the standard access method used to traverse collection classes. It can abstract the access logic from different types of collection classes, thereby avoiding exposing the internal structure of the collection to the client. For example, if you are not using iterator, the way to traverse an array is to use an index: for (int i=0; i<array.size (); i++) {... get (i) ...} Access to a list (LinkedList) must also use a while loop: while ((E=e.next ())!=null) {... e.data () ...} Both methods the client must know in advance the internal structure of the collection, the Access code and the collection itself are tightly coupled, the access logic cannot be separated from the collection class and client code, each of which corresponds to a traversal method, and client code cannot be reused. More frightening is that if you later need to replace the ArrayList to LinkedList, then the original client code must be rewritten. To solve these problems, the iterator pattern always iterates through the collection with the same logic: for (Iterator it = C.iterater (); It.hasnext ();) { ... } The secret is that the client itself does not maintain "pointers" to traverse the collection, all internal states (such as the current element position, and whether there is the next element) are maintained by iterator, and this iterator is generated by the collection class through the factory method, so it knows how to traverse the entire collection. The client never deals directly with the collection class, it always controls the iterator, sends it forward, "backwards", and "takes the current element" to traverse the entire collection indirectly. First look at the definition of the Java.util.Iterator interface: Public interface Iterator { Boolean hasnext (); Object next (); void Remove (); } Depending on the first two methods, the traversal can be completed, and the typical code is as follows: for (Iterator it = C.iterator (); It.hasnext ();) { Object o = It.next (); Operations on O ... } In JDK1.5, the above code is also simplified syntactically: Type is specific, such as String. for (Type t:c) { Operation on t ... } The iterator specific types returned by each collection class may be different, the array may return Arrayiterator,set may return Setiterator,tree may return treeiterator, but they all implement the iterator interface, so , the client doesn't care what kind of iterator it is, it just needs to get the iterator interface, which is the power of the object-oriented. Iterator Source Analysis Let's see how abstracylist creates iterator. First Abstractlist defines an internal class (inner class): Private class Itr implements iterator { ... } The iterator () method is defined as: Public iterator iterator () { return new Itr (); } So the client does not know it through iterator it = A.iterator (); the true type of iterator obtained. Now we are concerned with how the ITR class, which is declared private, implements traversal Abstractlist: Private class Itr implements iterator { int cursor = 0; int lastret =-1; int expectedmodcount = Modcount; } The ITR class relies on 3 int variables (and also an implied abstractlist reference) to traverse, cursor is the position of the element at the next () call, and the first call to next () returns the element with an index of 0. Lastret records the position of the last cursor, so it is always 1 less than the cursor. Variable cursor and the number of elements of the collection determine Hasnext (): public Boolean hasnext () { return cursor!= size (); } Method Next () returns the element indexed as cursor, and then modifies the values of cursor and Lastret: Public Object Next () { Checkforcomodification (); try { Object next = get (cursor); Lastret = cursor++; return to Next; catch (Indexoutofboundsexception e) { Checkforcomodification (); throw new Nosuchelementexception (); } } Expectedmodcount represents the expected Modcount value, which is used to determine whether the collection has been modified during the traversal process. Abstractlist contains a modcount variable whose initial value is 0, and Modcount plus 1 when the collection is modified once (call Add,remove, etc.). Therefore, if the modcount does not change, the contents of the collection are not modified. ITR is initialized with a expectedmodcount record of the Modcount variable of the collection, and thereafter it detects modcount values where necessary: final void Checkforcomodification () { if (Modcount!= expectedmodcount) throw new Concurrentmodificationexception (); } If the modcount is not equal to the value of the first record in Expectedmodecount, the contents of the collection are modified and the concurrentmodificationexception is thrown. This concurrentmodificationexception is runtimeexception, do not capture it on the client. If this exception occurs, there is a problem with writing the program code, and you should carefully examine the code instead of ignoring it in the catch. However, it is completely fine to call the Remove () method of the iterator itself to delete the current element because the values of Expectedmodcount and Modcount are automatically synchronized in this method: public void Remove () { ... AbstractList.this.remove (Lastret); ... Expectedmodcount is reset after calling the Remove () method of the collection: Expectedmodcount = Modcount; ... } To ensure that the traversal process completes smoothly, you must ensure that the contents of the collection are not changed during traversal (except for the Remove () method of the iterator), so the principle of ensuring traversal is that the collection is used only in one thread, or that the traversal code is synchronized across multiple threads. Finally, give a complete example: 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 change the ArrayList of the first line of code to LinkedList or vector, the rest of the code can be compiled without changing a line, and the functionality is the same, which is the principle of abstract programming: the least dependence on specific classes. |