Miscellaneous language updates - Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Java 8 in Action: Lambdas, streams, and functional-style programming (2015)

Appendix A. Miscellaneous language updates

In this appendix, we discuss three other language updates in Java 8: repeated annotations, type annotations, and generalized target-type inference. Appendix B discusses library updates in Java 8. We don’t discuss JDK 8 updates such as Nashorn and Compact Profiles because they’re new JVM features. This book focuses on library and language updates. We invite you to read the following links if you’re interested in Nashorn and Compact Profiles: http://openjdk.java.net/projects/nashorn/ and http://openjdk.java.net/jeps/161.

A.1. Annotations

The annotation mechanism in Java 8 has been enhanced in two ways:

· You can repeat annotations.

· You can annotate any type uses.

Before we explain these updates, it’s worth quickly refreshing what you could do with annotations before Java 8.

Annotations in Java are a mechanism that lets you decorate program elements with additional information (note that prior to Java 8 only declarations could be annotated). In other words, it’s a form of syntactic metadata. For example, annotations are popular with the JUnit framework. In the following code, the method setUp is annotated with the annotation @Before, and the method testAlgorithm is annotated with @Test:

@Before

public void setUp(){

this.list = new ArrayList<>();

}

@Test

public void testAlgorithm(){

...

assertEquals(5, list.size());

}

Annotations are suitable for several use cases:

· In the context of JUnit, annotations can differentiate methods that should be run as a unit test and methods that are used for setup work.

· Annotations can be used for documentation. For instance, the @Deprecated annotation is used to indicate that a method should no longer be used.

· The Java compiler can also process annotations in order to detect errors, suppress warnings, or generate code.

· Annotations are popular in Java EE, where they’re used to configure enterprise applications.

A.1.1. Repeated annotations

Previous versions of Java forbid more than one annotation of a given annotation type to be specified on a declaration. For this reason, the following code is invalid:

Java EE programmers often make use of an idiom to circumvent this restriction. You declare a new annotation, which contains an array of the annotation you want to repeat. It looks like this:

@interface Author { String name(); }

@interface Authors {

Author[] value();

}

@Authors(

{ @Author(name="Raoul"), @Author(name="Mario") , @Author(name="Alan")}

)

class Book{}

The nested annotation on the Book class is pretty ugly. This is why Java 8 essentially removes this restriction, which tidies things a bit. You’re now allowed to specify multiple annotations of the same annotation type on a declaration, provided they stipulate that the annotation is repeatable. It’s not the default behavior; you have to explicitly ask for an annotation to be repeatable.

Making an annotation repeatable

If an annotation has been designed to be repeatable, you can just use it. But if you’re providing annotations for your users, then setup is required to specify that an annotation can be repeated. There are two steps:

1. Mark the annotation as @Repeatable.

2. Provide a container annotation.

Here’s how you can make the @Author annotation repeatable:

@Repeatable(Authors.class)

@interface Author { String name(); }

@interface Authors {

Author[] value();

}

As a result, the Book class can be annotated with multiple @Author annotations:

@Author(name="Raoul") @Author(name="Mario") @Author(name="Alan")

class Book{ }

At compile time Book is considered to be annotated by @Authors({ @Author(name= "Raoul"), @Author(name="Mario"), @Author(name="Alan")}), so you can view this new mechanism as syntactic sugar around the previous idiom used by Java programmers. Annotations are still wrapped in a container to ensure behavioral compatibility with legacy reflection methods. The method getAnnotation(Class<T> annotationClass) in the Java API returns the annotation of type T for an annotated element. Which annotation should this method return if there are several annotations of type T?

Without diving into too much detail, the class Class supports a new get-AnnotationsByType method that facilitates working with repeatable annotations. For example, you can use it as follows to print all the Author annotations on the Book class:

For this to work, both the repeatable annotation and its container must have a RUNTIME retention policy. More information about compatibility with legacy reflection methods can be found here: http://cr.openjdk.java.net/~abuckley/8misc.pdf.

A.1.2. Type annotations

As of Java 8, annotations can be also applied to any type uses. This includes the new operator, type casts, instanceof checks, generic type arguments, and implements and throws clauses. Here we indicate that the variable name of type String can’t be null using a @NonNullannotation:

@NonNull String name = person.getName();

Similarly, you can annotate the type of the elements in a list:

List<@NonNull Car> cars = new ArrayList<>();

Why is this interesting? Annotations on types can be useful to perform program analysis. In these two examples, a tool could ensure that getName doesn’t return null and that the elements of the list of cars are always non-null. This can help reduce unexpected errors in your code.

Java 8 doesn’t provide official annotations or a tool to use them out of the box. It provides only the ability to use annotations on types. Luckily, a tool called the Checker framework exists, which defines several type annotations and lets you enhance type checking using them. If you’re curious, we invite you to take a look at its tutorial: http://www.checker-framework.org. More information about where you can use annotations in your code can be found here: http://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.7.4.

A.2. Generalized target-type inference

Java 8 enhances the inference of generic arguments. You’re already familiar with type inference using context information before Java 8. For example, the method empty-List in Java is defined as follows:

static <T> List<T> emptyList();

The method emptyList is parameterized with the type parameter T. You can call it as follows to provide an explicit type to the type parameter:

List<Car> cars = Collections.<Car>emptyList();

But Java is capable of inferring the generic argument. The following is equivalent:

List<Car> cars = Collections.emptyList();

Before Java 8, this inference mechanism based on the context (that is, target typing) was limited. For example, the following wasn’t possible:

static void cleanCars(List<Car> cars) {

}

cleanCars(Collections.emptyList());

You’d get the following error:

cleanCars (java.util.List<Car>)cannot be applied to (java.util.List<java.lang.Object>)

To fix it you’d have to provide an explicit type argument like the one we showed previously.

In Java 8 the target type includes arguments to a method, so you don’t need to provide an explicit generic argument:

List<Car> cleanCars = dirtyCars.stream()

.filter(Car::isClean)

.collect(Collectors.toList());

In this code, it’s exactly this enhancement that lets you write Collectors.toList() instead of Collectors.<Car>toList().