Tải bản đầy đủ (.pdf) (108 trang)

Thinking in Java 4th Edition phần 7 pdf

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (1.38 MB, 108 trang )


print("Produced 0.0!");
}
else if(args[0].equals("upper")) {
while(Math.random() != 1.0)
; // Keep trying
print("Produced 1.0!");
}
else
usage();
}
} ///:~

To run the program, you type a command line of either:
java RandomBounds lower
or
java RandomBounds upper
In both cases, you are forced to break out of the program manually, so it would appear that
Math.random( ) never produces either o.o or l.o. But this is where such an experiment can
be deceiving. If you consider that there are about 262 different double fractions between o
and 1, the likelihood of reaching any one value experimentally might exceed the lifetime of
one computer, or even one experimenter. It turns out that 0.0 is included in the output of
Math.random( ). Or, in math lingo, it is [0,1). Thus, you must be careful to analyze your
experiments and to understand their limitations.
Choosing between Sets
Depending on the behavior you desire, you can choose a TreeSet, a HashSet, or a
LinkedHashSet. The following test program gives an indication of the performance trade-
off between these implementations:
//: containers/SetPerformance.java
// Demonstrates performance differences in Sets.
// {Args: 100 5000} Small to keep build testing short


import java.util.*;

public class SetPerformance {
static List<Test<Set<Integer>>> tests =
new ArrayList<Test<Set<Integer>>>();
static {
tests.add(new Test<Set<Integer>>("add") {
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops;
int size = tp.size;
for(int i = 0; i < loops; i++) {
set.clear();
for(int j = 0; j < size; j++)
set.add(j);
}
return loops * size;
}
});
tests.add(new Test<Set<Integer>>("contains") {
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops;
Containers in Depth 627
Simpo PDF Merge and Split Unregistered Version -

int span = tp.size * 2;
for(int i = 0; i < loops; i++)
for(int j = 0; j < span; j++)
set.contains(j);
return loops * span;
}

});
tests.add(new Test<Set<Integer>>("iterate") {
int test(Set<Integer> set, TestParam tp) {
int loops = tp.loops * 10;
for(int i = 0; i < loops; i++) {
Iterator<Integer> it = set.iterator();
while(it.hasNext())
it.next();
}
return loops * set.size();
}
});
}
public static void main(String[] args) {
if(args.length > 0)
Tester.defaultParams = TestParam.array(args);
Tester.fieldWidth = 10;
Tester.run(new TreeSet<Integer>(), tests);
Tester.run(new HashSet<Integer>(), tests);
Tester.run(new LinkedHashSet<Integer>(), tests);
}
} /* Output: (Sample)
TreeSet
size add contains iterate
10 746 173 89
100 501 264 68
1000 714 410 69
10000 1975 552 69
HashSet
size add contains iterate

10 308 91 94
100 178 75 73
1000 216 110 72
10000 711 215 100
LinkedHashSet
size add contains iterate
10 350 65 83
100 270 74 55
1000 303 111 54
10000 1615 256 58
*///:~
The performance of HashSet is generally superior to TreeSet, but especially when adding
elements and looking them up, which are the two most important operations. TreeSet exists
because it maintains its elements in sorted order, so you use it only when you need a sorted
Set. Because of the internal structure necessary to support sorting and because iteration is
something you’re more likely to do, iteration is usually faster with a TreeSet than a
HashSet.
Note that LinkedHashSet is more expensive for insertions than HashSet; this is because
of the extra cost of maintaining the linked list along with the hashed container.
Exercise 34: (1) Modify SetPerformance.java so that the Sets hold String objects
instead of Integers. Use a Generator from the Arrays chapter to create test values.
628 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

Choosing between Maps
This program gives an indication of the trade-off between Map implementations:
//: containers/MapPerformance.java
// Demonstrates performance differences in Maps.
// {Args: 100 5000} Small to keep build testing short
import java.util.*;


public class MapPerformance {
static List<Test<Map<Integer,Integer>>> tests =
new ArrayList<Test<Map<Integer,Integer>>>();
static {
tests.add(new Test<Map<Integer,Integer>>("put") {
int test(Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops;
int size = tp.size;
for(int i = 0; i < loops; i++) {
map.clear();
for(int j = 0; j < size; j++)
map.put(j, j);
}
return loops * size;
}
});
tests.add(new Test<Map<Integer,Integer>>("get") {
int test(Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops;
int span = tp.size * 2;
for(int i = 0; i < loops; i++)
for(int j = 0; j < span; j++)
map.get(j);
return loops * span;
}
});
tests.add(new Test<Map<Integer,Integer>>("iterate") {
int test(Map<Integer,Integer> map, TestParam tp) {
int loops = tp.loops * 10;

for(int i = 0; i < loops; i ++) {
Iterator it = map.entrySet().iterator();
while(it.hasNext())
it.next();
}
return loops * map.size();
}
});
}
public static void main(String[] args) {
if(args.length > 0)
Tester.defaultParams = TestParam.array(args);
Tester.run(new TreeMap<Integer,Integer>(), tests);
Tester.run(new HashMap<Integer,Integer>(), tests);
Tester.run(new LinkedHashMap<Integer,Integer>(),tests);
Tester.run(
new IdentityHashMap<Integer,Integer>(), tests);
Tester.run(new WeakHashMap<Integer,Integer>(), tests);
Tester.run(new Hashtable<Integer,Integer>(), tests);
}
} /* Output: (Sample)
TreeMap
size put get iterate
Containers in Depth 629
Simpo PDF Merge and Split Unregistered Version -

10 748 168 100
100 506 264 76
1000 771 450 78
10000 2962 561 83

HashMap
size put get iterate
10 281 76 93
100 179 70 73
1000 267 102 72
10000 1305 265 97
LinkedHashMap
size put get iterate
10 354 100 72
100 273 89 50
1000 385 222 56
10000 2787 341 56
IdentityHashMap
size put get iterate
10 290 144 101
100 204 287 132
1000 508 336 77
10000 767 266 56
WeakHashMap
size put get iterate
10 484 146 151
100 292 126 117
1000 411 136 152
10000 2165 138 555
Hashtable
size put get iterate
10 264 113 113
100 181 105 76
1000 260 201 80
10000 1245 134 77

*///:~
Insertions for all the Map implementations except for IdentityHashMap get significantly
slower as the size of the Map gets large. In general, however, lookup is much cheaper than
insertion, which is good because you’ll typically be looking items up much more often than
you insert them.
Hashtable performance is roughly the same as HashMap. Since HashMap is intended to
replace Hashtable, and thus uses the same underlying storage and lookup mechanism
(which you will learn about later), this is not too surprising.
A TreeMap is generally slower than a HashMap. As with TreeSet, a TreeMap is a way to
create an ordered list. The behavior of a tree is such that it’s always in order and doesn’t have
to be specially sorted. Once you fill a TreeMap, you can call keySet( ) to get a Set view of
the keys, then toArray( ) to produce an array of those keys. You can then use the static
method Arrays.binarySearch( ) to rapidly find objects in your sorted array. Of course,
this only makes sense if the behavior of a HashMap is unacceptable, since HashMap is
designed to rapidly find keys. Also, you can easily create a HashMap from a TreeMap with
a single object creation or call to putAll( ). In the end, when you’re using a Map, your first
choice should be HashMap, and only if you need a constantly sorted Map will you need
TreeMap.
LinkedHashMap tends to be slower than HashMap for insertions because it maintains
the linked list (to preserve insertion order) in addition to the hashed data structure. Because
of this list, iteration is faster.
630 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

