Java library evolution and other puzzlers

Exploring puzzling code is one way to improve your programming skills

puzzlers
Brad Montgomery

Exploring puzzling code to figure out why it doesn't do what seems obvious is one way to improve your programming skills. In this post, I introduce you to various Java-oriented puzzlers from Jens Dietrich, Joshua Bloch and Neal Gafter, and myself.

Java Library Evolution Puzzlers

Q: What is the Java Library Evolution Puzzlers?

A: The Java Library Evolution Puzzlers is a survey focused on changing Java libraries and then recompiling their client programs' source codes or running these clients unchanged against the modified libraries. The goal is to find out how well Java developers understand the ramifications of library modification. This survey was created by JavaWorld contributor Jens Dietrich, an Associate Professor in the Engineering School at New Zealand's Massey University.

There are two versions of the survey: a short version and a long version.

Q: What is the focus of each puzzler?

A: Each puzzler focuses on a client program and a pair of library JAR files. It's always possible for the client program to be compiled and executed with the first version of the JAR file. However, what happens when the client program is recompiled against the second version of the library? Also, what happens when the client program is executed with the second library version without recompiling the client?

Q: What puzzlers are available in the long version of the survey?

A: The long version of the survey consists of the following 25 puzzlers:

  • Adding a Method to an Interface
  • Removing a Method from an Interface 1
  • Removing a Method from an Interface 2
  • Removing a Method from an Interface 3
  • Specializing a Method Return Type 1
  • Specializing a Method Return Type 2
  • Specializing a Method Return Type 3
  • Specializing a Method Return Type 4
  • Generalizing a Method Parameter Type 1
  • Generalizing a Method Parameter Type 2
  • Generalizing a Method Parameter Type 3
  • Adding an Exception to a Method 1
  • Adding an Exception to a Method 2
  • Specializing an Exception
  • Removing an Exception from a Method 1
  • Removing an Exception from a Method 2
  • Primitive vs Wrapper Types 1
  • Primitive vs Wrapper Types 2
  • Changing a Generic Type Parameter 1
  • Changing a Generic Type Parameter 2
  • Changing the Value of a Constant 1
  • Changing the Value of a Constant 2
  • Changing the Value of a Constant 3
  • Changing the Value of a Constant 4
  • Nested Classes

Q: Can you provide some detail on one of these puzzlers?

A: You bet. Consider Adding a Method to an Interface, which focuses on how a client program is impacted by the addition of a method to a second version of a library's interface. Here's the source code:

// This interface is stored in its own Foo.java file source
// file and compiled into lib-1.0.jar.

package lib.addtointerface; 
public interface Foo {
   public void foo();
}

// This variant of the previous interface is stored in its
// own Foo.java source file and compiled into lib-2.0.jar.

package lib.addtointerface; 
public interface Foo {
   public void foo();
   public void bar();
}

package addtointerface;
import lib.addtointerface.*;
public class Main implements Foo {
   @Override public void foo() {
      System.out.println("foo");
   }
   public static void main(String[] args) {
      new Main().foo(); 
   }
}

This puzzler asks the following pair of questions:

  1. Can the version of the client program compiled with lib-1.0.jar be executed with lib-2.0.jar?
  2. Can the client program be compiled and then executed with lib-2.0.jar?

For the first question, you respond by choosing one of the following options:

  • no, an error occurs
  • yes, but the behaviour of the program changes
  • yes, and the behaviour of the program does not change
  • other (please specify)

For the second question, you respond by choosing one of the following options:

  • no, compilation fails
  • yes, but the behaviour of the program is different from the program version compiled and executed with lib-1.0.jar
  • yes, and the behaviour of the program is the same as the program version compiled and executed with lib-1.0.jar
  • other (please specify)

Q: Can I take part in this survey?

A: No. Unfortunately, the survey is closed. However, you can find all survey files (including the results) at Jens Dietrich's BitBucket page.

Java Puzzlers

Q: What is Java Puzzlers?

A: Java Puzzlers is a popular Java book written by Java gurus Joshua Bloch and Neal Gafter. This book helps Java developers improve their Java skills by introducing them to the Java language's traps, pitfalls, and corner cases.

Q: Can you give me an example of a puzzler from this book?

A: The following code fragment outputs cafebabe instead of 1cafebabe:

System.out.println(Long.toHexString(0x100000000L + 0xcafebabe));

If you would like the answer to this puzzler, check out the Java Puzzlers sampler.

My own puzzler

Q: Why does the following code fragment show that 127 equals 127 and then show that 30000 doesn't equal 30000?

Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // Output: true
i1 = 30000;
i2 = 30000;
System.out.println(i1 == i2); // Output: false

A: Integer i1 = 127; autoboxes 32-bit integer 127 into a java.lang.Integer object whose reference is assigned to i1. Similarly, Integer i2 = 127; autoboxes 127 into an Integer whose reference is assigned to i2. Behind the scenes, Integer i1 = 127; is converted to Integer i1 = Integer.valueOf(127); and Integer i2 = 127; is converted to Integer i2 = Integer.valueOf(127);. According to valueOf()'s Java documentation, this method takes advantage of caching to improve performance.

The Integer class maintains an internal cache of unique Integer objects over a small range of values. The low bound of this range is -128 and the high bound defaults to 127. For value 127, a reference to an Integer object containing this value is returned from the cache and assigned to i1 and i2. Hence, the == operator, which compares object references, compares the same reference in i1 and i2, and so true outputs. However, for 30000, a pair of Integer objects containing this value are created and their different references are assigned to i1 and i2. This time, == compares two different references and false outputs.

You can change the cache's high bound by assigning a different value to system property java.lang.Integer.IntegerCache.high, and you would do so when you run the application. For example, assume that the bytecode equivalent of the previous code fragment was stored in IntegerCompare.class. Execute the following command, which states that all Integer objects containing values ranging from -128 through 30000 are to be cached, so that you observe true instead of false in the second comparison of i1 and i2:

java -Djava.lang.Integer.IntegerCache.high=30000 IntegerCompare

What's next?

Next time, I launch a three-part series that answers various questions related to the java.lang.Object class and its various methods. In Part 1, I overview Object and examine its clone() and equals() methods.

download
Get the source code for this post's applications. Created by Jeff Friesen for JavaWorld

The following software was used to develop the post's code:

  • 64-bit JDK 7u6

The post's code was tested on the following platform(s):

  • JVM on 64-bit Windows 7 SP1