You are on page 1of 7

Essential Java Classes

Your feedback is important to us! If you have comments about this trail, you can send us
mail about it by filling out the form on our feedback page.
This trail discusses classes from the Java platform that are essential to most programmers. In
particular, it focuses on classes from the java.lang and java.io packages, including these:

• String and StringBuffer


• Properties
• System
• SecurityManager
• Thread and its related classes
• Throwable and Exception, and their friends
• The Reader, Writer, InputStream, and OutputStream classes from java.io and
their descendants

Like the rest of the tutorial, this trail is designed so that you can skip around. Feel free to read
only the lessons for the classes that interest you.

Using String and StringBuffer illustrates how to manipulate character data using the String
and StringBuffer classes. It also teaches you about accessor methods and how the compiler
uses Strings and StringBuffers behind the scenes.

Setting Program Attributes describes how you can set attributes for your Java programs
through the use of properties and command-line arguments. Use properties to change attributes
for every invocation of your program; use command-line arguments to change attributes for
only the current invocation of your program.

Accessing System Resources shows you how, through the System class, your Java programs
can manage properties, set up a security manager, and access system resources such as the
standard input and output streams. The System class provides a system-independent
programming interface to system resources, thus allowing your programs to use them without
compromising portability. This lesson also contains a brief discussion of the Runtime class and
why most programmers should avoid using it.

Handling Errors with Exceptions explains how you can use Java's exception mechanism to
handle errors in your programs. This lesson describes what an exception is, how to throw and
catch exceptions, what to do with an exception once you've caught it, and how to best use the
exception class hierarchy provided by the Java platform.

