Java 101: The Next Generation

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

Java 8's method references, interface default and static methods, and more; plus features to watch for in Java 9

Java 101: The Next Generation

Show More
1 2 3 Page 3
Page 3 of 3

To evolve the Java Collections Framework to support Java 8's Streams API, it was necessary to evolve the interface feature (on which the framework is based) to support default and static methods without breaking binary compatibility.

Another reason for supporting default and static methods in interfaces is to better organize library methods. For example, now that default void sort(Comparator<? super E> c) has been added to java.util.List<E>, you can more naturally write myList.sort(null); instead of having to write Collections.sort(myList);.

Three lesser-known Java 8 language features

Lambdas, functional interfaces, method references, and interface default and static methods have significantly changed the Java language, but these weren't Java 8's only new language contributions. Type annotations, repeating annotations, and improved generic type inference are some of Java 8's lesser known, but still powerful, language features.

Type annotations

Before Java 8, you could only apply annotations to declarations. In Java 8 annotations can be applied to any use of a type, such as object creation expressions (e.g., new @Immutable Contact("John Doe", "x@y")), type casts (e.g., id = (@NonNull String) getID()), and throws clauses (e.g., void login() throws @Fatal AccessDeniedException).

Type annotations were introduced to help improve the analysis of Java programs by ensuring stronger type checking. Because Java 8 doesn't provide a type-checking framework, download the Checker Framework to play with type annotations. For more Checker tutorials, see Dustin Marx's introduction to the Checker Framework and "Practical pluggable types for Java" (PDF) by Matthew Papi.

Java 8 supports type annotations by extending the java.lang.annotation.ElementType enum with new constants TYPE_PARAMETER (type parameter declaration) and TYPE_USE (use of a type). TYPE_PARAMETER annotates type variables, such as E in Queue<E>. TYPE_USE annotates any use of a type (including type parameter declarations).

Repeating annotations

Java 8 also introduced repeating annotations, an annotations enhancement for applying multiple instances of an annotation type to a declaration or type use. For example, as Listing 15 shows, say you've declared an Account class and want to annotate it with multiple @ToDo annotations to specify the various tasks left to accomplish.

Listing 15. Account.java

@ToDo("Add Javadoc")
@ToDo("Add constructor")
@ToDo("Add deposit() method")
@ToDo("Add withdrawal() Method")
public class Account
{
}

A pre-Java 8 compiler would generate an error upon encountering multiple instances of the same annotation type being applied to an element. However, this is no longer a problem. To make Listing 15 legal, you first need to declare an appropriate ToDo annotation type, such as the type shown in Listing 16.

Listing 16. ToDo.java


import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(ToDos.class)
public @interface ToDo
{
   String value();
}

The difference between a repeating and a non-repeating annotation type is the application of the @Repeatable meta-annotation to the annotation type. The java.lang.annotation.Repeatable annotation type (new in Java 8) indicates that the annotation type whose declaration it annotates (ToDo) is repeatable.

For compatibility reasons with previous Java versions, repeating annotations are stored in a container annotation that is automatically generated by the Java 8 compiler. The container annotation type must be specified as @Repeatable's value. In this case, ToDos.class is specified as this value. Listing 17 presents ToDos.java.

Listing 17. ToDos.java

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ToDos
{
   ToDo[] value();
}

The containing annotation type (ToDos) must have a value element with an array type. Also, the component type of the array type must be the repeatable annotation type (ToDo). Otherwise, the compiler will report one or more errors. Although you must specify the container annotation type, you don't have to specify the container annotation.

I've specified the same target type and retention policy to avoid compiler errors. For example, if I had omitted the @Target annotation, I would have received an error about ToDos being applicable to more targets than ToDo. If I had omitted @Retention, the error would have stated that ToDos has a shorter retention than ToDo.

java.lang.Class declares a <A extends Annotation> A[] getAnnotationsByType(Class<A> annotationClass) method that you can call to return an array of type annotations identified by annotationClass. Listing 18 shows you how to use this method to return Account's @ToDo annotations.

Listing 18. RADemo.java


public class RADemo
{
   public static void main(String[] args) throws ClassNotFoundException
   {
      ToDo[] annotations =
         Class.forName("Account").getAnnotationsByType(ToDo.class);
      for (ToDo annotation: annotations)
         System.out.println(annotation.value());
   }
}

