Thursday, March 31, 2011

JDK 7: New Interfaces, Classes, Enums, and Methods

There are numerous new classes, enums, methods, and interfaces being added to JDK 7 that may not get significant coverage in the blogs and articles on Java 7. In this post, I list and describe some of these. Note that several of these are not really "miscellaneous," but were necessary additions to make greater new functionality available.


All of the above are representative of changes coming to the Java SDK with Java SE 7. However, there are numerous additional changes. The Javadoc seems to have been updated fairly well to indicate "Since: 1.7" for features new to JDK 7. One way to "search" for new Java 7 features would be to download the source code and search it for "1.7." Another approach would be to search the generated Javadoc for the same string.

The following Groovy script provides a brute force mechanism for searching the online JDK 7 Javadoc API documentation for methods, classes, enums, methods, and interfaces that contain the string "1.7." I threw this together quickly and believe there are several more elegant ways to do this, but this was easy to write and does the job.

#!/usr/bin/env groovy
// searchJdk7ApiDocsForSince17.groovy

def anchorString = '<a href="'
def anchorStringUpper = anchorString.toUpperCase()
def urls = new ArrayList<String>()
"http://download.oracle.com/javase/7/docs/api/allclasses-frame.html".toURL().eachLine
{
   def startIndex = -1
   if (it.contains(anchorString))
   {
      startIndex = it.indexOf(anchorString) + anchorString.size()
   }
   else if (it.contains(anchorStringUpper))
   {
      startIndex = it.indexOf(anchorStringUpper) + anchorStringUpper.size()
   }

   if (startIndex > -1)
   {
      def endIndex = it.indexOf('"', startIndex)
      if (endIndex > startIndex)
      {
         def url = it.substring(startIndex, endIndex)
         urls.add(url)
      }
   }
}

def numUrlsProcessed = 0
def numUrlsMatching = 0
println "${urls.size()} total URLs to search for 1.7 text."
def withJava7 = new ArrayList<String>()
urls.each
{
   def match = false
   numUrlsProcessed++
   def eachUrl = "http://download.oracle.com/javase/7/docs/api/${it}"
   try
   {
      if (eachUrl.toURL().text.contains("1.7"))
      {
         numUrlsMatching++
         match = true
         withJava7.add(it)
      }
   }
   catch (Exception ex) // catch HTTP problems like 404 or 504.
   {
      println "ERROR trying to acces ${eachUrl}: ${ex.toString()}"
   }
   println "${numUrlsMatching} of ${numUrlsProcessed} match 1.7 (${match ? eachUrl : '-'})!"
}

println "Javadoc files with 1.7: "
withJava7.each
{
   println "\t${it}"
}

The above Groovy script searches the Javadoc documentation for "1.7" and provides a list of Javadoc files for classes, enums, interfaces, and methods that include the "1.7" string. According to the script's output, 4020 files were searched and 304 of those files contained at least once occurrence of the "1.7" string.

The following Javadoc HTML files were identified by the script as containing the string "1.7" somewhere within the page's documentation. With the exception of a few possible cases in which "1.7" is used for something else, it can be assumed that these are classes, interfaces, and enums that have been marked "Since 1.7."