Containers in Depth 631
IdentityHashMap has different performance because it uses == rather than equals( ) for
comparisons. WeakHashMap is described later in this chapter.
Exercise 35: (1) Modify MapPerformance.java to include tests of SlowMap.
Exercise 36: (5) Modify SlowMap so that instead of two ArrayLists, it holds a single
ArrayList of MapEntry objects. Verify that the modified version works correctly. Using

MapPerformance.java, test the speed of your new Map. Now change the put( ) method
so that it performs a sort( ) after each pair is entered, and modify get( ) to use
Collections.binarySearch( ) to look up the key. Compare the performance of the new
version with the old ones.
Exercise 37: (2) Modify SimpleHashMap to use ArrayLists instead of LinkedLists.
Modify MapPerformance.java to compare the performance of the two implementations.
HashMap performance factors
It’s possible to hand-tune a HashMap to increase its performance for your particular
application. So that you can understand performance issues when tuning a HashMap, some
terminology is necessary:

Capacity: The number of buckets in the table.

Initial capacity: The number of buckets when the table is created. HashMap and
HashSet have constructors that allow you to specify the initial capacity.

Size: The number of entries currently in the table.

Load factor: Size/capacity. A load factor of o is an empty table, 0.5 is a half-full table,
etc. A lightly loaded table will have few collisions and so is optimal for insertions and
lookups (but will slow down the process of traversing with an iterator). HashMap and
HashSet have constructors that allow you to specify the load factor, which means that
when this load factor is reached, the container will automatically increase the capacity
(the number of buckets) by roughly doubling it and will redistribute the existing objects
into the new set of buckets (this is called rehashing).

The default load factor used by HashMap is 0.75 (it doesn’t rehash until the table is three-
fourths full). This seems to be a good trade-off between time and space costs. A higher load
factor decreases the space required by the table but increases the lookup cost, which is
important because lookup is what you do most of the time (including both get( ) and put(

)).
If you know that you’ll be storing many entries in a HashMap, creating it with an
appropriately large initial capacity will prevent the overhead of automatic rehashing.
11

Exercise 38: (3) Look up the HashMap class in the JDK documentation. Create a
HashMap, fill it with elements, and determine the load factor. Test the lookup speed with
this map, then attempt to increase the speed by making a new HashMap with a larger initial
capacity and copying the old map into the new one, then run your lookup speed test again on
the new map.

11
In a private message, Joshua Bloch wrote: " I believe that we erred by allowing implementation details (such as hash
table size and load factor) into our APIs. The client should perhaps tell us the maximum expected size of a collection, and
we should take it from there. Clients can easily do more harm than good by choosing values for these parameters. As an
extreme example, consider Vector’s capacitylncrement. No one should ever set this, and we shouldn’t have provided
it. If you set it to any nonzero value, the asymptotic cost of a sequence of appends goes from linear to quadratic. In other
words, it destroys your performance. Over time, we’re beginning to wise up about this sort of thing. If you look at
IdentityHashMap, you’ll see that it has no low-level tuning parameters."
Simpo PDF Merge and Split Unregistered Version -

Exercise 39: (6) Add a private rehash( ) method to SimpleHashMap that is
invoked when the load factor exceeds 0.75. During rehashing, double the number of buckets,
then search for the first prime number greater than that to determine the new number of
buckets.
Utilities
There are a number of standalone utilities for containers, expressed as static methods inside
the java.util.Collections class. You’ve already seen some of these, such as addAll( ),
reverseOrder( ) and binarySearch( ). Here are the others (the synchronized and
unmodifiable utilities will be covered in sections that follow). In this table, generics are

used when they are relevant:
checkedCollection(
Collection<T>, Class<T> type)
checkedList(
List<T>, Class<T> type)
checkedMap(Map<K,V>,
Class <K> keyType,
Class <V> valueType)
checkedSet(Set<T>,
Class<T> type)
checkedSortedMap(
SortedMap<K,V>,
Class<K> keyType,
Class <V> valueType)
checkedSortedSet(
SortedSet<T>,
Class<T> type)
Produces a dynamically type-safe
view of a Collection, or a specific
subtype of Collection. Use this
when it’s not possible to use the
statically checked version.
These were shown in the Generics
chapter under the heading
"Dynamic type safety."
max(Collection)
min(Collection)
Produces the maximum or
minimum element in the argument
using the natural comparison

method of the objects in the
Collection.
max(Collection, Comparator)
min(Collection, Comparator)
Produces the maximum or
minimum element in the
Collection using the
Comparator.
indexOfSubList(List source,
List target)
Produces starting index of the first
place where target appears inside
source, or -1 if none occurs.
lastIndexOfSubList(List
source, List target)
Produces starting index of the last
place where target appears inside
source, or -1 if none occurs.
replaceAll(List<T>,
T oldVal, T newVal)
Replaces all oldVal with newVal.
reverse(List) Reverses all the elements in place.
reverseOrder( )
reverseOrder(
Comparator<T>)
Returns a Comparator that
reverses the natural ordering of a
collection of objects that implement
Comparable<T>. The second
version reverses the order of the

supplied Comparator.
632 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

rotate(List, int distance) Moves all elements forward by
distance, taking the ones off the
end and placing them at the
beginning.
shuffle(List)
shuffle(List, Random)
Randomly permutes the specified
list. The first form provides its own
randomization source, or you may
provide your own with the second
form.
sort(List<T>)
sort(List<T>,
Comparator<? super T> c)
Sorts the List<T> using its natural
ordering. The second form allows
you to provide a Comparator for
sorting.
copy(List<? super T> dest,
List<? extends T> src)
Copies elements from src to dest.
swap(List, int i, int j) Swaps elements at locations i and j
in the List. Probably faster than
what you’d write by hand.
fill(List<? super T>, T x) Replaces all the elements of list
with x.

