Home > Articles > Programming > Java

📄 Contents

  1. Predefined Functional Interfaces
  2. Method References
  3. Enhanced Generic Type Inference / Type Annotations
  • Print
  • + Share This
Like this article? We recommend

Enhanced Generic Type Inference

Java 8 enhances the Java compiler to use target typing to infer a generic method invocation's type parameters. An expression's target type is the type expected by the Java compiler where the expression appears. For example, in Java 7 you can use an assignment statement's target type for type inference. However, in Java 8, the target type for type inference can be used in more contexts, such as inferring the types of a method invocation's arguments from its target types. Consider the following example:

Set birds = new TreeSet<>();
birds.add("Robin");
birds.add("Hawk");
birds.addAll(Arrays.asList());

If you disregard generics, addAll() expects to receive a java.util.Collection instance as its argument. Also, Arrays.asList() returns a java.util.List instance. There's no problem because List subtypes Collection. When you consider generics, addAll()'s target type is Collection<? extends String>, and Arrays.asList() returns a List<T> instance. Before Java 8, the compiler often selected Object as the type argument (as in List<Object>), causing code not to compile when a more specific type was required.

Compilers in Java 7 and earlier won't compile this code because they don't use target typing to infer types for method call arguments. For example, the Java 7 compiler outputs the following error message (reformatted for readability):

error: no suitable method found for addAll(List<Object>)
      birds.addAll(Arrays.asList());
           ^
    method Set.addAll(Collection<? extends String>) is not applicable
      (actual argument List<Object> cannot be converted to Collection<? extends String>
      by method invocation conversion)
1 error

In contrast, from target type Collection<? extends String>, the Java 8 compiler can infer that the value of type variable T is String.

In situations where the Java compiler cannot infer types, you must explicitly specify values for type variables. You do so by using type witnesses, as demonstrated here:

birds.addAll(Arrays.<String>asList());

Here, <String> has been introduced as a type witness. This expression will now compile under Java 7.

Type Annotations

Java 8 supports type annotations, which are annotations that can appear anywhere a type is used. Table 3 shows some examples.

Table 3: Examples of New Locations Where Annotations Can Appear

Option

Example

new operator

new @Immutable Planet()

type cast

name = (@NonNull String) getName();

throws clause

void monitorPressure() throws @Critical PressureException { ... }

Oracle created type annotations to improve the analysis of Java programs through stronger type checking. Java 8 doesn't provide a type-checking framework, but you can create one of your own (or even download a framework created by someone else) that's implemented as one or more pluggable modules for use with the Java compiler.

Consider an example where you want to ensure that specific variables are never assigned null references, to avoid thrown NullPointerExceptions. You could write a custom plug-in to check for this possibility, and then modify your code to annotate the variable(s), indicating that they're never assigned null references. The variable declaration might look like the following:

@NonNull String str;

When you compile the code (and include the NonNull module at the command line), the compiler prints a warning when it detects a potential problem, letting you change the code to avoid the error. After you correct your code to remove all warnings, this error won't occur when the program runs.

Java 8 supports type annotations by extending the java.lang.annotation.ElementType enum with two new constants:

  • TYPE_PARAMETER: Type parameter declaration. You can use TYPE_PARAMETER to annotate type variables (such as E in Stack<E>).
  • TYPE_USE: Use of a type. You can use TYPE_USE to annotate any use of a type.

Listing 8 shows how you might declare the NonNull annotation type for a "use of type" context.

Listing 8 NonNull.java

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

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.SOURCE)
public @interface NonNull
{
}

Conclusion

Although this two-part series on the new language features in Java 8 is now finished, there is more to learn. For example, the need for brevity prevented my discussing repeating annotations, which is an annotations enhancement that lets you apply multiple instances of an annotation type to a declaration or type use. To create a repeating annotation, annotate its type with the new @Repeatable meta-annotation. For example, by annotating a hypothetical ToDo annotation type @Repeatable, you can legally specify the following:

@ToDo(task="Add Javadoc.") @ToDo(task="Flesh out method.") public void amortizeLoan() {}

To learn more about the language features in Java SE 8, I recommend checking out Oracle's Enhancements in Java SE 8 documentation.

  • + Share This
  • 🔖 Save To Your Account