javax/lang/model/util/AbstractAnnotationValueVisitor7.html
javax/lang/model/util/AbstractElementVisitor7.html
java/util/concurrent/locks/AbstractQueuedLongSynchronizer.html
java/util/concurrent/locks/AbstractQueuedSynchronizer.html
javax/lang/model/util/AbstractTypeVisitor6.html
javax/lang/model/util/AbstractTypeVisitor7.html
java/nio/channels/AcceptPendingException.html
java/nio/file/AccessDeniedException.html
java/nio/file/AccessMode.html
java/nio/file/attribute/AclEntry.html
java/nio/file/attribute/AclEntry.Builder.html
java/nio/file/attribute/AclEntryFlag.html
java/nio/file/attribute/AclEntryPermission.html
java/nio/file/attribute/AclEntryType.html
java/nio/file/attribute/AclFileAttributeView.html
java/security/AlgorithmConstraints.html
java/nio/channels/AlreadyBoundException.html
java/applet/Applet.html
java/lang/AssertionError.html
java/nio/channels/AsynchronousByteChannel.html
java/nio/channels/AsynchronousChannel.html
java/nio/channels/AsynchronousChannelGroup.html
java/nio/channels/spi/AsynchronousChannelProvider.html
java/nio/channels/AsynchronousFileChannel.html
java/nio/channels/AsynchronousServerSocketChannel.html
java/nio/channels/AsynchronousSocketChannel.html
java/nio/file/AtomicMoveNotSupportedException.html
java/nio/file/attribute/AttributeView.html
javax/sound/sampled/AudioFormat.Encoding.html
java/lang/AutoCloseable.html
javax/swing/plaf/basic/BasicColorChooserUI.html
javax/swing/plaf/basic/BasicComboBoxUI.html
java/nio/file/attribute/BasicFileAttributes.html
java/nio/file/attribute/BasicFileAttributeView.html
javax/swing/plaf/basic/BasicFileChooserUI.html
javax/swing/plaf/basic/BasicMenuItemUI.html
javax/swing/plaf/basic/BasicScrollBarUI.html
javax/swing/plaf/basic/BasicTreeUI.html
java/util/BitSet.html
java/lang/Boolean.html
javax/swing/BorderFactory.html
java/nio/BufferPoolMXBean.html
java/lang/Byte.html
java/util/Calendar.html
java/sql/CallableStatement.html
java/security/cert/CertificateRevokedException.html
java/security/cert/CertPathValidatorException.html
java/security/cert/CertPathValidatorException.BasicReason.html
java/security/cert/CertPathValidatorException.Reason.html
java/nio/channels/Channels.html
java/lang/Character.html
java/lang/Character.UnicodeBlock.html
java/lang/Character.UnicodeScript.html
java/lang/ClassLoader.html
java/nio/file/ClosedDirectoryStreamException.html
java/util/Collections.html
javax/sql/CommonDataSource.html
java/nio/channels/CompletionHandler.html
java/util/concurrent/ConcurrentLinkedDeque.html
java/util/ConcurrentModificationException.html
java/sql/Connection.html
java/awt/Container.html
java/nio/file/CopyOption.html
java/security/cert/CRLReason.html
java/security/CryptoPrimitive.html
java/util/Currency.html
java/util/spi/CurrencyNameProvider.html
java/awt/Cursor.html
java/sql/DatabaseMetaData.html
java/nio/channels/DatagramChannel.html
javax/swing/text/DefaultStyledDocument.html
javax/swing/tree/DefaultTreeCellRenderer.html
javax/swing/tree/DefaultTreeSelectionModel.html
java/util/zip/Deflater.html
java/util/zip/DeflaterOutputStream.html
javax/print/attribute/standard/DialogTypeSelection.html
java/nio/file/DirectoryIteratorException.html
java/nio/file/DirectoryNotEmptyException.html
java/nio/file/DirectoryStream.html
java/nio/file/DirectoryStream.Filter.html
javax/lang/model/type/DisjunctiveType.html
java/nio/file/attribute/DosFileAttributes.html
java/nio/file/attribute/DosFileAttributeView.html
java/sql/Driver.html
javax/lang/model/element/ElementKind.html
javax/lang/model/util/ElementKindVisitor6.html
javax/lang/model/util/ElementKindVisitor7.html
javax/lang/model/util/ElementScanner7.html
java/awt/EventQueue.html
java/beans/Expression.html
javax/net/ssl/ExtendedSSLSession.html
java/security/cert/Extension.html
java/beans/FeatureDescriptor.html
java/io/File.html
java/nio/file/FileAlreadyExistsException.html
java/nio/file/attribute/FileAttribute.html
java/nio/file/attribute/FileAttributeView.html
java/nio/channels/FileChannel.html
javax/swing/plaf/FileChooserUI.html
java/awt/FileDialog.html
java/nio/channels/FileLock.html
java/nio/file/attribute/FileOwnerAttributeView.html
java/nio/file/Files.html
java/nio/file/FileStore.html
java/nio/file/attribute/FileStoreAttributeView.html
java/nio/file/FileSystem.html
java/nio/file/FileSystemException.html
java/nio/file/FileSystemLoopException.html
java/nio/file/spi/FileSystemProvider.html
java/nio/file/FileSystems.html
java/nio/file/attribute/FileTime.html
java/nio/file/spi/FileTypeDetector.html
java/nio/file/FileVisitOption.html
java/nio/file/FileVisitor.html
java/nio/file/FileVisitResult.html
java/util/concurrent/ForkJoinPool.html
java/util/concurrent/ForkJoinTask.html
java/util/concurrent/ForkJoinWorkerThread.html
java/awt/GraphicsConfiguration.html
java/awt/GraphicsDevice.html
java/awt/GraphicsDevice.WindowTranslucency.html
java/util/GregorianCalendar.html
java/nio/file/attribute/GroupPrincipal.html
java/util/zip/GZIPOutputStream.html
javax/swing/text/html/HTMLFrameHyperlinkEvent.html
java/net/HttpURLConnection.html
javax/swing/event/HyperlinkEvent.html
java/nio/channels/IllegalChannelGroupException.html
java/util/IllformedLocaleException.html
java/net/InetAddress.html
java/net/InetSocketAddress.html
java/lang/Integer.html
java/nio/channels/InterruptedByTimeoutException.html
java/beans/Introspector.html
java/awt/event/InvocationEvent.html
java/dyn/InvokeDynamicBootstrapError.html
javax/swing/JLayer.html
javax/swing/JList.html
javax/swing/JSlider.html
javax/swing/text/JTextComponent.html
javax/swing/JTree.EmptySelectionModel.html
java/awt/event/KeyEvent.html
javax/swing/plaf/LayerUI.html
java/lang/LinkageError.html
java/util/concurrent/LinkedTransferQueue.html
java/nio/file/LinkOption.html
java/nio/file/LinkPermission.html
java/util/Locale.html
java/util/Locale.Builder.html
java/util/Locale.Category.html
java/util/spi/LocaleNameProvider.html
java/util/logging/Logger.html
java/lang/Long.html
java/lang/management/ManagementFactory.html
java/nio/channels/MembershipKey.html
javax/sound/midi/MetaMessage.html
javax/sound/midi/MidiDeviceReceiver.html
javax/sound/midi/MidiDeviceTransmitter.html
java/lang/reflect/Modifier.html
java/awt/event/MouseWheelEvent.html
java/nio/channels/MulticastChannel.html
java/nio/channels/NetworkChannel.html
java/net/NetworkInterface.html
javax/swing/plaf/nimbus/NimbusLookAndFeel.html
java/nio/file/NoSuchFileException.html
java/nio/file/NotDirectoryException.html
java/nio/file/NotLinkException.html
java/awt/font/NumericShaper.html
java/awt/font/NumericShaper.Range.html
java/util/Objects.html
java/nio/file/OpenOption.html
javax/lang/model/element/Parameterizable.html
java/nio/file/Path.html
java/nio/file/PathMatcher.html
java/nio/file/Paths.html
java/util/concurrent/Phaser.html
java/security/cert/PKIXReason.html
java/util/logging/PlatformLoggingMXBean.html
java/lang/management/PlatformManagedObject.html
java/nio/file/attribute/PosixFileAttributes.html
java/nio/file/attribute/PosixFileAttributeView.html
java/nio/file/attribute/PosixFilePermission.html
java/nio/file/attribute/PosixFilePermissions.html
java/lang/ProcessBuilder.html
java/lang/ProcessBuilder.Redirect.html
java/beans/PropertyChangeEvent.html
java/net/ProtocolFamily.html
java/sql/PseudoColumnUsage.html
javax/lang/model/element/QualifiedNameable.html
java/nio/channels/ReadPendingException.html
java/util/concurrent/RecursiveAction.html
java/util/concurrent/RecursiveTask.html
java/lang/ReflectiveOperationException.html
java/sql/ResultSet.html
javax/sql/rowset/RowSetFactory.html
javax/sql/rowset/RowSetProvider.html
java/util/Scanner.html
java/util/concurrent/ScheduledThreadPoolExecutor.html
java/awt/SecondaryLoop.html
java/nio/file/SecureDirectoryStream.html
java/nio/channels/SeekableByteChannel.html
java/nio/channels/spi/SelectorProvider.html
java/nio/channels/ServerSocketChannel.html
java/lang/Short.html
javax/sound/midi/ShortMessage.html
java/nio/channels/ShutdownChannelGroupException.html
javax/lang/model/util/SimpleAnnotationValueVisitor7.html
javax/lang/model/util/SimpleElementVisitor7.html
java/nio/file/SimpleFileVisitor.html
java/util/SimpleTimeZone.html
javax/lang/model/util/SimpleTypeVisitor7.html
java/nio/channels/SocketChannel.html
java/net/SocketOption.html
javax/lang/model/SourceVersion.html
javax/net/ssl/SSLEngine.html
javax/net/ssl/SSLParameters.html
javax/rmi/ssl/SslRMIServerSocketFactory.html
javax/net/ssl/SSLServerSocket.html
javax/net/ssl/SSLSocket.html
java/nio/file/StandardCopyOption.html
java/nio/file/StandardOpenOption.html
java/net/StandardProtocolFamily.html
java/net/StandardSocketOption.html
java/nio/file/StandardWatchEventKind.html
java/sql/Statement.html
javax/swing/border/StrokeBorder.html
javax/swing/SwingUtilities.html
javax/swing/plaf/synth/SynthButtonUI.html
javax/swing/plaf/synth/SynthCheckBoxMenuItemUI.html
javax/swing/plaf/synth/SynthCheckBoxUI.html
javax/swing/plaf/synth/SynthColorChooserUI.html
javax/swing/plaf/synth/SynthComboBoxUI.html
javax/swing/plaf/synth/SynthDesktopIconUI.html
javax/swing/plaf/synth/SynthDesktopPaneUI.html
javax/swing/plaf/synth/SynthEditorPaneUI.html
javax/swing/plaf/synth/SynthFormattedTextFieldUI.html
javax/swing/plaf/synth/SynthInternalFrameUI.html
javax/swing/plaf/synth/SynthLabelUI.html
javax/swing/plaf/synth/SynthListUI.html
javax/swing/plaf/synth/SynthLookAndFeel.html
javax/swing/plaf/synth/SynthMenuBarUI.html
javax/swing/plaf/synth/SynthMenuItemUI.html
javax/swing/plaf/synth/SynthMenuUI.html
javax/swing/plaf/synth/SynthOptionPaneUI.html
javax/swing/plaf/synth/SynthPanelUI.html
javax/swing/plaf/synth/SynthPasswordFieldUI.html
javax/swing/plaf/synth/SynthPopupMenuUI.html
javax/swing/plaf/synth/SynthProgressBarUI.html
javax/swing/plaf/synth/SynthRadioButtonMenuItemUI.html
javax/swing/plaf/synth/SynthRadioButtonUI.html
javax/swing/plaf/synth/SynthRootPaneUI.html
javax/swing/plaf/synth/SynthScrollBarUI.html
javax/swing/plaf/synth/SynthScrollPaneUI.html
javax/swing/plaf/synth/SynthSeparatorUI.html
javax/swing/plaf/synth/SynthSliderUI.html
javax/swing/plaf/synth/SynthSpinnerUI.html
javax/swing/plaf/synth/SynthSplitPaneUI.html
javax/swing/plaf/synth/SynthTabbedPaneUI.html
javax/swing/plaf/synth/SynthTableHeaderUI.html
javax/swing/plaf/synth/SynthTableUI.html
javax/swing/plaf/synth/SynthTextAreaUI.html
javax/swing/plaf/synth/SynthTextFieldUI.html
javax/swing/plaf/synth/SynthTextPaneUI.html
javax/swing/plaf/synth/SynthToggleButtonUI.html
javax/swing/plaf/synth/SynthToolBarUI.html
javax/swing/plaf/synth/SynthToolTipUI.html
javax/swing/plaf/synth/SynthTreeUI.html
javax/swing/plaf/synth/SynthUI.html
javax/swing/plaf/synth/SynthViewportUI.html
javax/sound/midi/SysexMessage.html
java/util/concurrent/ThreadLocalRandom.html
java/lang/Throwable.html
java/util/TimeZone.html
java/awt/Toolkit.html
java/util/concurrent/TransferQueue.html
java/beans/Transient.html
javax/lang/model/type/TypeKind.html
javax/lang/model/util/TypeKindVisitor7.html
javax/lang/model/util/Types.html
javax/lang/model/type/TypeVisitor.html
javax/lang/model/UnknownEntityException.html
java/net/URLClassLoader.html
java/nio/file/attribute/UserDefinedFileAttributeView.html
java/nio/file/attribute/UserPrincipal.html
java/nio/file/attribute/UserPrincipalLookupService.html
java/nio/file/attribute/UserPrincipalNotFoundException.html
java/nio/file/Watchable.html
java/nio/file/WatchEvent.html
java/nio/file/WatchEvent.Kind.html
java/nio/file/WatchEvent.Modifier.html
java/nio/file/WatchKey.html
java/nio/file/WatchService.html
java/awt/Window.html
java/awt/Window.Type.html
java/nio/channels/WritePendingException.html
java/dyn/WrongMethodTypeException.html
java/security/cert/X509CertSelector.html
java/security/cert/X509CRLEntry.html
javax/net/ssl/X509ExtendedTrustManager.html
java/beans/XMLDecoder.html
java/beans/XMLEncoder.html
java/util/zip/ZipFile.html
java/util/zip/ZipInputStream.html
java/util/zip/ZipOutputStream.html