nCopies(int n, T x) Returns an immutable List<T> of
size n whose references all point to
x.
disjoint(Collection, Collection) Returns true if the two collections
have no elements in common.
frequency(Collection, Object x) Returns the number of elements in
the Collection equal to x.
emptyList( )
emptyMap( )
emptySet( )
Returns an immutable empty List,
Map, or Set. These are generic, so
the resulting Collection will be
parameterized to the desired type.
singleton(T x)
singletonList(T x)
singletonMap(K key, V value)
Produces an immutable Set<T>,
List<T>, or Map<K,V>
containing a single entry based on
the given argument(s).
list(Enumeration<T> e) Produces an ArrayList<T>
containing the elements in the
order in which they are returned by
the (old-style) Enumeration
(predecessor to the Iterator). For
converting from legacy code.
enumeration(Collection<T>) Produces an old-style
Enumeration<T> for the
argument.

Note that min( ) and max( ) work with Collection objects, not with Lists, so you don’t
need to worry about whether the Collection should be sorted or not. (As mentioned earlier,
you do need to sort( ) a List or an array before performing a binarySearch( ).)
Here’s an example showing the basic use of most of the utilities in the above table:
Containers in Depth 633
Simpo PDF Merge and Split Unregistered Version -

//: containers/Utilities.java
// Simple demonstrations of the Collections utilities.
import java.util.*;
import static net.mindview.util.Print.*;

public class Utilities {
static List<String> list = Arrays.asList(
"one Two three Four five six one".split(" "));
public static void main(String[] args) {
print(list);
print("‘list’ disjoint (Four)?: " +
Collections.disjoint(list,
Collections.singletonList("Four")));
print("max: " + Collections.max(list));
print("min: " + Collections.min(list));
print("max w/ comparator: " + Collections.max(list,
String.CASE_INSENSITIVE_ORDER));
print("min w/ comparator: " + Collections.min(list,
String.CASE_INSENSITIVE_ORDER));
List<String> sublist =
Arrays.asList("Four five six".split(" "));
print("indexOfSubList: " +
Collections.indexOfSubList(list, sublist));

print("lastIndexOfSubList: " +
Collections.lastIndexOfSubList(list, sublist));
Collections.replaceAll(list, "one", "Yo");
print("replaceAll: " + list);
Collections.reverse(list);
print("reverse: " + list);
Collections.rotate(list, 3);
print("rotate: " + list);
List<String> source =
Arrays.asList("in the matrix".split(" "));
Collections.copy(list, source);
print("copy: " + list);
Collections.swap(list, 0, list.size() - 1);
print("swap: " + list);
Collections.shuffle(list, new Random(47));
print("shuffled: " + list);
Collections.fill(list, "pop");
print("fill: " + list);
print("frequency of ‘pop’: " +
Collections.frequency(list, "pop"));
List<String> dups = Collections.nCopies(3, "snap");
print("dups: " + dups);
print("‘list’ disjoint ‘dups’?: " +
Collections.disjoint(list, dups));
// Getting an old-style Enumeration:
Enumeration<String> e = Collections.enumeration(dups);
Vector<String> v = new Vector<String>();
while(e.hasMoreElements())
v.addElement(e.nextElement());
// Converting an old-style Vector

// to a List via an Enumeration:
ArrayList<String> arrayList =
Collections.list(v.elements());
print("arrayList: " + arrayList);
}
} /* Output:
[one, Two, three, Four, five, six, one]
‘list’ disjoint (Four)?: false
max: three
min: Four
634 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

max w/ comparator: Two
min w/ comparator: five
indexOfSubList: 3
lastIndexOfSubList: 3
replaceAll: [Yo, Two, three, Four, five, six, Yo]
reverse: [Yo, six, five, Four, three, Two, Yo]
rotate: [three, Two, Yo, Yo, six, five, Four]
copy: [in, the, matrix, Yo, six, five, Four]
swap: [Four, the, matrix, Yo, six, five, in]
shuffled: [six, matrix, the, Four, Yo, five, in]
fill: [pop, pop, pop, pop, pop, pop, pop]
frequency of ‘pop’: 7
dups: [snap, snap, snap]
‘list’ disjoint ‘dups’?: true
arrayList: [snap, snap, snap]
*///:~
The output explains the behavior of each utility method. Note the difference in min( ) and

max( ) with the String.CASE_INSENSITIVE_ORDER Comparator because of
capitalization.
Sorting and searching Lists
Utilities to perform sorting and searching for Lists have the same names and signatures as
those for sorting arrays of objects, but are static methods of Collections instead of Arrays.
Here’s an example that uses the list data from Utilities.java:
//: containers/ListSortSearch.java
// Sorting and searching Lists with Collections utilities.
import java.util.*;
import static net.mindview.util.Print.*;

public class ListSortSearch {
public static void main(String[] args) {
List<String> list =
new ArrayList<String>(Utilities.list);
list.addAll(Utilities.list);
print(list);
Collections.shuffle(list, new Random(47));
print("Shuffled: " + list);
// Use a ListIterator to trim off the last elements:
ListIterator<String> it = list.listIterator(10);
while(it.hasNext()) {
it.next();
it.remove();
}
print("Trimmed: " + list);
Collections.sort(list);
print("Sorted: " + list);
String key = list.get(7);
int index = Collections.binarySearch(list, key);

print("Location of " + key + " is " + index +
", list.get(" + index + ") = " + list.get(index));
Collections.sort(list, String.CASE_INSENSITIVE_ORDER);
print("Case-insensitive sorted: " + list);
key = list.get(7);
index = Collections.binarySearch(list, key,
String.CASE_INSENSITIVE_ORDER);
print("Location of " + key + " is " + index +
", list.get(" + index + ") = " + list.get(index));
}
Containers in Depth 635
Simpo PDF Merge and Split Unregistered Version -

} /* Output:
[one, Two, three, Four, five, six, one, one, Two, three, Four, five,
six, one]
Shuffled: [Four, five, one, one, Two, six, six, three, three, five,
Four, Two, one, one]
Trimmed: [Four, five, one, one, Two, six, six, three, three, five]
Sorted: [Four, Two, five, five, one, one, six, six, three, three]
Location of six is 7, list.get(7) = six
Case-insensitive sorted: [five, five, Four, one, one, six, six, three,
three, Two]
Location of three is 7, list.get(7) = three
*///:~
Just as when searching and sorting with arrays, if you sort using a Comparator, you must
binarySearch( ) using the same Comparator.
This program also demonstrates the shuffle( ) method in Collections, which randomizes
the order of a List. A ListIterator is created at a particular location in the shuffled list, and
used to remove the elements from that location until the end of the list.

Exercise 40: (5) Create a class containing two String objects and make it Comparable
so that the comparison only cares about the first String. Fill an array and an ArrayList with
objects of your class, using the RandomGenerator generator. Demonstrate that sorting
works properly. Now make a Comparator that only cares about the second String, and
demonstrate that sorting works properly. Also perform a binary search using your
Comparator.
Exercise 41: (3) Modify the class in the previous exercise so that it will work with
HashSets and as a key in HashMaps.
Exercise 42: (2) Modify Exercise 40 so that an alphabetic sort is used.
Making a Collection or Map
unmodifiable
Often it is convenient to create a read-only version of a Collection or Map. The
Collections class allows you to do this by passing the original container into a method that
hands back a read-only version. There are a number of variations on this method, for
Collections (if you can’t treat a Collection as a more specific type), Lists, Sets, and Maps.
This example shows the proper way to build read-only versions of each:
//: containers/ReadOnly.java
// Using the Collections.unmodifiable methods.
import java.util.*;
import net.mindview.util.*;
import static net.mindview.util.Print.*;

public class ReadOnly {
static Collection<String> data =
new ArrayList<String>(Countries.names(6));
public static void main(String[] args) {
Collection<String> c =
Collections.unmodifiableCollection(
new ArrayList<String>(data));
print(c); // Reading is OK

//! c.add("one"); // Can’t change it

List<String> a = Collections.unmodifiableList(
636 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

new ArrayList<String>(data));
ListIterator<String> lit = a.listIterator();
print(lit.next()); // Reading is OK
//! lit.add("one"); // Can’t change it

Set<String> s = Collections.unmodifiableSet(
new HashSet<String>(data));
print(s); // Reading is OK
//! s.add("one"); // Can’t change it

// For a SortedSet:
Set<String> ss = Collections.unmodifiableSortedSet(
new TreeSet<String>(data));

Map<String,String> m = Collections.unmodifiableMap(
new HashMap<String,String>(Countries.capitals(6)));
print(m); // Reading is OK
//! m.put("Ralph", "Howdy!");

// For a SortedMap:
Map<String,String> sm =
Collections.unmodifiableSortedMap(
new TreeMap<String,String>(Countries.capitals(6)));
}

} /* Output:
[ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO]
ALGERIA
[BULGARIA, BURKINA FASO, BOTSWANA, BENIN, ANGOLA, ALGERIA]
{BULGARIA=Sofia, BURKINA FASO=Ouagadougou, BOTSWANA=Gaberone,
BENIN=Porto-Novo, ANGOLA=Luanda, ALGERIA=Algiers}
*///:~
Calling the "unmodifiable" method for a particular type does not cause compile-time
checking, but once the transformation has occurred, any calls to methods that modify the
contents of a particular container will produce an UnsupportedOperationException.
In each case, you must fill the container with meaningful data before you make it read-only.
Once it is loaded, the best approach is to replace the existing reference with the reference that
is produced by the "unmodifiable" call. That way, you don’t run the risk of accidentally trying
to change the contents once you’ve made it unmodifiable. On the other hand, this tool also
allows you to keep a modifiable container as private within a class and to return a read-only
reference to that container from a method call. So, you can change it from within the class,
but everyone else can only read it.
Synchronizing a Collection or Map
The synchronized keyword is an important part of the subject of multithreading, a more
complicated topic that will not be introduced until the Concurrency chapter. Here, I shall
note only that the Collections class contains a way to automatically synchronize an entire
container. The syntax is similar to the "unmodifiable" methods:
//: containers/Synchronization.java
// Using the Collections.synchronized methods.
import java.util.*;

public class Synchronization {
public static void main(String[] args) {
Collection<String> c =
Collections.synchronizedCollection(

new ArrayList<String>());
Containers in Depth 637
Simpo PDF Merge and Split Unregistered Version -

List<String> list = Collections.synchronizedList(
new ArrayList<String>());
Set<String> s = Collections.synchronizedSet(
new HashSet<String>());
Set<String> ss = Collections.synchronizedSortedSet(
new TreeSet<String>());
Map<String,String> m = Collections.synchronizedMap(
new HashMap<String,String>());
Map<String,String> sm =
Collections.synchronizedSortedMap(
new TreeMap<String,String>());
}
} ///:~
It is best to immediately pass the new container through the appropriate "synchronized"
method, as shown above. That way, there’s no chance of accidentally exposing the
unsynchronized version.
Fail fast
The Java containers also have a mechanism to prevent more than one process from
modifying the contents of a container. The problem occurs if you’re in the middle of iterating
through a container, and then some other process steps in and inserts, removes, or changes
an object in that container. Maybe you’ve already passed that element in the container,
maybe it’s ahead of you, maybe the size of the container shrinks after you call size( )—there
are many scenarios for disaster. The Java containers library uses a fail-fast mechanism that
looks for any changes to the container other than the ones your process is personally
responsible for. If it detects that someone else is modifying the container, it immediately
produces a ConcurrentModification- Exception. This is the "fail-fast" aspect—it doesn’t

try to detect a problem later on using a more complex algorithm.
It’s quite easy to see the fail-fast mechanism in operation—all you must do is create an
iterator and then add something to the collection that the iterator is pointing to, like this:
//: containers/FailFast.java
// Demonstrates the "fail-fast" behavior.
import java.util.*;

public class FailFast {
public static void main(String[] args) {
Collection<String> c = new ArrayList<String>();
Iterator<String> it = c.iterator();
c.add("An object");
try {
String s = it.next();
} catch(ConcurrentModificationException e) {
System.out.println(e);
}
}
} /* Output:
java.util.ConcurrentModificationException
*///:~
The exception happens because something is placed in the container after the iterator is
acquired from the container. The possibility that two parts of the program might modify the
same container produces an uncertain state, so the exception notifies you that you should
change your code—in this case, acquire the iterator after you have added all the elements to
the container.
638 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

The ConcurrentHashMap, CopyOnWriteArrayList, and CopyOnWriteArraySet use

techniques that avoid ConcurrentModificationExceptions.
Holding references
The java.lang.ref library contains a set of classes that allow greater flexibility in garbage
collection. These classes are especially useful when you have large objects that may cause
memory exhaustion. There are three classes inherited from the abstract class Reference:
SoftReference, WeakReference, and PhantomReference. Each of these provides a
different level of indirection for the garbage collector if the object in question is only
reachable through one of these Reference objects.
If an object is reachable, it means that somewhere in your program the object can be found.
This could mean that you have an ordinary reference on the stack that goes right to the
object, but you might also have a reference to an object that has a reference to the object in
question; there can be many intermediate links. If an object is reachable, the garbage
collector cannot release it because it’s still in use by your program. If an object isn’t
reachable, there’s no way for your program to use it, so it’s safe to garbage collect that object.
You use Reference objects when you want to continue to hold on to a reference to that
object—you want to reach that object—but you also want to allow the garbage collector to
release that object. Thus, you have a way to use the object, but if memory exhaustion is
imminent, you allow that object to be released.
You accomplish this by using a Reference object as an intermediary (a proxy) between you
and the ordinary reference. In addition, there must be no ordinary references to the object
(ones that are not wrapped inside Reference objects). If the garbage collector discovers that
an object is reachable through an ordinary reference, it will not release that object.
In the order of SoftReference, WeakReference, and PhantomReference, each one is
"weaker" than the last and corresponds to a different level of reachability. Soft references are
for implementing memory-sensitive caches. Weak references are for implementing
"canonicalizing mappings"—where instances of objects can be simultaneously used in
multiple places in a program, to save storage—that do not prevent their keys (or values) from
being reclaimed. Phantom references are for scheduling pre-mortem cleanup actions in a
more flexible way than is possible with the Java finalization mechanism.
With SoftReferences and WeakReferences, you have a choice about whether to place

them on a ReferenceQueue (the device used for premortem cleanup actions), but a
PhantomReference can only be built on a ReferenceQueue. Here’s a simple
demonstration:
//: containers/References.java
// Demonstrates Reference objects
import java.lang.ref.*;
import java.util.*;

class VeryBig {
private static final int SIZE = 10000;
private long[] la = new long[SIZE];
private String ident;
public VeryBig(String id) { ident = id; }
public String toString() { return ident; }
protected void finalize() {
System.out.println("Finalizing " + ident);
}
}

Containers in Depth 639
Simpo PDF Merge and Split Unregistered Version -

public class References {
private static ReferenceQueue<VeryBig> rq =
new ReferenceQueue<VeryBig>();
public static void checkQueue() {
Reference<? extends VeryBig> inq = rq.poll();
if(inq != null)
System.out.println("In queue: " + inq.get());
}

public static void main(String[] args) {
int size = 10;
// Or, choose size via the command line:
if(args.length > 0)
size = new Integer(args[0]);
LinkedList<SoftReference<VeryBig>> sa =
new LinkedList<SoftReference<VeryBig>>();
for(int i = 0; i < size; i++) {
sa.add(new SoftReference<VeryBig>(
new VeryBig("Soft " + i), rq));
System.out.println("Just created: " + sa.getLast());
checkQueue();
}
LinkedList<WeakReference<VeryBig>> wa =
new LinkedList<WeakReference<VeryBig>>();
for(int i = 0; i < size; i++) {
wa.add(new WeakReference<VeryBig>(
new VeryBig("Weak " + i), rq));
System.out.println("Just created: " + wa.getLast());
checkQueue();
}
SoftReference<VeryBig> s =
new SoftReference<VeryBig>(new VeryBig("Soft"));
WeakReference<VeryBig> w =
new WeakReference<VeryBig>(new VeryBig("Weak"));
System.gc();
LinkedList<PhantomReference<VeryBig>> pa =
new LinkedList<PhantomReference<VeryBig>>();
for(int i = 0; i < size; i++) {
pa.add(new PhantomReference<VeryBig>(

new VeryBig("Phantom " + i), rq));
System.out.println("Just created: " + pa.getLast());
checkQueue();
}
}
} /* (Execute to see output) *///:~
When you run this program (you’ll want to redirect the output into a text file so that you can
view the output in pages), you’ll see that the objects are garbage collected, even though you
still have access to them through the Reference object (to get the actual object reference,
you use get( )). You’ll also see that the ReferenceQueue always produces a Reference
containing a null object. To use this, inherit from a particular Reference class and add
more useful methods to the new class.
The WeakHashMap
The containers library has a special Map to hold weak references: the WeakHashMap.
This class is designed to make the creation of canonicalized mappings easier. In such a
mapping, you are saving storage by creating only one instance of a particular value. When the
program needs that value, it looks up the existing object in the mapping and uses that (rather
than creating one from scratch). The mapping may make the values as part of its
initialization, but it’s more likely that the values are made on demand.
640 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

Since this is a storage-saving technique, it’s very convenient that the WeakHashMap allows
the garbage collector to automatically clean up the keys and values. You don’t have to do
anything special to the keys and values you want to place in the WeakHashMap; these are
automatically wrapped in WeakReferences by the map. The trigger to allow cleanup is that
the key is no longer in use, as demonstrated here:
//: containers/CanonicalMapping.java
// Demonstrates WeakHashMap.
import java.util.*;


class Element {
private String ident;
public Element(String id) { ident = id; }
public String toString() { return ident; }
public int hashCode() { return ident.hashCode(); }
public boolean equals(Object r) {
return r instanceof Element &&
ident.equals(((Element)r).ident);
}
protected void finalize() {
System.out.println("Finalizing " +
getClass().getSimpleName() + " " + ident);
}
}

class Key extends Element {
public Key(String id) { super(id); }
}

class Value extends Element {
public Value(String id) { super(id); }
}

public class CanonicalMapping {
public static void main(String[] args) {
int size = 1000;
// Or, choose size via the command line:
if(args.length > 0)
size = new Integer(args[0]);

Key[] keys = new Key[size];
WeakHashMap<Key,Value> map =
new WeakHashMap<Key,Value>();
for(int i = 0; i < size; i++) {
Key k = new Key(Integer.toString(i));
Value v = new Value(Integer.toString(i));
if(i % 3 == 0)
keys[i] = k; // Save as "real" references
map.put(k, v);
}
System.gc();
}
} /* (Execute to see output) *///:~
The Key class must have a hashCode( ) and an equals( ) since it is being used as a key in a
hashed data structure. The subject of hashCode( ) was described earlier in this chapter.
When you run the program, you’ll see that the garbage collector will skip every third key,
because an ordinary reference to that key has also been placed in the keys array, and thus
those objects cannot be garbage collected.

Containers in Depth 641
Simpo PDF Merge and Split Unregistered Version -

Java 1.0/1.1 containers
Unfortunately, a lot of code was written using the Java 1.0/1.1 containers, and even new code
is sometimes written using these classes. So although you should never use the old containers
when writing new code, you’ll still need to be aware of them. However, the old containers
were quite limited, so there’s not that much to say about them, and since they are
anachronistic, I will try to refrain from overemphasizing some of their hideous design
decisions.
Vector & Enumeration

