As a Java programmer, generics have long been part of everyday programming. Generics have really improved productivity since the introduction of JDK1.5. A simple generic T, with a few lines of code, allows us to dynamically substitute any type we want during use, eliminating the need to implement cumbersome conversion methods.

Although we use it every day, there are many students who may not understand how it works. In today’s post we will talk about Java generics from the following points:

  • Java generic implementation
  • Defects caused by type erasure
  • The history of Java generics

Click “like” to see, form a habit, wechat search “program tongshi”. Click here for more related articles

Java generic implementation

Java implements generics by Type Erasure Generics **. In plain English, this generic type exists only in the source code. When the compiler compiles the source code into bytecode, it “erases” the generic type, so there is no generic type in bytecode.

For the following code, after compilation, we use javap -s class to look at the bytecode.

If you look at the bytecode part of setParam, you can see from the descriptor that the generic T has been erased and replaced with Object.

Ps: Not every generic parameter is an Object when it is erased. If T extends String is used, it will eventually become a String when erased.

In the same way as the getParam method, the generic return value is replaced with Object.

To ensure that String param = genericType.getParam(); If the code is correct, the compiler has to insert a conversion here.

In addition, the compiler defends against generic security. If we add an Integer to ArrayList

, the program will report an error during compilation.

The code after the final type erasure is equivalent to the following:

Defects caused by type erasure

By way of comparison, let’s talk briefly about how C# generics are implemented.

**C#** generics can be used to implement Reifiable generics. If you are not familiar with C# generics, you don’t need to worry about Reifiable generics.

To put it simply, **C#** implementations of generics are real, both in the program source code, after compilation, and even at runtime.

Java generics look like “fake” generics compared to C# generics. Java generics exist only in the program’s source code and are erased after compilation, a defect that has its own problems.

Basic data types are not supported

After the generic parameter is erased, it is forced to be Object. Doing so is fine for reference types, since Object is the parent of all types. But for eight basic data types, such as int/long, this is difficult. Because Java cannot cast int/long and Object.

To achieve this transformation, a series of modifications are required, and the modifications are not trivial. So Java came up with a crude solution: since there was no way to convert, primitive type generics were not supported at all.

If needed, specify a generic for the related wrapper class, such as ArrayList

. In addition, automatic unboxing/boxing of native data types has been added for developer convenience.

It is this “lazy” approach that has resulted in the current inability to use primitive generics and the overhead of packing/unpacking the wrapper classes that has resulted in operational efficiency issues.

Operation efficiency

As we saw in the bytecode example above, generic erasers will change the type to Object. When generics are present at the method input, there is no problem because Java can cast them up.

But when a generic parameter is present at the output (return value) of a method, the method is called where a downward cast is required to cast Object to the desired type, so the compiler inserts a checkcast bytecode.

In addition to this, we also talked about primitive data types above, and the compiler also needs to help us box/unbox them.

So for this code:

List<Integer> list = new ArrayList<Integer>();
list.add(66); / / 1
int num = list.get(0); / / 2
Copy the code

For ①, all the compiler has to do is add boxing to the base type. But for the second step, the compiler first needs to cast Object to Integer, and then the compiler needs to unbox it.

After type erasure, the above code is equivalent to:

List list = new ArrayList();
list.add(Integer.valueOf(66));
int num = ((Integer) list.get(0)).intValue();
Copy the code

If the generic code above were implemented in C#, there wouldn’t be so many extra steps. Therefore, Java’s implementation of type erasure generics is both effective and efficient, and still lags behind C#’s embodied generics.

The actual type of the generic cannot be obtained at run time

Since generics are erased after compilation, the Java virtual machine cannot get the actual type of the generics while the code is running.

In the following code, the two lists appear to be collections of different types from the source code, but after generic erasing, the collections become arrayLists. So the code in the if statement will be executed.

ArrayList<Integer> li = new ArrayList<Integer>();
ArrayList<Float> lf = new ArrayList<Float>();
if (li.getClass() == lf.getClass()) { // Generic erase, both List types are the same
    System.out.println("6666");
}
Copy the code

This makes the code look a little counter-intuitive, which is not very friendly to beginners.

There are also some practical limitations, such as the fact that we cannot directly implement the following code:

As a final example, if we need to implement a method that converts a generic List to an array, we can’t get the actual type of the generic from the List directly, so we have to pass in an additional Class that specifies the type of the array:

public static<E> E[] convert(List<E> list, Class<E> componentType) { E[] array = (E[]) Array.newInstance(componentType, list.size());  . }Copy the code

As we can see from the above examples, Java uses type erasure to implement generics, which has many drawbacks. So why doesn’t Java adopt the generic implementation of C#? Or a better way to do it?

It is only when we understand the history of Java generics and the current state of the Java language at the time that we can begin to understand why Java adopted this approach to generics.

Java generics history background

Java generics were first introduced in JDK5, but the idea of generics first came from C++ templates. In 1996, Martin Odersky (creator of Scala language) extended generics, functional programming and other functions on the basis of just released Java to form a new language – “Pizza”.

Later, the Java core development team became interested in Pizza’s Generic design and partnered with Martin on a new project called Generic Java. The goal of this project is to add generics support to Java without introducing functionality such as functional programming. The final success was the formal introduction of generic support in Java5.

The generics migration process is not initially oriented towards type erasure, in fact generics in Pizza are more similar to generics in C#.

However, due to Java’s own characteristics and strict constraints, Martin had to give up the Generic design in Pizza in the process of Generic Java development.

This feature is that Java requires strict backward compatibility. This means that a Class file compiled in JDK1.2 will not only work in JDK1.2, but must also work in subsequent JDK versions such as JDK12.

This feature is explicitly written into the Java language specification, which is a serious promise to Java users.

It is important to note that backward compatibility refers to binary compatibility, not source compatibility. There is also no guarantee that older Class files will run on older JDK versions.

The difficulty now is that while generics were not supported before Java 1.4.2, the sudden need to support generics in Java5 and make programs compiled before JDK1.4 work in new versions means that restrictions that were not there before cannot suddenly be added.

Here’s an example:

ArrayList arrayList=new ArrayList();
arrayList.add("6666");
arrayList.add(Integer.valueOf(Awesome!));
Copy the code

Before generics, lists could store different types of data, so with generics introduced, this code must work correctly.

In order to ensure that these old Clas files work well after Java5, designers have basically two approaches:

  1. The containers that need to be genericized (mainly the container types) will remain the same and a new set of genericized versions will be added in parallel.
  2. Existing types are genericized in situ without adding any new generic versions of existing types.

If Java had taken the first approach, we might now have two sets of collection types. ArrayList, for example, a set of ordinary Java. Util. ArrayList, a set of possible for Java. The util. Generic. The ArrayList < T >.

With this approach, if you need to use generics in your development, you use the new type directly. In addition, the old code can be run directly in the new JDK without modification.

This seems to be a good solution, but in fact C# uses this solution. But why doesn’t Java use this solution?

This is because C# was only two years old at the time, there wasn’t a lot of old code, and if old code needed to use generics, it could be changed quickly. Java, however, had been released for a decade, and many programs were already running and deployed in production, as you can imagine.

If these applications need to use generics in newer versions of Java, they will need to make a lot of source code changes, which is a development effort you can imagine.

Before Java 5, we already had two sets of collections: Vector/Hashtable and ArrayList/ HashMap. The existence of these two sets of containers has actually caused some inconvenience, and new Java developers have to learn the difference between them.

If new types were introduced for generics at this point, four containers would coexist. Imagine a new developer, faced with four sets of containers, having no idea what to do. If Java is really so implemented, I think there will be more people teasing Java.

So Java took the second route, using type erasers, which only required changes to the Javac compiler, no bytecode changes, no virtual machine changes, and ensured that code without generics in the past could still be run in the new JDK.

The second approach, however, does not necessarily mean that type erasure implementation is necessary. If you had enough time to design it well, you might have come up with a better solution.

The Valhalla project is now the only way to repay the technical debt left behind. The project, which began in 2014, was originally intended to address various shortcomings of existing languages in JDK10. Now we have JDK14, we have only completed a small part of the wooden goal, but we have not solved the core goal, so we can see the difficulty of this change.

conclusion

In this article, we start with the Java generics implementation, followed by a few examples, let you know that there are some shortcomings in the current generics implementation.

Then we take a look at the history of Java generics, from the perspective of Java core developers, to understand why Java generics are so impractical.

Finally, as Java developers, let’s complain less and understand more about the shortcomings of Java today. It will be interesting to see how the Java core developers work out the shortcomings of generics.

Help information

  1. www.zhihu.com/question/38…
  2. www.zhihu.com/question/28…
  3. Hllvm-group.iteye.com/group/topic…
  4. Blog. Zhaojie. Me / 2010/05 / according to…
  5. Blog. Zhaojie. Me / 2010/04 / according to…
  6. En.wikipedia.org/wiki/Generi…
  7. www.zhihu.com/question/34…
  8. www.artima.com/scalazine/a…

Finally (attention, likes, retweets)

This article is based on the story of Java generics from “Inside the Java Virtual Machine (Version 3).”

First of all, thanks to the little brother of China Machine Press for the book.

When I first learned about the release of “Deep Java Virtual Machine (Version 3),” I thought it was just a slight supplement to version 2. When I received the book, I realized I was wrong. The two books, put together, are not of the same magnitude.

Ps: Steal a picture of Why god

The third edition adds a large number of supplements on the basis of the second edition, and also solves some unexplained problems left by the second edition. So if you haven’t bought it, I recommend you buy the third edition directly.

For details of the differences between the two versions, you can see Why god’s article, which was awarded by the author of this book.

Compare the two versions of the Java Virtual Machine in depth

I am the dark brother downstairs, a not bald programmer monkey, we will see you next Wednesday ~

Welcome to pay attention to my public account: procedures to get daily dry goods push. If you are interested in my topics, you can also follow my blog: studyidea.cn