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

6-7-8 hopscotch
Credit: Thinkstock

Java 101: The Next Generation

Show More

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.

1 2 Page 1
Notice to our Readers
We're now using social media to take your comments and feedback. Learn more about this here.