The only self-expanding sequence in Java 1.0/1.1 was the Vector, so it saw a lot of use. Its
flaws are too numerous to describe here (see the 1
st edition of this book, available as a free
download from www.MindView.net). Basically, you can think of it as an ArrayList with
long, awkward method names. In the revised Java container library, Vector was adapted so
that it could work as a Collection and a List. This turns out to be a bit perverse, as it may
confuse some people into thinking that Vector has gotten better, when it is actually included
only to support older Java code.
The Java 1.0/1.1 version of the iterator chose to invent a new name, "enumeration," instead
of using a term that everyone was already familiar with ("iterator"). The Enumeration
interface is smaller than Iterator, with only two methods, and it uses longer method names:
boolean hasMoreElements( ) produces true if this enumeration contains more
elements, and Object nextElement( ) returns the next element of this enumeration if there
are any more (otherwise it throws an exception).
Enumeration is only an interface, not an implementation, and even new libraries
sometimes still use the old Enumeration, which is unfortunate but generally harmless.
Even though you should always use Iterator when you can in your own code, you must be
prepared for libraries that want to hand you an Enumeration.
In addition, you can produce an Enumeration for any Collection by using the
Collections.enumeration( ) method, as seen in this example:
//: containers/Enumerations.java
// Java 1.0/1.1 Vector and Enumeration.
import java.util.*;
import net.mindview.util.*;

public class Enumerations {
public static void main(String[] args) {
Vector<String> v =
new Vector<String>(Countries.names(10));
Enumeration<String> e = v.elements();

while(e.hasMoreElements())
System.out.print(e.nextElement() + ", ");
// Produce an Enumeration from a Collection:
e = Collections.enumeration(new ArrayList<String>());
}
} /* Output:
ALGERIA, ANGOLA, BENIN, BOTSWANA, BULGARIA, BURKINA FASO, BURUNDI,
CAMEROON, CAPE VERDE, CENTRAL AFRICAN REPUBLIC,
*///:~
To produce an Enumeration, you call elements( ), then you can use it to perform a
forward iteration.
642 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

The last line creates an ArrayList and uses enumeration( ) to adapt an Enumeration
from the ArrayList Iterator. Thus, if you have old code that wants an Enumeration, you
can still use the new containers.
Hashtable
As you’ve seen in the performance comparison in this chapter, the basic Hashtable is very
similar to the HashMap, even down to the method names. There’s no reason to use
Hashtable instead of HashMap in new code.
Stack
The concept of the stack was introduced earlier, with the LinkedList. What’s rather odd
about the Java 1.0/1.1 Stack is that instead of using a Vector with composition, Stack is
inherited from Vector. So it has all of the characteristics and behaviors of a Vector plus
some extra Stack behaviors. It’s difficult to know whether the designers consciously thought
that this was an especially useful way of doing things, or whether it was just a naive design; in
any event it was clearly not reviewed before it was rushed into distribution, so this bad design
is still hanging around (but you shouldn’t use it).
Here’s a simple demonstration of Stack that pushes each String representation of an

enum. It also shows how you can just as easily use a LinkedList as a stack, or the Stack
class created in the Holding Your Objects chapter:
//: containers/Stacks.java
// Demonstration of Stack Class.
import java.util.*;
import static net.mindview.util.Print.*;

enum Month { JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE,
JULY, AUGUST, SEPTEMBER, OCTOBER, NOVEMBER }

public class Stacks {
public static void main(String[] args) {
Stack<String> stack = new Stack<String>();
for(Month m : Month.values())
stack.push(m.toString());
print("stack = " + stack);
// Treating a stack as a Vector:
stack.addElement("The last line");
print("element 5 = " + stack.elementAt(5));
print("popping elements:");
while(!stack.empty())
printnb(stack.pop() + " ");

// Using a LinkedList as a Stack:
LinkedList<String> lstack = new LinkedList<String>();
for(Month m : Month.values())
lstack.addFirst(m.toString());
print("lstack = " + lstack);
while(!lstack.isEmpty())
printnb(lstack.removeFirst() + " ");


// Using the Stack class from
// the Holding Your Objects Chapter:
net.mindview.util.Stack<String> stack2 =
new net.mindview.util.Stack<String>();
for(Month m : Month.values())
stack2.push(m.toString());
Containers in Depth 643
Simpo PDF Merge and Split Unregistered Version -

print("stack2 = " + stack2);
while(!stack2.empty())
printnb(stack2.pop() + " ");

}
} /* Output:
stack = [JANUARY, FEBRUARY, MARCH, APRIL, MAY, JUNE, JULY, AUGUST,
SEPTEMBER, OCTOBER, NOVEMBER]
element 5 = JUNE
popping elements:
The last line NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL
MARCH FEBRUARY JANUARY lstack = [NOVEMBER, OCTOBER, SEPTEMBER, AUGUST,
JULY, JUNE, MAY, APRIL, MARCH, FEBRUARY, JANUARY]
NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH FEBRUARY
JANUARY stack2 = [NOVEMBER, OCTOBER, SEPTEMBER, AUGUST, JULY, JUNE, MAY,
APRIL, MARCH, FEBRUARY, JANUARY]
NOVEMBER OCTOBER SEPTEMBER AUGUST JULY JUNE MAY APRIL MARCH FEBRUARY
JANUARY
*///:~
A String representation is generated from the Month enum constants, inserted into the

Stack with push( ), and later fetched from the top of the stack with a pop( ). To make a
point, Vector operations are also performed on the Stack object. This is possible because,
by virtue of inheritance, a Stack is a Vector. Thus, all operations that can be performed on a
Vector can also be performed on a Stack, such as elementAt( ).
As mentioned earlier, you should use a LinkedList when you want stack behavior, or the
net.mindview.util.Stack class created from the LinkedList class.
BitSet
A BitSet is used if you want to efficiently store a lot of on-off information. It’s efficient only
from the standpoint of size; if you’re looking for efficient access, it is slightly slower than
using a native array.
In addition, the minimum size of the BitSet is that of a long: 64 bits. This implies that if
you’re storing anything smaller, like 8 bits, a BitSet will be wasteful; you’re better off
creating your own class, or just an array, to hold your flags if size is an issue. (This will only
be the case if you’re creating a lot of objects containing lists of on-off information, and should
only be decided based on profiling and other metrics. If you make this decision because you
just think something is too big, you will end up creating needless complexity and wasting a
lot of time.)
A normal container expands as you add more elements, and the BitSet does this as well. The
following example shows how the BitSet works:
//: containers/Bits.java
// Demonstration of BitSet.
import java.util.*;
import static net.mindview.util.Print.*;

