Generics - [OOP & Java #5]
Tags: computerscience, java, oop
Published: 2020-09-18 14:49:52

A slightly more difficult concept, but it links fundamental ideas in previous articles together and presents a neater image of type awareness in Java.

---
In my previous article on [Array](https://dev.to/tlylt/python-array-vs-java-array-31ee), I talked about this need to put things into collections. In Python, a list stores integers, strings, etc. In Java, a primitive array stores a certain type of object based on the type declaration.

```java
int[] arrayOfInts = new int[2];
String[] arrayOfStrings = new String[2];
```

Back then, I said that in Python you seem to have this freedom to put integers and strings into the same container. There is another degree of freedom that I missed out. While keeping the type of content within a container equal, in Python you can first create a "general" list and then decide whether it is used as a list of integers or a list of strings. In Java, a primitive array is created with a type declaration and will not change afterward. This is why we need Generics.  

#### The Evolution
Suppose you have a drawer. A drawer can contain many things. Being a disorganized person, you don't want to label the drawers, making them specific to only certain objects (a drawer for clothes, another for files, etc). In Java, since you have to state a type in compile-time, you may go for the following approach:
> A drawer can contain an Object.
```java
class Drawer {
  Object obj;
  Drawer(Object obj) {
    this.obj = obj;
  }

  Object get() {
    return this.obj;
  }
}
```
Now that you can create a drawer, you may realize that the compiler does not seem to care about what you put into a drawer:
```java
// store a shirt into the drawer
Drawer drawer = new Drawer("shirt");
// store integers into the drawer
Drawer drawer = new Drawer(123);

drawer.get() // returns an "Object"
```
How do I get something out of the drawer? Now anything stored into the drawer will be of type `Object`. Whenever you get something out of the drawer, you have to be responsible for type conversion.  
In Java, the compile-time type determines what your objects can do, at least before runtime. This means that any method calls to the object taken out of the drawer has to be compatible with the type. If the item is of type `Object`, then the compiler only knows that it is safe to do `Object` level things such as `toString()`.
```java
Drawer drawer = new Drawer("shirt");
Drawer drawer = new Drawer(123);

// ERROR: incompatible types: .. Object cannot be converted to String
String shirt = drawer.get();

// Error: ClassCastException Integer cannot be String
String shirt = (String)drawer.get();

Drawer drawer = new Drawer("shirt");
// SAFE: type cast Object to String before assignment
String shirt = (String)drawer.get();
```
The above illustrates two issues.
1. You have to typecast the item taken out of the drawer into the correct type for assignment/chaining other method calls.
2. Due to point 1, calling methods without typecasting may lead to `cannot find symbol` error. This is the compiler saying "I didn't know this method is available for this type of object!"

```java
// ERROR: what I get out is an Object, which does not
// have a 'length()' method
new Drawer("string").get().length()

// ERROR: order of casting is wrong
(String)(new Drawer("string").get()).length()

// SAFE
((String)new Drawer("string").get()).length()
```
This approach is clumsy, error-prone, and requires purposeful typecasting every time you take something out of the drawer.

#### Birth of Generics
To address the issues and requirements I mentioned in the above example, namely:
- A drawer should be flexible enough to turn into a container for clothes, files, etc without defining similar classes that only differ in type. 
- A drawer has to allow objects to be taken out and retain their type to avoid compulsory typecasting.
- With all the convenience, a drawer should still make full use of the compiler to do type checking whenever possible.

Which lead us to the following ideas:
- Don't state what type of object is going into a drawer during "manufacturing".
- Have the freedom of a template system. Put in placeholders and during actual use, replace placeholders with real values.

---
With generics,
- Include type parameters in class/interface/method definitions.
- The "placeholder" type parameters are in different scope according to where you put them, such as class/method body respectively.
- Later on, you will replace the type parameters with concrete type arguments, hence a display of polymorphism.

>Terminology: type parameters and type arguments
```java
// the "T" and "U" below are type parameters
// generic class declaration
class Car<T> {}
// generic method declaration
public static <U> void doThis(U item) {}

// The "String" below is a type argument
new Car<String>();
```
Drawer redefined with Generics
```java
class Drawer<T> {
  T obj;
  Drawer(T obj) {
    this.obj = obj;
  }
  T get() {
    return this.obj;
  }
}
```
In the above class definition, we have `T` within the `<>` to mean that we are defining a "placeholder". The letter "T" is trivial, you can change it to other strings such as "U", "O" etc. In this sense, you can treat "T" as a variable, it will store a type that you are going to specify later on. Note that you can have multiple placeholders. 

##### <center>Typical Type Parameter Names in Java</center>
| Name | Meaning |
|------|---------|
| E    | Element |
| K    | Key     |
| V    | Value   |
| N    | Number  |
| T    | Type    |

Now, the retrieval of objects is easy.
```java
// PREVIOUSLY
Drawer drawer = new Drawer("string");
((String)drawer.get()).length()

// CURRENT
Drawer<String> drawer = new Drawer<String>("string");
drawer.get().length()
```

Following the ideas of type parameters on class definitions, we can do the same for methods.
```java
// type parameter | return type | method name | method arguments
<U> Drawer<U> customDrawer(U obj) {
  return new Drawer<U>(obj);
}
```
For generic methods, the type parameter is scoped to the method itself. If this method is within a generic class, the method's type parameter has nothing to do with the class's type parameters.

We can place the generic method into the generic class as well. Again, note that the two type parameters are in different scope.
```java
class Drawer<T> {
  T obj;
  Drawer(T obj) {
    this.obj = obj;
  }
  T get() {
    return this.obj;
  }
  static <U> Drawer<U> customDrawer(U u) {
    return new Drawer<U>(u);
  }
}

// calling the static method like this
Drawer<Integer> drawer = Drawer.customDrawer(123);
drawer.get() // outputs 123
```

#### Auto-boxing and unboxing
Usually, the discussion on how Java automatically processes primitive data types into reference types is introduced early in a Java course. Generics revisit this concept:
- Generics only accept reference types as type arguments.
```java
// ERROR, does not accept primitive types
Drawer<int> drawer = new Drawer<int>(123);
```
- Generics support standard auto-boxing & unboxing behavior between primitive and reference data types
```java
// auto-boxing
Drawer<Interger> drawer = new Drawer<Integer>(123);

// retrieve the Integer within the drawer 
Integer x = drawer.get();

// auto-unboxing
int x = drawer.get();
```

---
#### Variance of types

|                      | Covariance | Contravariant | Invariant                  |
|----------------------|------------|---------------|----------------------------|
| Subtype relationship | Preserved  | Reversed      | Neither preserved nor reversed |

Don't be afraid of these technical words, let's go through some examples.

**Covariance**
> A subclass relationship can be extended to complex data types such as Array.

```java
// since Integer is a subtype/subclass of Object
Object o = new Integer(123);
// this relationship is preserved in Java arrays
Object[] arr = new int[1];
```
**Contravariant** 
> The opposite of Covariance. Will be addressed in details when we discuss wildcards in the following article :joy:

**Invariant**
```java
// ERROR
Drawer<Object> drawer = new Drawer<Integer>(123);

// SAFE
Drawer<Integer> drawer = new Drawer<Integer>(123);
```
Generics are invariant. Hence the type arguments substituted in the left-hand side and the right-hand side of the equal sign must always be the same. Thus, we can leave out the right-hand side `Integer`.
```java
Drawer<Integer> drawer = new Drawer<>(123);
```
Java will be able to infer the correct type.

---
Next up: Wildcards :dash:
###### P.S. Written with reference to NUS CS2030S Lecture on Generics