| ★ wanayoo — archive 1999 http://developer.java.sun.com/developer/Books/corejava/page3.html | Nouvelle recherche | Portail wanayoo |
|
|
|
Book Excerpt Index
Core Java 2, Volume IIby Cay S. Horstmann and Gary CornellChapter 2: CollectionsConcrete CollectionsRather than getting into more details about all the interfaces, we thought it would be helpful to first discuss the concrete data structures that the Java library supplies. Once you have a thorough understanding of what classes you will want to use, we will return to abstract considerations and see how the collections framework organizes these classes.Linked Lists
We used arrays and their dynamic cousin, the
Another well-known data structure, the linked list, solves this problem. Whereas an array stores object references in consecutive memory locations, a linked list stores each object in a separate link. Each link also stores a reference to the next link in the sequence. In the Java programming language, all linked lists are actually doubly linked, that is, each link also stores a reference to its predecessor (see Figure 2-5).
Removing an element from the middle of a linked list is an inexpensive operation--only the links around the element to be removed need to be updated (see Figure 2-6).
Perhaps you once had a course in data structures where you learned how to
implement linked lists. You may have bad memories of tangling up the links
when removing or adding elements in the linked list. If so, you will be
pleased to learn that the Java collections library supplies a class
The
LinkedList staff =
new LinkedList();
staff.add("Angela");
staff.add("Bob");
staff.add("Carl");
Iterator iter = staff.iterator();
for (int i = 0; i < 3; i++)
System.out.println(iter.next());
iter.remove(); // remove last
//visited element
However, there is an important difference between linked lists
and generic collections. A linked list is an ordered collection where
the position of the objects matters. The LinkedList.add method
adds the
object to the end of the list. But you often want to add objects somewhere
in the middle of a list. This position-dependent add method is the
responsibility of an iterator, since iterators describe positions in
collections. Using iterators to add elements only makes sense for collections
that have a natural ordering. For example, the set data type that we discuss
in the next section does not impose any ordering on its elements. Therefore,
there is no add method in the Iterator interface. Instead, the collections
library supplies a subinterface ListIterator that contains an
add method:
interface ListIterator
extends Iterator
{ void add(Object);
. . .
}
Unlike Collection.add, this method does not return a boolean--it is assumed
that the add operation always succeeds.
In addition, the Object previous() boolean hasPrevious()--that you can use for traversing a list backwards. Like the next method,
the previous method returns the object that it skipped over.
The
ListIterator iter =
staff.listIterator();
The add method adds the new element before the
iterator position. For example,
the code
ListIterator iter =
staff.listIterator();
iter.next();
iter.add("Juliet");
skips past the first element in the linked list and adds "Juliet"
before
the second element (see Figure 2-7).
If you call the
When you use the |ABC A|BC AB|C ABC| NOTE: You have to be careful with the "cursor" analogy. The remove
operation
does not quite work like the BACKSPACE key. Immediately after a call to next,
the remove method indeed removes the element to the left of the iterator,
just like the backspace key would. However, if you just called previous, the
element to the right is removed. And you can't call remove twice in a row.
Unlike the
Finally, there is a ListIterator iter = list.listIterator(); Object oldValue = iter.next(); // returns first element iter.set(newValue); // sets first element to newValueAs you might imagine, if an iterator traverses a collection while
another
iterator is modifying it, confusing situations can occur. For example,
suppose an iterator points before an element that another
iterator has
just removed. The iterator is now invalid and should no longer be used.
The linked list iterators have been designed to detect such modifications.
If an iterator finds that its collection has been modified by another
iterator or by a method of the collection itself, then it throws a
ConcurrentModificationException. For example, consider the
following code:
LinkedList list = . . .;
ListIterator iter1 =
list.listIterator();
ListIterator iter2 =
list.listIterator();
iter1.next();
iter1.remove();
iter2.next();
// throws ConcurrentModificationException
The call to iter2.next throws a ConcurrentModificationException since iter2
detects that the list was modified externally.
To avoid concurrent modification exceptions, follow this simple rule: You can attach as many iterators to a container as you like, provided that all of them are only readers. Alternatively, you can attach a single iterator that can both read and write.
Concurrent modification detection is achieved in a simple way. The container
keeps track of the number of mutating operations (such as adding and removing
elements). Each iterator keeps a separate count of the number of mutating
operations that it was responsible for. At the beginning of each iterator
method, the iterator simply checks whether its own mutation count equals that
of the container. If not, it throws a This is an excellent check and a great improvement over the fundamentally unsafe iterators in the C++ STL framework. Note, however, that it does not automatically make collections safe for multithreading. We discuss thread safety issues later in this chapter.
NOTE: There is, however, a curious exception to the detection of concurrent modifications. The linked list only keeps track of structural modifications to the list, such as adding and removing links. The set method does not
count as a structural modification. You can attach multiple iterators to a
linked list, all of which call set to change the contents of existing links.
This capability is required for a number of algorithms in the
Collections class that we discuss later in this chapter.
Now you have seen the fundamental methods of the
As you saw in the preceding section, there are many other useful methods for
operating on linked lists that are declared in the
CAUTION: The Java platform documentation points out that you should not add a reference of a collection to itself. Otherwise, it is easy to generate a stack overflow in the JavaTM virtual machine1. For example, the following call is fatal: LinkedList list = new LinkedList(); list.add(list); // add list to itself String contents = list.toString(); // dies with infinite recursionNaturally, this is not a situation that comes up in everyday programming. The library also supplies a number of methods that are, from a theoretical perspective, somewhat dubious. Linked lists do not support fast random access. If you want to see the nth element of a linked list, you have to start at the beginning and skip past the first n - 1 elements first. There is no shortcut. For that reason, programmers don't usually use linked lists in programming situations where elements need to be accessed by an integer index.
Nevertheless, the Object obj = list.get(n);Of course, this method is not very efficient. If you find yourself using it, you are probably using the wrong data structure for your problem. You should never use this illusory random access method to step through a linked list. The code for (int i = 0; i < list.size(); i++) do something with list.get(i);is staggeringly inefficient. Each time you look up another element, the search starts again from the beginning of the list. The LinkedList object
makes no effort to cache the position information.
NOTE: The get method has one slight optimization: if the index is at least size() / 2, then the search for the element starts at the end of the list.
The list iterator interface also has a method to tell you the index of the
current position. In fact, because Java iterators conceptually point between
elements, it has two of them: the
If you have a linked list with only a handful of elements, then you don't have
to be overly paranoid about the cost of the get and set methods. But then why
use a linked list in the first place? The only reason to use a linked list is
to minimize the cost of insertion and removal in the middle of the list. If
you only have a few elements, you can just use an array or a collection such
as
We recommend that you simply stay away from all methods that use an integer
index to denote a position in a linked list. If you want random access into
a collection, use an array or
The program in Example 2-1 puts linked lists to work. It simply creates two
lists, merges them, then removes every second element from the second list,
and finally tests the |ACE |BDFG A|CE |BDFG AB|CE B|DFG . . .Note that the call System.out.println(a);prints all elements in the linked list a.
Example 2-1: LinkedListTest.java
import java.util.*;
public class LinkedListTest
{ public static void main(
String[] args)
{ List a = new LinkedList();
a.add("Angela");
a.add("Carl");
a.add("Erica");
List b = new LinkedList();
b.add("Bob");
b.add("Doug");
b.add("Frances");
b.add("Gloria");
// merge the words from b into a
ListIterator aIter = a.listIterator();
Iterator bIter = b.iterator();
while (bIter.hasNext())
{ if (aIter.hasNext()) aIter.next();
aIter.add(bIter.next());
}
System.out.println(a);
// remove every second
//word from b
bIter = b.iterator();
while (bIter.hasNext())
{ bIter.next();
// skip one element
if (bIter.hasNext())
{ bIter.next();
// skip next element
bIter.remove();
// remove that element
}
}
System.out.println(b);
// bulk operation: remove all
//words in b from a
a.removeAll(b);
System.out.println(a);
}
}
java.util.List
|