Java Generics — Advanced Cases
Tags: java, programming
Published: 2021-09-20 17:57:02

Today we are going to discuss how to implement generics in your code in a most effective way.

## Don't Use Raw Types
This statement seems obvious. Raw types break the whole idea of generics. Its usage doesn't allow the compiler to detect type errors. But that’s not the only problem. Suppose we have such class.
```java
class Container<T> {
    private final T value;
    ...
    public List<Integer> getNumbers() {
        return numbersList;
    }
}
```
Assume that we don’t care about the generic type itself. All we need to do is to traverse `numbersList`.
```java
public void traverseNumbersList(Container container) {
    for (int num : container.getNumbers()) {
        System.out.println(num);
    }
}
```
Surprisingly this code doesn’t compile.
```
error: incompatible types: Object cannot be converted to int
        for (int num : container.getNumbers()) {
                                           ^
```
The thing is that the raw type's usage erases not only the information about the generic type of a class but even predefined ones. So, `List<Integer>` becomes just List.

What can we do about it? The answer is straightforward. If you don’t care about the generic type, use the *wildcard operator*.

```java
public void traverseNumbersList(Container<?> container) {
    for (int num : container.getNumbers()) {
        System.out.println(num);
    }
}
```
This code snippet works perfectly fine.

## Prefer Wildcard-Based Inputs

The main difference between arrays and generics is that arrays are *[covariant](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science))* while generics are not. It means that `Number[]` is a supertype for `Integer[]`. And `Object[]` is a supertype for any array (except primitive ones). That seems logical, but it may lead to bugs at runtime.

```java
Number[] nums = new Long[3];
nums[0] = 1L;
nums[1] = 2L;
nums[2] = 3L;
Object[] objs = nums;
objs[2] = "ArrayStoreException happens here";
```
This code does compile but it throws an unexpected exception. Generics were brought to solve this problem.
```java
List<Number> nums = new ArrayList<Number>();
List<Long> longs = new ArrayList<Long>();
nums = longs;   // compilation error
```
`List<Long>` cannot be assigned to `List<Number>`. Though it helps us to avoid `ArrayStoreException`, it also puts bounds that can make API not flexible and too strict.
```java
interface Employee {
    Money getSalary();
}

interface EmployeeService {
    Money calculateAvgSalary(Collection<Employee> employees);
}
```
Everything looks good, isn’t it? We have even put `Collection` providently as an input parameter. That allows us to pass `List`, `Set`, `Queue`, etc. But don’t forget that `Employee` is just an interface. What if we worked with collections of particular implementations? For example, `List<Manager>` or `Set<Accountant>`? We couldn't pass them directly. So, it would require to shift the elements to the collection of `Employee` type each time.

Or we can use the wildcard operator.

```java
interface EmployeeService {
    Money calculateAvgSalary(Collection<? extends Employee> employees);
}

List<Manager> managers = ...;
Set<Accountant> accountants = ...;
Collection<SoftwareEngineer> engineers = ...;

// All these examples compile successfully
employeeService.calculateAvgSalary(managers);
employeeService.calculateAvgSalary(accountants);
employeeService.calculateAvgSalary(engineers);
```

As you can see, the proper generic usage makes the life of a programmer much easier. Let’s see another example.

Suppose we need to declare an API for the sorting service. Here is the first naive attempt.

```java
interface SortingService {
    <T> void sort(List<T> list, Comparator<T> comparator); 
}
```

Now we’ve got a different kind of a problem. We have to be sure that `Comparator` was created exactly for the `T` type. But that’s not always true. We could build a universal one for `Employee` which wouldn't work for either `Accountant` or `Manager` in this case.

Let’s make the API a bit better.

```java
interface SortingService {
    <T> void sort(List<T> list, Comparator<? super T> comparator); 
}

// universal comparator
Comparator<Employee> comparator = ...;

List<Manager> managers = ...;
List<Accountant> accountants = ...;
List<SoftwareEngineer> engineers = ...;

// All these examples compile successfullly
sortingService.sort(managers, comparator);
sortingService.sort(accountants, comparator);
sortingService.sort(engineers, comparator);
```

You know, the constraints are a little bit confusing. All these `? extends T` and `? super T` seems overcomplicated. Thankfully there is an easy rule that can help to identify the correct usage — [PECS](https://howtodoinjava.com/java/generics/java-generics-what-is-pecs-producer-extends-consumer-super/) (producer-extends, consumer-super). It means that a producer should be of type `? extends T` while consumer of `? super T` one.

Let’s take a look at particular examples. The method `MoneyService.calculateAvgSalary` that we described earlier accepts a producer. Because the collection *produces* elements that are used for further computations.

Another example comes right from the JDK standard library. I’m talking about `Collection.addAll` method.

```java
interface Collection<E> {
    boolean addAll(Collection<? extends E> c);
    ...
}
```

Defining upper bound generic allows us to concatenate `Collection<Employee`> and `Collection<Manager>` or any other classes that share the same interface.

What about consumers? `Comparator` that we used in `SortingService` is a perfect example. This interface has one method that accepts a generic type and returns a concrete one. A typical example of a consumer. Other ones are `Predicate`, `Consumer`, `Comparable`, and many others from `java.util` package. Mostly all of these interfaces should be used with `? super T` bound.

There is also a unique one that is a producer and a consumer at the same time. It’s `java.util.Function`. It converts the input value from one type to another. So, the commonFunction usage is `Function<? super T, ? extends R>`. That may look scary but it really helps to build robust software. You can find out that all mapping functions in `Stream` interface follow this rule.

```java
interface Stream<T> extends BaseStream<T, Stream<T>> {
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    ...
}
```

> One may notice that `SortingService.sort` accepts `List<T>` instead 
> of `List<? extends T>`. Why is it so? This is a producer after 
> all. Well, the thing is that upper and lowerbounds make sense in 
> comparing to the predefined type. Because `SortingService.sort`
> method parameterizes itself, there is no sense to restrict `List` 
> with additional bounds. On the other hand, if `SortingService` had a generic type, `? extends T` would have its value.

```java
interface SortingService<T> {
    void sort(List<? extends T> list, Comparator<? super T> comparator); 
}
```

## Don't Return Bounded Containers

### Upper Bounds

Some developers that discovered the power of bounded generic types may consider that it’s a silver bullet. That can lead to the code snippets like the next one.

```java
interface EmployeeRepository {
    List<? extends Employee> findEmployeesByNameLike(String nameLike);
}
```

What’s wrong here? Firstly, `List<? extends Employee>` cannot be assigned to `List<Employee>` without casting. More than that, this upper bound puts restrictions which are not obvious.

For example, values of what type can we put inside the collection returned by `EmployeeRepository.findEmployeesByNameLike(String)`? You may suggest that it’s something like `Accountant`, `Manager`, `SoftwareEngineer`, and so on. But it’s a wrong assumption.

```java
List<? extends Employee> employees = 
employeeRepository.findEmployeesByNameLike(nameLike);

employees.add(new Accountant());        // compile error
employees.add(new SoftwareEngineer());  // compile error
employees.add(new Manager());           // compile error
employees.add(null);                    // passes successfully 👍
```

This code snippet looks counter-intuitive but in reality, everything works just fine. Let’s deconstruct this case. First of all, we need to determine what collections can be assigned to `List<? extends Employee>`.

```java
List<? extends Employee> accountants = new ArrayList<Accountant>();
List<? extends Employee> managers    = new ArrayList<Manager>();
List<? extends Employee> engineers   = new ArrayList<SoftwareEngineer>();
// ...any other type that extends from Employee 
```

So, basically list of any type that inherits from `Employee` can be assigned to `List<? extends Employee>`. This makes adding new items tricky. The compiler cannot be aware of the exact type of the list. That’s why it forbids to add any items in order to eliminate potential heap pollution. But `null` is a special case. This value does not have its own type. It can be assigned to anything (except primitives). It is the reason why `null` is the only allowed value to add.

What about retrieving items from the list?

```java
List<? extends Employee> employees = ...;

// passes successfully 👍
for (Employee e : employees) {
    System.out.println(e);
}
```

`Emloyee` is a supertype for any potential element the list may contain. No caveats here.

### Lower Bounds
What element can we add to `List<? super Employee>`? The logic tells us that it's either `Object` or `Employee`. And it fools us again.

```java
List<? super Employee> employees = ...;

employees.add(new Accountant());       // passes successfully 👍
employees.add(new Manager());          // passes successfully 👍
employees.add(new SoftwareEngineer()); // passes successfully 👍
employees.add(
    new Employee(){/*implementation*/} // passes successfully 👍
);     
employees.add(new Object());           // compile error
```

Again, to figure out this case let’s find out what collections can be assigned to `List<? super Employee>`.

```java
List<? super Employee> employees = new ArrayList<Employee>();
List<? super Employee> objects   = new ArrayList<Object>();
```

The compiler knows that the list can consist of either `Object` types or `Employee` ones. That’s why `Accountant`, `Manager`, `SoftwareEngineer`, and `Employee` can be safely added. They all implement `Employee` interface and inherits from `Object` class. At the same time, `Object` cannot be added because it does not implement `Employee`.

On the contrary, reading from `List<? super Employee>` is not so easy.

```java
List<? super Employee> employees = ...;

// compile error
for (Employee e : employees) {
    System.out.println(e);
}
```

The compiler cannot be sure that returned item is of `Employee` type. Perhaps it is an `Object`. That’s why the code does not compile.

## Upper-Lower Bounds Conclusion

We can resume that upper bound make a collection *read-only* while lower bound make it *write-only*. Does it mean that we can use them as return types in order to restrict client’s access to data manipulation? I wouldn’t recommend to do it.

Upper bound collections are not *completely* read-only because you can still add `null` to them. Lower bound collections are not *completely* write-only because you can still read values as an `Object`. I consider that it’s much better to use special containers that shall give the required access to an instance. You can either apply standard JDK utilities like `Collections.unmodifiableList` or use libraries that will do the job ([Vavr](https://docs.vavr.io/#_list), for instance).

Upper and lower bound collections act much better as input parameters. You should not mix them with return types.

## Recursive Generics
We’ve already mentioned recursive generics in this article. It’s the `Stream` interface. Let’s take a look again.

```java
interface Stream<T> extends BaseStream<T, Stream<T>> {
    <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
    IntStream flatMapToInt(Function<? super T, ? extends IntStream> mapper);
    ...
}
```

As you can see, `Stream` extends from `BaseStream` that is parameterized with `Stream` itself. What’s the reason for it? Let’s dive into `BaseStream` to find out.

```java
public interface BaseStream<T, S extends BaseStream<T, S>> extends AutoCloseable {  
    S sequential();
    S parallel();
    S unordered();
    S onClose(Runnable closeHandler);
    Iterator<T> iterator();
    Spliterator<T> spliterator();
    boolean isParallel();
    void close();
}
```

`BaseStream` is a typical example of the [fluent API](https://en.wikipedia.org/wiki/Fluent_interface#:~:text=In%20software%20engineering%2C%20a%20fluent,Eric%20Evans%20and%20Martin%20Fowler.) but instead of returning the type itself methods return `S extends BaseStream<T, S>`. Let’s imagine that `BaseStream` was designed without this feature.

```java
public interface BaseStream<T> extends AutoCloseable {  
    BaseStream<T> sequential();
    BaseStream<T> parallel();
    BaseStream<T> unordered();
    BaseStream<T> onClose(Runnable closeHandler);
    Iterator<T> iterator();
    Spliterator<T> spliterator();
    boolean isParallel();
    void close();
}
```

How would it affect the whole Stream API?

```java
List<Employee> employees = ...;
employees.stream()
         .map(Employee::getSalary)
         .parallel()
         .reduce(0L, (acc, next) -> acc + next);
          // compile error ⛔: cannot find symbol
```

The `reduce` method belongs to `Stream` but not to `BaseStream` interface. Therefore `parallel` method returns `BaseStream`. So, `reduce` cannot be found. This becomes clearer in the schema below.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qvm65k3ghwpxyqdg0rbj.png)

Recursive generics come in handy in this situation.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u53tn6sewb4mkdloxw8r.png)

This approach allows us to segregate interfaces that leads to better maintainability and readability.

## Conclusion
I hope that you’ve learned something new about Java generics. If you have any questions or suggestions, please leave your comments down below. Thanks for reading!

## Resources
* [Covariance](https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science))
* [PECS](https://howtodoinjava.com/java/generics/java-generics-what-is-pecs-producer-extends-consumer-super/)
* [Vavr library](https://docs.vavr.io/#_list)