Not everything new to JDK 7 is properly labeled with "1.7" in its Javadoc documentation (many new constructs in java.lang.dyn do not have "1.7" in their documentation, for example). However, the list above is probably fairly close to the set of files with changes significant enough to be identified in the documentation.

Conclusion

The changes to JDK 7 APIs range from specific (reflection, concurrency, client, JMX) to general (new Objects class) and range in degree to which they will impact the typical Java developer. The list above helps provide an idea of what types of changes an individual Java developer might look forward to with JDK 7.

Monday, March 28, 2011

JDK 7: Reflection Exception Handling with ReflectiveOperationException and Multi-Catch

Java 7 adds a new exception class called ReflectiveOperationException. The Javadoc documentation describes this class as a "Common superclass of exceptions thrown by reflective operations in core reflection." I look at this new exception class and discuss advantages of using it in this blog post and compare it to Java 7's more general approach for handling multiple exceptions in a single catch clause.

The ReflectiveOperationException is a checked exception class extended by older (available prior to 1.7) exception classes ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchFieldException, and NoSuchMethodException. There is an obvious advantage to having a checked exception that is a parent to all of these pre-existing checked exceptions that could be thrown by reflection operations: it is much easier to "catch" a single exception or declare a "throws" clause for a single exception rather than for all of the checked reflection exceptions.

The new JDK 7 feature supporting handling of multiple exceptions with a single catch block might also be used in this situation, but I like this general reflection operation exception approach. The following code examples demonstrate all three approaches (catching each individual checked exception, catching the new checked parent exception ReflectiveOperationException, and using the new JDK 7 multiple exception catching mechanism).

