JavaRush /Java Blog /Random EN /Generics for cats
Viacheslav
Level 3

Generics for cats

Published in the Random EN group
Generics for cats - 1

Introduction

Today is a great day to remember what we know about Java. According to the most important document, i.e. Java Language Specification (JLS - Java Language Specifiaction), Java is a strongly typed language, as described in the chapter " Chapter 4. Types, Values, and Variables ". What does this mean? Let's say we have a main method:
public static void main(String[] args) {
String text = "Hello world!";
System.out.println(text);
}
Strong typing ensures that when this code is compiled, the compiler will check that if we specified the type of the text variable as String, then we are not trying to use it anywhere as a variable of another type (for example, as an Integer). For example, if we try to save a value instead of text 2L(i.e. long instead of String), we will get an error at compile time:

Main.java:3: error: incompatible types: long cannot be converted to String
String text = 2L;
Those. Strong typing allows you to ensure that operations on objects are performed only when those operations are legal for those objects. This is also called type safety. As stated in the JLS, there are two categories of types in Java: primitive types and reference types. You can remember about primitive types from the review article: “ Primitive types in Java: They are not so primitive .” Reference types can be represented by a class, interface, or array. And today we will be interested in reference types. And let's start with arrays:
class Main {
  public static void main(String[] args) {
    String[] text = new String[5];
    text[0] = "Hello";
  }
}
This code runs without error. As we know (for example, from " Oracle Java Tutorial: Arrays "), an array is a container that stores data of only one type. In this case - only lines. Let's try adding long to the array instead of String:
text[1] = 4L;
Let's run this code (for example, in Repl.it Online Java Compiler ) and get an error:
error: incompatible types: long cannot be converted to String
The array and the type safety of the language did not allow us to save into an array what did not fit the type. This is a manifestation of type safety. We were told: “Fix the error, but until then I won’t compile the code.” And the most important thing about this is that this happens at the time of compilation, and not when the program is launched. That is, we see errors immediately, and not “someday.” And since we remembered about arrays, let’s also remember about the Java Collections Framework . We had different structures there. For example, lists. Let's rewrite the example:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List text = new ArrayList(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(0);
  }
}
When compiling it, we will receive testan error on the variable initialization line:
incompatible types: Object cannot be converted to String
In our case, List can store any object (i.e. an object of type Object). Therefore, the compiler says that it cannot take on such a burden of responsibility. Therefore, we need to explicitly specify the type that we will get from the list:
String test = (String) text.get(0);
This indication is called type conversion or type casting. And everything will work fine now until we try to get the element at index 1, because it is of type Long. And we will get a fair error, but already while the program is running (in Runtime):

type conversion, typecasting
Exception in thread "main" java.lang.ClassCastException: java.lang.Long cannot be cast to java.lang.String
As we can see, there are several important disadvantages here. Firstly, we are forced to “cast” the value obtained from the list to the String class. Agree, this is ugly. Secondly, in case of an error, we will see it only when the program is executed. If our code were more complex, we might not immediately detect such an error. And developers began to think about how to make work in such situations easier and the code more clear. And they were born - Generics.
Generics for cats - 2

Generics

So, generics. What is it? A generic is a special way of describing the types used, which the code compiler can use in its work to ensure type safety. It looks something like this:
Generics for cats - 3
Here is a short example and explanation:
import java.util.*;
class Main {
  public static void main(String[] args) {
    List<String> text = new ArrayList<String>(5);
    text.add("Hello");
    text.add(4L);
    String test = text.get(1);
  }
}
In this example, we say that we have not just List, but List, which ONLY works with objects of type String. And no others. What is just indicated in brackets, we can store it. Such "brackets" are called "angle brackets", i.e. angle brackets. The compiler will kindly check for us whether we have made any mistakes when working with a list of strings (the list is named text). The compiler will see that we are brazenly trying to put Long into the String list. And at compilation time it will give an error:
error: no suitable method found for add(long)
You may have remembered that String is a descendant of CharSequence. And decide to do something like:
public static void main(String[] args) {
	ArrayList<CharSequence> text = new ArrayList<String>(5);
	text.add("Hello");
	String test = text.get(0);
}
But this is not possible and we will get the error: error: incompatible types: ArrayList<String> cannot be converted to ArrayList<CharSequence> It seems strange, because. the line CharSequence sec = "test";contains no errors. Let's figure it out. They say about this behavior: “Generics are invariant.” What is an "invariant"? I like how it is said about this on Wikipedia in the article “ Covariance and contravariance ”:
Generics for cats - 4
Thus, Invariance is the absence of inheritance between derived types. If Cat is a subtype of Animals, then the Set<Cats> is not a subtype of the Set<Animals> and the Set<Animals> is not a subtype of the Set<Cats>. By the way, it’s worth saying that starting with Java SE 7, the so-called “ Diamond Operator ” appeared. Because the two angle brackets <> are like a diamond. This allows us to use generics like this:
public static void main(String[] args) {
  List<String> lines = new ArrayList<>();
  lines.add("Hello world!");
  System.out.println(lines);
}
Based on this code, the compiler understands that if we indicated on the left side that it Listwill contain objects of type String, then on the right side we mean that we want to linessave a new ArrayList into a variable, which will also store an object of the type specified on the left side. So the compiler from the left side understands or infers the type for the right side. This is why this behavior is called type inference or "Type Inference" in English. Another interesting thing worth noting is RAW Types or “raw types”. Because Generics have not always been around, and Java tries to maintain backward compatibility whenever possible, then generics are forced to somehow work with code where no generic is specified. Let's see an example:
List<CharSequence> lines = new ArrayList<String>();
As we remember, such a line will not compile due to the invariance of generics.
List<Object> lines = new ArrayList<String>();
And this one won’t compile either, for the same reason.
List lines = new ArrayList<String>();
List<String> lines2 = new ArrayList();
Such lines will compile and will work. It is in them that Raw Types are used, i.e. unspecified types. Once again, it is worth pointing out that Raw Types SHOULD NOT be used in modern code.
Generics for cats - 5

