Generics

·

7 min read

Generics has been present since Java 5. They help in maintaining static typing by inserting casts and give error at compile time if inconsistent. They however comes at a price and the chapter aims to assist in using them efficiently.

For reference purpose Generic terms used in this post (taken from book) are:

Generics.png

Item 26: Don't use raw types

Generic types: collection of generic classes and interfaces

A parameterized type is an instantiation of a generic type with actual type arguments. A generic type is a reference type that has one or more type parameters. These type parameters are later replaced by type arguments when the generic type is instantiated (or declared).

Reference

Each generic type (List<E>) defines a set of parameterised types (List<String>), which consist of type name (List) followed angle brackets (<>) list of actual type parameters (String) corresponding to generic type's formal type parameters (E).

Each generic type defines a raw type which is the name of the generic type used without any accompanying type parameters. Ex. List is raw type of List<E>. They behave as if the generic type information is erased from type declaration and are for backward compatibility only. If we use raw types, we lose all the safety and expressiveness benefits of generics.

List v/s List<Object>

List has opted out of generic type system. List<Object> is capable to hold any type.

There are subtyping rules for generics, and List<String> is a subtype of the raw type List, but not of the parameterized type List<Object>. This means Generics are invariant, unlike overriding scenario where return types are covariant. The invariant is in line with the Liskov Substitution Principle. List<String> can't do everything what List<Object> can (like store an Integer), thus the former isn't a subtype of latter.

At heart, these terms describe how the subtype relation is affected by type transformations. That is, if A and B are types, f is a type transformation, and the subtype relation (i.e. A ≤ B means that A is a subtype of B), we have:

  • f is covariant if A ≤ B implies that f(A) ≤ f(B)

  • f is contravariant if A ≤ B implies that f(B) ≤ f(A)

  • f is invariant if neither of the above holds

Stackoverflow

List v/s List<?>

If we want to use a generic type but we don’t know or care what the actual type parameter is we can use unbounded wildcard types. It is most general parameterized type capable of holding any type. (Can we add? I think No. Correct me!)

We can’t put any element (other than null) into a Collection<?>, neither we can assume anything about the object that gets out. Thus using ? is type safe unlike raw types.

Raw Types use cases

Yes they are not type safe, are there for backward compatibility but they have a couple of use cases as well:

  1. You must use raw types in class literals. In other words, List.class, String[].class, and int.class are all legal, but List<String>.class and List<?>.class are not.
  2. The preferred way to use the instanceof operator with generic types. Reasons:
    • Type information is erased at run time, using parameterised types is thus illegal
    • Angle brackets(<>) and ? are just noise as it doesn't impact the result
    • After type checking, the object must be cast as an instance of unbounded wildcard type (?) for
      • continued type safety.
      • we don't know the type of objects which would be produced

Item 27: Eliminate unchecked warnings

Below paragraph is taken entirely from the book

Unchecked warnings are important. Don’t ignore them. Every unchecked warning represents the potential for a ClassCastException at runtime. Do your best to eliminate these warnings. If you can’t eliminate an unchecked warning and you can prove that the code that provoked it is typesafe, suppress the warning with a @SuppressWarnings("unchecked") annotation in the narrowest possible scope (even if it means creating an extra variable) otherwise other exceptions may also be ignored. Record the rationale for your decision to suppress the warning in a comment.

Item 28: Prefer lists to arrays

List v:s Arrays.png

Lists v/s Arrays with Generics

Here reified means that the type information persists at runtime. The arrays are reified and enforce them at runtime. Generics, on the other hand, are implemented by erasure for backward compatibility. Therefore, none of these array creation expressions are legal: new List<E>[], new List<String>[], new E[].

Item 29: Favour generic types

Generic types are safer, type safe and easier to use than the code that require casts. Existing code can also be generified. It is not always possible or desirable to use lists inside your generic types. Java doesn’t support lists natively, so some generic types, such as ArrayList, must be implemented atop arrays. Other generic types, such as HashMap, are implemented atop arrays for performance. Read this answer on stackoverflow which explains two possible ways of ArrayList implementation. An excerpt from the same:

That's why most reasonable Java coding conventions require unchecked casts to be type-correct. (E) elementData[i] is type-correct because ArrayList makes sure only Es can be stored in elementData. (E[]) new Object[size] is never type-correct unless E is Object.

where elementData is the internal array of ArrayList.

The thing to keep in mind is that, this whole approach works because the internal array never leaves outside of our control i.e. implementation. The second approach (E[]) new Object[size] is also ignored because it causes heap pollution: the runtime type (type erased/ non-reified) of the array does not match its compile-time type (E) (unless E happens to be Object).

Item 30: Favour generic methods

The type parameter list, which declares the type parameters, goes between a method’s modifiers and its return type.

Recursive type bounds

Enum<E extends Enum<E>> can be decyphered as: Enum is a generic type that can only be instantiated for its subtypes, and those subtypes will inherit some useful methods, some of which take subtype specific arguments (or otherwise depend on the subtype)

Recursive type bounds has variants -> wildcard variant, self-type idiom

Item 31: Use bounded wildcards to increase API flexibility

Generics are invariant, but sometimes we need subtyping rules so that the scope of generics can be widened, which in turn would be beneficial for the users. There is one word for that:

PECS stands for producer-extends, consumer-super

For maximum flexibility, use wildcard types on input parameters that represent producers or consumers.

Iterable<? extends T> choices: Producer of choices, thus use extends
Collection<? super E> filteredObj: Consumes filtered objects, thus super

Do not use bounded wildcard types as return types, else it would force clients to use wildcard in code, sabotaging our motive.
Comparable and Comparators are always consumers, thus super should be used, ex:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)

If a type parameter appears only once in a method declaration, replace it with a wildcard. That may mean creating a private helper with type parameter for simplifying API.

Item 32: Combine generics and varargs judiciously

varargs and generics do not interact well because the varargs facility is a leaky abstraction built atop arrays, and arrays have different type rules from generics. Though generic varargs parameters are not typesafe, they are legal. If you choose to write a method with a generic (or parameterized) varargs parameter, first ensure that the method is typesafe, and then annotate it with @SafeVarargs so it is not unpleasant to use. the SafeVarargs annotation constitutes a promise by the author of a method that it is typesafe.

A generic varargs method is safe (candidate for @SafeVarargs) if:

  1. it doesn’t store anything in the varargs parameter array, and
  2. it doesn’t make the array (or a clone) visible to untrusted code.

We can always use generic types like lists instead, at minor cost of performance.

Item 33: Consider typesafe heterogeneous containers

The normal use of generics, exemplified by the collections APIs, restricts us to a fixed number of type parameters per container. We can get around this restriction by placing the type parameter on the key rather than the container. We can use Class objects as keys for such typesafe heterogeneous containers. A Class object used in this fashion is called a type token. We can also use a custom key type. For example, we could have a DatabaseRow type representing a database row (the container), and a generic type Column as its key. JpaRepository uses an entity (row/value) and primary key (key).

Map<Class<?>, Object>

Above even though the key has unbounded wildcard type (?), we can still put entry in the map. It is because it(?) is nested, Class<?> means every key is parameterized and can take values like Class for String.class, etc. In this we have to take care of 2 things: avoid raw types and it cannot be used for non-reified types as they don't have a Class object. This map can be used for typesafe heterogenous containers.

This is it!