Core Java

JDK 8 Versus JDK 10: Ternary/Unboxing Difference

A recent Nicolai Parlog (@nipafx) tweet caught my attention because it referenced an interesting StackOverflow discussion on a changed behavior between JDK 8 and JDK 10 and asked “Why?” The issue cited on the StackOverflow thread by SerCe ultimately came down to the implementation being changed between JDK 8 and JDK 10 to correctly implement the Java Language Specification.

The following code listing is (very slightly) adapted from the original example provided by SerCe on the StackOverflow thread.

Adapted Example That Behaves Differently in JDK 10 Versus JDK 8

public static void demoSerCeExample()
{
   try
   {
      final Double doubleValue = false ? 1.0 : new HashMap<String, Double>().get("1");
      out.println("Double Value: " + doubleValue);
   }
   catch (Exception exception)
   {
      out.println("ERROR in 'demoSerCeExample': " + exception);
   }
}

When the above code is compiled and executed with JDK 8, it generates output like this:
Double Value: null
When the above code is compiled and executed with JDK 10, it generates output like this:
ERROR in 'demoSerCeExample': java.lang.NullPointerException
In JDK 8, the ternary operator returned null for assigning to the local variable doubleValue, but in JDK 10 a NullPointerException is instead thrown for the same ternary statement.

Two tweaks to this example lead to some interesting observations. First, if the literal constant 1.0 expressed in the ternary operator is specified instead as Double.valueOf(1.0), both JDK 8 and JDK 10 set the local variable to null rather than throwing a NullPointerException. Second, if the local variable is declared with primitive type double instead of reference type Double, the NullPointerException is always thrown regardless of Java version and regardless of whether Double.valueOf(double) is used. This second observation makes sense, of course, because no matter how the object or reference is handled by the ternary operator, it must be dereferenced at some point to be assigned to the primitive double type and that will always result in a NullPointerException in the example.

The following table summarizes these observations:

 

Complete Ternary StatementSetting of Local Variable doubleValue
JDK 8JDK 10
Double doubleValue
= false
? 1.0
: new HashMap<String, Double>().get("1");
nullNullPointerException
 

double doubleValue
   =  false
    ? 1.0
    : new HashMap<String, Double>().get("1");
NullPointerExceptionNullPointerException
Double doubleValue
   =  false
    ? Double.valueOf(1.0)
    : new HashMap<String, Double>().get("1");
nullnull
double doubleValue
   =  false
    ? Double.valueOf(1.0)
    : new HashMap<String, Double>().get("1");
NullPointerExceptionNullPointerException

The only approach that avoids NullPointerException in both versions of Java for this general ternary example is the version that declares the local variable as a reference type Double (no unboxing is forced) and uses Double.valueOf(double) so that reference Double is used throughout the ternary rather than primitive double. If the primitive double is implied by specifying only 1.0, then the Double returned by the Java Map is implicitly unboxed (dereferenced) in JDK 10 and that leads to the exception. According to Brian Goetz, JDK 10 brings the implementation back into compliance with the specification.

Published on Java Code Geeks with permission by Dustin Marx, partner at our JCG program. See the original article here: JDK 8 Versus JDK 10: Ternary/Unboxing Difference

Opinions expressed by Java Code Geeks contributors are their own.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button