public class Bits {
public static void printBitSet(BitSet b) {
print("bits: " + b);
StringBuilder bbits = new StringBuilder();
for(int j = 0; j < b.size() ; j++)
bbits.append(b.get(j) ? "1" : "0");

print("bit pattern: " + bbits);
}
644 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

public static void main(String[] args) {
Random rand = new Random(47);
// Take the LSB of nextInt():
byte bt = (byte)rand.nextInt();
BitSet bb = new BitSet();
for(int i = 7; i >= 0; i )
if(((1 << i) & bt) != 0)
bb.set(i);
else
bb.clear(i);
print("byte value: " + bt);
printBitSet(bb);

short st = (short)rand.nextInt();
BitSet bs = new BitSet();
for(int i = 15; i >= 0; i )
if(((1 << i) & st) != 0)
bs.set(i);
else
bs.clear(i);
print("short value: " + st);
printBitSet(bs);

int it = rand.nextInt();
BitSet bi = new BitSet();

for(int i = 31; i >= 0; i )
if(((1 << i) & it) != 0)
bi.set(i);
else
bi.clear(i);
print("int value: " + it);
printBitSet(bi);

// Test bitsets >= 64 bits:
BitSet b127 = new BitSet();
b127.set(127);
print("set bit 127: " + b127);
BitSet b255 = new BitSet(65);
b255.set(255);
print("set bit 255: " + b255);
BitSet b1023 = new BitSet(512);
b1023.set(1023);
b1023.set(1024);
print("set bit 1023: " + b1023);
}
} /* Output:
byte value: -107
bits: {0, 2, 4, 7}
bit pattern:
1010100100000000000000000000000000000000000000000000000000000000
short value: 1302
bits: {1, 2, 4, 8, 10}
bit pattern:
0110100010100000000000000000000000000000000000000000000000000000
int value: -2014573909

bits: {0, 1, 3, 5, 7, 9, 11, 18, 19, 21, 22, 23, 24, 25, 26, 31}
bit pattern:
1101010101010000001101111110000100000000000000000000000000000000
set bit 127: {127}
set bit 255: {255}
set bit 1023: {1023, 1024}
*///:~
Containers in Depth 645
Simpo PDF Merge and Split Unregistered Version -

646 Thinking in Java Bruce Eckel
The random number generator is used to create a random byte, short, and int, and each
one is transformed into a corresponding bit pattern in a BitSet. This works fine because a
BitSet is 64 bits, so none of these cause it to increase in size. Then larger BitSets are
created. You can see that the BitSet is expanded as necessary.
An EnumSet (see the Enumerated Types chapter) is usually a better choice than a BitSet if
you have a fixed set of flags that you can name, because the EnumSet allows you to
manipulate the names rather than numerical bit locations, and thus reduces errors.
EnumSet also prevents you from accidentally adding new flag locations, which could cause
some serious, difficult-to-find bugs. The only reasons you should use BitSet instead of
EnumSet is if you don’t know how many flags you will need until run time, or if it is
unreasonable to assign names to the flags, or you need one of the special operations in
BitSet (see the JDK documentation for BitSet and EnumSet).
Summary
The containers library is arguably the most important library for an objectoriented language.
Most programming will use containers more than any other library components. Some
languages (Python, for example) even include the fundamental container components (lists,
maps and sets) as built-ins.
As you saw in the Holding Your Objects chapter, it’s possible to do a number of very
interesting things using containers, without much effort. However, at some point you’re

forced to know more about containers in order to use them properly—in particular, you must
know enough about hashing operations to write your own hashCode( ) method (and you
must know when it is necessary), and you must know enough about the various container
implementations that you can choose the appropriate one for your needs. This chapter
covered these concepts and discussed additional useful details about the container library. At
this point you should be reasonably well prepared to use the Java containers in your everyday
programming tasks.
The design of a containers library is difficult (this is true of most library design problems). In
C++, the container classes covered the bases with many different classes. This was better
than what was available prior to the C++ container classes (nothing), but it didn’t translate
well into Java. At the other extreme, I’ve seen a containers library that consists of a single
class, "container," which acts like both a linear sequence and an associative array at the same
time. The Java container library strikes a balance: the full functionality that you expect from
a mature container library, but easier to learn and use than the C++ container classes and
other similar container libraries. The result can seem a bit odd in places. Unlike some of the
decisions made in the early Java libraries, these oddities were not accidents, but carefully
considered decisions based on trade-offs in complexity.
Solutions to selected exercises can be found in the electronic document The Thinking in Java Annotated Solution Guide,
available for sale from www.MindView.net.

Simpo PDF Merge and Split Unregistered Version -

I/O
Creating a good input/output (I/O) system is one of the more difficult
tasks for a language designer. This is evidenced by the number of
different approaches.
The challenge seems to be in covering all possibilities. Not only are there different sources
and sinks of I/O that you want to communicate with (files, the console, network connections,
etc.), but you need to talk to them in a wide variety of ways (sequential, random-access,
buffered, binary, character, by lines, by words, etc.). The Java library designers attacked this

problem by creating lots of classes. In fact, there are so many classes for Java’s I/O system
that it can be intimidating at first (ironically, the Java I/O design actually prevents an
explosion of classes). There was also a significant change in the I/O library after Java i.o,
when the original byte-oriented library was supplemented with char-oriented, Unicode-
based I/O classes. The nio classes (for "new I/O," a name we’ll still be using years from now
even though they were introduced in JDK 1.4 and so are already "old") were added for
improved performance and functionality. As a result, there are a fair number of classes to
learn before you understand enough of Java’s I/O picture that you can use it properly. In
addition, it’s rather important to understand the evolution of the I/O library, even if your
first reaction is "Don’t bother me with history, just show me how to use it!" The problem is
that without the historical perspective, you will rapidly become confused with some of the
classes and when you should and shouldn’t use them. This chapter will give you an
introduction to the variety of I/O classes in the standard Java library and how to use them.
The File class
Before getting into the classes that actually read and write data to streams, we’ll look at a
library utility that assists you with file directory issues. The File class has a deceiving name;
you might think it refers to a file, but it doesn’t. In fact, "FilePath" would have been a better
name for the class. It can represent either the name of a particular file or the names of a set
of files in a directory. If it’s a set of files, you can ask for that set using the list( ) method,
which returns an array of String. It makes sense to return an array rather than one of the
flexible container classes, because the number of elements is fixed, and if you want a
different directory listing, you just create a different File object. This section shows an
example of the use of this class, including the associated FilenameFilter interface.
A directory lister
Suppose you’d like to see a directory listing. The File object can be used in two ways. If you
call list( ) with no arguments, you’ll get the full list that the File object contains. However, if
you want a restricted list—for example, if you want all of the files with an extension of .Java—
then you use a "directory filter," which is a class that tells how to select the File objects for
display. Here’s the example. Note that the result has been effortlessly sorted (alphabetically)
using the java.util.Arrays.sort( ) method and the

String.CASE_INSENSITIVE_ORDER Comparator:

//: io/DirList.java
// Display a directory listing using regular expressions.
// {Args: "D.*\.java"}
import java.util.regex.*;
import java.io.*;

Simpo PDF Merge and Split Unregistered Version -

import java.util.*;

