This blog introduces the definition of Type erasure in Java and details the scenarios in which type erasure occurs in Java.

1. What is type erasure

To give you a quick idea of type erasure, let’s start with a very simple and classic example.

// Specify the generic type as String
List<String> list1 = new ArrayList<>();
// Specify the generic type as Integer
List<Integer> list2 = new ArrayList<>();

// true
System.out.println(list1.getClass() == list2.getClass()); 
Copy the code

The result above is true. Represents that two lists passed in different generics are eventually compiled into arrayLists of the same type, with the original generic arguments String and Integer erased. This is a classic example of type erasure.

And if we want to talk about why type erasure happens, we need to understand generics.

2. The generic

2.1. Definition of generics

Generics were introduced with the release of JDK 1.5, project code Tiger, on September 30, 2004. JDK 1.5 makes significant improvements in the ease of use of Java syntax. In addition to generics, auto-boxing, dynamic annotations, enumerations, variable-length arguments, foreach loops, and more have been added.

Before 1.5, in order to make Java classes universal, the parameter type and return type are usually set to Object. It can be seen that if the type is not needed, it needs to be cast in the corresponding place, so that the program can run normally. It is very troublesome, and a little attention will be wrong.

The nature of generics is parameterized types. That is, a data type is specified as a parameter. What are the benefits of introducing generics?

Generics are a way of bringing forward to compile time errors that were only discovered at runtime before JDK 1.5. That is, generics provide compile-time type-safety checks. For example, if a variable is of type Integer, and we set it to String in our code, if we don’t use generics, we’ll only get an error if our code runs here.

This problem does not arise with the introduction of generics. This is because generics let you know the specified type of the parameter, and then, at compile time, determine whether the type matches the specified type.

There are three ways to use generics, one for classes, one for methods, and one for interfaces.

3. How generics are used

3.1 a generic class

3.1.1 Defining generic classes

A simple generic class can be defined as follows.

public class Generic<T> {
    T data;
    
    public Generic(T data) {
        setData(data);
    }
    
    public T getData(a) {
        return data;
    }
    
    public void setData(T data) {
        this.data = data; }}Copy the code

Where T stands for parameter type, any type. Of course, it doesn’t have to be a T, it’s just a convention. With the generic class above we can use it as follows.

3.1.2 Using generic classes

// Suppose you have a concrete class
public class Hello {
    private Integer id;

    private String name;

    private Integer age;

    private String email;
}

// Use generic classes
Hello hello = new Hello();
Generic<Hello> result = new Generic<>();
resule.setData(hello);

// Get data from generic classes
Hello data = result.getData();
Copy the code

Of course, if the Generic class does not pass in the specified type, a method or a member variable in a Generic class can be of any type. If you print result.getClass(), you will get Generic.

3.2. Generic methods

3.2.1 Define generic methods

First let’s look at generic methods with no return value, which can be defined as the following structure.

// Define a generic method with no return value
public <T> void genericMethod(T field) {
    System.out.println(field.getClass().toString());
}

// Define a generic method with a return value
private <T> T genericWithReturnMethod(T field) {
    System.out.println(field.getClass().toString());
    return field;
}
Copy the code

3.2.2 Calling generic methods

// Call a generic method with no return value
genericMethod("This is string"); // class java.lang.String
genericMethod(56L); // class java.lang.Long

// Call a generic method with a return value
String test = genericWithReturnMethod("TEST"); // TEST class java.lang.String
Copy the code

In a method with a return value, T is the return type of the current function.

3.3. Generic interfaces

The generic interface is defined as follows

public interface genericInterface<T> {}Copy the code

The methods used are similar to those of generic classes, which I won’t repeat here.

4. Generic wildcards

What is a generic wildcard? The official explanation is

Type of unknown.

That is, an unqualified wildcard that can represent any type. There are also three uses, <? >, <? Extends T > and <? Super T >.

Why do we need such an unqualified wildcard when we already have T for any type? Because the main problem it addresses is the problem of generic inheritance.

4.1. Inheritance of generics

Let’s start with an example

List<Integer> integerList = new ArrayList<>();
List<Number> numberList = integerList;
Copy the code

We know that Integer inherits from the Number class.

public final class Integer extends Number implements Comparable {

.Copy the code

}

Will the above code compile? Certainly not. Integer inheriting from Number does not mean that lists inherit from lists. So what are the scenarios for wildcards?

4.2. Application scenarios of wildcards

In other functions, such as JavaScript, the parameters of a function can be of any type without any type conversion, so such functions can be very versatile in some application scenarios.

In Java, a strongly typed language, the parameter types of a function are fixed. So what if you want to implement general-purpose functions like JavaScript in Java? That’s why we need generic wildcards.

Suppose we have many animal classes, such as Dog, Pig, and Cat, and we need a general function to count the total number of legs of all animals in the animal list. How do we do that in Java?

Some might say, well, generics, isn’t that what generics are for? Generics must specify a specific type. Because generics can’t solve the problem… I came up with the wildcard for generics.

4.3. Unbounded wildcards

The unbounded wildcard is what? . And you might look at this and say, isn’t this the same thing as T? Why do you have to have one? . The main difference is that T is mainly used to declare a generic class or method. It is mainly used for using generic classes and generic methods. Here’s a simple example.

// Define a function that prints a list of any type
public static void printList(List
        list) {
    for (Object elem: list) {
        System.out.print(elem + ""); }}// Call the above function
List<Integer> intList = Arrays.asList(1.2.3);
List<String> stringList = Arrays.asList("one"."two"."three");
printList(li);/ / 1 2 3
printList(ls);// one two three
Copy the code

The purpose of the above function is to print a list of any type. You can see that inside the function, you don’t really care what type of generic is in the List, you can put <? It removes the ability to add a specific element and retains only functionality independent of the specific type. As you can see from the example above, it only cares about the number of elements and whether they are empty or not, but nothing else.

Back to T, we also listed how to define generic methods and how to call them. Generic methods are internally concerned with specific types, not just numbers and not null.

4.4. Upper bound Wildcard <? extends T>

Now that? Can represent any type, so what does extends do?

Suppose we have a requirement that we only allow certain types to call our functions (for example, all Animal classes and their derived classes), but currently use? All types can call functions, which is not enough for our needs.

private int countLength(List< ? extends Animal> list) {... }Copy the code

After completing the public function with the upper bound wildcard, you can call it as follows.

List<Pig> pigs = new ArrayList<>();
List<Dog> dogs = new ArrayList<>();
List<Cat> cats = new ArrayList<>();

// Pretend to write data
int sum = 0;
sum += countLength(pigs);
sum += countLength(dogs);
sum += countLength(cats);
Copy the code

After looking at the examples, we can easily draw a conclusion. An upper bound wildcard is a wildcard that can handle any particular type and any derived class of that particular type.

The upper bound wildcard is a box that any animal can put in.

4.5. Lower wildcard <? super Animal>

Above we talked about upper bound wildcards, which restrict unknown types to a particular type or subtypes of that particular type (that is, the animals discussed above and subtypes of all animals). A lower bound wildcard limits an unknown type to a particular type or supertype of that particular type, that is, a superclass or base class.

In the above upper bound wildcard, we gave an example. Writes a function that can handle any animal class and its derived classes. And now we’re going to write a function that handles any function that is Integer and that is a superclass of Integer.

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) { list.add(i); }}Copy the code

5. Type erasure

With a brief look at a few simple ways generics can be used, let’s return to the subject of this blog post – type erasure. While generics have some of the benefits listed above, the life cycle of generics is limited to the compile phase.

The example given at the beginning of this article is a typical example. After compiling, generics will be degenericized. During compiling, generics will be erased after detecting the result of generics. As with the examples mentioned at the beginning of this article, we use the Generic Generic classes defined above to give a simple example.

Generic<String> generic = new Generic<>("Hello");
Field[] fs = generic.getClass().getDeclaredFields();
for (Field f : fs) {
    System.out.println("type: " + f.getType().getName()); // type: java.lang.Object
}
Copy the code

GetDeclaredFields is a reflection method that retrieves various fields declared by the current class, including public, protected, and private.

You can see that the generic String we passed has been erased and replaced with Object. So what happened to the generic information for String and Integer? Do all generic types become objects when erased? Don’t worry, keep reading.

What happens when we use upper bound wildcards on generics? We change the Generic class to the following form.

public class Generic<T extends String> {
    T data;

    public Generic(T data) {
        setData(data);
    }

    public T getData(a) {
        return data;
    }

    public void setData(T data) {
        this.data = data; }}Copy the code

Then use reflection again to see the type after the generic erasure. This time the console will print type: java.lang.string. As you can see, if we limit the generic class, the generic type is erased and replaced with the upper limit of the type. If not specified, Object is replaced uniformly. Accordingly, the same is true of the types of methods defined in generic classes.

6. Write at the end

If you find any problems in the article, please kindly comment and I will correct them in time.

Reference:

  1. Java language type erasure
  2. Lower bound wildcard
  3. List<? > < span style = “box-sizing: border-box! Important

Previous articles:

  • Go Module is used as package manager to build go Web server
  • It’s time to have a command-line tool of your own

Related:

  • Personal website: Lunhao Hu
  • Wechat official account: full stack notes of SH (or directly search wechat LunhaoHu in the interface of adding official account)