Sep 22, 2014 1:26 PM PT
Java 101: The Next Generation

Java 101: The essential Java language features tour, Part 5

Four more small-change language features introduced in Project Coin

Thinkstock

This article completes my introduction to new Java language features introduced in Project Coin, the JDK 7 language update. So far I have introduced try-with-resources, switch-on-string, multi-catch, and final re-throw. This month I demonstrate enhancements found in the remaining four features, namely binary literals, underscores in numeric literals, type inference changes, and simplified varargs method invocation. I'll provide an overview of each new feature and demonstrate how it is used in a small Java application example. Where appropriate I'll also discuss performance and other improvements over prior implementations.

download
Source code for "Java 101: The Next Generation: The essential Java language features tour, Part 5." Created by Jeff Friesen for JavaWorld.

Note that this article is a continuation of Java 101: The next generation: The essential Java language features tour, Part 4, which introduced the first four of eight new Java language features introduced in Project Coin.

Project Coin small change #5: Binary literals

Java has always supported decimal, hexadecimal, and octal literal values for its byte integer, short integer, integer, and long integer types. Starting with Java 7, the language also supports binary literals.

A binary literal is an integer literal prefixed with 0b or 0B and followed by a sequence of zeros and ones. Consider the following examples:


byte byte1 = 0b01111111;
System.out.println(byte1); // Output: 127

byte byte2 = (byte) 0b10000000;
System.out.println(byte2); // Output: -128

long long1 =
0B1010101010101010101010101010101010101010101010101010101010101010L;
System.out.println(long1)

The first example doesn't require a (byte) cast operator because 127 fits into the byte-integer range of -128 through 127. In the second example, you might think that a cast isn't required because -128 also fits into this range. However, there's more going on than meets the eye.

Unless suffixed by an L or l, which is required to specify a 64-bit binary literal as demonstrated in the third example, a binary literal has 32-bit integer type and is extended to 32 bits through sign extension, in which the most significant bit (the sign bit) is copied into the extra bits:

  • For the first example, a zero is copied to each of the 24 prepended bits, resulting in 00000000000000000000000001111111. This value equates to 127. A cast operator isn't required because the leading zeros can be truncated without changing the value.
  • For the second example, a one is copied to the prepended bits, resulting in 11111111111111111111111110000000. Although this value equates to -128, the extra bits would be lost when truncated, and so the cast operator is necessary.

Although they may seem irrelevant, binary literals have their uses. For example, they can improve the readability of bitmasks and bitmaps, as demonstrated in Listing 9.

Listing 9. Binary literals in a bitmask


short mask = 0b1100001;
System.out.println(Integer.toBinaryString(0b11001101 & mask)); // Output:
10000001

short[][] font =
{
   // Letter A

   // ...

   // Letter E

   {
      0b0111111111,
      0b0100000000,
      0b0100000000,
      0b0100000000,
      0b0111111110,
      0b0100000000,
      0b0100000000,
      0b0100000000,
      0b0111111111
   },

   // Letter T

   {
      0b0111111111,
      0b0000010000,
      0b0000010000,
      0b0000010000,
      0b0000010000,
      0b0000010000,
      0b0000010000,
      0b0000010000,
      0b0000010000
   }

   // ...
};

int offsetE = 0;
int offsetT = 1;
outputLetter(font, offsetE);
System.out.println();
outputLetter(font, offsetT);

Project Coin small change #6: Underscores in numeric literals

To improve the readability of numeric literals, Java 7 made it possible to insert underscores between successive digits. The following examples demonstrate:


long creditCardNumber = 1234_5678_9012_3456L;
double PI = 3.141_592_654;
short s = 12__345;

The third example shows that you can insert multiple successive underscores. However, if you specify a leading underscore (for example, _123), the result is interpreted as an identifier and would undoubtedly result in a compiler error. Trailing underscores (such as 8.5_) are not allowed.

Project Coin small change #7: Type inference changes

The Java compiler includes a type inference algorithm that examines each generic method invocation and corresponding declaration to figure out the invocation's type argument(s). The inference algorithm identifies argument types and (when available) the type of the assigned or returned result.

The algorithm attempts to identify the most specific type that works with all arguments. For example, in the following code fragment, type inference determines that the java.io.Serializable interface is the type of the second argument (new TreeSet<String>()) that's passed to select():


Serializable s = select("x", new TreeSet<String>());

static <T> T select(T a1, T a2)
{
   return a2;
}

Type inference applies to generic methods, generic class instantiation, and generic constructors for generic and non-generic classes. Java 7 introduced type inference changes regarding generic class instantiation and generic constructors. Although no type inference changes were made regarding generic methods, I'll discuss this category for completeness. (For an overview of generics see the first article in my Essential Java language features tour series.)

Type inference and generic methods

Type inference makes it possible to invoke a generic method without having to specify a type or types between angle brackets. For example, Listing 10 shows how you would have to invoke the java.util.Arrays class's <T> void sort(T[] a, Comparator<? super T> c) class method without type inference.

Listing 10. Invoking a generic method without type inference


import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;

public class SortInts
{
   public static void main(String[] args)
   {
      Integer[] ints = new Integer[10000];
      Random r = new Random();
      for (int i = 0; i < ints.length; i++)
         ints[i] = r.nextInt(30000);
      Arrays.<Integer>sort(ints, new
Comparator<Integer>()
                                 {
                                    @Override
                                    public int compare(Integer i1, Integer
i2)
                                    {
                                       return i2.intValue()-i1.intValue();
                                    }
                                 });
      for (int i = 0; i <; ints.length; i++)
         System.out.println(ints[i]);
   }
}

Listing 10 creates an array of 1000 randomly generated integers and sorts this array into descending order by invoking a sort() method with a comparator for a descending sort. The array is then output. The key item is Arrays.<Integer>, which passes the actual type argument Integer to this generic method's formal type parameter list. In contrast, Listing 11 omits this type and depends on type inference to detect Integer.

Listing 11. Invoking a generic method with type inference


import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;

public class SortInts
{
   public static void main(String[] args)
   {
      Integer[] ints = new Integer[10000];
      Random r = new Random();
      for (int i = 0; i < ints.length; i++)
         ints[i] = r.nextInt(30000);
      Arrays.sort(ints, new Comparator<Integer>()
                        {
                            @Override
                            public int compare(Integer i1, Integer i2)
                            {
                               return i2.intValue()-i1.intValue();
                            }
                        });
      for (int i = 0; i < ints.length; i++)
         System.out.println(ints[i]);
   }
}