Typed classes

So, typed classes. Let's see how we can write our own typed class. For example, we have a class hierarchy:
public static abstract class Animal {
  public abstract void voice();
}

public static class Cat extends Animal {
  public void voice(){
    System.out.println("Meow meow");
  }
}

public static class Dog extends Animal {
  public void voice(){
    System.out.println("Woof woof");
  }
}
We want to create a class that implements an animal container. It would be possible to write a class that would contain any Animal. This is simple, understandable, BUT... mixing dogs and cats is bad, they are not friends with each other. In addition, if someone receives such a container, he may mistakenly throw cats from the container into a pack of dogs... and this will not lead to any good. And here generics will help us. For example, let's write the implementation like this:
public static class Box<T> {
  List<T> slots = new ArrayList<>();
  public List<T> getSlots() {
    return slots;
  }
}
Our class will work with objects of type specified by a generic named T. This is a kind of alias. Because The generic is specified in the class name, then we will receive it when declaring the class:
public static void main(String[] args) {
  Box<Cat> catBox = new Box<>();
  Cat murzik = new Cat();
  catBox.getSlots().add(murzik);
}
As we can see, we indicated that we have Box, which only works with Cat. The compiler realized that catBoxinstead of a generic, Tyou need to substitute the type Catwherever the name of the generic is specified T:
Generics for cats - 6
Those. it is thanks to Box<Cat>the compiler that it understands what slotsit should actually be List<Cat>. For Box<Dog>inside there will be slots, containing List<Dog>. There can be several generics in a type declaration, for example:
public static class Box<T, V> {
The generic name can be anything, although it is recommended to adhere to some unspoken rules - "Type Parameter Naming Conventions": Element type - E, key type - K, number type - N, T - for type, V - for value type. By the way, remember we said that generics are invariant, i.e. do not preserve the inheritance hierarchy. In fact, we can influence this. That is, we have the opportunity to make generics COvariant, i.e. keeping inheritances in the same order. This behavior is called "Bounded Type", i.e. limited types. For example, our class Boxcould contain all animals, then we would declare a generic like this:
public static class Box<T extends Animal> {
That is, we set the upper limit to the class Animal. We can also specify several types after the keyword extends. This will mean that the type we will work with must be a descendant of some class and at the same time implement some interface. For example:
public static class Box<T extends Animal & Comparable> {
In this case, if we try to put Boxsomething into such that is not an inheritor Animaland does not implement Comparable, then during compilation we will receive an error:
error: type argument Cat is not within bounds of type-variable T
Generics for cats - 7

Method Typing

Generics are used not only in types, but also in individual methods. The application of the methods can be seen in the official tutorial: " Generics Methods ".

Background:

Generics for cats - 8
Let's look at this picture. As you can see, the compiler looks at the method signature and sees that we are taking some undefined class as input. It does not determine by signature that we are returning some kind of object, i.e. Object. Therefore, if we want to create, say, an ArrayList, then we need to do this:
ArrayList<String> object = (ArrayList<String>) createObject(ArrayList.class);
You have to explicitly write that the output will be an ArrayList, which is ugly and adds a chance of making a mistake. For example, we can write such nonsense and it will compile:
ArrayList object = (ArrayList) createObject(LinkedList.class);
Can we help the compiler? Yes, generics allow us to do this. Let's look at the same example:
Generics for cats - 9
Then, we can create an object simply like this:
ArrayList<String> object = createObject(ArrayList.class);
Generics for cats - 10

WildCard

According to Oracle's Tutorial on Generics, specifically the " Wildcards " section, we can describe an "unknown type" with a question mark, called a question mark. Wildcard is a handy tool to mitigate some of the limitations of generics. For example, as we discussed earlier, generics are invariant. This means that although all classes are descendants (subtypes) of the Object type, it List<любой тип>is not a subtype List<Object>. BUT, List<любой тип>it is a subtype List<?>. So we can write the following code:
public static void printList(List<?> list) {
  for (Object elem: list) {
    System.out.print(elem + " ");
  }
  System.out.println();
}
Like regular generics (i.e. without the use of wildcards), generics with wildcards can be limited. The Upper bounded wildcard looks familiar:
public static void printCatList(List<? extends Cat> list) {
  for (Cat cat: list) {
    System.out.print(cat + " ");
  }
  System.out.println();
}
But you can also limit it by the Lower bound wildcard:
public static void printCatList(List<? super Cat> list) {
Thus, the method will begin to accept all cats, as well as higher in the hierarchy (up to Object).
Generics for cats - 11

Type Erasure

Speaking about generics, it’s worth knowing about “Type Erasing”. In fact, type erasure is about the fact that generics are information for the compiler. During program execution, there is no more information about generics, this is called “erasing”. This erasure has the effect that the generic type is replaced by the specific type. If the generic did not have a boundary, then the Object type will be substituted. If the border was specified (for example <T extends Comparable>), then it will be substituted. Here's an example from Oracle's Tutorial: " Erasure of Generic Types ":
Generics for cats - 12
As was said above, in this example the generic Tis erased to its border, i.e. before Comparable.
Generics for cats - 13

Conclusion

Generics are a very interesting topic. I hope this topic is of interest to you. To summarize, we can say that generics are an excellent tool that developers have received to prompt the compiler with additional information to ensure type safety on the one hand and flexibility on the other. And if you are interested, then I suggest you check out the resources that I liked: #Viacheslav
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION