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 (
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 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
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
fis a type transformation, and
≤the subtype relation (i.e.
A ≤ Bmeans that
Ais a subtype of
B), we have:
fis covariant if
A ≤ Bimplies that
f(A) ≤ f(B)
fis contravariant if
A ≤ Bimplies that
f(B) ≤ f(A)
fis invariant if neither of the above holds
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,
int.classare all legal, but
- The preferred way to use the
instanceofoperator with generic types. Reasons:
- Type information is erased at run time, using parameterised types is thus illegal
- Angle brackets(
?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 (
- 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
ClassCastExceptionat 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:
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
ArrayListmakes sure only
Es can be stored in
(E) new Object[size]is never type-correct unless
elementData is the internal array of
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 happens to be
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
Collection<? super E> filteredObj: Consumes filtered objects, thus
Do not use bounded wildcard types as return types, else it would force clients to use wildcard in code, sabotaging our motive.
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
- 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).
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!