preface

Generics in Java are often called pseudo-generics because the actual Type parameters are Type Erasure when the actual code is run. So what led to this compromise in Java? Here I will take you to the Java language designer’s point of view, lead you to understand the bitter past here.

What is a true generic

Before we look at Java “pseudo-generics”, let’s briefly discuss the difference between “real generics” and “pseudo-generics”.

  • True generics: Types in generics are real.
  • Pseudo-generics: Type checking only at compile time, erasing type information at run time.

The general idea that programming languages implement “true generics”

Most programming languages introduce generics through compile-time bloat.

Take the implementation of “true generics” in Java language as an example. First, special encoding is used for generic types (generic classes, generic interfaces) and generic method names. For example, Factory

class is generated into a class named Factory @@t. To determine whether it is generic or not. A method where placeholder T is used can be temporarily generated as an Object with a special Annotation to let the Java compiler know that it is a placeholder. Then, if the use of Factory

is found at compile time, copy all the logic of “Factory @@t”, create a new “Factory@String@” class, and replace the original placeholder T with String. Then generate new Factory@String@() when compiling new Factory

().


The term Chinese meaning For example,
Parameterized type Parameterized type List<String>
Actual type parameter Actual type parameter String
Formal type parameter Formal type parameter E

For example, Factory

and Factory

generate the following code:

// The code before the replacement
Factory<String>() f1 = new Factory<String>();
Factory<Integer>() f2 = new Factory<Integer>();

// Replace the code
Factory<String>() f1 = new Factory@String@()
Factory<Integer>() f2 = new Factory@Integer@ ();Copy the code
  • Among themFactory@String@In the classTHas been replaced with String.
  • Among themFactory@Integer@In the classTHas been replaced with Integer.

This is because generic types with different actual type parameters are replaced with different classes, and the types in the generic types are validated. So we can do this in our program:

class Factory<T> {
    T data;
    public static void f(Object arg){
        if(arg instanceofT){... }//success
        T var = new T();//success
        T[] array = new T[10];//success}}Copy the code

Problems with Java’s direct use of “true generics”

Technically, Java is 100% capable of what we call “true generics.” To implement true generics, Java must deal with two things:

  • Modify the JVM source code so that the JVM can correctly read and verify generics information (previously Java did not have the concept of generics, so need to change).
  • To be compatible with older programs, you need to add a set of generic apis (mainly container types) that do not support generics in parallel.

ArrayList, for example, has to do this:

  • Java.util. ArrayList 👈 Old Java version
  • java.util.generic.ArrayList<T>👈 Java5 added the generic version

Even when all of the above is done, Java cannot adopt this solution. Consider the following:

Generics were introduced in Java 5

If I have A Java 5 project and A third-party B1 lib, where A project references an ArrayList in B1 lib, with the Java upgrade, the developer of B1 Lib in order to use the new Java feature – generics, So if the code is migrated to Java 5 and reborn as B2 Lib, then the A project must be upgraded to Java 5 and the code changed at the same time to be compatible with B2 Lib.

Why does the A project have to upgrade Java 5?

In Java does not support the high version of the Java compiler generates a class file to run on a low version of the JRE, if you try to do so, will get UnsupportedClassVersionError error. As shown below:

Therefore, to adapt to B2 Lib, project A must upgrade Java to Java 5.

Now let’s go back and think about how many open source frameworks are out there and how many projects have forced developers to change their code in order to introduce generics. In this case, class. Imagine for yourself. There are probably tens of thousands of developers with knives in their hands, blocking the door of Java language architects.

Mandatory type erasure

In the previous section, we explored the practical reasons why Java cannot directly introduce “true generics.” Because of the introduction of “true generics,” it was inevitable to add a generic API in parallel to an API that did not support generics. And the addition of API, for Java developers, must do migration.

So what else can developers do to smooth the transition to Java 5 and use the new features of generics?

Yes, yes. Java if you want to get rid of the migration problems of new versions of users. Java needs to do two things:

  • Instead of adding a generic API, you generalize existing types in situ.
  • Handles type compatibility before and after generalization.

Let’s discuss the purpose and reasons for doing these two things.

The first thing to do is to ensure that developers do not make changes to old code because of Java upgrades. In the case of ArrayList, changes are made directly under the original package (java.util), which looks like this:

/ / 👇 Java old version
class ArrayList{}

👇Java5 generic version
class ArrayList<T>{}
Copy the code

The second thing I want to do is to use our previous example. In project A, project A references the ArrayList in B1 lib (using the list variable), so if project A upgrades to Java 5, it still references the B1 lib. Then the following situation must occur:

In the following code, the A project passes the generalized ArrayList

to the ArrayList in B1 lib.

ArrayList list = new ArrayList<String>();
Copy the code
  • Left: older version ArrayList in B1 Lib
  • Right: new generic version in project AArrayList<T>

When this happens, it leads to a problem. ArrayList

= ArrayList

= ArrayList

= ArrayList

= ArrayList

= ArrayList





If we follow the “true generics” approach to Java generics, then new ArrayList

() will actually be replaced with new ArrayList@String@(), and the actual code to run looks like this:

ArrayList list = new ArrayList@String@ ()Copy the code

From the point of view of the code logic, it doesn’t work. Because ArrayList is not in the same class as ArrayList@String@, so what?

The most straightforward solution is to stop creating new classes for parameterized types and replace all the type parameters in the generic type with Object at compile time. (Since no new classes are created, the type T in the generic class can only be replaced with Object.)

In the actual implementation of generics in Java, the choice of whether to replace a boundary or Object is based on whether a type parameter in a generic type has a boundary.

Here’s an example:

In the code above, you declare a generic type Node

, which is actually Node after the compiler replaces it. And that’s it:

 // Compiler code
 Node node = new Node<String>();
 // The compiled code
 Node node = new Node();
Copy the code

Through the magic of the compiler, Java solves the problem of dealing with older versions of generics.

conclusion

By now, I’m sure you understand why generics in Java erase type information. Although Java’s “pseudo-generics” have long been stigmatised by other programming languages, it is still respectable and acceptable to be compatible with older versions.

Whatever Java does, it always does more good than bad. Here’s a video for you. After watching this video, you will know how important Java is to the whole world.

  • What if Java were suddenly banned all over the world

The last

Standing on the shoulders of giants, can see further ~

  • The internals of generics: type erasure and the problems associated with type erasure
  • Kotlin generics
  • Type Erasure
  • State of the Specialization
  • What is the reason why Java can’t implement true generics?
  • Java generics official documentation
  • Why isn’t C# available on the JVM? Brief introduction to Type Erasure
  • Think in Java
  • “Effective Java”