The most important new feature of Java 1.4 is probably the New I/O API. This new API is defined in the java.nio package and its sub-packages, and provides high-performance input and output using an architecture that is mostly unrelated to the existing java.io API. One of the new classes, java.nio.channels.FileChannel, defines a new way to read and write files. Reading and writing files isn't very cool in and of itself, since we could already do that with existing java.io classes. What is cool about FileChannel, however, is that it provides facilities for memory mapping and locking files. Memory mapping allows you to view the contents of a file as if they were stored in an array directly in memory. The FileChannel API doesn't use arrays directly, but instead uses a java.nio.ByteBuffer. Here is code that uses memory mapping:
import java.io.*;// For FileInputStream
import java.nio.*;// For ByteBuffer
import java.nio.channels.*;// For FileChannel
// Create a FileChannel, get the file size and map the file to a ByteBuffer
FileChannel in = new FileInputStream("test.data").getChannel();
// Assume the file contains fixed-size records of binary data.
static final int RECORDSIZE = 80; // The size of each record
int recordNumber = 1;// This is the record we want to read
byte[] recorddata = new byte[RECORDSIZE];// An array to hold record data
mappedfile.position(recordNumber*RECORDSIZE); // Start of desired record
mappedfile.get(recorddata);// Fill the array from the buffer
Memory mapping can be lots of fun, but there is an overhead associated with it. In typical Java implementations, it is usually more efficient to just read small (< 100Kb, perhaps) files than it is to use memory mapping.
The other cool feature of FileChannel that we'll consider here is its ability to lock a file or a portion of a file. Locks can be used to prevent all concurrent access to the file (an exclusive lock) or to prevent concurrent writes (a shared lock). (Note that some operating systems strictly enforce all locks. Others only provide an advisory locking facility that requires programs to cooperate and to attempt to acquire a lock before reading or writing portions of a shared file.) Here is code you might use to obtain a shared lock on a region of a file in order to prevent any other (cooperating) programs from writing to that region while you were reading from it:
FileChannel in;// Initialized elsewhere
FileLock lock = in.lock(recordNumber*RECORDSIZE, // Start of locked region
RECORDSIZE,// Length of locked region
true);// Shared lock: prevent concurrent updates
// Now read the desired record, then release the lock when done
lock.release()
3. Non-Blocking I/O
For programmers writing high-performance network servers, the most important feature of the New I/O API is the ability to perform non-blocking I/O. The SocketChannel class of java.nio.channels allows you to read and write from a network connection; it is the New I/O equivalent of java.net.Socket.
SocketChannel and other SelectableChannel subclasses can be put into non-blocking mode and registered with a Selector object. The Selector class defines a method named select() that does the block; this method monitors one or more channels, and returns when one (or more) of them is ready for I/O. With a single Selector object, a server can monitor any number of SocketChannel client connections. Prior to the introduction of this new API, each client connection had to be handled with its own thread and blocking Socket object. This was a heavy-weight solution, and did not scale well for large servers. The code below shows the basic loop used in servers that perform non-blocking I/O:
import java.nio.channels.*;
import java.util.*;
// Create a selector object to monitor all open client connections.
Selector selector = Selector.open();
// Create a new non-blocking ServerSocketChannel, bind it to port 8000, and
// register it with the Selector
ServerSocketChannel server = ServerSocketChannel.open();// Open channel
server.configureBlocking(false);// Non-blocking
server.socket().bind(new java.net.InetSocketAddress(8000)); // Bind to port
// Now read bytes from the client into a buffer allocated elsewhere
int bytesread = client.read(buffer);
// If read() returns -1, it indicates end-of-stream, which
// means the client has disconnected, so de-register the
// selection key and close the channel.
if (bytesread == -1) {
key.cancel();
client.close();
continue;
}
}
}
}
2. Regular Expressions
Number two on the top ten list of cool new features in Java 1.4 is regular expressions. A regular expression is a way of describing a pattern of text. The java.util.regex package allows you to compare strings to regular expressions to determine if they match, and, if so, to determine exactly what parts of the string matched the pattern (and possibly its sub-patterns). Regular expressions also provide a powerful search-and-replace capability.
Regular expressions are heavily used in the Perl programming language, and Java 1.4 has adopted the regular expression pattern syntax of Perl 5. This means that Perl programmers can easily learn to use regular expressions in Java. In addition to the java.util.regex package, the String class has had regular-expression utility methods added that are simpler to use, in some cases. The following code shows some of the cool things you can do in Java with regular expressions. (Unfortunately, there is no room here to explain regular expression syntax, so if you are not already familiar with how regular expressions work in Perl or a similar language, this code won't make much sense to you.)
// Use a String convenience method to replace all occurrences of the word
// "java" (with any capitalization) with the correctly capitalized "Java"
s = s.replaceAll("(?i)\\bjava\\b", // The pattern: "java", case-insensitive
"Java");// The replacement string
// Here's a more complex use of regular expressions, using the Pattern and
// Matcher classes from java.util.regex.
// Create a pattern that describes any word beginning with "Java", and
// which also "captures" whatever suffix follows the "Java" prefix.It
// uses the CASE_INSENSITIVE flag, so it matches any capitalization
Pattern p = Pattern.compile("\\bJava(\\w*)", Pattern.CASE_INSENSITIVE);
// This is a sentence we want to compare the pattern to.
String text = "Java is fun; JavaScript is funny.";
// Create a Matcher object to compare the pattern to the text
Matcher m = p.matcher(text);
// Now loop to find all matches of the pattern in the text
while(m.find()) {
// For each match, print the text that matched the pattern, and its
// position within the string
System.out.println("Found '" + m.group(0) + "' at position " + m.start(0));
// Also, if there was a suffix, print the suffix.
if (m.start(1) < m.end(1)) System.out.println("Suffix is " + m.group(1));
}
1. Assertions
And the number one cool feature of Java 1.4 is... the assert statement!
An assert statement is used to document and verify design assumptions in Java code. An assertion consists of the assert keyword, followed by a boolean expression that the programmer believes should always evaluate to true. By default, assertions are not enabled, and the assert statement does not actually do anything. It is possible to enable assertions as a debugging and testing tool, however, and when this is done, the assert statement evaluates the expression. If it is indeed true, then assert does nothing. On the other hand, if the expression evaluates to false, then the assertion fails, and the assert statement throws a java.lang.AssertionError.
The assert statement may optionally include a second expression, separated from the first by a colon. When assertions are enabled, and the first expression evaluates to false, the value of the second expression is taken as an error code or error message of some sort, and is passed to the AssertionError() constructor. The full syntax of the statement is:
assert assertion ;
or:
assert assertion : errorcode ;
It is important to remember that the assertion expression must be a boolean expression, which typically means that it contains a comparison operator or invokes a boolean-valued method. The errorcode expression may have any value.
Now let's consider how assert can be used in real code. Suppose you are writing a method in which you believe that the variable x will always have a value of 0 or 1. You can state this belief explicitly with an assertion:
if (x == 0) {
...
}
else {
assert x == 1 : x;// x must be 0 or 1.
...
}
A similar technique works with switch statements. If you ever find yourself writing a switch that does not have a default case, use an assertion to encode your assumption that all the possible cases are explicitly enumerated. For example:
switch(x) {
case -1: return LESS;
case 0: return EQUALS;
case 1: return GREATER;
default: assert false:x; // throw AssertionError if x is not -1, 0, or 1.
}
Note that assert false; always fails. This form of the statement is a useful "dead end" statement when you believe that the statement can never be reached.
One of Java's greatest strengths, ever since Java 1.0, has been the ability to perform powerful networking with very little code. Java 1.4 adds support for secure sockets (using the SSL and TLS protocols) so that you can now easily write powerful and secure networking code. SSL support is provided by the javax.net.ssl package. (Note: javax not java.) As with all security-related packages in Java, this package is complex and highly configurable. Fortunately, however, the most common uses of SSL are quite easy to implement. The following code sets up an SSL socket to securely communicate with a Web server, using the HTTPS protocol:
import javax.net.ssl.*;
import java.io.*;
// Get a SocketFactory object for creating SSL sockets
Halfway through my list of cool features of Java 1.4 is the java.util.LinkedHashMap class. This new addition to the Collections API is a Map implementation that is based on a hashtable, just as HashMap is. LinkedHashMap differs in that it also maintains a linked list running through the map entries, and so can iterate through those entries in a predictable order. Usually, this order is the order in which entries are inserted into the map; you may occasionally encounter situations in which this is a very useful feature. Java 1.4 also includes a LinkedHashSet which is very similar.
If the idea of merging a HashMap with a LinkedList is not cool enough for you, consider this: LinkedHashMap can be configured (pass true as the third argument to the constructor) to order its entries from most-recently-accessed (i.e. queried or set) to least-recently-accessed. That's a nifty feature, but there's more! LinkedHashMap has a protected method named removeEldestEntry() that is called every time a new entry is added to the map. If this method returns true, then the "eldest" entry in the map (i.e. the one that was least-recently inserted or least-recently used) is automatically deleted. The default implementation of removeEldestEntry() always returns false, but you can override it to return true when the size of the map reaches a certain maximum size. Presto: you've got an LRU (least-recently-used) cache!
public class LRUCache extends java.util.LinkedHashMap {
The ability to parse XML in Java is hugely important, and the only reason that this new feature isn't ranked higher on my list is that the JAXP API for parsing XML has been available as a standard extension for a while. What is new in Java 1.4, however, is that JAXP has been added to the core platform, which is important because it makes XML parsing ubiquitous. The JAXP parsing API is defined in javax.xml.parsers and includes classes for SAX and DOM parsing, which are used with the org.xml.sax packages (and its sub-packages) and with the org.w3c.dom package; these have also been added to Java 1.4. Here's how you could use the JAXP API to parse an XML document into a DOM tree, and then use the DOM API to extract information from that document.
import java.io.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
File f;// The file to parse.Assume this is initialized elsewhere
// Create a factory object for creating DOM parsers
// Print the text contained in the Text node child of this element
if (title!=null) System.out.println(title.getFirstChild().getNodeValue());
}
9. Transforming XML
The JAXP API includes classes for XML transformations as well as XML parsing. javax.xml.transform and its sub-packages allow you to change an XML document from one representation (as a stream of XML tags, as a stream of SAX events, or as a DOM tree) to another representation, and it allows you to apply an XSLT transformation to the document content at the same time. It is very easy to use, and like the XML parsing feature listed above, this would be higher up on the list if it hadn't been available as a standard extension before Java 1.4 was released.
The code below shows how you could apply an XSLT transformation to a DOM document tree and output the transformed document as XML text to standard output.
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
import org.w3c.dom.*;
File xsltfile;// An XSLT file; initialized elsewhere
Document document;// Assume we've already read or created this DOM document
Source xsltSource = new StreamSource(xsltfile); // Source for transformation
Source source = new DOMSource(document);// Document to be transformed
Result result = new StreamResult(System.out);// Where to put result document
Many of the important new features of Java 1.4 are new utilities in or beneath the java.util package. java.util.prefs is one such utility: a facility for persistently storing and querying user-specific preferences and system-wide application configuration information. The key class is Preferences, which represents a persistent set of named preferences for a package (all classes in a package typically share the same user-specific and system-wide Preferences object). Preferences defines static methods for obtaining the user and system Preferences objects for a package, and defines instance methods for setting and querying named values of various types. The following is typical code that an application might use to query preferences when initializing itself. The cool thing about the Preferences API is that it is just so easy to use -- all of the hard work of managing configuration files just goes away.
// Get Preferences objects for user and system preferences for the package
// Look up a user preference using a system preference as the default
String dictionary = userprefs.get("dictionary"
sysprefs.get("dictionary",
"default_dictionary"));
7. Logging
Logging is another important utility, defined by the java.util.logging package. It is particularly useful for servers and other applications that run unattended or do not have a user interface. In typical usage, the application developer uses a Logger object with a name that corresponds to the class or package name of the application to generate log messages -- at any of seven severity levels (defined by the Level class).
These messages might report errors and warnings, or provide informational messages about interesting events in the application's lifecycle. They can include debugging information, or even trace the execution of important methods within the program. It is not intended that all of these messages will actually be generated and logged all of the time; there is a default logging configuration file that specifies where log messages are directed to (the console, a file, a network socket, or a combination of these), how they are formatted (as plain text or XML documents), and at what severity threshold they are logged (log messages with a severity below the specified threshold are discarded with very little overhead and should not significantly impact the performance of the application.) The system administrator or end user of the application can modify this default configuration to suit their needs.
The java.util.logging package is very flexible, and can be used in a number of ways. For most applications, however, use of the Logging API is quite simple. Obtain a named Logger object whenever necessary by calling the static Logger.getLogger() method, passing the class or package name of the application as the logger name. Then use one of the many Logger instance methods to generate log messages. The easiest methods to use have names that correspond to severity levels, such as severe(), warning(), info(), and debug():
import java.util.logging.*;
// Get a Logger object named after the current package.
HOT Features-3
4. FileChannel
The most important new feature of Java 1.4 is probably the New I/O API. This new API is defined in theReading and writing files isn't very cool in and of itself, since we could already do that with existing
java.niopackage and its sub-packages, and provides high-performance input and output using an architecture that is mostly unrelated to the existingjava.ioAPI. One of the new classes,java.nio.channels.FileChannel, defines a new way to read and write files.java.ioclasses. What is cool aboutFileChannel, however, is that it provides facilities for memory mapping and locking files. Memory mapping allows you to view the contents of a file as if they were stored in an array directly in memory. TheFileChannelAPI doesn't use arrays directly, but instead uses ajava.nio.ByteBuffer. Here is code that uses memory mapping:import java.io.*; // For FileInputStreamMemory mapping can be lots of fun, but there is an overhead associated with it. In typical Java implementations, it is usually more efficient to just read small (< 100Kb, perhaps) files than it is to use memory mapping.
The other cool feature of
FileChannelthat we'll consider here is its ability to lock a file or a portion of a file. Locks can be used to prevent all concurrent access to the file (an exclusive lock) or to prevent concurrent writes (a shared lock). (Note that some operating systems strictly enforce all locks. Others only provide an advisory locking facility that requires programs to cooperate and to attempt to acquire a lock before reading or writing portions of a shared file.) Here is code you might use to obtain a shared lock on a region of a file in order to prevent any other (cooperating) programs from writing to that region while you were reading from it:3. Non-Blocking I/O
For programmers writing high-performance network servers, the most important feature of the New I/O API is the ability to perform non-blocking I/O. The
SocketChannelclass ofjava.nio.channelsallows you to read and write from a network connection; it is the New I/O equivalent ofjava.net.Socket.SocketChanneland otherSelectableChannelsubclasses can be put into non-blocking mode and registered with aSelectorobject. TheSelectorclass defines a method namedselect()that does the block; this method monitors one or more channels, and returns when one (or more) of them is ready for I/O. With a singleSelectorobject, a server can monitor any number ofSocketChannelclient connections. Prior to the introduction of this new API, each client connection had to be handled with its own thread and blockingSocketobject. This was a heavy-weight solution, and did not scale well for large servers. The code below shows the basic loop used in servers that perform non-blocking I/O:2. Regular Expressions
Number two on the top ten list of cool new features in Java 1.4 is regular expressions. A regular expression is a way of describing a pattern of text. The
java.util.regexpackage allows you to compare strings to regular expressions to determine if they match, and, if so, to determine exactly what parts of the string matched the pattern (and possibly its sub-patterns). Regular expressions also provide a powerful search-and-replace capability.Regular expressions are heavily used in the Perl programming language, and Java 1.4 has adopted the regular expression pattern syntax of Perl 5. This means that Perl programmers can easily learn to use regular expressions in Java. In addition to the
java.util.regexpackage, theStringclass has had regular-expression utility methods added that are simpler to use, in some cases. The following code shows some of the cool things you can do in Java with regular expressions. (Unfortunately, there is no room here to explain regular expression syntax, so if you are not already familiar with how regular expressions work in Perl or a similar language, this code won't make much sense to you.)1. Assertions
And the number one cool feature of Java 1.4 is... the
assertstatement!An
assertstatement is used to document and verify design assumptions in Java code. An assertion consists of theassertkeyword, followed by a boolean expression that the programmer believes should always evaluate totrue. By default, assertions are not enabled, and theassertstatement does not actually do anything. It is possible to enable assertions as a debugging and testing tool, however, and when this is done, theassertstatement evaluates the expression. If it is indeedtrue, thenassertdoes nothing. On the other hand, if the expression evaluates tofalse, then the assertion fails, and theassertstatement throws ajava.lang.AssertionError.The
assertstatement may optionally include a second expression, separated from the first by a colon. When assertions are enabled, and the first expression evaluates tofalse, the value of the second expression is taken as an error code or error message of some sort, and is passed to theAssertionError()constructor. The full syntax of the statement is:or:
It is important to remember that the assertion expression must be a boolean expression, which typically means that it contains a comparison operator or invokes a boolean-valued method. The errorcode expression may have any value.
Now let's consider how
assertcan be used in real code. Suppose you are writing a method in which you believe that the variablexwill always have a value of 0 or 1. You can state this belief explicitly with an assertion:A similar technique works with
switchstatements. If you ever find yourself writing aswitchthat does not have adefaultcase, use an assertion to encode your assumption that all the possible cases are explicitly enumerated. For example:Note that
assert false;always fails. This form of the statement is a useful "dead end" statement when you believe that the statement can never be reached.HOT Features-2
6. Secure Sockets and HTTPS
One of Java's greatest strengths, ever since Java 1.0, has been the ability to perform powerful networking with very little code. Java 1.4 adds support for secure sockets (using the SSL and TLS protocols) so that you can now easily write powerful and secure networking code. SSL support is provided by the
javax.net.sslpackage. (Note:javaxnotjava.) As with all security-related packages in Java, this package is complex and highly configurable. Fortunately, however, the most common uses of SSL are quite easy to implement. The following code sets up an SSL socket to securely communicate with a Web server, using the HTTPS protocol:5. LinkedHashMap
Halfway through my list of cool features of Java 1.4 is the
java.util.LinkedHashMapclass. This new addition to the Collections API is aMapimplementation that is based on a hashtable, just asHashMapis.LinkedHashMapdiffers in that it also maintains a linked list running through the map entries, and so can iterate through those entries in a predictable order. Usually, this order is the order in which entries are inserted into the map; you may occasionally encounter situations in which this is a very useful feature. Java 1.4 also includes aLinkedHashSetwhich is very similar.If the idea of merging a
HashMapwith aLinkedListis not cool enough for you, consider this:LinkedHashMapcan be configured (passtrueas the third argument to the constructor) to order its entries from most-recently-accessed (i.e. queried or set) to least-recently-accessed. That's a nifty feature, but there's more!LinkedHashMaphas a protected method namedremoveEldestEntry()that is called every time a new entry is added to the map. If this method returnstrue, then the "eldest" entry in the map (i.e. the one that was least-recently inserted or least-recently used) is automatically deleted. The default implementation ofremoveEldestEntry()always returnsfalse, but you can override it to returntruewhen the size of the map reaches a certain maximum size. Presto: you've got an LRU (least-recently-used) cache!HOT Features
Parsing XML
The ability to parse XML in Java is hugely important, and the only reason that this new feature isn't ranked higher on my list is that the JAXP API for parsing XML has been available as a standard extension for a while. What is new in Java 1.4, however, is that JAXP has been added to the core platform, which is important because it makes XML parsing ubiquitous. The JAXP parsing API is defined in
javax.xml.parsersand includes classes for SAX and DOM parsing, which are used with theorg.xml.saxpackages (and its sub-packages) and with theorg.w3c.dompackage; these have also been added to Java 1.4. Here's how you could use the JAXP API to parse an XML document into a DOM tree, and then use the DOM API to extract information from that document.9. Transforming XML
The JAXP API includes classes for XML transformations as well as XML parsing.
javax.xml.transformand its sub-packages allow you to change an XML document from one representation (as a stream of XML tags, as a stream of SAX events, or as a DOM tree) to another representation, and it allows you to apply an XSLT transformation to the document content at the same time. It is very easy to use, and like the XML parsing feature listed above, this would be higher up on the list if it hadn't been available as a standard extension before Java 1.4 was released.The code below shows how you could apply an XSLT transformation to a DOM document tree and output the transformed document as XML text to standard output.
8. Preferences
Many of the important new features of Java 1.4 are new utilities in or beneath the
java.utilpackage.java.util.prefsis one such utility: a facility for persistently storing and querying user-specific preferences and system-wide application configuration information. The key class isPreferences, which represents a persistent set of named preferences for a package (all classes in a package typically share the same user-specific and system-widePreferencesobject).Preferencesdefines static methods for obtaining the user and systemPreferencesobjects for a package, and defines instance methods for setting and querying named values of various types. The following is typical code that an application might use to query preferences when initializing itself. The cool thing about the Preferences API is that it is just so easy to use -- all of the hard work of managing configuration files just goes away.7. Logging
Logging is another important utility, defined by the
java.util.loggingpackage. It is particularly useful for servers and other applications that run unattended or do not have a user interface. In typical usage, the application developer uses aLoggerobject with a name that corresponds to the class or package name of the application to generate log messages -- at any of seven severity levels (defined by theLevelclass).These messages might report errors and warnings, or provide informational messages about interesting events in the application's lifecycle. They can include debugging information, or even trace the execution of important methods within the program. It is not intended that all of these messages will actually be generated and logged all of the time; there is a default logging configuration file that specifies where log messages are directed to (the console, a file, a network socket, or a combination of these), how they are formatted (as plain text or XML documents), and at what severity threshold they are logged (log messages with a severity below the specified threshold are discarded with very little overhead and should not significantly impact the performance of the application.) The system administrator or end user of the application can modify this default configuration to suit their needs.
The
java.util.loggingpackage is very flexible, and can be used in a number of ways. For most applications, however, use of the Logging API is quite simple. Obtain a namedLoggerobject whenever necessary by calling the staticLogger.getLogger()method, passing the class or package name of the application as the logger name. Then use one of the manyLoggerinstance methods to generate log messages. The easiest methods to use have names that correspond to severity levels, such assevere(),warning(),info(), anddebug():