1. What exactly is a generic?
Before discussing type inference, we must review what is Generic. Generics are new features of Java SE 1.5. The essence of generics is a parameterized type, that is, the data type operated is specified as a parameter. In layman's terms, it would be "type variables". This type of variable can be used in the creation of classes, interfaces, and methods. The easiest way to understand Java generics is to regard it as a convenient syntax that can save you some operations on Java type casting :
List<Apple> box = new ArrayList<Apple>();box.add(new Apple());Apple apple =box.get(0);
The above code itself has clearly expressed: box is a List with Apple objects. The get method returns an Apple object instance, and this process does not require type conversion. There are no generics, the above code needs to be written like this:
Apple apple = (Apple)box.get(0);
Of course, generics are not as simple as what I described here, but this is not the protagonist of our day. Students who don’t understand generics very well need to make up for the lessons~ Of course, the best reference materials are still the official documents.
2. Problems caused by generics (before Java 7)
The biggest advantage of generics is that they provide the type safety of the program and can be backward compatible. However, there are also things that make developers unhappy. The type of generics must be written every time they define. This display specification not only feels a bit verbose, but most importantly, many programmers are not familiar with generics, so they cannot give correct type parameters in many cases. Now, the compiler automatically infers the parameter types of generics, which can reduce this situation and improve code readability.
3. Improvements in type derivation of generics in Java 7
Using generic types in previous versions of Java 7 requires adding generic types on both sides when declaring and assigning values. For example:
Map<String,Integer> map = new HashMap<String,Integer>();
Many people must have been the same as me at the beginning, and they were puzzled by this: Didn’t I declare the parameter type in the variable declaration? Why do I still need to write it out when the object is initialized? This is also what makes generics complain about when they first appeared. However, it is gratifying that while Java is improving, designers are also constantly improving Java compiler to make it more intelligent and humanized. Here is our protagonist today: type pushdown...well... it's not type derivation, that is, type inference. When this guy appears, when he writes the above code, he can happily omit the parameter types when object instantiation is instantiated, and it becomes like this:
Map<String,Integer> map = new HashMap<>();
In this statement, the compiler will automatically infer the generic type when instantiating HashMap based on the generic type when variable declaration. Again, please be sure to pay attention to the "<>" behind new HashMap . Only by adding this "<>" means that it is automatic type inference, otherwise it is a non-generic HashMap , and a warning prompt will be given when compiling the source code using the compiler. This pair of angle brackets "<>" is called "diamond" in the official document.
However, the type derivation at this time is not complete (even a semi-finished product), because type inference when creating generic instances in Java SE 7 is limited: only if the parameterized type of the constructor is significantly declared in the context, type inference can be used, otherwise it will not work. For example: The following example cannot be compiled correctly in Java 7 (but it can be compiled in Java 8 now, because the generic type is automatically inferred based on method parameters):
List<String> list = new ArrayList<>();list.add("A");// Since addAll expects to obtain parameters of type Collection<? extends String>, the following statement cannot pass list.addAll(new ArrayList<>());4. Reevolution in Java 8
In the latest official Java documentation, we can see the definition of type derivation:
Type inference is a Java compiler's ability to look at each method invocation and corresponding declaration to determine the type argument (or arguments) that makes the invocation applicable. The inference algorithm determines the types of the arguments and, if available, the type that the result is being assigned, or returned. Finally, the inference algorithm tries to find the most specific type that works with all of the arguments.
In short, type derivation refers to the ability of the compiler to determine the required parameter types based on the method you call and the corresponding declaration. And an example is also given in the official documentation to explain:
static <T> T pick(T a1, T a2) { return a2; }Serializable s = pick("d", new ArrayList<String>()); Here, the compiler can deduce that the type of the second parameter passed in pick method is Serializable .
In previous Java versions, if the above example is able to be compiled, you need to write this:
Serializable s = this.<Serializable>pick("d", new ArrayList<String>());The detailed reason for writing this can be seen in the generic chapter of Bruce Eckel's Java Programming Thought (Fourth Edition). Of course, this book is based on Java 6, and this version has no concept of type derivation. Seeing this, many people can clearly see the power of type derivation in the latest version. It is no longer limited to the declaration and instantiation process of generic classes, but is extended to methods with generic parameters.
4.1 Type Inference and Generic Methods
Regarding the type derivation and generic methods in the new version, the document also gives a slightly more complex example. I posted it here. The principle is the same as the Serializable example above, so I won't go into details. If you want to consolidate it, you can take a look:
public class BoxDemo { public static <U> void addBox(U u, java.util.List<Box<U>> boxes) { Box<U> box = new Box<>(); box.set(u); boxes.add(box); } public static <U> void outputBoxes(java.util.List<Box<U>> boxes) { int counter = 0; for (Box<U> box: boxes) { U boxContents = box.get(); System.out.println("Box #" + counter + " contains [" + boxContents.toString() + "]"); counter++; } } public static void main(String[] args) { java.util.ArrayList<Box<Integer>> listOfIntegerBoxes = new java.util.ArrayList<>(); BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(20), listOfIntegerBoxes); BoxDemo.addBox(Integer.valueOf(30), listOfIntegerBoxes); BoxDemo.outputBoxes(listOfIntegerBoxes); }}The above code output is:
Box #0 contains [10]Box #1 contains [20]Box #2 contains [30]
Let me mention that the focus of the generic method addBox is the type description you no longer need to display in the method call in the new Java version, like this:
BoxDemo.<Integer>addBox(Integer.valueOf(10), listOfIntegerBoxes);
The compiler can automatically infer that the parameter type is Integer from the parameters passed into addBox .
4.2 Type Inference and Generic Constructors of Generic and Non-Generic Classes
Well...This may be a better sentence in English: Type Inference and Generic Constructors of Generic and Non-Generic Classes
In fact, generic constructors are not patents for generic classes. Non-generic classes can also have their own generic constructors. Take a look at this example:
class MyClass<X> { <T> MyClass(T t) { // ... }}If the following instantiation is made to the MyClass class:
new MyClass<Integer>("") OK, here we show that the parameter type X of MyClass is Integer , and for the constructor, the compiler deduces that the formal parameter T is String based on the incoming String object (""). This has been implemented in the Java7 version. What improvements have been made in Java8? After Java8, we can write this instantiation of a generic class with a generic constructor like this:
MyClass<Integer> myObject = new MyClass<>(""); Yes, it is still the pair of angle brackets (<>), which is called diamond, so that our compiler can automatically deduce that the formal parameters X is Integer and T is String . This is actually very similar to our initial example of Map<String,String> , except that there is a genericization of the constructor.
It should be noted that type derivation can only be derived based on the call's parameter type, target type (this will be discussed soon) and return type (if there is a return), and cannot be derived based on some requirements after the program.
4.3 Target Type
As mentioned earlier, the compiler can perform type derivation based on the target type. The target type of an expression refers to the correct data type that the compiler needs based on where the expression appears. For example, this example:
static <T> List<T> emptyList();List<String> listOne = Collections.emptyList();
Here, List<String> is the target type, because what is needed here is List<String> , and Collections.emptyList() returns List<T> , so the compiler here infers that T must be String . This is OK in Java 7 and 8. However, in Java 7, it cannot be compiled normally in the following situation:
void processStringList(List<String> stringList) { // process stringList}processStringList(Collections.emptyList());At this time, java7 will give this error message:
//List<Object> cannot be converted to List<String>
Reason: Collections.emptyList() returns List<T> , and T here requires a specific type, but because it cannot be inferred from the method declaration that what is required is String , the compiler gives T an Object value. Obviously, List<Object> cannot be converted to List<String>. So in the java7 version you need to call this method like this:
processStringList(Collections.<String>emptyList());
However, in Java 8, due to the introduction of the target type concept, it is obvious that what the compiler needs is List<String> (that is, the Target Type here), so the compiler infers that the T in the returned List<T> must be String , so the description of processStringList(Collections.emptyList()); is OK.
The use of target types is most obvious in Lambda expressions.
Summarize
OK, the above are some personal insights about type derivation in Java. In summary, the increasingly perfect type derivation is to complete some type conversion work that seems to be natural, but all of these work is left to the compiler for automatic derivation rather than to allow developers to display it. I hope the content of this article will be helpful to everyone in learning Java. If you have any questions, you can leave a message to communicate.