T E C H
T I P S
TIPS, TECHNIQUES, AND SAMPLE CODE
//list.set(0, "purple");
}
}
Notice that this program makes the names array private, and
defines an accessor method for it, getNames(). However, simply
going this far does not solve the problem. You can still change
the first slot in the array. So the program clones the array.
After the cloning, if you change the first slot of the array, it
only affects the copy. Of course, cloning a large array has
a speed and space cost attached to it.
Notice too the Collections Framework mechanisms used in the
program. Arrays.asList() creates a list backed by an array.
Collections.unmodifiableList then creates a read-only view of the
list. The array is not copied in this case, but instead a
read-only List view is built on top of it.
These solutions involve either a shallow copy or no copy of the
underlying array. If the array contains mutable objects, then
there is still a problem because errant user code can change the
value of an individual object within the array. In the CopyDemo5
program above, an array of strings is used, and String objects
are immutable.
The need for defensive copying also shows up in other contexts,
such as after an object has been deserialized, or when an object
is added to a Set or Map. For example, one of the properties of
a set is that it contains no duplicate elements. What if a
mutable object is added to a set, and then it later changes so
that equals() is true between that object and another one in the
set? At this point, the set is corrupt. To avoid this problem,
a defensive copy should have been made.
For more information about making defensive copies of objects,
see item 24 "Make defensive copies when needed" in "Effective
Java Programming Language Guide" by Joshua Bloch
(http://java.sun.com/docs/books/effective/).
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - USING ITERATORS
An iterator is a mechanism used to step through the elements in
a data structure. The interface java.util.Iterator is part of
the Collections Framework, and specifies three methods:
boolean hasNext();
Object next();
void remove();
import java.util.*;
public class IterDemo1 {
public static void main(String args[]) {
List list = new ArrayList();
list.add("test1");
list.add("test2");
list.add("test3");
//for (int i = 1; i <= list.size(); i++) {
// System.out.println(list.get(i));
//}
Iterator iter = list.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
}
In this example, there is a list containing three strings, and
you want to print out the value of each string. You might do
this using the random-access properties of lists. In this case,
you call get() to return arbitrary elements. If you uncomment the
code in IterDemo1 that does this, and then rerun the program, you
get the following results:
test2
test3
Exception in thread "main"
java.lang.IndexOutOfBoundsException:
Index: 3, Size: 3
This shows that the program skipped the first string, and tried
to access index 3 in a list whose valid indices are in the range
0-2. So you get an "off-by-one" error.
This trivial example illustrates one reason why iterators are
important -- they handle the details of data structure traversal.
You don't have to remember, for example, that a list starts at
index 0 instead of 1. This is even more important for complex
structures such as trees, where the right way to traverse the
structure isn't always obvious.
Another advantage of iterators is that they provide a uniform
interface across different data types. For example, here is
a small program that displays the elements in any type of
collection or map:
import java.util.*;
public class IterDemo2 {
static void dumpElements(Object o) {
// if have a Map, pick up the set of
// key/value pairs
if (o instanceof Map) {
o = ((Map)o).entrySet();
}
// dump elements of collection
if (o instanceof Collection) {
Collection c = (Collection)o;
Iterator iter = c.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}
}
// give error if not a Collection
else {
System.err.println(
"Not a valid collection");
}
}
public static void main(String args[]) {
// Lists
List list = new ArrayList();
list.add("test1");
list.add("test2");
dumpElements(list);
// Sets
Set set = new TreeSet();
set.add("test3");
set.add("test4");
dumpElements(set);
// Maps
Map map = new HashMap();
map.put("test5key", "test5value");
map.put("test6key", "test6value");
dumpElements(map);
}
}
Lists and Sets extend the Collection interface. Map does not, but
it's possible to obtain the "entry set" for a Map, and then
iterate over that. When you run the IterDemo2 program, the output
is:
test1
test2
test3
test4
test6key=test6value
test5key=test5value
How do you write your own iterator? Let's look at an example, one
that defines an iterator for a BitSet:
import java.util.*;
class BitSetIterator implements Iterator {
// underlying BitSet
private final BitSet bitset;
// current index into BitSet
private int index;
// constructor
public BitSetIterator(BitSet bitset) {
this.bitset = bitset;
}
// return true if has more elements
public boolean hasNext() {
return index < bitset.length();
}
// return next element
public Object next() {
if (index >= bitset.length()) {
throw new NoSuchElementException();
}
boolean b = bitset.get(index++);
return new Boolean(b);
}
// remove element
public void remove() {
throw new UnsupportedOperationException();
}
}
public class IterDemo3 {
public static void main(String args[]) {
// create BitSet and set some bits
BitSet bitset = new BitSet();
bitset.set(1);
bitset.set(19);
bitset.set(47);
// go through and display True/False values
Iterator iter = new BitSetIterator(bitset);
while (iter.hasNext()) {
Boolean b = (Boolean)iter.next();
String tf = (
b.booleanValue() ? "T" : "F");
System.out.print(tf);
}
System.out.println();
}
}
A BitSetIterator object keeps track of the underlying BitSet
object reference, along with an index into the set of bits. The
IterDemo3 program uses BitSet.length() to obtain the highest
numbered bit that is set. The program then iterates until the
index reaches this value. Each element is returned as a Boolean
object. Note that it is legal to call hasNext() repeatedly
without calling next(). It's also legal to call next() without
calling hasNext(). And next() has to handle the situation where
there are no additional elements to return.
When you run this program, the output is:
FTFFFFFFFFFFFFFFFFFTFFFFFFFFFFFFFFFFFFFFFFFFFFFT
What happens if the underlying data structure that the iterator
is working on changes during the iteration? Let's look at a
simple example:
import java.util.*;
class BitSetIterator implements Iterator {
private final BitSet bitset;
private int index;
public BitSetIterator(BitSet bitset) {
this.bitset = bitset;
}
public boolean hasNext() {
return index < bitset.length();
}
public Object next() {
if (index >= bitset.length()) {
throw new NoSuchElementException();
}
boolean b = bitset.get(index++);
return new Boolean(b);
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public class IterDemo4 {
public static void main(String args[]) {
// create Bitset and set bit 0
BitSet bitset = new BitSet();
bitset.set(0);
// set up iterator
Iterator iter = new BitSetIterator(bitset);
//BitSet clone = (BitSet)bitset.clone();
//Iterator iter = new BitSetIterator(clone);
// display True/False bits
while (iter.hasNext()) {
bitset.clear(0);
Boolean b = (Boolean)iter.next();
String tf = (
b.booleanValue() ? "T" : "F");
System.out.print(tf);
}
System.out.println();
}
}
The IterDemo4 program creates a BitSet, sets one bit in it, sets
up an iterator, and calls hasNext(). Then, before next() is
invoked, the program clears the bit that was set. At this point,
no bits are set in the BitSet. So when next() is called, the
result is a NoSuchElementException.
One way to get around this type of problem is to copy the data
structure, and then create an iterator for the copy. In the
IterDemo4 example, there's an alternative approach using clone()
to copy the BitSet (the code for this approach is commented out).
Recall that the tip "Making Defensive Copies of Objects" stated
that it can be dangerous to call clone if you have a subclass
with a malicious clone method. But in the IterDemo4 example,
there's an actual BitSet object, not an object of a subclass.
Here's another example of modifying a list while iterating over
it:
import java.util.*;
public class IterDemo5 {
public static void main(String args[]) {
List list = new ArrayList();
list.add("test1");
list.add("test2");
list.add("test3");
list.add("test4");
Iterator iter = list.iterator();
for (
int index = 0; iter.hasNext(); index++) {
String next = (String)iter.next();
if (next.equals("test2")) {
list.remove(index);
//iter.remove();
}
}
}
}
If you run this program, you get a
ConcurrentModificationException. This is caused by an attempt to
remove an element from the list, while iterating over the list.
The list and iterator implementations work together to detect
this problem. You can, however, safely remove the element by
calling the remove() method defined for the iterator
implementation. This type of iterator goes by the name
"fail-fast".
A final example shows how you can use an iterator to filter the
output of another iterator:
import java.util.*;
class FilterNumbers implements Iterator {
// underlying iterator
private final Iterator iter;
// current Number object
private Object nextnum;
public FilterNumbers(Iterator iter) {
this.iter = iter;
}
public boolean hasNext() {
// if already have a Number object, return
// true
if (nextnum != null) {
return true;
}
// scan for a Number object
while (iter.hasNext()) {
nextnum = iter.next();
if (nextnum instanceof Number) {
return true;
}
}
// didn't find one
nextnum = null;
return false;
}
public Object next() {
// either already have a Number object,
// or must scan for one via hasNext()
if (nextnum == null && !hasNext()) {
throw new NoSuchElementException();
}
// return Number object
Object savenum = nextnum;
nextnum = null;
return savenum;
}
public void remove() {
throw new UnsupportedOperationException();
}
}
public class IterDemo6 {
- SUBSCRIBE
To subscribe to a JDC newsletter mailing list, go to the
Subscriptions page
http://developer.java.sun.com/subscription/
choose the newsletters you want to subscribe to, and click
Update.
- FEEDBACK
Comments? Send your feedback on the JDC Tech Tips to:
jdc-webmaster@sun.com
- ARCHIVES
You'll find the JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
- COPYRIGHT
Copyright 2001 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
- LINKS TO NON-SUN SITES
The JDC Tech Tips may provide, or third parties may provide,
links to other Internet sites or resources. Because Sun has no
control over such sites and resources, You acknowledge and agree
that Sun is not responsible for the availability of such external
sites or resources, and does not endorse and is not responsible
or liable for any Content, advertising, products, or other
materials on or available from such sites or resources. Sun will
not be responsible or liable, directly or indirectly, for any
damage or loss caused or alleged to be caused by or in connection
with use of or reliance on any such Content, goods or services
available on or through any such site or resource.
This issue of the JDC Tech Tips is written by Glen McCluskey.
JDC Tech Tips
September 4, 2001
Sun, Sun Microsystems, Java, and Java Developer Connection are
trademarks or registered trademarks of Sun Microsystems, Inc. in
the United States and other countries.