In Listing 11, the compiler automatically infers (from the sort() method's arguments) that the type parameter is Integer, and so it doesn't have to be specified.

The compiler can usually infer a generic method call's type parameters, and so you don't have to specify them. However, situations arise where type parameters must be specified. For example, consider Planets.java in Listing 12.

Listing 12. Specifying a generic method call's type parameter


import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Planets
{
   private List<String> planets;

   Planets()
   {
      planets = Collections.emptyList();
   }

   public void setPlanets(List<String> planets)
   {
      this.planets = planets;
   }

   @Override
   public String toString()
   {
      return planets.toString();
   }

   public static void main(String[] args)
   {
      Planets p = new Planets();
      System.out.println(p);
      p.setPlanets(Arrays.asList("Mercury", "Venus", "Earth", "Mars"));
      System.out.println(p);
      p.setPlanets(Collections.emptyList());
//      p.setPlanets(Collections.<String>emptyList());
      System.out.println(p);
   }
}

Planets stores the names of various planets in a list. Its noargument constructor invokes the java.util.Collections class's <T> List<T> emptyList() class method to initialize its private planets field to an empty java.util.List of java.lang.String -- the compiler infers emptyList()'s return type from its context.

Why initialize planets to the result of emptyList()? If planets wasn't initialized to a List<String> implementation, this field would contain null (the default value for this instance field when the object is created). Furthermore, planets.toString() would result in NullPointerException when Planets's toString() method was called.

When you compile this source code (javac Planets.java), the compiler outputs the following error message:


Planets.java:31: error: method setPlanets in class Planets cannot be applied
to given types;
      p.setPlanets(Collections.emptyList());
       ^
  required: List<String>
  found: List<Object>
  reason: actual argument List<Object> cannot be converted to
List<String> by method invocation conversion
1 error

The error message arises because the compiler's type inference algorithm cannot figure out the proper type from the p.setPlanets(Collections.emptyList()); method-call context. The compiler was unable to infer that Collections.emptyList() should return List<String>. Instead, it sees this method call as returning List<Object>.

The solution is to specify String as the actual type argument for the emptyList() method, which is demonstrated in the commented-out p.setPlanets(Collections.<String>emptyList()); method call. Simply comment out the offending line and uncomment this line, and recompile. When you execute java Planets, you should observe the following output:


[]
[Mercury, Venus, Earth, Mars]
[]

Type inference and generic class instantiation with the diamond operator

Java 7 modified the type inference algorithm in a generic class instantiation context so that you can replace the actual type arguments that are required to invoke a generic class's constructor with an empty list (<>), provided that the compiler can infer the type arguments from the instantiation context. Informally, <> is referred to as the diamond operator, although it isn't a real operator.

For example, consider the following code fragment:


Map<String, Set<String>> marbles = new HashMap<String,
Set<Integer>>();

The diamond operator lets you replace this verbose code fragment with the following shorter code fragment:


Map<String, Set<String>> marbles = new HashMap<>();

To leverage type inference during generic class instantiation, you must specify the diamond notation. For example, in the following code fragment, the compiler generates an "unchecked conversion warning" because the HashMap() constructor refers to the java.util.HashMap raw type and not to the java.util.Map<String, Set<String>> type:


Map<String, Set<String>> marbles = new HashMap();

The compiler supports limited type inference for generic instance creation. Type inference can be used only when a constructor's parameterized type is obvious from the context. For example, the following code fragment doesn't compile because the parameterized type isn't obvious:


Set<String> marbles = new TreeSet<>();
marbles.add("Aggie");
marbles.add("Steely");
marbles.add("Swirly");
marbles.addAll(new TreeSet<>());

The marbles.addAll(new TreeSet<>()); method call fails because boolean addAll(Collection<? extends E> c) expects Collection<? extends String>. To make the code compile, replace the previous addAll() call with the following code:


Set<? extends String> marbles2 = new TreeSet<>();
marbles.addAll(marbles2);

This little exercise shows that, although you can often use the diamond operator in method calls (for instance, marbles.retainAll(new TreeSet<>());), you should use it primarily to initialize a variable where it's declared.

Type inference and generic constructors for generic and non-generic classes

Generic and non-generic classes can declare generic constructors in which a constructor has a formal type parameter list. For example, you could declare the following generic class with a generic constructor:


public class Box<E>
{
   public <T> Box(T t)
   {
      // ...
   }
}

This declaration specifies generic class Box<E> with formal type parameter E. It also specifies a generic constructor with formal type parameter T. You could instantiate the generic class and invoke its constructor as follows:


new Box<Marble>("Aggies")

This expression creates an instance of Box<Marble>, passing Marble to E. Also, the compiler infers String as T's actual type argument because the constructor's argument is a String object.

Pre-Java 7 compilers infer a generic constructor's actual type arguments similarly to those of a generic method. However, Java 7's compiler can infer the actual type arguments of the generic class being instantiated in a diamond operator context. Consider the following example:


Box<Marble> box = new Box<>("Aggies");

As well as inferring the type Marble for formal type parameter E of generic class Box<E>, the compiler infers type String for formal type parameter T of this generic class's constructor.

Project Coin small change #8: Simplified varargs method invocation

Before Java 7, each attempt to invoke a varargs (variable arguments, also known as variable arity) method with a non-reifiable varargs type caused the compiler to output an "unsafe operation" warning. To eliminate the potential for many similar warning messages (one per call site), Java 7 moved the warning from the call site to the method declaration.

Generic methods that include vararg input parameters can cause heap pollution, in which a variable of a parameterized type refers to an object that isn't of that parameterized type (for instance if a raw type has been mixed with a parameterized type). The compiler reports an "unchecked warning" because the correctness of an operation involving a parameterized type (like a cast or method call) cannot be verified.

Listing 13 demonstrates heap pollution in a non-varargs context.

Listing 13. Demonstrating heap pollution in a non-varargs context


import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;

public class HeapPollutionDemo
{
   public static void main(String[] args)
   {
      Set s = new TreeSet<Integer>();
      Set<String> ss = s;            // unchecked warning
      s.add(new Integer(42));        // another unchecked warning
      Iterator<String> iter = ss.iterator();
      while (iter.hasNext())
      {
         String str = iter.next();   // ClassCastException thrown
         System.out.println(str);
      }
   }
}

Variable ss has parameterized type Set<String>. When the java.util.Set that's referenced by s is assigned to ss, the compiler generates an unchecked warning. It does so because the compiler cannot determine that s refers to a Set<String> type (it does not). The result is heap pollution. (The compiler allows this assignment to preserve backwards compatibility with legacy Java versions that don't support generics. Furthermore, type erasure transforms Set<String> into Set, which results in one Set being assigned to another Set.)

