Java Generics

Java generics present many specificities.
Here these I frequently rely on and that I consider as an unavoidable requirement to understand and work with generics.   
I will illustrate these points by  endeavoring to stay as general as possible about the principles. About the code examples, they will rely on the Java generic collections.

Variable assignments and passing arguments with generic classes and collections

1) A method of a generic class that accepts the generic as parameter may accept as argument any instance of the class (and so subclasses too) of the declared generic type.


1-bis) As a consequence, a generic collection variable may accept to add any instance of the class (and subclass) of the declared generic type :

List<Number> numbers = ...;
numbers.add(1L); // Long
numbers.add(1F); // Float

Generics are invariant.

2) Invariant for assignments

A generic collection variable cannot be assigned with a generic collection which the generic type is a subclass of its generic type :

List<Integer> integers = ...;
List<Number> numbers = ...;
numbers = integers; // doesn't compile

Conceptually it is not valid because it would break the type safety according to the point 1.
If it would be legal, the two lists would refer the same object. So changing the state of the first one would be reflected in the state of the second one.
For example, if I added a Float object in a List of Number, which is legal (point 1) and then suppose that it would be valid to assign a List of Integer variable to this List of Number, what would it mean ?
It would mean that the list of Integer variable could refer a List of Number containing a Float. We don’t want that as it defeats the generics purpose : the type safety.
Here is the example code :

List<Integer> integers = ...;
List<Number> numbers = ...;
numbers = integers; // doesn't compile but suppose that it would
numbers.add(1F); // valid
Integer integer = integers.get(0); // inconsistent result at runtime. I expect to have a Integer but actually I retrieve a Float


3) Invariant for parameters

A generic collection parameter cannot be valued with a generic collection which the generic type is a subclass of its generic type.

Similarly to what we have seen in the first point, inside a method we could also change the content of an collection object referenced by a variable parameter where the declared type doesn’t have any explicit bound on the generic type such as void doThat(List<Foo> foos).
So it has the same constraints as previously.
For example, if we could pass a List of Integer as argument to a method that expects to have a List of Number,  it would break the type safety of the List of Integer as the method may add a Float or Long in it. So it is not possible either :

List<Number> numbers = new ArrayList<>();
numbers.add(1);
numbers.add(2F);
List<Integer> integers = new ArrayList<>();
integers.add(1);
 
addNumberAsNewElement(numbers); // valid
addNumberAsNewElement(integers); // doesn't compile to prevent type safety issues
 
...
 
public void addNumberAsNewElement(List<Number> numbers) {
   numbers.add(1F);
}

The conclusions for 2) and 3) : Without specifying a wildcard, the compiler expects that the generic type be exactly the same between the assigned type and the variable target of the assignment or between the parameter of the method and the declared type passed to :

List<Integer> integers = ...;
List<Integer> otherIntegers= ...;
integers = otherIntegers; // valid

and

public int sumInt(List<Number> numbers){..};
...
List<Number> numbers = ...;
int sum = sumInt(numbers); // valid

However the need to  assign/pass as argument a reference of a Collection parameterized with a subtype to a Collection parameterized with a super type is a so common need as generics had to introduce a way to do it : the bounded type parameters.
However, this ability has a constraint : the compiler doesn’t allow to add any type of object in the collection object that is the target of the assignment.

//TO DELETE
In this way, the compiler is ensured that the type safety is respected. We could assign a List of Integer to a List of Number but we could not add anything in the List of number (as for example a Float) so to not break the type of List Of Integer (point 2 and 3).
//TO DELETE

The next points (point 4, 5 and 6) present three ways to accomplish it.

4) Upper bounded wildcard (covariance) to relax the restriction on a generic type.

A generic collection variable can be valued with a generic collection which the declared generic is a subclass of its declared generic (or the same generic) if the generic collection target of the assignment specifies an upper bounded wildcard.
But the use of  an upper bounded wildcard also means that we could not pass any object but null in any invoked method accepting the generic type.
In the case of Collection, it means that you cannot add nothing in the Collection but null.

List<Integer> integers = ...;
List<? extends Number> numbers = ...;
numbers = integers; // valid
List<String> strings = ...;
numbers = strings ; // doesn't compile as bounded wildcard to Number
numbers.add(1L); // doesn't compile as a collection with wildcard generic doesn't allow to add elements in but null