Doing Two or More Tasks at Once: Threads discusses in detail the use of threads that enable
your Java applications or applets to perform multiple tasks simultaneously. This lesson
describes when and why you might want to use threads, how to create and manage threads and
thread groups in your Java program, and how to avoid common pitfalls such as deadlock,
starvation, and race conditions.
Reading and Writing (but No 'rithmetic) describes the process of getting information into
your program and sending it out again through the use of the stream classes in java.io.
Reading and writing information provides the basis for all kinds of interesting behaviors, such
as serializing objects, invoking methods on objects in another VM, communicating over a
network, or just accessing the file system.

Collections Custom Implementations


Many programmers will never need to implement their own collections classes. You can go
pretty far using the implementations described in the previous sections of this lesson. Someday,
however, you might find yourself wanting to write your own implementation of a core
collection interface. It turns out that it's easy to do with the abstract implementations provided
by the Java platform. But before we discuss how to write an implementation, let's discuss why
you might want to do such a thing.

Reasons to Write Your Own Implementation

This list enumerates several kinds of collections you might want to implement. It is not
intended to be exhaustive.

• Persistent: All of the built-in collection implementations reside in main memory, and
vanish when the VM exits. Suppose you want a collection that will still be present the
next time the VM starts. One fine way to implement such a collection is to build a
veneer over an external database. Such a collection might conceivably be concurrently
accessible by multiple VMs, since it resides outside the VM.
• Application-specific: This is a very broad category. One example is an unmodifiable
Map containing real-time telemetry data. The keys might represent locations, and the
values could be read from sensors at these locations in response to the get operation.
• Highly Concurrent: The built-in collections are not designed to support high
concurrency. The synchronization wrappers (and the legacy implementations) lock the
entire collection every time it's accessed. Suppose you're building a server and you need
a Map implementation that can be accessed by many threads concurrently. It is
reasonably straightforward to build a hash table that locks each bucket separately,
allowing multiple threads to access the table concurrently (assuming they're accessing
keys that hash to different buckets).
• High-performance, Special-purpose: There are many data structures that take
advantage of restricted usage to offer better performance than is possible with general-
purpose implementations. For example, consider a Set whose elements are restricted to
a small, fixed universe. Such a Set can be represented as a bit-vector, which offers
blindingly fast performance as well low memory usage. Another example concerns a
List containing long runs of identical element values. Such lists, which occur
frequently in text processing, can be run-length encoded: runs can be represented as a
single object containing the repeated element and the number of consecutive repetitions.
This example is interesting because it trades off two aspects of performance: It requires
far less space than an ArrayList, but more time.
• High-performance, General-purpose: The engineers who designed the collections
framework tried to provide the best general-purpose implementations for each interface,
but there are many, many data structures that could have been used, and new ones are
invented every day. Maybe you can come up with something faster!
• Enhanced functionality: Suppose you need a Map (or Set) implementation that offers
constant time access, as well as insertion-order iteration. This combination can be
achieved with a hash table, all of whose elements are further joined, in insertion order,
into a doubly-linked list. Alternatively, suppose you need an efficient bag
implementation (also known as a multiset): a Collection that offers constant time
access while allowing duplicate elements. It's reasonably straightforward to implement
such a collection atop a HashMap.
• Convenience: You may want additional convenience implementations beyond those
offered by the Java platform. For instance, you may have a frequent need for immutable
Map objects representing a single key-value mapping, or List objects representing a
contiguous range of Integers, or whatever.
• Adapter: Suppose you are using some legacy API that has its own ad hoc collections
API. You can write an adapter implementation that permits these collections to operate
in the Java Collections Framework. An adapter implementation is a thin veneer that
wraps objects of one type and makes them behave like objects of another type by
translating operations on the latter type into operations on the former.

How to Write a Custom Implementation

Writing a custom implementation is surprisingly easy with the aid of the abstract
implementations furnished by the Java platform. Abstract implementations are skeletal
implementations of the core collection interfaces designed expressly to facilitate custom
implementations. We'll start with an example. Here's an implementation of Arrays.asList .
public static List asList(Object[] a) {
return new ArrayList(a);
}

private static class ArrayList extends AbstractList


implements java.io.Serializable
{
private Object[] a;

ArrayList(Object[] array) {
a = array;
}

public Object get(int index) {


return a[index];
}

public Object set(int index, Object element) {


Object oldValue = a[index];
a[index] = element;
return oldValue;
}
public int size() {
return a.length;
}
}
Believe it or not, this is almost exactly the implementation contained in the JDK. It's that
simple! You provide a constructor, the get, set, and size methods, and AbstractList does
all the rest. You get the ListIterator, bulk operations, search operations, hash code
computation, comparison and string representation for free.

Suppose you want to make the implementation a bit faster. The API documentation for the
abstract implementations describes precisely how each method is implemented so you'll know
which methods to override in order to get the performance that you want. The performance of
the implementation above is fine, but it can be improved a bit. In particular, the toArray
method iterates over the List copying one element at a time. Given the internal representation,
it's a lot faster and more sensible just to clone the array:

public Object[] toArray() {


return (Object[]) a.clone();
}
With the addition of this override, and a similar one for toArray(Object[]), this
implementation is exactly the one found in the JDK. In the interests of full disclosure, it's a bit
tougher to use the other abstract implementations because they require you to write your own
iterator, but it's still not that hard.

The abstract implementations are summarized below:

• AbstractCollection : A Collection that is neither a Set nor a List, such as a bag.


At a minimum, you must provide the iterator and the size method.
• AbstractSet : A Set. Use is identical to AbstractCollection.
• AbstractList : A List backed by a random-access data store (such as an array). At a
minimum, you must provide the positional access methods (get(int) and, optionally,
set(int), remove(int), and add(int)) and the size method. The abstract class takes
care of listIterator (and iterator).
• AbstractSequentialList : A List backed by a sequential-access data store (such as
a linked list). At a minimum, you must provide the listIterator and size methods.
The abstract class takes care of the positional access methods. (This is the opposite of
AbstractList.)
• AbstractMap : A Map. At a minimum you must provide the entrySet view. This is
typically implemented with the AbstractSet class. If the Map is modifiable, you must
also provide the put method.

The process of writing a custom implementation is summarized below:

1. Choose the appropriate abstract implementation class from the list above.
2. Provide implementations for all of the class's abstract methods. If your custom
collection is to be modifiable, you'll have to override one or more concrete methods as
well. The API documentation for the abstract implementation class will tell you which
methods to override.
3. Test and, if necessary, debug the implementation. You now have a working custom
collection implementation!
4. If you're concerned about performance, read the abstract implementation class's API
documentation for all of the methods whose implementations you're inheriting. If any of
them seem too slow, override them. If you override any methods, be sure to measure the
performance of the method before and after the override! How much effort you put into
tweaking the performance should be a function of how much use the implementation
will get, and how performance-critical the use. (Often, this step is best omitted.)

Solving Common Collections Problems


Problem: When I try to use Arrays.sort() or Collections.sort() I get a
ClassCastException.
The problem is that you're using the 1.1 backport. To sort objects without providing an
explicit comparator, the objects must be "mutually comparable." In 1.2, String (and
many other JDK classes) have been retrofitted to implement Comparable. In 1.1, you
have to provide an explicit String comparator. Since this is your lucky day, I'll provide
you with one:
static final Comparator stringCmp = new Comparator() {
public int compare(Object o1, Object o2) {
String s1 = (String)o1;
String s2 = (String)o2;
int len1 = s1.length();
int len2 = s2.length();
for (int i=0, n=Math.min(len1, len2); i<n; i++) {
char c1 = s1.charAt(i);
char c2 = s2.charAt(i);
if (c1 != c2)
return c1 - c2;
}
return len1 - len2;
}
};
With this in your program, merely replace the line:
Collections.sort(l);
With:
Collections.sort(l, stringCmp);
and you're in business!
Note: A better solution might be to download the Java 2 Platform (formerly known as JDK 1.2)
to take advantage of the implementation of Comparable.

Problem: What collection interface behaves like the legacy Vector class? The feature I'm most
interested in is a Vector's ability to grow dynamically in size.

• All of the new general-purpose collection implementations have the ability to grow
dynamically in size. The new interface that models Vector's behavior is List. The two
general purpose implementations of List are ArrayList and LinkedList. The one
whose performance properties are similar to Vector's is ArrayList. All other things
being equal, ArrayList is the preferred List implementation. So, to recap, the Java 2
replacement for the legacy:
• Vector a = new Vector();

is:

List a = new ArrayList();

Problem: Are Hashtables synchronized in Java 2 (formerly known as JDK 1.2)?

• Yes, Hashtables are still synchronized in Java 2. The fact that the new
implementations in Java 2 are unsynchronized represents a break with the past: Vector
and Hashtable were synchronized in versions of the JDK prior to 1.2. In other words,
legacy collections (like Vector and Hashtable) are synchronized, whereas new
collections (like ArrayList and HashMap) are unsynchronized, and must be "wrapped"
via Collections.SynchronizedList or Collections.synchronizedMap if
synchronization is desired.

Problem: Hoes does the Java collections compare to STL in C++?

• While the new Java collections is somewhat similar to STL, it's also markedly different:
Iterators and iterator-pairs play a central role in STL which they do not play in our
framework; collection interfaces play a central role in our framework which they don't
play in STL.

We designed Java collections from a "clean sheet of paper" and chose names for clarity,
brevity, and similarity with preexisting Java APIs. Happily, most STL users have had
little difficulty adapting to the framework's nomenclature.

The Reflection API


by Dale Green

Your feedback is important to us! If you have comments about this trail, you can send us
mail about it by filling out the form on our feedback page.
The reflection API represents, or reflects, the classes, interfaces, and objects in the current Java
Virtual Machine. You'll want to use the reflection API if you are writing development tools
such as debuggers, class browsers, and GUI builders. With the reflection API you can:

• Determine the class of an object.


• Get information about a class's modifiers, fields, methods, constructors, and
superclasses.
• Find out what constants and method declarations belong to an interface.
• Create an instance of a class whose name is not known until runtime.
• Get and set the value of an object's field, even if the field name is unknown to your
program until runtime.
• Invoke a method on an object, even if the method is not known until runtime.
• Create a new array, whose size and component type are not known until runtime, and
then modify the array's components.

First, a note of caution. Don't use the reflection API when other tools more natural to the Java
programming language would suffice. For example, if you are in the habit of using function
pointers in another language, you might be tempted to use the Method objects of the reflection
API in the same way. Resist the temptation! Your program will be easier to debug and maintain
if you don't use Method objects. Instead, you should define an interface, and then implement it
in the classes that perform the needed action.

Other trails use the term "member variable" instead of "field." The two terms are synonymous.
Because the Field class is part of the reflection API, this trail uses the term "field."

This trail uses a task-oriented approach to the reflection API. Each lesson includes a set of
related tasks, and every task is explained, step by step, with a sample program. The lessons are
as follows:

Examining Classes
If you are writing a class browser, you need a way to get information about classes at runtime.
For example, you might want to display the names of the class fields, methods, and
constructors. Or, you might want to show which interfaces are implemented by a class. To get
this information you need to get the Class object that reflects the class.

For each class, the Java Runtime Environment (JRE) maintains an immutable Class object that
contains information about the class. A Class object represents, or reflects, the class. With the
reflection API, you can invoke methods on a Class object which return Constructor ,
Method , and Field objects. You can use these objects to get information about the
corresponding constructors, methods, and fields defined in the class.

Class objects also represent interfaces. You invoke Class methods to find out about an
interface's modifiers, methods, and public constants. Not all of the Class methods are
appropriate when a Class object reflects an interface. For example, it doesn't make sense to
invoke getConstructors when the Class object represents an interface. The section
Examining Interfaces explains which Class methods you may use to get information about
interfaces.

You might also like