Tuesday, August 31, 2010

Using groovyc To Compile Groovy Scripts

For most Groovy scripts I use, I simply run the script from its Groovy source code as-is and allow the compilation to take place implicitly.  However, it can be helpful at times to use groovyc to compile Groovy code into .class files and then execute those .class files via the normal Java launcher (java).  The groovyc compiler is also a necessity when mixing Groovy and Java code in the same compilation step.

For traditional Java, such as the HelloWorld.java class shown next, javac is used to compile the class into a .class file and then java is used to run that class file.  The example class is shown first followed by a screen snapshot showing the compilation and execution process.

HelloWorld.java
import static java.lang.System.out;
import java.util.Date;

public class HelloWorld
{
   public static void main(String[] args)
   {
      out.println("Hello, " + args[0] + ", the time is " + new Date());
   }
}


When running Groovy scripts, I usually run the script directly.  A simple Groovy script equivalent of the above Java class is shown next and is followed by a screen snapshot demonstrating running the script directly.  There is no .class file in the directory.

helloToWorld.groovy
println "Hello, ${args[0]}, the time is ${new Date()}"


The above approach to running Groovy scripts is the approach I typically take.  However, there are times when it is advantageous to explicitly compile a Groovy script into a .class file using groovyc and then run it just as one would run a file created with javac.  This is demonstrated in the next screen snapshot for the simple helloToWorld.groovy script shown above.


To run the Groovy script with the normal java launcher, it was necessary to include the Groovy JAR file on the classpath (I took advantage of Java 6's JAR wildcard specification feature in this case to include all JARs in the Groovy installation lib directory).  I also needed to include the classpath entries within quotes to make it work properly.

Had I not included the Groovy JAR in the classpath for the java launcher, I would see an error like this:

java.lang.NoClassDefFoundError: groovy/lang/Script
 at java.lang.ClassLoader.defineClass1(Native Method)
 at java.lang.ClassLoader.defineClassCond(ClassLoader.java:632)
 at java.lang.ClassLoader.defineClass(ClassLoader.java:616)
 at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
 at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
 at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
 at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Caused by: java.lang.ClassNotFoundException: groovy.lang.Script
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
 ... 12 more
Could not find the main class: helloToWorld.  Program will exit.
Exception in thread "main" 

Had I not included the classpath in quotes, the error looks like this:

java.lang.NoClassDefFoundError: Files\Groovy\Groovy-1/7/4\lib\*
Caused by: java.lang.ClassNotFoundException: Files\Groovy\Groovy-1.7.4\lib\*
 at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
 at java.security.AccessController.doPrivileged(Native Method)
 at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:307)
 at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
 at java.lang.ClassLoader.loadClass(ClassLoader.java:248)
Could not find the main class: Files\Groovy\Groovy-1.7.4\lib\*.  Program will exit.
Exception in thread "main"

In the examples above, the Groovy source (one line) was much shorter than the Java source code. Notice that the respectively compiled .class files, however, are much different in their relative size.  As the last screen snapshot demonstrated, the helloToWorld.class file generated with groovyc is 7524 bytes compared to the 703 bytes of the HelloWorld.class generated with javac.  The javap command is useful here to explain the difference in compiled sizes.  The output from running javap against the respective disassembled .class files is shown next.

javap-Disassembled javac-Generated HelloWorld.class File
Compiled from "HelloWorld.java"
public class HelloWorld extends java.lang.Object{
    public HelloWorld();
    public static void main(java.lang.String[]);
}

javap-Disassembled groovyc-Generated helloToWorld.class File
Compiled from "helloToWorld.groovy"
public class helloToWorld extends groovy.lang.Script{
    public static java.lang.Long __timeStamp;
    public static java.lang.Long __timeStamp__239_neverHappen1283320660787;
    public helloToWorld();
    public helloToWorld(groovy.lang.Binding);
    public static void main(java.lang.String[]);
    public java.lang.Object run();
    public java.lang.Object this$dist$invoke$4(java.lang.String, java.lang.Object);
    public void this$dist$set$4(java.lang.String, java.lang.Object);
    public java.lang.Object this$dist$get$4(java.lang.String);
    protected groovy.lang.MetaClass $getStaticMetaClass();
    static {};
    public java.lang.Object super$3$getProperty(java.lang.String);
    public java.lang.String super$1$toString();
    public void super$3$setProperty(java.lang.String, java.lang.Object);
    public void super$1$notify();
    public void super$3$println();
    public void super$1$notifyAll();
    public void super$3$print(java.lang.Object);
    public void super$3$printf(java.lang.String, java.lang.Object[]);
    public java.lang.Object super$1$clone();
    public java.lang.Object super$3$evaluate(java.lang.String);
    public void super$1$wait();
    public groovy.lang.MetaClass super$2$getMetaClass();
    public void super$1$wait(long, int);
    public void super$2$setMetaClass(groovy.lang.MetaClass);
    public java.lang.Class super$1$getClass();
    public groovy.lang.Binding super$3$getBinding();
    public void super$1$finalize();
    public void super$3$printf(java.lang.String, java.lang.Object);
    public void super$3$setBinding(groovy.lang.Binding);
    public void super$1$wait(long);
    public void super$3$run(java.io.File, java.lang.String[]);
    public java.lang.Object super$3$evaluate(java.io.File);
    public void super$3$println(java.lang.Object);
    public boolean super$1$equals(java.lang.Object);
    public java.lang.Object super$3$invokeMethod(java.lang.String, java.lang.Object);
    public int super$1$hashCode();
    static java.lang.Class class$(java.lang.String);
}

As the output from the javap class disassembler demonstrates, the shorter Groovy code translates to a much larger .class file because of all the support that must go into it to provide that Groovy magic.

The groovyc compiler is a necessity for compiling Groovy and Java together simultaneously.  It can also be useful for compiling .class files to execute without need for recompilation every time the script is run.  Finally, groovyc's output provides insight into the magic behind Groovy for those who are interested.  If nothing else, using groovyc explicitly also helps one realize how much is done automatically by the groovy native launcher.

Saturday, August 28, 2010

The Impact of a Googless JavaOne

Although disappointing, it was not surprising to hear, according to Josh Bloch's blog post, that Google "won't be able to present at JavaOne this year" because "Oracle’s recent lawsuit against Google and open source has made it impossible for us to freely share our thoughts about the future of Java and open source generally."  Paul Krill covers this story in his post Google backs out of JavaOne Conference and Mitch Pronschinske covers it in Google Boycotts JavaOne (the latter of which also features some great reader feedback and comments).  Google employee Jeremy Manson also confirms Bloch's post.  The Darryl K. Taft post Google Blows Off JavaOne, Citing Oracle's Android Lawsuit also covers this announcement and discusses other ways in which JavaOne 2010 will be different from previous editions.

This will affect the "grunts" of the Java software development community such as myself far more than it will hurt Oracle or Google.  I thought that Reza Rahman summarized this nicely in his feedback to the Pronschinske post.  I also thought that Fabrizio Giudici's comment on that same post put it well:
I'd like Google to speak clearly. Instead of saying "We can't participate at JavaOne 2010", I'd like to read: a) Oracle is practically preventing us from speaking - b) Our lawyers told us that it would be risky for the corporate if we speak - c) We're boycotting JavaOne.

If a) is true, then shame on Oracle. If b) is true, then shame on the law system, but we can't do anything about that. . . .  If it's c) - sorry, but I say shame on Google. They would treat us, attendees and members of the community, as human shields in their war.
Losing a level of participation at JavaOne from a major player in the Java world is nothing new.  For example, IBM reduced its level of participation in JavaOne 2003 significantly from previous years.  In fact, the referenced article's Jason Bloomberg quote on IBM's relationship to Sun and Java could almost be applied to what many of us hope happens today with Oracle, Google, and Java:
I wouldn't say that by any means they're [IBM] distancing themselves from Java, but I would say they're distancing themselves from Sun. . . .  They are looking to drive their efforts with Java more independently from Sun, and JavaOne is a Sun event.
As much as we'd all like JavaOne to be about all things Java, there's no denying that in recent years JavaOne has had serious bent toward Sun.  Until last year's event, neither Oracle nor IBM had anywhere near the coverage that Sun did at recent JavaOne conferences despite being two major players in the Java community.  I even heard some quip that the conference should have been named SunONE.  The heavy emphasis on Sun-specific JavaFX for three straight editions of the conference is evidence of that.  Dismal discussions surrounding JavaOne are also nothing new as evidenced by this 2004 JavaWorld article.

