Five Favorite Features from 5.0
by David Flanagan, author of Java in a Nutshell, 5th Edition04/20/2005
By now, you've undoubtedly seen more than one online
article enumerating the great new language features of Java
5.0: generics, annotations, enumerated types, autoboxing,
varargs, the for/in loop, and even static imports. I think
these are great features, too, but you've already read
about them. So I'll concentrate here on new API features
of Java 5.0 that you may not have encountered yet.
What follows, then, are five of my favorite new API features of Java 5.0. I've written about these in Chapter 5 of the fifth edition of Java in a Nutshell, and I've blogged about some of them at my website. Diligent readers who make it to the end of this article will find a bonus sixth feature: new language syntax supported by Java 5.0 but known to very few, and used by even fewer. It's a favorite of mine simply because it is bizarre.
Callable and Future Interfaces
My first favorite feature is drawn from the new
java.util.concurrent package. As its name implies, this is
a package of utilities for concurrent programming. There
is much to explore here, and one of my nominees for First
Favorite is the TimeUnit enumerated type. What intrigues
me about TimeUnit is that includes useful time-related
utilities, which you invoke through an enumerated constant
that represents the unit in which you choose to measure
time. For example:
TimeUnit.MILLISECONDS.sleep(200);
TimeUnit doesn't win the prize, however. One of the most
powerful features of java.util.concurrent is its task-execution/thread-pooling architecture. The ExecutorService
interface provides the ability to execute tasks. The
Executors class defines factory methods for obtaining
ExecutorService implementations that use thread pools. This
is powerful stuff.
My favorite part of this task-execution framework is how it
represents the task and the results of executing it: the
Callable and Future interfaces. We're all familiar with
the Runnable interface and its run() method used to define
threads. Callable is like Runnable, except that its method
is named call() and this method can return a result or
throw an exception, neither of which is possible with
Runnable.run().
Callable is a generic type, and is parameterized with the
type of its result. A task that computes a BigInteger, for
example is a Callable<BigInteger>, and its call() method is
declared to return BigInteger. Here is the Callable
interface in its three lines of glory:
public interface Callable<V> {
V call() throws Exception;
}
When I want to asynchronously execute a Callable task, I
pass it to the submit() method of an ExecutorService. The
return value of submit()--and this is the part I love--is a Future object: essentially an IOU for a result some
time in the future. When I'm ready to use the result of my
task, I simply call the get() method of the Future object.
If the task is done executing, then get() returns my result
right away. Otherwise, it blocks until the result is
ready. If Callable throws an exception, then the get()
method wraps that exception in an ExecutionException and
throws that. Future also has methods for cancelling and
querying the status of task execution, but you'll have to
look those up yourself.
Future uses generics, too, and is also parameterized in the
type of the result. So if I submit() a
Callable<BigInteger> for execution, I get a
Future<BigInteger> in return.
Here's a short example:
/**
* This is a Callable for computing big primes.
*/
public class PrimeSearch implements Callable<BigInteger>
{
static Random prng = new SecureRandom();
int n;
public PrimeSearch(int bitsize) { n = bitsize; }
public BigInteger call() {
return BigInteger.probablePrime(n, prng);
}
}
// Try to compute two primes at the same time
ExecutorService pool = Executors.newFixedThreadPool(2);
Future<BigInteger> p = pool.submit(new PrimeSearch(512));
Future<BigInteger> q = pool.submit(new PrimeSearch(512));
// Multiply the primes to form a composite number
BigInteger product = p.get().multiply(q.get());
Varargs and Autoboxing
I said I wasn't going to talk about the new language features of Java 5.0, and I won't, but I do want to draw attention to new APIs enabled by (or old APIs enhanced with) varargs and autoboxing.
First, of course, is Java 5.0's printf-style text formatting
capability, with the java.util.Formatter class and utility
methods like String.format(). This kind of text formatting
was the use case cited most often to support the addition
of varargs and autoboxing to the language. Consider this:
String s = String.format("%s:%d: %s%n", filename,
lineNumber,
exception.getMessage());
There is nothing particularly remarkable about this code. I list it as a favorite simply because varargs and autoboxing make it so much simpler than the alternative:
String s = String.format("%s:%d: %s%n", new Object[] {
filename,
new Integer(lineNumber),
exception.getMessage()});
Varargs and autoboxing also have a substantial impact on
the usability of the java.lang.reflect API. There is no
longer any need for arrays of classes and objects when
looking up and invoking methods:
Method m = c.getMethod("put", Object.class,Object.class);
m.invoke(map, "key", "value");
If I had to pick a favorite varargs method, though, it
would be java.util.Arrays.asList(). This method is a
really convenient factory for creating immutable List
objects. It takes any number of arguments of the type T and
returns them as a List<T>:
List<Integer> smallPrimes =
Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19);
Abilities
We talked about Runnable and Callable above, and you
undoubtedly already know about the important Comparable,
Serializable, and Cloneable interfaces. Java 5.0 adds five
important new ability interfaces. The first, of course, is
java.lang.Iterable. You probably know that the new for/in
loop of Java 5.0 can iterate through arrays and
collections. You may not know that it can work with any
object that implements the Iterable interface. So, if you
want to allow easy iteration of a data structure that is
not already a Collection, just implement the Iterable
interface. All you have to do is add an iterator() method
that returns a java.util.Iterator. Writing the Iterator
can be nontrivial, of course.
The code below is a TextFile class that implements
Iterable<String> (yes, Iterable is generic) and allows text
files to be iterated line by line with the for/in loop.
You might use it with code like this:
TextFile textfile = new TextFile(new File(f), "UTF-8");
int lineNumber = 0;
for(String line : textfile)
System.out.printf("%6d: %s%n", ++lineNumber, line);
Here's the code. Note that the iterator makes no attempt
to detect concurrent modifications to the underlying file.
If you want to do that yourself, take a look at
java.nio.channels.FileLock.
import java.io.*;
import java.util.Iterator;
public class TextFile implements Iterable<String> {
File f;
String charsetName;
public TextFile(File f, String charsetName)
throws IOException
{
this.f = f;
this.charsetName = charsetName;
if (!f.exists())
throw new FileNotFoundException(f.getPath());
if (!f.canRead())
throw new IOException("Can't read: " +
f.getPath());
}
public Iterator<String> iterator() {
try {
return new TextFileIterator(f, charsetName);
}
catch(IOException e) {
throw new IllegalArgumentException(e);
}
}
static class TextFileIterator
implements Iterator<String>
{
BufferedReader in;
String nextline;
boolean closed = false;
public TextFileIterator(File f, String charsetName)
throws IOException
{
InputStream fis = new FileInputStream(f);
Reader isr =
new InputStreamReader(fis, charsetName);
in = new BufferedReader(isr);
getNextLine();
}
public boolean hasNext() {
return nextline != null;
}
public String next() {
String returnValue = nextline;
getNextLine();
return returnValue;
}
public void remove() {
throw new UnsupportedOperationException();
}
void getNextLine() {
if (!closed) {
try { nextline = in.readLine(); }
catch(IOException e) {
throw new IllegalArgumentException(e);
}
if (nextline == null) {
try { in.close(); }
catch(IOException ignored) {}
closed = true;
}
}
}
}
}
|
Related Reading
Java in a Nutshell |
Iterable is by far the most important of the new ability
interfaces, but the others are pretty nifty, too. Moving
on, then, we come to java.lang.Appendable. An Appendable
object is one that can have a character or CharSequence (or
a sub-sequence of a CharSequence) appended to it.
Implementations include StringBuffer and StringBuilder (if
you don't already know about StringBuilder, do look it up),
Writer (and subclasses), PrintStream, and
java.nio.CharBuffer. Factoring the appendability out of
these classes into the Appendable interface makes the new
java.util.Formatter class more powerful: it can format text
to any Appendable object, including your own
implementations. (Exercise for the reader: can you make
the TextFile class shown above both Iterable and
Appendable?)
The java.lang.Readable interface is the opposite of
Appendable: a Readable object can transfer characters into
a supplied CharBuffer. java.io.Reader and all of its
subclasses are Readable (of course), and so is CharBuffer
itself. Just as Appendable was created for the benefit of
java.util.Formatter, Readable was created for the benefit
of java.util.Scanner. (Scanner was added in Java 5.0, along
with Formatter. It is Java's answer to C's scanf()
function, but it is not nearly as close an approximation as
Formatter is to printf().)
The last two ability interfaces I want to discuss are
java.io.Closeable and java.io.Flushable. As their names
imply, they are intended to be implemented by any classes
that have a close() or a flush() method. Closeable is
implemented by all input and output stream classes,
RandomAccessFile, and Formatter. Flushable is implemented
by output stream classes and Formatter. These interfaces
were also defined for the benefit of the Formatter class.
Note that Appendable objects (such as StringBuilder) are
not always closeable or flushable. By factoring
closeability and flushability out into these interfaces,
the close() and flush() methods of Formatter are able to
determine whether the Appendable object they operate upon
needs to be closed or flushed.
(There is a sixth ability interface added in Java 5.0, and
it, too, is related to the Formatter class. Classes that
want intimate control over how their instances are
formatted can implement java.util.Formattable. The API for
this interface is awkward, however, and I don't want to
talk about it.)
@Override
You've undoubtedly heard about the ability to annotate Java
5.0 types and method with metadata. You may be less
familiar with the standards annotation types added to
java.lang. My fourth favorite feature is the
java.lang.Override annotation. When you write a method that
is supposed to override another, annotate it with
@Override, and the compiler will check to ensure that you
did, in fact, override the method you intended.
If you misspell the method name or get a method argument
wrong, then you don't actually override the method you think
you are overriding. This can be a tricky bug to catch
without @Override. I know because my article
about the new API features of Java 1.4 included this bug, and the bug
went undetected (or at least unreported) for more than a
year. In the article, you can see my mistake at the end of the first page.
The article now contains a link to my blog entry, in which I
correct the bug and add an @Override declaration to the
code!
MatchResult
My final favorite feature of Java 5.0 is
java.util.regex.MatchResult. I'd never really been quite
satisfied with the Pattern/Matcher API for working with
regular expressions. The addition of MatchResult to Java
5.0 goes a long way towards making me happy. When using a
non-trivial Pattern, each call to the find() method of a
Matcher can generate a lot of state: the start position,
end position, and text of the match, as well as the start,
end, and text of each sub-expression of the Pattern.
Before Java 5.0, you had to get this state from the Matcher
by following each call to find() with various calls to
start(), end(), and group(), as needed.
In Java 5.0, however, you can just call toMatchResult() to
obtain all of the state as a MatchResult object that you can
save and examine later. MatchResult has the same start(),
end(), and group() methods that Matcher does, and, in fact,
Matcher now implements MatchResult.
Here's a useful method that returns MatchResults:
public static List<MatchResult> findAll(Pattern pattern,
CharSequence text) {
List<MatchResult> results =
new ArrayList<MatchResult>();
Matcher m = pattern.matcher(text);
while(m.find()) results.add(m.toMatchResult());
return results;
}
And here's code that uses that method:
List<MatchResult> results = findAll(pattern, text);
for(MatchResult r : results) {
System.out.printf("Found '%s' at (%d,%d)%n",
r.group(), r.start(), r.end());
}
Hexadecimal Floating Point Literals
I promised that I'd talk about the most obscure new language feature of Java 5.0. Here it is: floating point literals in hexadecimal format!
Here are the bizarre details: a floating-point literal in
hexadecimal notation begins with 0X or 0x. This is
followed by hexadecimal digits that form the significand
of the number. The catch is that these digits may include a
decimal point (a hexadecimal point?). After the significand
comes the exponent, which is required. Instead of using e
or E to introduce the exponent, hexadecimal floating-point
literals use p or P. (Think "power" as a mnemonic.) The p
or P is followed by the exponent, which must be a decimal
number, not a hexadecimal number. And this is a binary
exponent, not a decimal exponent. That is, is represents
the power of two to which the significand should be
raised. Finally, the whole thing can be followed by an f or
F to indicate a float literal, or a d or D to indicate a
double literal, just as a decimal floating-point literal
can.
Here are some examples:
double x = 0XaP0; // 10 * 2^0 = 10.0
double y = 0XfP2D; // 15 * 2^2 = 60.0
float z = 0Xf.aP1F; // (15 + 10/16ths) * 2^1 = 31.25f
// Print in decimal
System.out.printf("%f %f %f%n", x, y, z);
// Print in hex
System.out.printf("%a %a %a%n", x, y, z);
Why did Sun do this to the language? The release notes for 5.0 say:
To allow precise and predictable specification of particular floating-point values, hexadecimal notation can be used for floating-point literals and for string- to-floating-point conversion methods in Float and Double.
The point is a reasonable one. Decimal fractions such as
0.1 are not precisely representable in floating point
format, and if you really need to know exactly what bits
are being set in a float or double value, then you really
want a hexadecimal literal. For example, the Javadoc for
Float.MAX_VALUE points out that the largest float value is
0x1.fffffeP+127f.
If you know and love the IEEE-754 floating point standard, then hexadecimal floating-point literals may be one of your favorite features. I just think they're funny.
In March 2005, O'Reilly Media, Inc., released Java in a Nutshell, 5th Edition.
You can also look at the full description of the book.
For more information, or to order the book, click here.
David Flanagan is the author of a number of O'Reilly books, including Java in a Nutshell, Java Examples in a Nutshell, Java Foundation Classes in a Nutshell, JavaScript: The Definitive Guide, and JavaScript Pocket Reference.
Return to ONJava.com.