Traditional Individual Reflection Exception Handling
/**
    * Instantiate an instance of Person using the provided parameters and invoke
    * its toString() method with code using the traditional catching of
    * individual checked exceptions related to the various Java reflection calls.
    *
    * @param newLastName Last name of Person instance to be instantiated.
    * @param newFirstName First name of Person instance to be instantiated.
    * @param newAge Age of Person instance to be instantiated.
    */
   public void instantiatePersonReflectivelyTraditionally(
      final String newLastName, final String newFirstName, final int newAge)
   {
      final String className = "dustin.examples.Person";
      final String methodName = "toString";
      try
      {
         final Class clazz = Class.forName(className);
         final Class[] ctorParams = {String.class, String.class, Integer.TYPE};
         final Constructor ctor = clazz.getConstructor(ctorParams);
         final Person person = (Person) ctor.newInstance(newLastName, newFirstName, newAge);
         final Method toStringMethod = clazz.getDeclaredMethod(methodName);
         out.println(toStringMethod.invoke(person));
      }
      catch (ClassNotFoundException cnfEx)
      {
         err.println("Unable to instantiate class " + className + ": " + cnfEx);
      }
      catch (NoSuchMethodException nsmEx)
      {
         err.println("No method " + className + "." + methodName + ": " + nsmEx);
      }
      catch (IllegalAccessException illAccEx)
      {
         err.println(
              "IllegalAccessException encountered invoking " + className + "."
            + methodName + ": " + illAccEx);
      }
      catch (InvocationTargetException invokeTargetEx)
      {
         err.println(
              "InvocationTargetException encountered invoking " + className + "."
            + methodName + ": " + invokeTargetEx);
      }
      catch (InstantiationException ctorEx)
      {
         err.println("Unable to instantiate " + className + ": " + ctorEx);
      }
   }

Java 7: Catching Single ReflectiveOperationException
/**
    * Instantiate an instance of Person using the provided parameters and invoke
    * its toString() method with code that only needs to capture the single
    * exception ReflectiveOperationException available with Java 7.
    *
    * @param newLastName Last name of Person instance to be instantiated.
    * @param newFirstName First name of Person instance to be instantiated.
    * @param newAge Age of Person instance to be instantiated.
    */
   public void instantiatePersonReflectivelyJava7ReflectiveOperationException(
      final String newLastName, final String newFirstName, final int newAge)
   {
      final String className = "dustin.examples.Person";
      final String methodName = "toString";
      try
      {
         final Class clazz = Class.forName(className);
         final Class[] ctorParams = {String.class, String.class, Integer.TYPE};
         final Constructor ctor = clazz.getConstructor(ctorParams);
         final Person person = (Person) ctor.newInstance(newLastName, newFirstName, newAge);
         final Method toStringMethod = clazz.getDeclaredMethod(methodName);
         out.println(toStringMethod.invoke(person));
      }
      catch (ReflectiveOperationException reflectOpEx)  // single exception!
      {
         err.println(
              "Reflection error trying to invoke " + className + "."
            + methodName + ": " + reflectOpEx);
      }
   }

Java 7: Catching Multiple Reflection Exceptions with Single Catch
/**
    * Instantiate an instance of Person using the provided parameters and invoke
    * its toString() method with code using the Java 7 language feature allowing
    * multiple exceptions to be explicitly specified for capture in a single
    * catch block.
    *
    * @param newLastName Last name of Person instance to be instantiated.
    * @param newFirstName First name of Person instance to be instantiated.
    * @param newAge Age of Person instance to be instantiated.
    */
   public void instantiatePersonReflectivelyJava7MultiCatch(
      final String newLastName, final String newFirstName, final int newAge)
   {
      final String className = "dustin.examples.Person";
      final String methodName = "toString";
      try
      {
         final Class clazz = Class.forName(className);
         final Class[] ctorParams = {String.class, String.class, Integer.TYPE};
         final Constructor ctor = clazz.getConstructor(ctorParams);
         final Person person = (Person) ctor.newInstance(newLastName, newFirstName, newAge);
         final Method toStringMethod = clazz.getDeclaredMethod(methodName);
         out.println(toStringMethod.invoke(person));
      }
      catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | InstantiationException reflectionEx)
      {
         err.println(
              "Reflection error trying to invoke " + className + "."
            + methodName + ": " + reflectionEx);
      }
   }

I didn't show it here because it's considered "bad form," but another option would have been to catch generic Exception as the singly caught exception (similar to example catching only ReflectiveOperationException but much broader). I generally prefer to catch an exception as specific to the situation as possible. Assuming the exception handling is the same for all of the relevant exceptions, I prefer not to list them each individually. This makes the approach of catching the new ReflectiveOperationException or the new Java 7 multiple exception catching mechanism most appealing. I slightly prefer catching ReflectiveOperationException when appropriate (reflection), but the multiple exception catching mechanism is a more general tactic for situations where no convenient parent exception exists.


Conclusion

JDK 7 provides two new and better mechanisms for handling exceptions commonly thrown when using Java reflection. The new ability to catch multiple exceptions with a single catch clause is generally useful for catching multiple exceptions that are to be handled the same way. The availability of ReflectiveOperationException in Java 7 is a specific exception parent class that can be caught to handle all its child reflection exception classes in a single catch.

Saturday, March 26, 2011

JDK 7: The New Objects Class

It was announced approximately 18 months ago that JDK 7 would include a new java.util.Objects class that would "hold commonly-written utility methods." As part of this announcement, Joe Darcy asked the community, "What other utility methods would have broad enough use and applicability to go into a common java.util class?" There were forums and posts on the matter and I blogged about this forthcoming class. The JDK 7 preview release includes this class and it can be tried out now. In this post, I look at use of most of the methods provided by this class and look at how NetBeans 6.9 already uses this class in some of its auto-generated methods.

The java.util.Objects class is new to JDK 7 and its Javadoc states that the class is "since 1.7" and describes the class as: "This class consists of static utility methods for operating on objects. These utilities include null-safe or null-tolerant methods for computing the hash code of an object, returning a string for an object, and comparing two objects." There are currently nine static methods with public accessibility: a compare method, a deepEquals method (for comparing arrays for equality), an equals method, a hash code calculation method potentially for multiple objects, a hashCode method for a single object, overloaded requireNonNull methods, and overloaded toString methods.

There are some general themes associated with the methods provided by the new Objects class. The methods tend to increase null safety. In other words, use of these methods is something I'd be happy to add to my list of Java NullPointerException handling techniques. When one desires an instance's hash code or string representation or one wants to compare an instance to another instance, it is typically prudent to first check the object reference for null to avoid a NullPointerException occurring before the hash code, String representation, or comparison can be made. By moving these common operations out of one of the objects being acted upon into this separate Objects class, the external class can check for null rather than forcing the object itself to be checked explicitly every time.

The second major theme relevant to the methods of the new Objects class is that these methods encapsulate functionality that many developers have written on their own or that other non-standard libraries supply. This new Objects class provides a relatively concise, standardized approach to invoking this common functionality. As will be seen in this post, it makes it easy to clean up commonly implemented methods such as overridden toString, equals, and hashCode methods and to enforce non-null parameter contract constraints.

Unlike the ever-present java.lang.Object class, but like fellow classes in the java.util package, the java.util.Objects class must be explicitly imported in Java classes that make use of it. The Objects class does NOT need to be explicitly imported when used in Groovy because Groovy automatically imports classes in the java.util package. Because all examples in this post are written in Java, the java.util.Objects class will be explicitly imported in each class.

The next code listing is for a simple Person class. I generated this class's hashCode() and equals(Object) methods using NetBeans 6.9's "Insert Code" mechanism and was pleased to see that (assuming I had the Java 1.7 Platform set for my project) the relevant Objects class methods were used in these automatically generated methods. The automatically generated toString() did not make use of either of the overloaded Objects.toString() methods. I assumed that might have been because the Person class has only String attributes and these don't need to be checked for null before the implicit toString() is invoked on them. However, even when I added a more complex data type that would lead to an NPE when its implicit toString() was called, NetBeans did not use the Objects.toString() method to avoid the potential NPE.

Person.java
package dustin.examples;

import java.util.Objects;

/**
 * Simple class to be used in demonstration of JDK 7's java.util.Objects class.
 *
 * @author Dustin
 */
public class Person
{
   private String lastName;

   private String firstName;

   /**
    * Parameterized constructor for instantiating a Person. Both a non-null first
    * name and a non-null last name must be provided (no "Madonna" or "Lindsay"
    * or "Seal" allowed here [unless you pass one name as an empty String]).
    *
    * @param newLastName This Person instance's last name; must not be null.
    * @param newFirstName This Person instance's first name; must not be null.
    * @throws NullPointerException Thrown if either provided name parameter is
    *   null.
    */
   public Person(final String newLastName, final String newFirstName)
   {
      this.lastName = Objects.requireNonNull(newLastName, "Last name cannot be null.");
      this.firstName = Objects.requireNonNull(newFirstName, "First name cannot be null.");
   }

   public String getLastName()
   {
      return this.lastName;
   }

   public String getFirstName()
   {
      return this.firstName;
   }

   /**
    * NetBeans 6.9-generated equals(Object) method. It used
    * Objects.equals(Object, Object) to avoid the need to check for null on any
    * references before comparing them. This can really clean up equals method
    * implementations.
    *
    * @param obj Object to be compared to me for equality.
    * @return {@code true} if the provided object is considered equal to me;
    *    {@code false} otherwise.
    */
   public boolean equals(Object obj)
   {
      if (obj == null)
      {
         return false;
      }
      if (getClass() != obj.getClass())
      {
         return false;
      }
      final Person other = (Person) obj;
      if (!Objects.equals(this.lastName, other.lastName))
      {
         return false;
      }
      if (!Objects.equals(this.firstName, other.firstName))
      {
         return false;
      }
      return true;
   }

   /**
    * NetBeans 6.9-generated hashCode(). It used Objects.hashCode(Object)!
    * 
    * @return Hash code for this instance.
    */
   public int hashCode()
   {
      int hash = 5;
      hash = 97 * hash + Objects.hashCode(this.lastName);
      hash = 97 * hash + Objects.hashCode(this.firstName);
      return hash;
   }

   @Override
   public String toString()
   {
      return this.firstName + " " + this.lastName;
   }
}

The NetBeans-generated hashCode() method makes use of Objects.hashCode(Object) to get the individual hash codes of each of its constituent attributes. The advantage of doing this is that each attribute does not need to be checked for null before asking for its hash code. The null checking is still implicitly performed (and zero is returned if the object is null), but the code is much cleaner.

The NetBeans-generated equals(Object) method makes use of Objects.equals(Object, Object) to safely compare the current object's attributes to the provided object's attributes. This method is null-safe and returns true if both attributes being compared are null and returns false if either is null without the other being null. If both compared attributes are non-null, then a standard equality check is made. The Objects.equals(Object,Object) method provides consistent null-safe equality checking with much cleaner code than could be done before this.

The Person class listed above is used by the next code listing, which is the main code listing demonstrating the majority of the methods on the new Objects class.

ObjectsClassDemo.java
package dustin.examples;

import java.util.Objects;   // must be explicitly imported
import java.util.logging.Level;

import java.util.logging.Logger;

/**
 * Simple demonstration of the new java.util.Objects class coming with JDK 7.
 */
public class ObjectsClassDemo
{
   private static final Logger LOGGER = Logger.getLogger(ObjectsClassDemo.class.getName());

   /**
    * Demonstrate usage of Objects.requireNonNull(Object).
    *
    * @param shouldNotBeNull String object be passed to Objects.requireNonNull(Object).
    */
   private static void demoObjectsClassNullness(final String shouldNotBeNull)
   {
      String stringToUse = null;
      try
      {
         stringToUse= Objects.requireNonNull(shouldNotBeNull);
      }
      catch (NullPointerException npe)
      {
         LOGGER.severe(npe.toString());
      }
      LOGGER.log(Level.INFO, "Provided String was: ''{0}''", stringToUse);
   }

   /**
    * Demonstrate usage of Objects.requireNonNull(Object,String). This overloaded
    * version of Objects.requireNonNull is generally preferable because the
    * second (String) parameter is the "message" portion of the NullPointerException
    * that is generated. Without this parameter, the message portion is empty.
    *
    * @param shouldNotBeNull String object to be passed to
    *    Objects.requireNonNull(Object,String) where the first (Object) parameter
    *    is the object that should not be null and the second (String) parameter
    *    is the message to display if the first parameter is null.
    */
   private static void demoObjectsClassNullness(
      final String shouldNotBeNull,
      final String messageIfNull)
   {
      String stringToUse = null;
      try
      {
         stringToUse = Objects.requireNonNull(shouldNotBeNull, messageIfNull);
      }
      catch (NullPointerException npe)
      {
         LOGGER.severe(npe.toString());
      }
      LOGGER.log(Level.INFO, "Provided String was: ''{0}''", stringToUse);
   }

   /**
    * Demonstrate use of Objects.toString(Object) with default message if provided
    * object is null.
    *
    * @param objectToStringify Object to call Objects.toString(Object) on.
    */
   private static void demoNullSafeToStringDefault(
      final Object objectToStringify)
   {
      LOGGER.log(Level.INFO, "toString(): {0}", Objects.toString(objectToStringify));
   }

   /**
    * Demonstrate use of Objects.toString(Object, String) with customized String
    * used to "toString()" when the provided object is null.
    *
    * @param objectToStringify Object to call Objects.toString(Object) on.
    * @param toStringIfObjectIsNull String to be shown as result of "toString()"
    *    on a null reference.
    */
   private static void demoNullSafeToStringCustomized(
      final Object objectToStringify, final String toStringIfObjectIsNull)
   {
      LOGGER.log(Level.INFO, "toString(): {0}", Objects.toString(objectToStringify, toStringIfObjectIsNull));
   }

   /**
    * Demonstrate Objects.hash(). The Objects.hashCode() method is also
    * demonstrated and it is handy to be able to safely get a hash code without
    * explicit null check (0 is returned by Objects.hashCode(Object) if the
    * provided Object reference is null). It is also important to note that
    * calling Objects.hash(Object...) on a single object will NOT result in the
    * same hash code returned from Objects.hashCode(Object) on that same object.
    *
    * @param objectsToHash One or more objects to hash.
    */
   private static void demoHash(final Object firstObjectToHash, final Object ... objectsToHash)
   {
      final int numberObjects =
           objectsToHash.length
         + (firstObjectToHash != null ? 1 : 0);
      final int multipleObjectsHash = Objects.hash(objectsToHash);
      LOGGER.log(Level.INFO, "Hash Code for {0} objects: {1}",
                 new Object[]{numberObjects, multipleObjectsHash});
      LOGGER.log(Level.INFO, "Hash code for first object ({0}) of {1} object(s) is: {2}",
                 new Object[]{Objects.toString(firstObjectToHash), numberObjects, Objects.hashCode(firstObjectToHash)});
   }

   /**
    * Demonstrate Objects.equals(Object, Object) method.
    *
    * @param firstObject First object to be compared by Objects.equals(Object,Object).
    * @param secondObject Second object to be compared by Objects.equals(Object,Object).
    */
   private static void demoEquals(final Object firstObject, final Object secondObject)
   {
      final String aproposPhrase =  Objects.equals(firstObject, secondObject)
                                  ? " is equal to "
                                  : " is NOT equal to ";
      LOGGER.log(Level.INFO, "{0}{1}{2}",
                 new Object[]{Objects.toString(firstObject), aproposPhrase, Objects.toString(secondObject)});
   }

   /**
    * Main demonstration executable.
    *
    * @param arguments Command-line arguments; none anticipated.
    */
   public static void main(final String[] arguments)
   {
      demoObjectsClassNullness("Dustin");
      demoObjectsClassNullness(null);

      demoObjectsClassNullness("Dustin", "The String you passed is null!");
      demoObjectsClassNullness(null, "The String you passed is null!");

      final Person person = new Person("Smith", "William");
      Person nullPerson = null;
      try
      {
         nullPerson = new Person("Dump", null);
      }
      catch (NullPointerException npe)
      {
         LOGGER.severe(npe.toString());
      }

      demoNullSafeToStringDefault(person);
      demoNullSafeToStringDefault(nullPerson);

      demoNullSafeToStringCustomized(person, "No such person");
      demoNullSafeToStringCustomized(nullPerson, "No such person");

      demoHash(person, "Dustin");
      demoHash("Dustin", person);
      demoHash(person);
      demoHash("Dustin");
      demoHash(nullPerson);

      final Person person2 = new Person("Smith", "Barney");
      final Person person3 = new Person("Smith", "Barney");
      demoEquals(person, person2);
      demoEquals(person, nullPerson);
      demoEquals(person2, person3);
      demoEquals(nullPerson, null);
   }
}

The ObjectsClassDemo class contained in the code listing above demonstrates most of the methods on the Objects class. The output from running the above class's main function is shown next.

Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'Dustin'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
SEVERE: java.lang.NullPointerException
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'null'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'Dustin'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
SEVERE: java.lang.NullPointerException: The String you passed is null!
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoObjectsClassNullness
INFO: Provided String was: 'null'
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo main
SEVERE: java.lang.NullPointerException: First name cannot be null.
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringDefault
INFO: toString(): William Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringDefault
INFO: toString(): null
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringCustomized
INFO: toString(): William Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoNullSafeToStringCustomized
INFO: toString(): No such person

Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 2 objects: 2,058,375,062
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (William Smith) of 2 object(s) is: -2,111,928,853
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 2 objects: -2,111,928,822
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (Dustin) of 2 object(s) is: 2,058,375,031
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 1 objects: 1
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (William Smith) of 1 object(s) is: -2,111,928,853
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 1 objects: 1
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (Dustin) of 1 object(s) is: 2,058,375,031
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash Code for 0 objects: 1
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoHash
INFO: Hash code for first object (null) of 0 object(s) is: 0

Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: William Smith is NOT equal to Barney Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: William Smith is NOT equal to null
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: Barney Smith is equal to Barney Smith
Mar 26, 2011 11:35:26 PM dustin.examples.ObjectsClassDemo demoEquals
INFO: null is equal to null

The above code and its corresponding output lead to several observations:
  • The overloaded Objects.requireNonNull methods are useful for easily checking parameters to ensure that they are not null and throwing a NullPointerException if any is null.
    • The Objects.requireNonNull(T) method that only accepts a single parameter throws a NullPointerException without a message portion while the Objects.requireNonNull(T, String) method provides the String (second) parameter as the message portion of the NullPointerException. I prefer the latter because one of my personal pet peeves is exception's without a message String.
  • The overloaded Objects.toString(Object) methods are similar to the requireNonNull methods in that uses a "default" while the other allows a customized String to be provided. Both of these methods ensure that a String representation of some sort is provided, even for null references.
  • The Objects.hash(Object...) method and the Objects.hashCode(Object) methods provide null-safe functionality for accessing hash codes.
    • The Objects.hash(Object...) method provides a hash code constructed from multiple objects. Note that this method does NOT return the same hash code for a single provided object as would be returned for the same single object is passed to Objects.hashCode(Object).
    • The Objects.hashCode(Object) method is for acquiring a hash code for a single object in a null-safe way. A zero is returned by this method if the provided object is null.
  • Objects.equals(Object,Object) provides a null-safe way to check two objects for equality. As described above, it returns true if both arguments are null.

I don't specifically focus on the Objects.deepEquals(Object,Object) method or the Objects.compare(T,T,Comparator) method here. They perform as you'd expect from their names. The deepEquals method is used to compare objects that are arrays using Arrays.deepEquals. The compare method returns zero if both provided objects comparison would normally equal zero or if both provided references are null.

Other References

Additional coverage of the java.util.Objects class can be found in Java 7: The New java.util.Objects Class, The Java 7 Features Bound to Make Developers More Productive, and JDK 7: What Frequently Rewritten Methods Should be Included in java.util.Objects?

Conclusion

The java.util.Objects class will provide Java developers with a concise, standardized approach for performing tasks that they've previously had to use non-standard and/or verbose custom code to resolve. Even when developers use IDEs to generate their code, the benefit of more concise and readable code remains. As shown in this blog post, NetBeans 6.9 is already capable of generating code that makes use of the Objects class.

JDK 7: The Diamond Operator

Project Coin provides numerous "small language enhancements" as a subset of the new JDK 7 features. I recently blogged on Project Coin's switching on Strings and in this post I write about the new Diamond Operator (<>).

The Diamond Operator reduces some of Java's verbosity surrounding generics by having the compiler infer parameter types for constructors of generic classes. The original proposal for adding the Diamond Operator to the Java language was made in February 2009 and includes this simple example:

For example, consider the following assignment statement:

Map<String, List<String>> anagrams = new HashMap<String, List<String>>();

This is rather lengthy, so it can be replaced with this:

Map<String, List<String>> anagrams = new HashMap<>();

The above example provided in Jeremy Manson's proposal (which was one of the first in response to a call for Project Coin ideas) is simple, but adequately demonstrates how the Diamond Operator is applied in JDK 7. Manson's proposal also provides significant into why this addition was desirable:
The requirement that type parameters be duplicated unnecessarily like
this encourages an unfortunate
overabundance of static factory methods, simply because type inference
works on method invocations.

In other words, the JDK 7 Project Coin addition of a Diamond Operator brings type inference to constructors that has been available with methods. With methods type inference is implicitly done when one leaves off the explicit parameter type specification. With instantiation, on the other hand, the diamond operator must be specified explicitly to "tell" the compiler to infer the type.

In his original proposal, Manson points out that syntax without a special diamond operator could not be used to implicitly infer types for instantiations because "for the purposes of backwards compatibility, new Map() indicates a raw type, and therefore cannot be used for type inference." The Type Inference page of the Generics Lesson of the Learning the Java Language trail of the Java Tutorials includes a section called "Type Inference and Instantiation of Generic Classes" that has already been updated to reflect Java SE 7. This section also describes why the special operator must be specified to explicitly inform the compiler to use type inference on instantiation:
Note that to take advantage of automatic type inference during generic class instantiation, you must specify the diamond operator. In the following example, the compiler generates an unchecked conversion warning because the HashMap() constructor refers to the HashMap raw type, not the Map<String, List<String>> type

In Item 24 ("Eliminate Unchecked Warnings") of the Second Edition of Effective Java, Josh Bloch emphasizes in bold text, "Eliminate every unchecked warning that you can." Bloch shows an example of the unchecked conversion warning that occurs when one compiles code that uses a raw type on the right side of a declaration. The next code listing shows code that will lead to this warning.

final Map<String, Set<String>> statesToCities = new HashMap();  // raw!

The next two screen snapshots show the compiler's response to the above line of code. The first image shows the message when there are no -Xlint warnings enabled and the second shows the more explicit warning that occurs when -Xlint:unchecked is provided as an argument to javac.



If Effective Java, Bloch points out that this particular unchecked warning is easy to address by explicitly providing the parameter type to the instantiation of the generic class. With JDK 7, this will be even easier! Instead of needing to add the explicit text with these type names, the types can be inferred in many cases and specification of the diamond operator tells the compiler to make this inference rather than using the raw type.

The next Java code listing provides simplistic examples of these concepts. There are methods that demonstrate instantiation of a raw Set, instantiation of a Set with explicit specification of its parameter type, and instantiation of a Set with parameter type inferred because of specification of the diamond operator (<>).

package dustin.examples;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import static java.lang.System.out;

/**
 * Very simple demonstration of JDK 7's/Project Coin's "Diamond Operator."
 */
public class DiamondOperatorDemo
{
   /** Use of "raw" type. */
   private static Set<String> rawWithoutExplicitTyping()
   {
      final Set<String> names = new HashSet();
      addNames(names);
      return names;
   }

   /** Explicitly specifying generic class's instantiation parameter type. */
   private static Set<String> explicitTypingExplicitlySpecified()
   {
      final Set<String> names = new HashSet<String>();
      addNames(names);
      return names;
   }

   /** 
    * Inferring generic class's instantiation parameter type with JDK 7's
    * 'Diamond Operator.'
    */
   private static Set<String> explicitTypingInferredWithDiamond()
   {
      final Set<String> names = new HashSet<>();
      addNames(names);
      return names;
   }

   private static void addNames(final Set<String> namesToAddTo)
   {
      namesToAddTo.add("Dustin");
      namesToAddTo.add("Rett");
      namesToAddTo.add("Homer");
   }

   /**
    * Main executable function.
    */
   public static void main(final String[] arguments)
   {
      out.println(rawWithoutExplicitTyping());
      out.println(explicitTypingExplicitlySpecified());
      out.println(explicitTypingInferredWithDiamond());
   }
}

When the above code is compiled, only the "raw" case leads to a warning.


At this point, it can be insightful to look at what javap tells us about these three methods. This is done in this case with the command (-v option for verbose gives all the juicy details and -p displays these juicy details for the private methods):
javap -v -p -classpath classes dustin.examples.DiamondOperatorDemo

Because these methods were all in a single class, there is a single stream of output for the entire class. However, to make it easier to compare them, I have cut-and-paste the output into a format that aligns the javap output for each method against each other. Each column represents the javap output for one of the methods. I have changed the font color of the particular method to blue to make it stand out and label that column's output.


Other than the names of the methods themselves, there is NO difference in the javap output. This is because Java generics type erasure means the differentiation based on type is not available at runtime. The Java Tutorial on Generics includes a page called Type Erasure that explains this:
The compiler removes all information about the actual type argument at compile time.
Type erasure exists so that new code may continue to interface with legacy code. Using a raw type for any other reason is considered bad programming practice and should be avoided whenever possible.

As the quote above reminds us, erasure means that to bytecode a raw type is no different than an explicitly typed parameter type, but also encourages developers to not use raw types except for integrating with legacy code.

Conclusion

The inclusion of the diamond operator (<>) in Java SE 7 means that code that instantiates generic classes can be less verbose. Coding languages in general, and Java in particular, are moving toward ideas such as convention over configuration, configuration by exception, and inferring things as often as possible rather than requiring explicit specification. Dynamically typed languages are well known for type inference, but even statically-typed Java can do more of this than it does and the diamond operator is an example of this.

Tuesday, March 22, 2011

JDK 7: Java Switching on Strings Is Here

I have previously blogged on how Groovy and ActionScript provide demonstrations of switching on Strings functionality that has not been available in Java. JDK 7 changes this for Java by introducing the ability to switch on Strings. Specifically, Project Coin delivers on the promise of an ability to switch on Strings.

The next two code listings show String comparisons performed with the traditional if-else using equals approach and the newly available switching on strings approach.

StringsWithIfElseDemo.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Simple class demonstrating if/else comparisons of Strings.
 */
public class StringsWithIfElseDemo
{
   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      final String name =   arguments.length > 0
                          ? arguments[0]
                          : "Dustin";