The previously referenced blog posts from Google employees tell us that Google employees will not be presenting at JavaOne 2010.  It's not clear to me at this time whether presentations shared by Google employees and employees of other companies will continue.  My best guess is that they would because it seems entirely too heavy-handed for Google to tell employees of other organizations what they can and cannot do.  For example, will Bill Pugh present "Java Puzzlers: Scraping the Bottom of the Barrel" which is currently shown on the JavaOne/OpenWorld Schedule Builder as scheduled for Monday, September 20, at 4 pm?

Other presentations that are possibly impacted by Google's decision include:
⇒ "Is Java Servlet Good for WebSocket?" (BOF)
⇒ "The Collections Connection: Special 'It Goes Up to the 11th' Edition"(BOF)
⇒ "Google Web Toolkit Versus Rich Ajax Platform: Which Java-Based Ajax Is for You?" (Conference Session)
⇒ "High Performance Java Servers at Google" (Conference Session)
⇒ "Weaving a Tangled Web: Threading Best Practices at Google" (Conference Session)
⇒ "Google Web Toolkit (GWT) Cloud Applications: Fast, Fun, and Easier Than Ever" (Conference Session with VMWare presenter as well)
⇒ "Using Collections to Drive Your Swing Models" (BOF)
⇒ "Google Cloud Computing for Java Developers: Platform and Monetization" (Conference Session)
⇒ "NetBeans, IntelliJ, Eclipse, and Oracle JDeveloper: Comparing Four Plug-in APIs" (Conference Session)
⇒ "Your Instincts Are Wrong: Learn How by Microbenchmarking with Caliper" (Conference Session)
⇒ "Java Puzzlers: Scraping the Bottom of the Barrel" (Conference Session with University of Maryland professor)
⇒ "Taking Java to the Sky: Cloud Computing 2010 Expert Panel" (Panel Session that it likely to go on with five panelists that are not Google employees)
⇒ "OpenJDK at Google: A Year in the Life" (Conference Session)
⇒ "Java Bloopers: Bad Code That Never Should Have Been" (Conference Session)
⇒ "Cloud Cover: Testing Techniques for Google App Engine" (Conference Session)
⇒ "GUI Animation Rules (Conference Session)
⇒ "Dual-Pivot Quicksort and Timsort, or Sorting on Steroids" (Conference Session with an Oracle employee)

The fate of the last one listed above is particularly interesting because the co-presenter is an Oracle employee.  Besides all of us attendees who will miss out on the Google-presented presentations, the biggest losers in all of this may be the Google employees who have likely spent considerable time and energy preparing for this.  Although I've never spoken at JavaOne, I've spoken at several conferences and have found it to be exhilarating.  Although these speakers will obviously try to present their presentations in "other venues," JavaOne has to be one of the most exciting places to present for a Java-related topic.

Google's actions clearly impact their employees presenting at the conference.  It is not so clear how this impacts their Bronze Sponsorship.  The next image (see lower right corner) shows a screen snapshot I took earlier this evening from the JavaOne web page with Google still listed as a Bronze Sponsor.


As disappointing as it is to not have Google presenters at JavaOne, the potential ramifications of a furthering rift between Google and Oracle are far more serious.  If IBM can continue to be a major force in the Java community with less influence and involvement in JavaOne, so could Google if they wanted to.  However, the difference is that IBM has chosen to explicitly align itself with the Java community.  I believe that Oracle and Google both have much to benefit from a strong Java community and brand if they can work out their differences.  If they cannot, however, increasing fragmentation may be the inevitable result.

There is no question that Google's presence will be missed at JavaOne, particularly the employees' presentations and BOFs.  However, it is looking like JavaOne 2010 will not lack for drama, intrigue, and conspiracy theories.

Wednesday, August 25, 2010

More JavaOne 2010 Blogs and Articles

Several more interesting blogs and articles on JavaOne 2010 have been published recently.  In this blog post, I point to and summarize some of these.

In Oracle Takes Over JavaOne Conference, Paul Krill discusses several themes, both advertised and unadvertised, that are likely to prevail at JavaOne.  He alludes to the recent announcement that Oracle CEO Larry Ellison would join Executive Vice President Thomas Kurian for the JavaOne 2010 opening keynote ("Java Strategy and Directions").  James Sugrue highlights this keynote in One Keynote You Won't Want to Miss and there are numerous interesting comments on that post's feedback.  Krill also references the later JavaOne keynote that boats Mark Reinhold as a contributor.  I definitely plan to attend both of these keynotes while at JavaOne.

In Krill's post, he also alludes to the now well-known Oracle lawsuit against Google and Java market fragmentation as well as covering briefly on other topics likely to be covered significantly at this year's JavaOne: Groovy, Hudson, Project Jigaw, JDK 7, etc.

Another interesting JavaOne-related announcement came in the form of Follow JavaOne and Oracle Develop As It Happens: Oracle Technology Network Live.  Those attending JavaOne can attend these interviews of various individuals within the software development community (many of them Java-focused).  The full schedule for these Monday-Wednesday (September 20-22) live interviews is available at Oracle Technology Network Live.

Paul Krill wrote another JavaOne-related post this past week called JavaOne Conference May Get a Rival.  In this post, he talks about the possibility of another Java-focused conference in San Francisco in spring 2011:

Media company Software & Support Media (S&S) plans to offer a U.S.-based version of its JAX (Java Apache XML) conference, which the company has been conducting in Germany for several years.
As I blogged on previously, there are advantages to technical sessions at a software development conference and potential advantages to keynote sessions at a software development conference.  In this case, JavaOne 2010 with Oracle at the helm for the first time and with the state of the Java development market and community, I suspect that the keynote sessions have heightened interest for many of us and may be what we're looking forward to most.  The title of the opening JavaOne keynote, Java Strategy and Directions, further stokes my interest in what may be learned at JavaOne about the future of Java.