public class DirList {
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)
list = path.list();
else
list = path.list(new DirFilter(args[0]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
}

class DirFilter implements FilenameFilter {
private Pattern pattern;
public DirFilter(String regex) {
pattern = Pattern.compile(regex);

}
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
} /* Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///:~
The DirFilter class implements the interface FilenameFilter. Notice how simple the
FilenameFilter interface is:
public interface FilenameFilter {
boolean accept(File dir, String name);
}
DirFilter’s sole reason for existence is to provide the accept( ) method to the list( )
method so that list( ) can "call back" accept( ) to determine which file names should be
included in the list. Thus, this structure is often referred to as a callback. More specifically,
this is an example of the Strategy design pattern, because list( ) implements basic
functionality, and you provide the Strategy in the form of a FilenameFilter in order to
complete the algorithm necessary for list( ) to provide its service. Because list( ) takes a
FilenameFilter object as its argument, it means that you can pass an object of any class
that implements FilenameFilter to choose (even at run time) how the list( ) method will
behave. The purpose of a Strategy is to provide flexibility in the behavior of code.
The accept( ) method must accept a File object representing the directory that a particular
file is found in, and a String containing the name of that file. Remember that the list( )
method is calling accept( ) for each of the file names in the directory object to see which one
should be included; this is indicated by the boolean result returned by accept( ).
accept( ) uses a regular expression matcher object to see if the regular expression regex
matches the name of the file. Using accept( ), the list( ) method returns an array.



648 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

Anonymous inner classes
This example is ideal for rewriting using an anonymous inner class (described in Inner
Classes). As a first cut, a method filter( ) is created that returns a reference to a
FilenameFilter:
//: io/DirList2.java
// Uses anonymous inner classes.
// {Args: "D.*\.java"}
import java.util.regex.*;
import java.io.*;
import java.util.*;

public class DirList2 {
public static FilenameFilter filter(final String regex) {
// Creation of anonymous inner class:
return new FilenameFilter() {
private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
}; // End of anonymous inner class
}
public static void main(String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)

list = path.list();
else
list = path.list(filter(args[0]));
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
} /* Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///:~
Note that the argument to filter( ) must be final. This is required by the anonymous inner
class so that it can use an object from outside its scope. This design is an improvement
because the FilenameFilter class is now tightly bound to DirList2. However, you can take
this approach one step further and define the anonymous inner class as an argument to
list(), in which case it’s even smaller:
//: io/DirList3.java
// Building the anonymous inner class "in-place."
// {Args: "D.*\.java"}
import java.util.regex.*;
import java.io.*;
import java.util.*;

public class DirList3 {
public static void main(final String[] args) {
File path = new File(".");
String[] list;
if(args.length == 0)

list = path.list();
else
I/O 649
Simpo PDF Merge and Split Unregistered Version -

list = path.list(new FilenameFilter() {
private Pattern pattern = Pattern.compile(args[0]);
public boolean accept(File dir, String name) {
return pattern.matcher(name).matches();
}
});
Arrays.sort(list, String.CASE_INSENSITIVE_ORDER);
for(String dirItem : list)
System.out.println(dirItem);
}
} /* Output:
DirectoryDemo.java
DirList.java
DirList2.java
DirList3.java
*///:~
The argument to main( ) is now final, since the anonymous inner class uses args[0]
directly.
This shows you how anonymous inner classes allow the creation of specific, one-off classes to
solve problems. One benefit of this approach is that it keeps the code that solves a particular
problem isolated in one spot. On the other hand, it is not always as easy to read, so you must
use it judiciously.
Exercise 1: (3) Modify DirList.java (or one of its variants) so that the FilenameFilter
opens and reads each file (using the net.mindview.util.TextFile utility) and accepts the
file based on whether any of the trailing arguments on the command line exist in that file.

Exercise 2: (2) Create a class called SortedDirList with a constructor that takes a File
object and builds a sorted directory list from the files at that File. Add to this class two
overloaded list( ) methods: the first produces the whole list, and the second produces the
subset of the list that matches its argument (which is a regular expression).
Exercise 3: (3) Modify DirList.java (or one of its variants) so that it sums up the file
sizes of the selected files.
Directory utilities
A common task in programming is to perform operations on sets of files, either in the local
directory or by walking the entire directory tree. It is useful to have a tool that will produce
the set of files for you. The following utility class produces either an array of File objects in
the local directory using the local( ) method, or a List<File> of the entire directory tree
starting at the given directory using walk( ) (File objects are more useful than file names
because File objects contain more information). The files are chosen based on the regular
expression that you provide:
//: net/mindview/util/Directory.java
// Produce a sequence of File objects that match a
// regular expression in either a local directory,
// or by walking a directory tree.
package net.mindview.util;
import java.util.regex.*;
import java.io.*;
import java.util.*;

public final class Directory {
public static File[]
650 Thinking in Java Bruce Eckel
Simpo PDF Merge and Split Unregistered Version -

local(File dir, final String regex) {
return dir.listFiles(new FilenameFilter() {

private Pattern pattern = Pattern.compile(regex);
public boolean accept(File dir, String name) {
return pattern.matcher(
new File(name).getName()).matches();
}
});
}
public static File[]
local(String path, final String regex) { // Overloaded
return local(new File(path), regex);
}
// A two-tuple for returning a pair of objects:
public static class TreeInfo implements Iterable<File> {
public List<File> files = new ArrayList<File>();
public List<File> dirs = new ArrayList<File>();
// The default iterable element is the file list:
public Iterator<File> iterator() {
return files.iterator();
}
void addAll(TreeInfo other) {
files.addAll(other.files);
dirs.addAll(other.dirs);
}
public String toString() {
return "dirs: " + PPrint.pformat(dirs) +
"\n\nfiles: " + PPrint.pformat(files);
}
}
public static TreeInfo
walk(String start, String regex) { // Begin recursion

return recurseDirs(new File(start), regex);
}
public static TreeInfo
walk(File start, String regex) { // Overloaded
return recurseDirs(start, regex);
}
public static TreeInfo walk(File start) { // Everything
return recurseDirs(start, ".*");
}
public static TreeInfo walk(String start) {
return recurseDirs(new File(start), ".*");
}
static TreeInfo recurseDirs(File startDir, String regex){
TreeInfo result = new TreeInfo();
for(File item : startDir.listFiles()) {
if(item.isDirectory()) {
result.dirs.add(item);
result.addAll(recurseDirs(item, regex));
} else // Regular file
if(item.getName().matches(regex))
result.files.add(item);
}
return result;
}
// Simple validation test:
public static void main(String[] args) {
if(args.length == 0)
System.out.println(walk("."));
else
for(String arg : args)

System.out.println(walk(arg));
I/O 651
Simpo PDF Merge and Split Unregistered Version -

×