Table of contents
- Item 26: Don't use raw types
- Item 27: Eliminate unchecked warnings
- Item 28: Prefer lists to arrays
- Item 29: Favour generic types
- Item 30: Favour generic methods
- Item 31: Use bounded wildcards to increase API flexibility
- Item 32: Combine generics and varargs judiciously
- Item 33: Consider typesafe heterogeneous containers
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:
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).
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
andB
are types,f
is a type transformation, and≤
the subtype relation (i.e.A ≤ B
means thatA
is a subtype ofB
), we have:
f
is covariant ifA ≤ B
implies thatf(A) ≤ f(B)
f
is contravariant ifA ≤ B
implies thatf(B) ≤ f(A)
f
is invariant if neither of the above holds
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:
- You must use raw types in class literals. In other words,
List.class
,String[].class
, andint.class
are all legal, butList<String>.class
andList<?>.class
are not. - 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
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 becauseArrayList
makes sure onlyE
s can be stored inelementData
.(E[]) new Object[size]
is never type-correct unlessE
isObject
.
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)
- Reference (Angelika Langer)
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:
- it doesn’t store anything in the varargs parameter array, and
- 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!