We are all guessing at this point, but the Java.net poll reflects the community interest and asks, "Which area will receive the most attention in the 'Java Strategy and Directions' Keynote?"  The current leading selections are Enterprise Java (not surprising given Oracle's historical interests) and Java on Mobile Platforms (not surprising either given the recent Oracle lawsuit related to Android).  I'm looking forward to finding out what is the focus of that keynote.

Tuesday, August 24, 2010

JavaOne 2010: 97 Things Every Programmer Should Know

One of the interesting-sounding sessions at JavaOne 2010 is 97 Things Every Programmer Should Know by Kevlin Henney and Kirk Pepperdine. The abstract for that presentation is shown here:
Modern programmers have a lot on their minds: programming languages, programming techniques, development environments, coding style, tools, development process, deadlines, meetings, software architecture, design patterns, team dynamics, code, requirements, bugs, and code quality, just to name a few. The "97 Things Every Programmer Should Know" project has collected together the wisdom of many contributors to offer a distilled snapshot of what every programmer should know. This session weaves together a story from this collected wisdom to help us see what they?ve focused on that has helped make them so productive.
I saw a version of Kevlin Henney's presentation Programmer's Dozen: Thirteen Recommendations for Refactoring, Repairing, and Regaining Control of Your Code (see video of one of these versions) at SD West 2005 and was very impressed with the style and content of that presentation.  It's no wonder this presentation at JavaOne 2010 is already "full."

The good news, even for those who cannot get into this presentation at JavaOne, is that there is a video and associated review of a version of 97 Things Every Programmer Should Know as given at Jazoon in Zurich.  The presentation can be downloaded from the session's page (all Jazoon presentations are available as a zip file here) and another link to the video is available on Parleys.com.

The main home of the 97 Things Every Programmer Should Know project is at http://programmer.97things.oreilly.com with Kelvin Henney as the editor.  Portions of the contributions to this project have been collected as a book published by O'Reilly.


Monday, August 23, 2010

A First Look at Apache Lucene Java Version 3

Apache Lucene is "open-source search software" and its "flagship sub-project," Lucene Java, is a "high-performance, full-featured text search engine library written entirely in Java" (web site, 23 August 2010).  Lucene Java 3 has been available since late November 2009 (with 3.0.1 and 3.0.2 releases since then).  Because Lucene Java Version 3 is the first "Lucene release with Java 5 as a minimum requirement," its API is now able to take advantage of features such as enums, genericsvarargs, and autoboxing.  Lucene Java 3 offers more type safety in the API and removes the need for those dreaded explicit casts.  It is convenient that the Second Edition of Lucene in Action covers Lucense 3.

Apache Lucene is open source, is sponsored by the Apache Software Foundation (ASF), and is available via the Apache License, Version 2.  Apache Lucene can be downloaded from the Apache Download Mirrors. There is also third-party commercial support available through organizations such as Lucid Imagination.

Besides the implication of maturity conveyed by its Version 3 status, there is additional evidence of Lucene's maturity.  For example, Java.net featured the article Lucene Intro in July 2003 and the 2004 JavaOne conference (six years ago!) featured a Lucene presentation by Erik Hatcher called Lucene in Action.  According to that presentation, the first open source version of Lucene (0.1) was released clear back in 2000, and Release 2.0.0 came out in mid-2006.

Lucene is a widely-used product.  According to its PoweredBy Wiki page, Lucene Java is used by many applications and web applications that are familiar to most of us.  The list includes AOL.com, Comcast, Disney, Eclipse, IBM, jGuruJIRA, LinkedIn, Project Roller, TheServerSide, SourceForge.net, and Wikipedia.

There are several other useful resources for starting with Lucene Java.  The Lucene Java Wiki has several interesting pages including LuceneFAQ and HowTo.   The developerWorks article Using Apache Lucene to Search Text covers Apache Lucene 2.4.1.

This post has been a brief introduction to the Apache Lucene with particular focus on Lucene Java.  One of the nice things about Lucene is that the non-Java implementations of it can use the same Lucene-built indexes as built by Lucene Java.  I hope to write a few more blog posts in the near future talking about various aspects of using Lucene Java 3.

Sunday, August 22, 2010

Searching JAR Files with Groovy

My favorite use of Groovy continues to be for scripting in a Java development environment.  In this blog post, I demonstrate a simple Groovy script for searching JARs recursively under a provided directory for a file that includes the specified string.

Here is the Groovy script (findClassInJar.groovy):

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

/**
 * findClassInJar.groovy
 *
 * findClassInJar <<root_directory>> <<string_to_search_for>>
 *
 * Script that looks for provided String in JAR files (assumed to have .jar
 * extensions) in the provided directory and all of its subdirectories.
 */

import java.util.zip.ZipFile
import java.util.zip.ZipException

rootDir = args ? args[0] : "."
fileToFind = args && args.length > 1 ? args[1] : "class"
numMatchingItems = 0
def dir = new File(rootDir)
dir.eachFileRecurse
{ file->
   if (file.isFile() && file.name.endsWith("jar"))
   {
      try
      {
         zip = new ZipFile(file)
         entries = zip.entries()
         entries.each
         { entry->
            if (entry.name.contains(fileToFind))
            {
               println file
               println "\t${entry.name}"
               numMatchingItems++
            }
         }
      }
      catch (ZipException zipEx)
      {
         println "Unable to open file ${file.name}"
      }
   }
}
println "${numMatchingItems} matches found!"

The script above is simple, but accomplishes the task at hand.  The next two screen snapshots show the start and finish of the script when run against a large set of JARs (Spring Framework 2.5.6 distribution) for a common name in that set ("Exporter").



There are a few interesting observations to be made about this Groovy script.  It uses Groovy's closures several times, especially using GDK functionality such as File.eachFileRecurse and Enumeration.each.  The script also demonstrates one of Groovy's greatest strengths: the ability to use Java APIs and libraries.  In this script, the java.util.zip.ZipFile and java.util.zip.ZipException are used to read the contents of each encountered JAR file.  Although Groovy does not require any exceptions (checked or unchecked) to be caught, I chose to have this script catch the ZipException and handle it by logging out the inability to open the candidate JAR file.  The advantage of explicitly catching it and "handling" it is that the encountered exception does not propagate up and stop the execution of the script.  A side benefit is a potentially more pleasing message than a partial stack trace.

There are numerous things that could be done to make this simplistic script easier and more powerful to use.  The built-in Groovy CliBuilder support could be used for more sophisticated command-line parameter handling and decent usage handling.  The script could also be enhanced to support case insensitivity or to support exact matches versus contains matches.  The script could also provide more metadata such as the number of JAR files in which matches were found or the numbers of different types of files found.  The script might be changed to search for files of ZIP formats other than JAR or for files with extensions other than .jar.  The good news is that even this simplistic script is highly useful for most situations.

There are other approaches that can be used to search JAR files with Groovy.  The blog post Groovy Script to find Java classes in JAR files lists an even shorter Groovy script that uses Groovy's regular expression support in conjunction with Groovy's String.execute() to execute the jar tvf command.  The Groovy Cookbook Examples includes a related example called Search One of More JAR Files for a Specified File or Directory.

Friday, August 20, 2010

JavaOne 2010: "JavaFX Graphics and Animation Best Practices" Canceled

The session "JavaFX Graphics and Animation Best Practices" is the third session in my Schedule Builder agenda for JavaOne to be canceled.  Here are the details on this now canceled session:

We regret to inform you that the following session you are currently enrolled in has been cancelled and removed from your schedule.

Session ID: S314067
Title:  JavaFX Graphics and Animation Best Practices
Date: 9/22/2010
Start Time: 11:30:00 AM

The other two sessions on my schedule that have already been canceled are Scala and Clojure JVM Languages and Java and HTML5: Boldly Combine.  I only had a small number of JavaFX presentations on my schedule (several more are stored in My Interests), but this was one of them.  Fortunately, even though JavaFX may not dominate at this year's JavaOne like it did in 2007 JavaOne and 2008 JavaOne, there are still numerous JavaFX presentations to attend for those interested in it.  There's even a JavaFX presentation in the same time slot: "MLB Gets on Base with JavaFX."

The slot at 11:30 am on Wednesday, September 22, has several other interesting presentations to choose from, including: "Caching in the Clouds", "Java Concurrent Animated", "Efficient Development of Large NetBeans Platform Applications with Maven", "Project Lambda: To Multicore and Beyond", "Effective and Proven Approaches to Oracle Database Tuning", and many more.

One month from now, we'll be in the thick of JavaOne 2010.

Tuesday, August 17, 2010

Java Map.get and Map.containsKey

When using Java's Map implementations, it is sometimes common to invoke the Map's get(Object) method and to react differently based on whether the value returned is null or not.  A common assumption might be made that a null returned from Map.get(Object) indicates there is no entry with the provided key in the map, but this is not always the case.  Indeed, if a Java Map implementation allows for null values, then it is possible for the Map to return its value for the given key, but that value might be a null.  Often this doesn't matter, but if it does, one can use Map.containsKey() to determine if the Map entry has a key entry.  If it does and the Map returns null on a get call for that same key, then it is likely that the key maps to a null value.  In other words, that Map might return "true" for containsKey(Object) while at the same time returning "null" for get(Object).  There are some Map implementations that do not allow null values.  In those cases, a null from a "get" call should consistently match a "false" return from the "containsKey" method.

In this blog post, I demonstrate these aspects of Map.get(Object) and Map.containsKey(Object).  Before going into that demonstration, I will first point out that the Javadoc documentation for Map.get(Object) does explicitly warn about the subtle differences between Map.get(Object) and Map.containsKey(Object):

If this map permits null values, then a return value of null does not necessarily indicate that the map contains no mapping for the key; it's also possible that the map explicitly maps the key to null. The containsKey operation may be used to distinguish these two cases.

For the post's examples, I will be using the States enum defined next:

States.java
package dustin.examples;

/**
 * Enum representing select western states in the United Sates.
 */
public enum States
{
   ARIZONA("Arizona"),
   CALIFORNIA("California"),
   COLORADO("Colorado"),
   IDAHO("Idaho"),
   KANSAS("Kansas"),
   MONTANA("Montana"),
   NEVADA("Nevada"),
   NEW_MEXICO("New Mexico"),
   NORTH_DAKOTA("North Dakota"),
   OREGON("Oregon"),
   SOUTH_DAKOTA("South Dakota"),
   UTAH("Utah"),
   WASHINGTON("Washington"),
   WYOMING("Wyoming");

   /** State name. */
   private String stateName;

   /**
    * Parameterized enum constructor accepting a state name.
    *
    * @param newStateName Name of the state.
    */
   States(final String newStateName)
   {
      this.stateName = newStateName;
   }

   /**
    * Provide the name of the state.
    *
    * @return Name of the state
    */
   public String getStateName()
   {
      return this.stateName;
   }
}

The next code listing uses the enum above and populates a map of states to their capital cities.  The method accepts a Class that should be the specific implementation of Map to be generated and populated.

generateStatesMap(Class)
/**
    * Generate and populate a Map of states to capitals with provided Map type.
    * This method also logs any Map implementations for which null values are
    * not allowed.
    *
    * @param mapClass Type of Map to be generated.
    * @return Map of states to capitals.
    */
   private static Map<States, String> generateStatesMap(Class mapClass)
   {
      Map<States,String> mapToPopulate = null;
      if (Map.class.isAssignableFrom(mapClass))
      {
         try
         {
            mapToPopulate =
               mapClass != EnumMap.class
               ? (Map<States, String>) mapClass.newInstance()
               : getEnumMap();
            mapToPopulate.put(States.ARIZONA, "Phoenix");
            mapToPopulate.put(States.CALIFORNIA, "Sacramento");
            mapToPopulate.put(States.COLORADO, "Denver");
            mapToPopulate.put(States.IDAHO, "Boise");
            mapToPopulate.put(States.NEVADA, "Carson City");
            mapToPopulate.put(States.NEW_MEXICO, "Sante Fe");
            mapToPopulate.put(States.NORTH_DAKOTA, "Bismark");
            mapToPopulate.put(States.OREGON, "Salem");
            mapToPopulate.put(States.SOUTH_DAKOTA, "Pierre");
            mapToPopulate.put(States.UTAH, "Salt Lake City");
            mapToPopulate.put(States.WASHINGTON, "Olympia");
            mapToPopulate.put(States.WYOMING, "Cheyenne");
            try
            {
               mapToPopulate.put(States.MONTANA, null);
            }
            catch (NullPointerException npe)
            {
               LOGGER.severe(
                    mapToPopulate.getClass().getCanonicalName()
                  + " does not allow for null values - "
                  + npe.toString());
            }
         }
         catch (InstantiationException instantiationException)
         {
            LOGGER.log(
               Level.SEVERE,
                 "Unable to instantiate Map of type " + mapClass.getName()
               + instantiationException.toString(),
               instantiationException);
         }
         catch (IllegalAccessException illegalAccessException)
         {
            LOGGER.log(
               Level.SEVERE,
                 "Unable to access Map of type " + mapClass.getName()
               + illegalAccessException.toString(),
               illegalAccessException);
         }
      }
      else
      {
         LOGGER.warning("Provided data type " + mapClass.getName() + " is not a Map.");
      }
      return mapToPopulate;
   }

The method above can be used to generate Maps of various sorts.  I don't show the code right now, but my example creates these Maps with four specific implementations: HashMap, LinkedHashMap, ConcurrentHashMap, and EnumMap.  Each of these four implementations is then run through the method demonstrateGetAndContains(Map), which is shown next.

demonstrateGetAndContains(Map<states,string>)
/**
    * Demonstrate Map.get(States) and Map.containsKey(States).
    *
    * @param map Map upon which demonstration should be conducted.
    */
   private static void demonstrateGetAndContains(final Map<States, String> map)
   {
      final StringBuilder demoResults = new StringBuilder();
      final String mapType = map.getClass().getCanonicalName();

      final States montana = States.MONTANA;
      demoResults.append(NEW_LINE);
      demoResults.append(
           "Map of type " + mapType + " returns "
         + (map.get(montana)) + " for Map.get() using " + montana.getStateName());
      demoResults.append(NEW_LINE);
      demoResults.append(
           "Map of type " + mapType + " returns "
         + (map.containsKey(montana)) + " for Map.containsKey() using "
         + montana.getStateName());
      demoResults.append(NEW_LINE);

      final States kansas = States.KANSAS;
      demoResults.append(
            "Map of type " + mapType + " returns "
         + (map.get(kansas)) + " for Map.get() using " + kansas.getStateName());
      demoResults.append(NEW_LINE);
      demoResults.append(
           "Map of type " + mapType + " returns "
         + (map.containsKey(kansas)) + " for Map.containsKey() using "
         + kansas.getStateName());
      demoResults.append(NEW_LINE);
      LOGGER.info(demoResults.toString());
   }

For this demonstration, I intentionally set up the Maps to have null capital values for Montana to have no entry at all for Kansas.  This helps to demonstrate the differences in Map.get(Object) and Map.containsKey(Object).  Because not every Map implementation type allows for null values, I surrounded the portion that puts Montana without a capital inside a try/catch block.

The results of running the four types of Maps through the code appears next.


Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo
INFO: HashMap: {MONTANA=null, WASHINGTON=Olympia, ARIZONA=Phoenix, CALIFORNIA=Sacramento, WYOMING=Cheyenne, SOUTH_DAKOTA=Pierre, COLORADO=Denver, NEW_MEXICO=Sante Fe, NORTH_DAKOTA=Bismark, NEVADA=Carson City, OREGON=Salem, UTAH=Salt Lake City, IDAHO=Boise}
Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains
INFO:
Map of type java.util.HashMap returns null for Map.get() using Montana
Map of type java.util.HashMap returns true for Map.containsKey() using Montana
Map of type java.util.HashMap returns null for Map.get() using Kansas
Map of type java.util.HashMap returns false for Map.containsKey() using Kansas

Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo
INFO: LinkedHashMap: {ARIZONA=Phoenix, CALIFORNIA=Sacramento, COLORADO=Denver, IDAHO=Boise, NEVADA=Carson City, NEW_MEXICO=Sante Fe, NORTH_DAKOTA=Bismark, OREGON=Salem, SOUTH_DAKOTA=Pierre, UTAH=Salt Lake City, WASHINGTON=Olympia, WYOMING=Cheyenne, MONTANA=null}
Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains
INFO:
Map of type java.util.LinkedHashMap returns null for Map.get() using Montana
Map of type java.util.LinkedHashMap returns true for Map.containsKey() using Montana
Map of type java.util.LinkedHashMap returns null for Map.get() using Kansas
Map of type java.util.LinkedHashMap returns false for Map.containsKey() using Kansas

Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet generateStatesMap
SEVERE: java.util.concurrent.ConcurrentHashMap does not allow for null values - java.lang.NullPointerException
Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo
INFO: ConcurrentHashMap: {SOUTH_DAKOTA=Pierre, ARIZONA=Phoenix, WYOMING=Cheyenne, UTAH=Salt Lake City, OREGON=Salem, CALIFORNIA=Sacramento, IDAHO=Boise, NEW_MEXICO=Sante Fe, COLORADO=Denver, NORTH_DAKOTA=Bismark, WASHINGTON=Olympia, NEVADA=Carson City}
Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains
INFO:
Map of type java.util.concurrent.ConcurrentHashMap returns null for Map.get() using Montana
Map of type java.util.concurrent.ConcurrentHashMap returns false for Map.containsKey() using Montana
Map of type java.util.concurrent.ConcurrentHashMap returns null for Map.get() using Kansas
Map of type java.util.concurrent.ConcurrentHashMap returns false for Map.containsKey() using Kansas

Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet logMapInfo
INFO: EnumMap: {ARIZONA=Phoenix, CALIFORNIA=Sacramento, COLORADO=Denver, IDAHO=Boise, MONTANA=null, NEVADA=Carson City, NEW_MEXICO=Sante Fe, NORTH_DAKOTA=Bismark, OREGON=Salem, SOUTH_DAKOTA=Pierre, UTAH=Salt Lake City, WASHINGTON=Olympia, WYOMING=Cheyenne}
Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet demonstrateGetAndContains
INFO:
Map of type java.util.EnumMap returns null for Map.get() using Montana
Map of type java.util.EnumMap returns true for Map.containsKey() using Montana
Map of type java.util.EnumMap returns null for Map.get() using Kansas
Map of type java.util.EnumMap returns false for Map.containsKey() using Kansas



For the three Map types for which I was able to input null values, the Map.get(Object) call returns null even when the containsKey(Object) method returns "true" for Montana because I did put that key in the map without a value.  For Kansas, the results are consistently Map.get() returns null and Map.containsKey() returns "false" because there is no entry whatsoever in the Maps for Kansas.

The output above also demonstrates that I could not put a null value for Montana's capital into the ConcurrentHashMap implementation (an NullPointerException was thrown).


Aug 17, 2010 11:23:26 PM dustin.examples.MapContainsGet generateStatesMap
SEVERE: java.util.concurrent.ConcurrentHashMap does not allow for null values - java.lang.NullPointerException


This had the side effect of keeping Map.get(Object) and Map.containsKey(Object) a more consistent respective null and false return values.  In other words, it was impossible to have a key be in the map without having a corresponding non-null value.

In many cases, use of Map.get(Object) works as needed for the particular needs at hand, but it is best to remember that there are differences between Map.get(Object) and Map.containsKey(Object) to make sure the appropriate one is always used.  It is also interesting to note that Map features a similar containsValue(Object) method as well.

I list the entire code listing for the MapContainsGet class here for completeness:

MapContainsGet.java
package dustin.examples;

import java.util.EnumMap;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Simple example of using Map.get(key) versus Map.containsKey(key).
 */
public class MapContainsGet
{
   /** Handle to java.util.logging Logger. */
   private static Logger LOGGER = Logger.getLogger("dustin.examples.MapContainsGet");

   /** New line. */
   private static final String NEW_LINE = System.getProperty("line.separator");

   /**
    * Generate and populate a Map of states to capitals with provided Map type.
    * This method also logs any Map implementations for which null values are
    * not allowed.
    *
    * @param mapClass Type of Map to be generated.
    * @return Map of states to capitals.
    */
   private static Map<States, String> generateStatesMap(Class mapClass)
   {
      Map<States,String> mapToPopulate = null;
      if (Map.class.isAssignableFrom(mapClass))
      {
         try
         {
            mapToPopulate =
               mapClass != EnumMap.class
               ? (Map<States, String>) mapClass.newInstance()
               : getEnumMap();
            mapToPopulate.put(States.ARIZONA, "Phoenix");
            mapToPopulate.put(States.CALIFORNIA, "Sacramento");
            mapToPopulate.put(States.COLORADO, "Denver");
            mapToPopulate.put(States.IDAHO, "Boise");
            mapToPopulate.put(States.NEVADA, "Carson City");
            mapToPopulate.put(States.NEW_MEXICO, "Sante Fe");
            mapToPopulate.put(States.NORTH_DAKOTA, "Bismark");
            mapToPopulate.put(States.OREGON, "Salem");
            mapToPopulate.put(States.SOUTH_DAKOTA, "Pierre");
            mapToPopulate.put(States.UTAH, "Salt Lake City");
            mapToPopulate.put(States.WASHINGTON, "Olympia");
            mapToPopulate.put(States.WYOMING, "Cheyenne");
            try
            {
               mapToPopulate.put(States.MONTANA, null);
            }
            catch (NullPointerException npe)
            {
               LOGGER.severe(
                    mapToPopulate.getClass().getCanonicalName()
                  + " does not allow for null values - "
                  + npe.toString());
            }
         }
         catch (InstantiationException instantiationException)
         {
            LOGGER.log(
               Level.SEVERE,
                 "Unable to instantiate Map of type " + mapClass.getName()
               + instantiationException.toString(),
               instantiationException);
         }
         catch (IllegalAccessException illegalAccessException)
         {
            LOGGER.log(
               Level.SEVERE,
                 "Unable to access Map of type " + mapClass.getName()
               + illegalAccessException.toString(),
               illegalAccessException);
         }
      }
      else
      {
         LOGGER.warning("Provided data type " + mapClass.getName() + " is not a Map.");
      }
      return mapToPopulate;
   }

   /**
    * Provide the {@code Map<States, String>} as an EnumMap.
    *
    * @return EnumMap of States to String.
    */
   private static EnumMap<States, String> getEnumMap()
   {
      return new EnumMap<States, String>(States.class);
   }

   /**
    * Log the provided Map and its type.
    *
    * @param mapToLog Map to have its type and content logged.
    */
   private static void logMapInfo(final Map<States, String> mapToLog)
   {
      LOGGER.info(
           mapToLog != null
         ? (mapToLog.getClass().getSimpleName() + ": " + mapToLog.toString())
         : "null Map");      
   }

   /**
    * Demonstrate Map.get(States) and Map.containsKey(States).
    *
    * @param map Map upon which demonstration should be conducted.
    */
   private static void demonstrateGetAndContains(final Map<States, String> map)
   {
      final StringBuilder demoResults = new StringBuilder();
      final String mapType = map.getClass().getCanonicalName();

      final States montana = States.MONTANA;
      demoResults.append(NEW_LINE);
      demoResults.append(
           "Map of type " + mapType + " returns "
         + (map.get(montana)) + " for Map.get() using " + montana.getStateName());
      demoResults.append(NEW_LINE);
      demoResults.append(
           "Map of type " + mapType + " returns "
         + (map.containsKey(montana)) + " for Map.containsKey() using "
         + montana.getStateName());
      demoResults.append(NEW_LINE);

      final States kansas = States.KANSAS;
      demoResults.append(
            "Map of type " + mapType + " returns "
         + (map.get(kansas)) + " for Map.get() using " + kansas.getStateName());
      demoResults.append(NEW_LINE);
      demoResults.append(
           "Map of type " + mapType + " returns "
         + (map.containsKey(kansas)) + " for Map.containsKey() using "
         + kansas.getStateName());
      demoResults.append(NEW_LINE);
      LOGGER.info(demoResults.toString());
   }

   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments; none expected.
    */
   public static void main(final String[] arguments)
   {
      final Map<States, String> hashMap = generateStatesMap(HashMap.class);
      logMapInfo(hashMap);
      demonstrateGetAndContains(hashMap);
      final Map<States, String> linkedHashMap = generateStatesMap(LinkedHashMap.class);
      logMapInfo(linkedHashMap);
      demonstrateGetAndContains(linkedHashMap);
      final Map<States, String> concurrentHashMap = generateStatesMap(ConcurrentHashMap.class);
      logMapInfo(concurrentHashMap);
      demonstrateGetAndContains(concurrentHashMap);
      final Map<States, String> enumMap = generateStatesMap(EnumMap.class);
      logMapInfo(enumMap);
      demonstrateGetAndContains(enumMap);
   }
}

I used an enum as the key for this demonstration which was nice because of its satisfactory equals and hashCode implementations available immediately with no extra effort on my part.  Objects used as keys without these set properly can misbehave in general and cause other differences between Map.get(Object) and Map.containsKey(Object) in certain cases.

Monday, August 16, 2010

Most Valuable Type of Conference Session

The Java.net poll question this past week has been, "What type of technical conference sessions are most valuable?"  The options that can be selected for this poll question are Keynote Addresses, Panel Sessions, Technical Sessions, Birds of a Feather (BOF) Sessions, Other, and "I Don't Know."  As of this writing, there have been just over one hundred responses with 60% favoring Technical Sessions followed by the "I Don't Know" option being in second place with 20% of the responses.  I don't recall seeing many poll questions where this option is so high.  The next highest type of conference session after Technical Sessions's 60% is Birds of a Feather Sessions with 10%.  There are potential advantages associated with each of these session types as well as risks and drawbacks for each session.  In this post, I look at each of these types of sessions based on my previous experiences with them.


Keynote Sessions


I have attended keynote presentations at conferences that have been the highlight of the conference (especially in terms of long-term perspective), but I have also attended conference keynote sessions that have felt like barely more than a waste of time.  A keynote session has great potential if the subject is relevant to what I'm interested in because the keynote is often given by people with position, influence, and depth and breadth of experience.  For example, I enjoyed the Simon Phipps keynote presentations at Colorado Software Summit because of Phipps's obvious experience in the industry (especially open source) and because of his position and influence at Sun Microsystems (Chief Open Source Officer).

In many ways the opportunity cost of keynote sessions is often low because many conferences don't have any other sessions held during the keynotes.  This means that nothing potentially more valuable can be missed to attend the keynote.  There was a time when I thought that detailed technical sessions were always more valuable than keynote presentations and so resented keynotes precluding the holding of additional technical sessions.  However, looking back on things, select keynote presentations have had a profound impact on my development career and I now appreciate a thought-provoking and relevant keynote session.

While a technical session may provide more direct and specific details on how to do something, the keynote's nature is such that it typically has more breadth, is more far-reaching, and is future-looking.  Keynotes can give us insight into what a product's steward plans to do with their product in the future.  For example, the JavaOne 2010 keynote sessions have huge potential because they are likely to provide indication of the future of Java under Oracle.


Panel Sessions

The strength of panel sessions tends to be the potential diversity of opinion and the opportunity to see multiple sides of an issue.  While keynotes are often very one-sided (single speaker) and technical sessions are nearly always evangelistic in tone (one speaker cares enough about the topic to take the time and effort to prepare for and present), panels can bring out real controversy.  I have learned significant new concepts and formed or strengthened opinions based at least partially on observations in panel sessions.  It is particularly interesting to see where panelists with different agendas and different biases can find common ground.  I'm a strong believe that no single tool, language, or framework is right for all situations and no session is better equipped to help determine where certain things best fit than a panel session.

There are risks with panel sessions.  They can be significantly less effective if there is very little real diversity of opinion on the panel.  Second, panel sessions can easily go off on tangents and get lost in the weeds without a strong and knowledgeable moderator.  I've been in some truly awesome panel sessions where significant new ideas and concepts were energetically discussed and defended and I have been in some truly awful wastes of time with a bunch of like-minded individuals rehashing the same old opinions and masquerading as a panel session.

The Panel Session often has a higher opportunity cost than the Keynote Session because there often are other session types held simultaneously with the Panel Session.  It's bad enough to go to a poor Panel Session (either because of the poor Panelists or because of the poor moderator or both), but it seems even worse when you realize you could have attended a possibly more useful technical session, BOF, or unconference session.


Technical Sessions

Technical Sessions are the bread and butter of most software development conferences.  It is the technical sessions that I am most excited about when I build my preliminary agenda for which sessions I'm going to attend at a given conference.  One of the main advantages of the technical sessions is their typical narrow focus on a practical subject.  Panel Sessions and Keynote Sessions often focus on bigger picture concepts, trends, and "softer" things like politics and other issues.  In other words, focused detailed technical details are often an advantage of a good technical session.

Just as the value of the Panel Session is largely dependent on the quality of the panelists and moderator and the value of a Keynote Session is largely dependent on the experience and position of the person giving the keynote, a big part of a technical session's quality is grounded in the speaker's experience, speaking abilities, and preparation time.  Technical sessions have great potential to be aligned with a participant's interests because of their typical narrow focus and because many of them are offered at once so that participants can choose the most relevant session.

Not everyone likes the technical session format.  In the (currently) sole comment to the Java.net poll question, Olivier Allouch wrote, "IMHO, technical sessions are useless for me. I prefer a good doc or written tutorial."  I think he brings up a good point.  I generally prefer technical sessions that either introduce me to a new library or language (to decide if it's worth my time to look at the tutorials and documentation) or technical sessions that provide best practices, tips, and tricks that may not yet be readily collected and available in documentation.  Specifically, hard-earned lessons learned can make valuable technical sessions.  In other words, for me, the technical session is typically most valuable when it either provides an easy and soft introduction to something I have little familiarity with or when it provides the kind of specific details that can be difficult to glean from general documentation.


Birds of a Feather Sessions

The major advantage of Birds of a Feather (BOF) sessions lies in its nature: because these by definition are held by and for people with enough interest in the subject to flock together, they tend to be people either with deep and current experience or people truly interested in learning more about the subject.  Having like-minded participants can be a great advantage.

A second potential advantage of the BOF format is that these are often less formal than technical sessions.  The BOF can share some of the advantages of the panel sessions (different opinions and perspectives) as well as some of its disadvantages (potential for conversation to wander aimlessly).  Like the panel, the success of the BOF often depends on having one or a few strong moderators.  The key to a successful BOF seems to lie in its reduced formality, but without giving up formality altogether.  Many successful BOF sessions I have seen have even started with a small number of slides or notes on a projector to steer the conversation.  The best BOFs, in my experience, have been those held by someone with a prepared agenda, but with the ability to quickly adapt as the conversation goes without letting that conversation go too far off topic.

A great advantage of the BOF often comes outside of the session itself.  If nothing else, the BOF is often a great opportunity to share contact information with like-minded individuals for future collaboration and discussion.  The after-the-BOF in-the-hall sessions can often be worth more than the BOF itself.


Unconference Sessions

I'm not going to get into "Other" because I have no way of knowing what that entails.  However, one could argue that the recent popularity of Unconference Sessions has earned them a category of their own.  This is the type of session that I have the least experience with of the session types on this list, but its concept is pretty straightforward.  The Unconference Session is arguably often even less formal than the BOFs.  The main advantage of the Unconference Session is the opportunity to get sessions on topics that the conference organizer may not have covered as adequately as participants would have hoped.  Unconference sessions provide an opportunity for conference participants to set up their own presentations.  This is particularly useful for niche topics or for topics that might be a little outside of the conference's main charter.

JavaOne 2010 already has several Unconference Sessions scheduled.  An example is the recent announcement that Duchess will be holding an unconference session on "the role of women in Java and IT in general."

Like the BOFs, the Unconference Sessions enjoy advantages associated with reduced formality and the gathering of like-minded individuals.  However, like BOFs, they also have greater potential for drifting off topic if the organizers are not well prepared and adaptive.


Conclusion

I typically attempt to attend at least one of each type of session at a conference like JavaOne.  This approach reminds me of a diversified investing approach.  If I attend several different types of sessions, I'm more likely to receive tremendous benefit overall and to reduce my risk of wasted time.  For JavaOne 2010, I plan to attend the JavaOne-specific keynotes (especially the one on JDK7 and the road ahead) and will likely attend technical sessions most of the time when not in keynotes.  However, I will try to find one or two BOFs and one or two Unconference Sessions to break up the formality and to gain some of the benefits for which those types of sessions are better suited.


Sunday, August 15, 2010

The Subtle Nuance of the new Keyword with Reference Types in Java

One of the trickier aspects of "general Java" development is related to comparing Java reference types for equality.  Fortunately, most of us learn early in our Java development experience that we can generally use the reference types' overridden versions of Object.equals to safely check the content of the objects, which is almost always what we want.  Object identity equality comparison with == is not what we want as frequently, but it can sometimes be mistakenly added to Java code and not discovered immediately because often even == between two seemingly different reference type objects can evaluate to true.  This is demonstrated in this blog post.


The following simple class demonstrates how == can appear to behave erratically.

LongValue.java
package dustin.examples;

import java.util.HashMap;
import java.util.Map;
import static java.lang.System.out;

public class LongValue
{
   /**
    * Print descriptive text followed by the resultant equality.
    *
    * @param descriptiveText Descriptive text explaining which equality is being
    *    shown.
    * @param equality Equality being printed.
    */
   private static void printEqualsResults(
      final String descriptiveText, final boolean equality)
   {
      out.println(descriptiveText + " : " + equality);
   }

   /**
    * Demonstrate use of == and .equals with reference types obtained in
    * different ways (such as via instantiation with {@code new} keyword,
    * {@code Long.valueOf(String)}, {@code Long.valueOf(long)}, and autoboxing)
    * and with primitives.
    */
   private static void demonstrateEquality()
   {
      final long primitiveLong = 1L;
 
      final Long referenceLong1 = new Long(primitiveLong);
      final Long referenceLong2 = primitiveLong;
      final Long referenceLong3 = Long.valueOf("1");
      final Long referenceLong4 = Long.valueOf(1L);
      final Long referenceLong5 = new Long(primitiveLong);
      final Long referenceLong6 = referenceLong1;

      printEqualsResults("Primitive/Reference New", primitiveLong == referenceLong1);
      printEqualsResults("Primitive/Reference Autobox", primitiveLong == referenceLong2);
      printEqualsResults("Primitive/Reference Long.valueOf(String)", primitiveLong == referenceLong3);
      printEqualsResults("Primitive/Reference Long.valueOf(long)", primitiveLong == referenceLong4);

      out.println("   ---");

      printEqualsResults("Reference New/Reference Autobox", referenceLong1 == referenceLong2);
      printEqualsResults("Reference Autobox/Reference Long.valueOf(String)", referenceLong2 == referenceLong3);
      printEqualsResults("Reference Long.valueOf(String)/Long.valueOf(long)", referenceLong3 == referenceLong4);
      printEqualsResults("Reference New/Reference Long.valueOf(String)", referenceLong1 == referenceLong3);
      printEqualsResults("Reference New/Reference Long.valueOf(long)", referenceLong1 == referenceLong4);
      printEqualsResults("Reference Autobox/Reference Long.valueOf(long)", referenceLong2 == referenceLong4);
      printEqualsResults("Reference New1/Reference New5", referenceLong1 == referenceLong5);
      printEqualsResults("Reference New1/Reference New6", referenceLong1 == referenceLong6); 
   }

   /**
    * Compare object references to object references stored in a Map.
    */
   private static void demonstrateWithinMaps()
   {
      final Map<String, Long> longs = new HashMap<String, Long>();
      longs.put("1_Literal", 1L);
      longs.put("2_New Reference", new Long(1L));
      longs.put("3_LongValueOfLong Reference", Long.valueOf(1L));
      longs.put("4_LongValueOfString Reference", Long.valueOf("1"));
      for (final Map.Entry<String,Long> longReference : longs.entrySet())
      {
         printEqualsResults(longReference.getKey(), longReference.getValue() == 1L);
         printEqualsResults(longReference.getKey(), longReference.getValue() == new Long(1L));
      }
   }

   /**
    * Main executable function.
    *
    * @param arguments Command-line arguments; none anticipated.
    */
   public static void main(final String[] arguments)
   {
      demonstrateEquality();
      out.println("   ---");
      demonstrateWithinMaps();
   }
}

The output from running the above code is shown next.


This output demonstrates a few interesting things about using == to compare references types to each other and reference types to primitive types.  There are actually several cases in the examples where two reference types instantiated in different ways with the same underlying long value actually evaluate to true even when compared for equality with the == operator.  Indeed, in the first set of examples, the only reference types compared for equality with == that do NOT evaluate to true are those with an instance of the Long obtained using the new keyword to instantiate the instance.  This same observation holds true in the collections set as well.

As the above examples demonstrate, using the new keyword to explicitly get an instance of the Long reference type results in a truly unique instance whose identity is not the same as any other Long instances no matter how those Long instances are obtained.  However, instances of Long obtained in other ways (autoboxing from primitive to reference type, Long.valueOf(String), and Long.valueOf(long)) all have the same identity.  Speaking of autoboxing, all instances of Long reference type evaluated to true when compared with == to the primitive long.

With all of this in mind, I now move to a related, but slightly different, nuance in Java identity comparisons for Integer.  The code example below (see The Terrible Dangers of Autoboxing, Part 2) shows a simple class called Autoboxing:

Autoboxing.java
package dustin.examples;

import static java.lang.System.out;

/**
 * Simple class demonstrating a nuance of Java's Integer identity comparisons.
 * Two Integers separately instantiated via autoboxing from primitive 10 will
 * evaluate as identical via == while two integers instantiated via autoboxing
 * from primitive 1000 will not evaluate as identical using same == operator.
 */
public class Autoboxing
{
   public static void main(String[] args)
   {
      Integer a = 10;
      Integer b = 10;
      Integer c = 1000;
      Integer d = 1000;
      out.println("a == b: " + (a == b)); //true
      out.println("c == d: " + (c == d)); //false
   }
};

It can be somewhat surprising the first time the output of the above is seen.  It is shown in the next screen snapshot.


That's awkward.  The two reference type Integer instances based on autoboxing of the primitive ten are considered identical (== returns true when comparing the two) but two reference type Integer instances based on autoboxing of the primitive one thousand are considered not identical.  This is the case even with no "new" keyword in sight.

The next code snippet is Groovy code (script called generateAutoboxClass.groovy) that generates a simple Java class called GeneratedAutoboxing that will repeat the above experiment for many more primitives than simply 10 and 1000.

generateAutoboxClass.groovy
#!/usr/bin/env groovy
NEW_LINE = System.getProperty("line.separator")
newClass = new File("src/dustin/examples/GeneratedAutobox.java")
newClass << "package dustin.examples;${NEW_LINE}${NEW_LINE}"
newClass << "import static java.lang.System.out;${NEW_LINE}${NEW_LINE}"
newClass << "public class GeneratedAutobox${NEW_LINE}{${NEW_LINE}"
newClass << "   public static void main(final String[] args)${NEW_LINE}"
newClass << "   {${NEW_LINE}"
for (i in 0..250)
{
   newClass << "      final Integer a${i} = ${i};${NEW_LINE}"
   newClass << "      final Integer b${i} = ${i};${NEW_LINE}"
   newClass << "      final Integer c${i} = new Integer(${i});${NEW_LINE}"
   newClass << "      final Integer d${i} = new Integer(${i});${NEW_LINE}"   
   newClass << "      out.println(\"a${i} = b${i}: \" + (a${i} == b${i}));${NEW_LINE}"
   newClass << "      out.println(\"c${i} = d${i}: \" + (c${i} == d${i}));${NEW_LINE}"
}
newClass << "   }${NEW_LINE}"
newClass << "}"

This simple Groovy script generates the Java class GeneratedAutobox.java as shown below (with some of the monotonous middle portion removed):

GeneratedAutobox.java
package dustin.examples;

import static java.lang.System.out;

public class GeneratedAutobox
{
   public static void main(final String[] args)
   {
      final Integer a0 = 0;
      final Integer b0 = 0;
      final Integer c0 = new Integer(0);
      final Integer d0 = new Integer(0);
      out.println("a0 = b0: " + (a0 == b0));
      out.println("c0 = d0: " + (c0 == d0));
      final Integer a1 = 1;
      final Integer b1 = 1;
      final Integer c1 = new Integer(1);
      final Integer d1 = new Integer(1);
      out.println("a1 = b1: " + (a1 == b1));
      out.println("c1 = d1: " + (c1 == d1));
      final Integer a2 = 2;
      final Integer b2 = 2;
      final Integer c2 = new Integer(2);
      final Integer d2 = new Integer(2);
      out.println("a2 = b2: " + (a2 == b2));
      out.println("c2 = d2: " + (c2 == d2));
      final Integer a3 = 3;
      final Integer b3 = 3;
      final Integer c3 = new Integer(3);
      final Integer d3 = new Integer(3);
      out.println("a3 = b3: " + (a3 == b3));
      out.println("c3 = d3: " + (c3 == d3));
      final Integer a4 = 4;
      final Integer b4 = 4;
      final Integer c4 = new Integer(4);
      final Integer d4 = new Integer(4);
      out.println("a4 = b4: " + (a4 == b4));
      out.println("c4 = d4: " + (c4 == d4));
      final Integer a5 = 5;
      final Integer b5 = 5;
      final Integer c5 = new Integer(5);
      final Integer d5 = new Integer(5);
      out.println("a5 = b5: " + (a5 == b5));
      out.println("c5 = d5: " + (c5 == d5));
      final Integer a6 = 6;
      final Integer b6 = 6;
      final Integer c6 = new Integer(6);
      final Integer d6 = new Integer(6);
      out.println("a6 = b6: " + (a6 == b6));
      out.println("c6 = d6: " + (c6 == d6));
      final Integer a7 = 7;
      final Integer b7 = 7;
      final Integer c7 = new Integer(7);
      final Integer d7 = new Integer(7);
      out.println("a7 = b7: " + (a7 == b7));
      out.println("c7 = d7: " + (c7 == d7));
      final Integer a8 = 8;
      final Integer b8 = 8;
      final Integer c8 = new Integer(8);
      final Integer d8 = new Integer(8);
      out.println("a8 = b8: " + (a8 == b8));
      out.println("c8 = d8: " + (c8 == d8));
      final Integer a9 = 9;
      final Integer b9 = 9;
      final Integer c9 = new Integer(9);
      final Integer d9 = new Integer(9);
      out.println("a9 = b9: " + (a9 == b9));
      out.println("c9 = d9: " + (c9 == d9));
      final Integer a10 = 10;
      final Integer b10 = 10;
      final Integer c10 = new Integer(10);
      final Integer d10 = new Integer(10);
      out.println("a10 = b10: " + (a10 == b10));
      out.println("c10 = d10: " + (c10 == d10));
      final Integer a11 = 11;
      final Integer b11 = 11;
      final Integer c11 = new Integer(11);
      final Integer d11 = new Integer(11);
      out.println("a11 = b11: " + (a11 == b11));
      out.println("c11 = d11: " + (c11 == d11));
      final Integer a12 = 12;
      final Integer b12 = 12;
      final Integer c12 = new Integer(12);
      final Integer d12 = new Integer(12);
      out.println("a12 = b12: " + (a12 == b12));
      out.println("c12 = d12: " + (c12 == d12));

//
// . . . several lines omitted here . . .
//

      final Integer a246 = 246;
      final Integer b246 = 246;
      final Integer c246 = new Integer(246);
      final Integer d246 = new Integer(246);
      out.println("a246 = b246: " + (a246 == b246));
      out.println("c246 = d246: " + (c246 == d246));
      final Integer a247 = 247;
      final Integer b247 = 247;
      final Integer c247 = new Integer(247);
      final Integer d247 = new Integer(247);
      out.println("a247 = b247: " + (a247 == b247));
      out.println("c247 = d247: " + (c247 == d247));
      final Integer a248 = 248;
      final Integer b248 = 248;
      final Integer c248 = new Integer(248);
      final Integer d248 = new Integer(248);
      out.println("a248 = b248: " + (a248 == b248));
      out.println("c248 = d248: " + (c248 == d248));
      final Integer a249 = 249;
      final Integer b249 = 249;
      final Integer c249 = new Integer(249);
      final Integer d249 = new Integer(249);
      out.println("a249 = b249: " + (a249 == b249));
      out.println("c249 = d249: " + (c249 == d249));
      final Integer a250 = 250;
      final Integer b250 = 250;
      final Integer c250 = new Integer(250);
      final Integer d250 = new Integer(250);
      out.println("a250 = b250: " + (a250 == b250));
      out.println("c250 = d250: " + (c250 == d250));
   }
}

The output from this generated class is interesting. A small part of that is shown in the next image.

The output of the generated Java class demonstrates a couple things.  First, the "new" approach to instantiating the integers consistently resulted in them being considered not identical when compared with the == operator.  It did not matter what primitive was used in the instantiation of the reference type Integer when the "new" operator was used: they were never identical.  The second observation is related to the previous example where autoboxing 10 resulted in two identical Integer reference types, but autoboxing 1000 did NOT result in two identical Integer reference types.  This example demonstrates where the break-off is: integers less than 128 are considered identical and integers 128 and greater are not considered identical.

Of course, there is nothing "magic" about that 127/128 break.  Indeed, the Java Language Specification does spell out this behavior.  Specifically, Section 5.1.7 ("Boxing Conversions") of the Third Edition of the JLS prescribes this:
If the value p being boxed is truefalse, a byte, a char in the range \u0000 to \u007f, or an int or short number between -128 and 127, then let r1 and r2 be the results of any two boxing conversions of p. It is always the case that r1 == r2.
This is intentional.

Other resources on this nuance include Java 1.5 Autoboxing Wackyness, Confused About == to Compare Java Wrapper Objects, and EXP03-J: Do not use the equal and not equal operators to compare boxed primitives.

In this post, I have demonstrated some nuances and potentially surprising behaviors related to Java's treatment of primitives, reference types, and autoboxing/unboxing.  These nuances, when not understood or realized, can lead to subtle errors and logic problems.  Most importantly, they serve as reminder of the importance of carefully considering handling of primitives and reference types and especially the mixing of the two.  The good news is that in many cases, only logical equality (.equals) [and not identity equality (==)] is required.

Thursday, August 12, 2010

JavaOne 2010 Technical Session on Scala and Clojure Canceled

With any conference the size of JavaOne, it is not unusual to have some presentations canceled.  As I blogged previously, a session I had signed up for ("Java and HTML5: Boldly Combine") has been canceled.  I have since learned that another session that I had signed up for ("Scala and Clojure: Java Virtual Machine Languages") has also been canceled.  Here are the details of this now canceled session:


Session ID: S313852
Title: Scala and Clojure Java Virtual Machine Languages
Date: Monday, Sept 20
Start Time: 4:00pm
Location: Parc 55, Powell I / II

I was looking forward to this single presentation comparing the two JVM-based languages Scala and Clojure. Although it is disappointing to learn of this cancellation, there are several other presentations in the same time slot that look really interesting. These include (but are not limited to) "Java Puzzlers: Scraping the Bottom of the Barrel" (S314408), "Java Persistence API (JPA) 2.0 with EclipseLink" (S314492), "Best Practices for Signing Code" (S314345), "Top 10 Oracle Features to Supercharge Your SQL" (S314673), "Building Software with Rich Client Platforms (NetBeans RCP and Eclipse RCP)" (S314711), and even another presentation on Clojure ["Clojure's Approach to Identity, State, and Concurrency" (S313914)].

In addition, there are other presentations at different times on Scala, on Clojure, and on JVM languages.  For example, Stephen Colebourne will be presenting on "Next Big Java Virtual Machine Language" (S314355) and Hamlet D'Arcy will be presenting on "Code Generation on the JVM."  Other related examples include "Polyglot Programming in the Java Virtual Machine (JVM)" (S314424), "Speedy Scripting: Productivity and Performance" (S314094), and "Multiple Languages, One Virtual Machine" (S314432).

There are numerous talks and other events at JavaOne 2010 focused on the growing set of languages other than Java itself that run on the JVM.  Although I use Groovy quite a bit, I look forward to learning more about Groovy and more about some of the other JVM languages.