Listing 18's main() method first invokes Class.forName("Account") to load Account -- this method throws ClassNotFoundException when it cannot find Account. When found, it invokes getAnnotationsByType(ToDo.class) to return an array of @ToDo annotations. The array is iterated over and each annotation's value is output.

Compile Listing 18 (javac RADemo.java) and run the application (java RADemo). You should observe the following output:


Add Javadoc
Add constructor
Add deposit() method
Add withdrawal() Method

Improved generic type inference

Java 8 enhances the Java compiler to use target typing when inferring a generic method invocation's type parameters -- an expression's target type is the type that the Java compiler expects the expression to have depending on the expression's location. Consider Collections.emptyList, which has the following declaration:


static <T> List<T> emptyList()

Now, consider the following assignment statement:


List<String> moons = Collections.emptyList();

This statement expects to receive an instance of List<String>, which is the target type. Because emptyList() returns List<T>, the compiler infers that type argument T must be String. Java 7 and Java 8 support this inference. Alternatively, you could employ a type witness and specify T's value as shown below:

List<String> moons =
Collections.<String>emptyList();

Using a type witness in this context isn't necessary. However, it is necessary in other contexts. For example, consider the following constructor that initializes a Planet object to its name and list of moons:


Planet(String name, List<String> moons)
{
   this.name = name;
   this.moons = moons;
}

Suppose you invoke this constructor using an empty list, as follows:


Planet mercury = new Planet("Mercury", Collections.emptyList());

This statement doesn't compile under Java 7, which outputs an error message that's similar to the following message:


List<Object> cannot be converted to List<String>

Java 7's compiler needs a value for type argument T and chooses Object. As a result, Collections.emptyList() returns a value of type List<Object>, which isn't compatible with Planet(String name, List<String> moons). To make it compatible, you must use a type witness, as follows:

Planet mercury = new Planet("Mercury",
Collections.<String>emptyList());

Java 8 doesn't need a type witness because method arguments are eligible target types. In this case, Planet(String name, List<String> moons) needs a second argument of type List<String>. Because Collections.emptyList() returns a value of type List<T>, Java 8 infers that T's value is String.

Type annotations, repeating annotations, and improved generic type inference haven't risen to the attention of many Java developers, but these are three features that will improve the efficiency of your Java programs if you use them correctly.

Refining the Java language in Java 9

As of this writing, Java 9 is expected to debut in early 2016. Like lambda expressions in Java 8, modularity will receive the lion's share of attention from developers and others, but smaller language updates are worth looking out for in Java 9. These updates focus on Project Coin and smarter compilation.

Small changes in Project Coin

Java Enhancement Proposal 213: Milling Project Coin offers the following changes to the Project Coin language features introduced in JDK 7:

Smarter compilation

Three projects seek to improve compiler shortcomings in Java 9:

JEP 216 would enable javac to accept and reject programs "regardless of the order of import statements and extends and implements clauses." The code fragment below demonstrates the need for this enhancement:


package P;

import static P.Outer.Nested.*;

import P.Q.*;

public class Outer
{
   public static class Nested implements I
   {
   }
}

package P.Q;

public interface I
{
}

The above code describes the contents of two source files, Outer.java and I.java. Outer.java declares class Outer in package P whose nested Nested class implements interface I. I.java declares interface I in package P.Q.

JEP 216 describes the steps that the compiler takes to compile this source code. These steps lead to the following error message:


P\Outer.java:9: error: cannot find symbol
   public static class Nested implements I
                                         ^
  symbol:   class I
  location: class Outer
1 error

Fortunately the fix is simple: if you swap the order of import static P.Outer.Nested.*; and import P.Q.*;, the compiler is able to locate interface I and successfully compiles the source code.

In conclusion

In Java 8 the Java language evolved dramatically via the addition of lambdas and functional interfaces. In this article you've seen how method references, interface default and static methods, and lesser-known features can also make a difference in the quality and efficiency of your Java programs. Java 9 is similar; while modularity takes center stage, subtler language enhancements will improve the overall experience of writing Java code, as well as the function of your programs. As an example I've demonstrated how Java 9 will smooth the rough edges from the Project Coin feature set and enhance the function and intelligence of the Java compiler.

This is the final article in the Essential Java language features tour, which started with an introduction to assertions in JDK 1.4 and has proceeded through every major platform release since then. I'm sure I'll return to Java language features in a future Java 101 article, so watch this space for updates.

1 2 3 Page 3
Page 3 of 3