      if (name.equals("Dino"))
      {
         out.println("Flintstones?");
      }
      else if (name.equals("Neo"))
      {
         out.println("Matrix?");
      }
      else if (name.equals("Gandalf"))
      {
         out.println("Lord of the Rings?");
      }
      else if (name.equals("Dustin"))
      {
         out.println("Inspired by Actual Events");
      }
      else
      {
         out.println("The Good, the Bad, and the Ugly?");
      }
   }
}

StringsWithSwitchDemo.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Simple class demonstrating switch on Strings available with JDK 7.
 */
public class StringsWithSwitchDemo
{
   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments: none expected.
    */
   public static void main(final String[] arguments)
   {
      final String name =   arguments.length > 0
                          ? arguments[0]
                          : "Dustin";

      switch (name)
      {
         case "Dino" :
            out.println("Flintstones?");
            break;
         case "Neo" :
            out.println("Matrix?");
            break;
         case "Gandalf" :
            out.println("Lord of the Rings?");
            break;
         case "Dustin" :
            out.println("Inspired by Actual Events");
            break;
         default :
            out.println("The Good, the Bad, and the Ugly?");
      }
   }
}

In the "old" days (before JDK 7), the second class would not compile. When compilation of the code containing switching on Strings is performed, a compiler error is encountered that states "incompatible types" and shows that a "String" was "found" while an "int" was "required." This is shown in the next screen snapshot.


With JDK 7, the second class that switches on Strings does compile. In my case, I have compiled it using the "Developer Preview" (Milestone 12) version of JDK 7 (build 134). Project Coin enhancements were scheduled for Milestone 11.

Once both classes above are compiled, they can be executed as demonstrated in the next screen snapshot.


Both classes behave exactly the same whether the traditional if-then-else is used in conjunction with String.equals(Object) or the new switching on Strings is used.

The javap utility can be run against the class files generated for both versions of String comparison code shown above and the results do have some differences. The following screen snapshot demonstrates running this tool against those classes.


As the image above shows, the size of the output from javap is larger for the code with switch statements than the code for if-else statements. About the only significant conclusion that can be made based on that size difference alone is that the classes are not exactly the same (which was already evident from the size of the .class files). I'm no expert at interpreting the output of the javap -v command, but nothing sticks out to me that significantly differentiates one approach from another in terms of performance. It seems that the somewhat subjective measure of "readability" remains the main reason for choosing between if-else and switch on Strings.

The Java documentation for switch is already updated with JDK 7 details. For example, it now states early on that "a switch works with the byte, short, char, and int primitive data types. It also works with enumerated types (discussed in Enum Types), the String class, and a few special classes that wrap certain primitive types: Character, Byte, Short, and Integer" (I added the emphasis). The documentation also states (again I have added the emphasis) that "an if-then-else statement can test expressions based on ranges of values or conditions, whereas a switch statement tests expressions based only on a single integer, enumerated value, or String object."

Perhaps the most significant new text in Java documentation on switch is the addition of an entire section of this document titled "Using Strings in switch Statements" that talks about using Strings with switches and provides a code example of this. This section also points out a few items that may be seem minor, but are worth noting. First, it points out that the String being switched on should be checked for null first to avoid a NullPointerException. In my example above, this was unnecessary because the ternary operator used to set the String variable being switched on ensured that either at least one command line argument was specified (which should not be null in normal use of 'main') or else initialized the String to a non-null value ("Dustin") if no command line arguments were specified. The second point worth keeping in mind is that the switch on Strings behaves as String.equals(Object) behaves rather than as String.equalsIgnoreCase(String) behaves. This is appropriate because Strings with different cases are not necessarily the same, but it also means that the only control the developer has over case sensitivity issues is to change the String to lowercase [String.toLowerCase()] or uppercase [String.toUpperCase()] before switching on it (and then ensure the Strings in the case are the same case). Checking the String first for null and then converting to the expected case are often necessary to ensure the desired behavior.


The Desire for Switching on Strings

There is no question that some fellow Java developers are excited about the ability to switch on Strings. Two recent Java.net polls demonstrated this interest with 25% of respondents to the question "Which Project Coin (JSR 334) Java language enhancement will be most useful?" voting for the option "Strings in switch statements" (and 1/3 of respondents chose the "All of the above" option which obviously includes switching on strings). This was a follow-on to the previous Java.net poll question "What's the most important Java 7 enhancement for the work you do?" in which over half of all respondents voted for Project Coin.


Conclusion

I would have been much more likely to use the ability to switch on Strings several years ago before J2SE 5 made enums available to us. That being stated, there are times when I run into existing code or other situations where this ability can still increase code readability in a less risky and less costly way than redesign would require.

The New XML Stack in JDK 7

In the summary of new JDK 7 features, one of the categories is called Web and its major subcategory is Update the XML Stack. This support is available as of Milestone 12 (M12 AKA "Developer Preview" AKA "beta release") and is described as "Upgrade the JAXP, JAXB, and JAX-WS APIs to the most recent stable versions." In this post, I look at the versions of JAXP, JAXB, and JAX-WS associated with JDK 7 preview release (build 1.7.0-ea-b134).

In the article Better JPA, Better JAXB, and Better Annotations Processing with Java SE 6, I wrote about some of the advantages of Java SE 6 having JAXB 2.0 baked into it. It is not uncommon for future versions of Java to include newer versions of dependent libraries and JDK 7 updates JAXB from JAXB 2.1.10 (version since Java SE 6 Update 14) to JAXB 2.2.3 as shown in the next screen snapshot.


As the above screen snapshot shows, the xjc compiler is an easy command-line approach to determining the version of JAXB associated with a particular Java distribution (assuming that xjc is not on the path from a different location). The schemagen tool can also be used to determine the JAXB version.

It is similarly easy to determine the version of JAX-WS APIs supported in the Java SE 7 release by asking associated command line tools for their versions. The following screen snapshot demonstrates doing this with the tools wsgen and wsimport.


As indicated in the above screen snapshot, the JDK 1.7.0 b134 release has JAX-WS 2.2.2 associated with it (JAXB 2.1.6 was associated with Java 6 as of Java SE 6 Update 14).

I don't know of an equivalent method to those shown above to determine from the command line what version of JAXP is included with a particular Java distribution. Fortunately, the JDK 7 Documentation includes the JAXP page that states that "the Java Platform, Standard Edition version 7.0 includes JAXP 1.4" and explains that "JAXP 1.4 is a maintenance release of JAXP 1.3 with support for the Streaming API for XML (StAX)."

I expect that the anticipated Release Notes for JDK 7 will formally and conveniently list the versions of these XML-related products included with the JDK distribution.