The compiler generates a second unchecked warning on the line that invokes Set's add() method. It does so because it cannot determine if variable s refers to a Set<String> or Set<Integer> type. This is another heap pollution situation. (The compiler allows this method call because erasure transforms Set's boolean add(E e) method to boolean add(Object o), which can add any kind of object to the set, including the java.lang.Integer subtype of java.lang.Object.)

Heap pollution can easily occur in a varargs context. For example, consider Listing 14.

Listing 14. Demonstrating heap pollution in a varargs context


import java.util.Arrays;
import java.util.List;

public class UnsafeVarargsDemo
{
   public static void main(String[] args)
   {
      unsafe(Arrays.asList("A", "B", "C"),
             Arrays.asList("D", "E", "F"));
   }

   static void unsafe(List<String>... l)
   {
      Object[] oArray = l;
      oArray[0] = Arrays.asList(new Double(3.5));
      String s = l[0].get(0);
   }
}

The Object[] oArray = l; assignment introduces the possibility of heap pollution. A value not matching the parameterized type of the varargs parameter l can be assigned to variable oArray. However, the compiler doesn't generate an unchecked warning because it has already done so when translating List<String>... l to List[] l. This assignment is valid because variable l has the type List[], which subtypes Object[].

Also, the compiler doesn't issue a warning or error when assigning a List object of any type to any of oArray's array components; for example, oArray[0] = Arrays.asList(new Double(3.5));. This assignment assigns to the first array component of oArray a List object containing a single java.lang.Double object.

The String s = l[0].get(0); assignment is problematic. The object stored in the first array component of variable l has the type List<Double>, but this assignment expects an object of type List<String>. As a result, the JVM throws java.lang.ClassCastException.

Compile this source code (javac -Xlint:unchecked UnsafeVarargsDemo.java). You should observe the following output (slightly reformatted for readability) when compiled under Java SE 7 update 6:


UnsafeVarargsDemo.java:8: warning: [unchecked] unchecked generic array
creation for varargs parameter of
type List<String>[]
      unsafe(Arrays.asList("A", "B", "C"),
            ^
UnsafeVarargsDemo.java:12: warning: [unchecked] Possible heap pollution from
parameterized vararg type
List
   static void unsafe(List<String>... l)
                                      ^
2 warnings

In my Java 101 introduction to generics I stated that you cannot use type parameters in array-creation expressions. For example, you cannot specify elements = new E[size];. The compiler reports a "generic array creation error" message when you try to do so. However, it's still possible to create a generic array, but only in a varargs context, and that is what the first warning message is reporting. Behind the scenes, the compiler transforms List<String>... l to List<String>[] l and then to List[] l.

Notice that the heap pollution warning is generated at the unsafe() method's declaration site. This message isn't generated at this method's call site, which is the case with Java 5 and 6 compilers.

Not all varargs methods will contribute to heap pollution. However, a warning message will still be issued at the method's declaration site. If you know that your method doesn't contribute to heap pollution, you can suppress this warning by declaring it with the @SafeVarargs annotation -- Java 7 introduced the java.lang.SafeVarargs annotation type. For example, because there's no way for the Arrays class's asList() method to contribute to heap pollution, this method's declaration has been annotated with @SafeVarargs, as follows:


@SafeVarargs
public static <T> List<T> asList(T... a)

The @SafeVarargs annotation eliminates the generic array creation and heap pollution warning messages. It's a documented part of the method's contract and asserts that the method's implementation will not improperly handle the varargs formal parameter.

In conclusion

Java 7 improved developer productivity by introducing automatic resource management via the try-with-resources statement along with a new AutoCloseable interface, switch-on-string, multi-catch, final rethrow, binary literals, underscores in numeric literals, changes to the compiler's type inference algorithm that introduced the so-called diamond operator, and simplified varargs method invocation. Next up in the Java 101: The next generation series is a look at Java 8's lambda and functional interface language features.