With method parameters using generic collections, it follows the same logic.

public int sumInt(List<? extends Number> numbers){
   ...
    numbers.add(1L); // doesn't compile as a collection with wildcard generic doesn't allow to add elements in but null
}
 
List<Number> numbers = ...;
List<Integer> integers = ...;
List<String> strings = ...;
sumInt(numbers); // valid
sumInt(integers); // valid
sumInt(strings) // not valid

5) Unbounded wildcard (covariance) to relax the restriction on a variable referencing a generic type.
Common use : reading a collection.


If we don’t need to set a specific constraint on the generic of the collection target of the assignment, we could use an unbounded wildcard (?) :

List<Integer> integers = ...;
List<?> anyObjects = ...;
anyObjects = integers; // valid
List<String> strings = ...;
anyObjects = strings ; // valid


But this will have the same limitation as the previous point (upper bounded wildcard). Only null element may be used as parameter in the methods accepting the generic type.

6) Lower bounded wildcard (contravariance) to relax the restriction on a variable referencing a generic type.
Common use : modifying a collection.


A generic collection variable can be valued with a generic collection which the declared generic is  a super type of its declared generic (or the same generic) if the generic collection target of the assignment specifies an lower bounded wildcard.

Similarly to a generic type (point 1),  a variable specifying a lower bounded wildcard may pass as generic any instance of the class specified or of its subclasses in any invoked method accepting the generic type.  

Here a sample code  that illustrates that :

public int sumInt(List<? super Number> numbers) {
    ...
    numbers.add(1L); // valid
    ...
}
 
// Client code 
 
List<? super Number> numbers = ...;
 
// adding in the collection
numbers.add(1); // valid
numbers.add(1L); // valid
 
// variable assignment		
List<Integer> integers = ...;		
numbers = integers; // not valid
 
List<Serializable> serializables = ...;
numbers = serializables; // valid
 
 
// invocation
sumInt(numbers);
sumInt(serializables);
sumInt(integers); // not valid as subtype disallowed

Generics and Arrays : not the same rules.

Assignation/argument passing and adding element rules for generic collections don’t work in the same way for arrays.

7) Generic collections are not reifiable while arrays are. It means that at run time, a compiled List<String> is considered by the JVM as a List instance while a compiled String[] is considered by the JVM as a  array of String instance.
It has multiple consequences.

8) Generics collections have a more limited overloading capacity.
This code compiles fine as String[] and Number[] are considered as two distinct types after compilation :

public void method(String[] array){
...
}
 
public void method(Number[] array){
...
}

But this code doesn’t compile as after compilation and erasure of the generics, the two methods have exactly the same signature. Indeed, List class is used in both.

public void method(List<Number> list){	
...
}
public void method(List<String> list){
...
}

After compilation, the code (if it could compile) could look like that :

public void method(List list){	
...
}
public void method(List list){
...
}

Which is indeed not valid for the compiler.

9) Arrays are covariant while generics collections are invariant.
For generics, the point 2 and 3 illustrate it.
For arrays, things are different.
At compile time, we can indeed assign an array of a type to a array variable declared with the super type of it.
The check of the validity of the elements stored in the array is performed at runtime.

Number[] numberArray = {1, 5F};
Integer[] integerArray = {1};
numberArray = integerArray; // compile fine
numberArray[0] = 5;  // fine at runtime as valid type
numberArray[0] = 5F; // exception at runtime as Integer[] should not store Float.

It is the same behavior for method providing as parameter an array.

Integer[] integerArray = {1};
method(integerArray);
 
public void method(Number[] array){
  array[0] = 1; // fine at runtime
  array[0] = 1F; // // exception at runtime as Integer[] should not store Float.
}

10) Arrays may store primitives and objects while generics can store only objects.

int[] myPrimitiveArray = ... // valid
Integer[] myObjectArray = ... // valid
List<Integer> myObjectList = ...; // valid
List<int> myPrimitiveList = ...; // doesn't compile

It has implications on memory consumption as Integer is stored on more space as its primitive counterpart int.

Erasure





Raw types

12) Using raw types has also consequences in terms of inheritancy.

The superclasses (respectively, superinterfaces) of a raw type are the erasures of the superclasses (superinterfaces) of any of the parameterizations of the generic type.

Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *