ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.


AddThis Social Bookmark Button
Java in a Nutshell, 4th Edition

Top Ten Cool New Features of Java 2SE 1.4

by David Flanagan, author of Java in a Nutshell, 4th Edition

Java 1.4 has just been released. This is a major new release, with 62 percent more classes and interfaces than Java 1.3; needless to say, there are lots of new features. This article describes my personal favorite top ten new features, ordered from cool to extremely cool. You can learn more about all of these features in the 4th edition of Java in a Nutshell. Note that the new features I've selected for this article are drawn from those documented in that book; thus I do not include any new graphics and GUI features or new enterprise features. Those are the subjects of Java Foundation Classes in a Nutshell and Java Enterprise in a Nutshell.

The format of this article does not allow me to include complete working examples of each of the ten new features I describe. I do include extensive code fragments to illustrate how to use each of the new features, but don't expect to be able to compile and run the code as shown.

10. 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.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
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// Now use the factory to create a DOM parser (a.k.a. a DocumentBuilder)
DocumentBuilder parser = factory.newDocumentBuilder();
// Parse the file and build a Document tree to represent its content
Document document = parser.parse(f);
// Ask the document for a list of all <sect1> tags it contains
NodeList sections = document.getElementsByTagName("sect1");
// Loop through those <sect1> elements one at a time, and extract the
// content of their <h3> tags.
int numSections = sections.getLength();
for(int i = 0; i < numSections; i++) {
    Element section = (Element)sections.item(i);  // A <sect1>
    // Find the first element child of this section (a <h3> element)
    // and print the text it contains.
    Node title = section.getFirstChild();
    while(title != null && title.getNodeType() != Node.ELEMENT_NODE) 
	title = title.getNextSibling();
    // 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

// Start off with a factory object
TransformerFactory tf = TransformerFactory.newInstance();
// Use the factory to read the XSLT file into a Templates object
Templates transformation = tf.newTemplates(xsltSource);
// Create a Transformer object from the Templates object
Transformer transformer = transformation.newTransformer();
// Finally, perform the transformation
transformer.transform(source, result);

8. Preferences

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
// that contains the TextEditor class.
Preferences userprefs = Preferences.userNodeForPackage(TextEditor.class);
Preferences sysprefs = Preferences.systemNodeForPackage(TextEditor.class);

// Look up a user preference value as an integer.
// Note that we always pass a default value
int width = userprefs.getInt("width", 80);
// Look up a user preference using a system preference as the default
String dictionary = userprefs.get("dictionary"

7. Logging

Related Reading

Java In a Nutshell
By David Flanagan

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.
Logger logger = Logger.getLogger("com.davidflanagan.servers.pop");
logger.info("Starting server.");       // Log an informational message
ServerSocket ss;
try { ss = new ServerSocket(110); }
catch(Exception e) {                   // Log exceptions
  logger.log(Level.SEVERE, "Can't bind port 110", e);  // complex log message
  logger.warning("Exiting");                           // simple warning 
logger.fine("got server socket");  // low severity (fine detail) debug message

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.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
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();

// Create a secure socket connected to the HTTPS port (port 443) of a server
SSLSocket sslsock = (SSLSocket) factory.createSocket(hostname, 443);

// Get the certificate presented by the web server.  This may throw an 
// exception if the server didn't supply a certificate.  Look at the
// issuer of the certificate and decide if it is trusted.
SSLSession session = sslsock.getSession();
X509Certificate cert = (X509Certificate)session.getPeerCertificates()[0];
String issuer = cert.getIssuerDN().getName(); 

// Assuming we trust the certificate, we now use the socket just like a normal
// java.net.Socket object.  So send a HTTP request and read the response
PrintWriter out = new PrintWriter(sslsock.getOutputStream());
out.print("GET " + args[1] + " HTTP/1.0\r\n\r\n");

// Next, read the server's response and print it to the console.
BufferedReader in =
  new BufferedReader(new InputStreamReader(sslsock.getInputStream()));
String line;
while((line = in.readLine()) != null) System.out.println(line);

// Finally, close the socket.

5. LinkedHashMap

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 {
    public LRUCache(int maxsize) {
	super(maxsize*4/3 + 1, 0.75f, true);
	this.maxsize = maxsize;
    protected int maxsize;
    protected boolean removeEldestEntry() { return size() > maxsize; }

Editor's Update: The signature for removeEldestEntry() does not match that of the superclass and therefore this method does not override the corresponding method in the superclass. A fix that also includes the J2SE 1.5 feature metadata has been posted by David Flanagan as a blog entry Failure to override .

Pages: 1, 2

Next Pagearrow