preface
Recently on the Internet to see many novices do not understand Java generics, especially for the source code of various wildcard “?” , “T”, “S”, “R”, etc., do not understand their meaning, still less how to use generics. This article will start from the beginning of a thorough analysis of Java generics, and combined with the actual application scenarios of the project, I hope to help beginners.
What are generics and why were they introduced
Before we talk about generics, let’s take a look at some pre-JDK5 code without generics
public static void main(String[] args) { ArrayList list = new ArrayList(); list.add(521); // Add an element of type Integer list.add("wan"); // Add the String element list.add(true); // Add the Boolean element list.add('a'); Item1 = list.get(0); List. ForEach (item -> {// use item where item is of type Object. If (item instanceof Integer) {// Execute business... } else if (item instanceof String) {// Execute business... } else if (item instanceof Boolean) {// Execute business... } // continue to determine the type... }); }Copy the code
In the absence of generics, we declared a List collection that could store elements of any type by default, which at first glance might seem like a good idea. This is powerful enough to store…… of any type However, since we did not know the exact type of the elements in the set during development, the item we got when traversing was actually of Object type. If we want to use strong transformation, we must judge the specific type of the current element. Otherwise, direct use of strong transformation is likely to lead to type conversion exceptions. This makes development very inconvenient and requires extra judgment work every time.
In a word, it’s not safe!
You might already be thinking that instead of storing all of our data in a List, we should define multiple List types in our code
public static void main(String[] args) { ArrayList listInteger = new ArrayList(); ArrayList listString = new ArrayList(); ArrayList listBoolean = new ArrayList(); / /... So that it can be deposited in the corresponding type data in the different list / / -- -- -- -- -- -- -- -- -- -- -- line -- -- -- -- -- -- -- -- -- -- listString. Add (121); // Even so, it can only be used as a reminder.Copy the code
We declared listString to store only strings, but we can still store non-String data, and more importantly, this type conversion exception is usually only detected at runtime. We needed a mechanism to force us to store only elements of the corresponding type, otherwise the compilation would fail, so generics came into being.
In fact, generics are the same idea we just had, assigning a type to a collection on instantiation, limiting a collection to only storing elements of the type we assigned
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("wan"); list.add(521); String STR = list.get(0); List. ForEach (item -> {// where item is String}); }Copy the code
With generics specified, we declare that the list can only store the specified type String. When we store elements of other types, the editor will directly report an error. This way type mismatches can be detected at compile time rather than thrown at run time. And when we iterate, get, and so on, the get method returns a String
Summarize the benefits of generics
- Compile-time type safety
- Avoid cast runtime exceptions
- The same class can operate on multiple types of data, code reuse
I haven’t defined generics yet, so wait until the next section on generic classes
A generic class
When it comes to generic classes, the most typical example is ArrayList. Have you ever wondered why, once you specify a generic type for an ArrayList, you can only store the specified type, so get(0) gets the element and returns the specified generic type? Take a look at the source code for ArrayList
Public class ArrayList<E> implements List<E> implements List<E> public Boolean add(E E) {... } public E get(int index) {... }Copy the code
As you can see, ArrayList specifies a generic <E> when it is defined, and the following methods for adding and retrieving elements operate on E. I’m sure beginners will be confused when they see this…… What the hell is this “E”? In fact, this E is the type that we specify when we instantiate ArrayList, and when we specify String, the parameters of add and the return values of get are of type String, and when we specify Integer, The parameters of the add method and the return values of the GET method are of type Integer. In this way, ArrayList is parameterized, passing in different generics to manipulate different types when instantiating ArrayList.
Of course, we can also define multiple generic parameters in a class, such as HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>
Copy the code
Definition: The essence of generics is to parameterize a type. The data type being operated on is specified as a parameter and processed according to the dynamic incoming
Custom generic classes
Above we see generic classes in JDK source code, of course we can define our own generic classes, the most common is the return result of the Web application we used to encapsulate.
public class ResultHelper<T> implements Serializable { private T data; private boolean success; private Integer code; private String message; private ResultHelper() {} public static <T> ResultHelper<T> wrapSuccessfulResult(T data) { ResultHelper<T> result = new ResultHelper<T>(); result.data = data; result.success = true; result.code = 0; return result; }}Copy the code
This is a utility class that many companies use to encapsulate such a data structure to the front end (although most companies don’t use this anymore). As a result, the parameter T of my wrapSuccessfulResult generic method can take any parameter from the caller, whether it’s order data, item data, and so on, and wrap it.
Generic method
Sometimes in development there will be a requirement to return a different value type depending on the type of argument the method passes in. The custom generic class wrapSuccessfulResult method described above is a typical generic method with only one generic parameter, and we can use multiple generic parameters:
public static <E, T> List<T> convertListToList(List<E> input, Class<T> clzz) {
List<T> output = Lists.newArrayList();
if (CollectionUtils.isNotEmpty(input)) {
input.forEach(source -> {
T target = BeanUtils.instantiate(clzz);
BeanUtils.copyProperties(source, target);
output.add(target);
});
}
return output;
}
Copy the code
For example, the utility method above (without paying too much attention to the method body) converts a List of type E to a List of type T. So here E and T are open, whatever type the caller passes.
The unbounded generic wildcard “?”
“?” Represents the type of uncertainty. For example, our company APP has an order list/demand for details. We all know that the order is to be shipped, to be received, to be refunded and other different pages, and the data of different status pages are different. Putting all the fields in one class would be a terrible design, and the code would look even worse (which is actually what the first version did, and I changed it later). For example, if the backlog has a shipping time, the backlog does not. If you use a class with a shipping time field as a data structure for the details of the backlog, it is not appropriate. Therefore, I wrote a parent class AppOrderResponse to put the fields common to all pages (order ID, order number, order status, order time, etc.) in the parent class, and other unique extension subclasses, the inheritance relationship is as follows:
This type according to different state returns the corresponding data, such as momentum cargo list is returned AppOrderWaitDeliveredResponse generic types, for receiving the list is returned AppOrderDispatchedResponse generic types, But what if an interface returns four possible types at the same time? You might think so
public IPage<AppOrderResponse> list(Page<AppOrderResponse> page, AppOrderQueryRequest request) { IPage<AppOrderDispatchedResponse> demo1 = new Page<>(); IPage<AppOrderWaitDeliveredResponse> demo2 = new Page<>(); //return demo1; // Return demo2; / / error}Copy the code
Define the return value as IPage<AppOrderResponse>, so THAT I can return various types, but this will actually report an error. AppOrderResponse is AppOrderWaitDeliveredResponse parent class, But IPage < AppOrderResponse > not IPage < AppOrderWaitDeliveredResponse > parent class, inheritance of generics is not as you think. So you need to use the wildcard “?” To solve it.
public IPage<? > list(Page<AppOrderResponse> page, AppOrderQueryRequest request) { return appOrderService.list(page, request); }Copy the code
So we use the wildcard “?” Then return any IPage generic. But this is obviously not appropriate, because our type is AppOrderResponse, and it is impossible to allow a program to return a generic type that is not part of AppOrderResponse. So we can use the upper and lower bounds of generics to control this
The upper and lower bounds of generics
As in the order list example above, we should limit the generic type of the return value to AppOrderResponse or a subclass of it, so to say
public IPage<? extends AppOrderResponse> list(Page<AppOrderResponse> page, AppOrderQueryRequest request) {
return appOrderService.list(page, request);
}
Copy the code
This is called specifying the upper bound (upper bound) of a generic type. We can’t directly use IPage<AppOrderResponse> to indicate the upper bound, but we can use IPage<? extends AppOrderResponse>
This is easy to understand from the extends and super keywords
IPage<? Extends AppOrderResponse> extends AppOrderResponse> extends AppOrderResponse> extends AppOrderResponse> Super AppOrderResponse> // indicates that the lowest generic type is AppOrderResponse. It can only be it or its superclassCopy the code
Above we used unbounded wildcards for return values, but we can also use unbounded wildcards for method arguments
Use upper bound arguments
public void test1(List<? extends AppOrderResponse> list){ list.add(new AppOrderDispatchedResponse()); List<AppOrderWaitPaidResponse> list.add(new AppOrderResponse()); List<AppOrderWaitPaidResponse> list.add(null); // This is the only element that can be added null AppOrderResponse AppOrderResponse = list.get(0); // Accept element type AppOrderResponse}Copy the code
When using an upper bound parameter, you cannot add new elements to the parameter. Because the generic type required for the parameter is AppOrderResponse and its subclasses, we get an error adding an element to the body of the method because it could be either AppOrderResponse or a collection of subclasses. Such as we passed in parameter is the List of < AppOrderDispatchedResponse > so the List can add elements can only be AppOrderDispatchedResponse, and can’t add it included at the same level or the parent class, So when we can’t cover all the scenarios, the program won’t let us add them. The interesting thing is that it can add null……
And when we get an element, that element must be an AppOrderResponse or its subclass type, so we can take it as an AppOrderResponse, with a superclass reference to the subclass object.
A generic upper bound parameter can only be read, not written, within a method
Use a lower bound parameter
public void test2(List<? super AppOrderResponse> list){ list.add(new AppOrderResponse()); / / can be added to the list. The add (new AppOrderDispatchedResponse ()); Object = list.get(0); // Accept elements of type Object}Copy the code
When using lower parameters, as the parameter to generic is AppOrderResponse and its parent, so we add AppOrderDispatchedResponse type elements can, of course, Because AppOrderDispatchedResponse is subclass, is certainly AppOrderResponse type, which is the parent class reference is to subclass object.
And when we get an element, because the element type in the list is the AppOrderResponse parent, we have to accept it with Object, which really doesn’t make sense. Why would I get an Object?
A generic lower bound parameter can only be written to a method, not read
The generic wildcard “?” And T, E, R, K, V
I believe this is the most confusing place for the majority of students, after all, the source code is everywhere these wildcards, also can not see what the difference. In fact, T, E, R, K, V for the program to run no difference, the definition of A-Z when any of the letters can be used, but we are A few conventions, but also A specification.
- T, E, R, K, V make no difference to the program runtime, only the name is different
- ? Represents an indeterminate generic type
- T (type) represents a specific generic type
- K and V are key values in the Map
- E (Element) stands for elements, such as those in an ArrayList
What about the unbounded wildcard “?” What’s the difference?
- T is used to define generic classes and generic methods
For example, in our generic class code example above, T is used to define a generic type, and T can be manipulated in code. T cannot be used as a method parameter alone, but only in a defined generic class or in a defined generic method.
public class ResultHelper<T> implements Serializable { private T data; . data.toString(); data.equals(obj); . public static <T> ResultHelper<T> wrapSuccessfulResult(T data) {}Copy the code
- “?” Used for method parameters
Let’s say we have an unbounded wildcard “?” Even if it’s not in a generic class, “?” It can also be used as a method parameter, defining method return values, etc., but we can’t call the “? Define and operate separately
private List<? > list; private ? data; Public class OrderRequest<? > {} public void test(? Item) {} / / an errorCopy the code
Generic erasure
The so-called generic erasure is very simple. In short, generics are only used at compile time. Generics are treated as objects at run time
public static void main(String[] args) { ArrayList<String> list = new ArrayList<>(); list.add("wan"); // Add method parameter type is String String s = list.get(0); // The return value of the get method is String ArrayList<Integer> list2 = new ArrayList<>(); // The return value of the get method is String ArrayList<Integer> list2 = new ArrayList<>(); System.out.println(" list.getClass() == list2.getClass())); //true Two ArrayLists are of the same type [] methods = list.getClass().getmethods (); for (Method method : methods) { method.setAccessible(true); if (method.getName().equals("add")) { Class<? >[] parameterTypes = method.getParameterTypes(); if (parameterTypes.length == 1) { for (Class<? > parameterType: parameterTypes) {system.out.println ("add(E E) + parametertype.getName ()); }} else if (method.getName().equals("get")) {Class<? > returnType = method.getReturnType(); System.out.println("E get(int index) returns E of type: "+ returnType.getName()); }}}Copy the code
Print the result
Do list and list2 have the same type at run time: true add(E E) Parameter E has the type at run time: java.lang.Object E Return value of get(int index) E has the type at run time: java.lang.objectCopy the code
You can see that when we instantiate ArrayList we pass in different generics, but they’re still of the same type. For the parameters of add and the return value of get, the logic is that if we specify a String we should print a String, but we get Object at runtime, so this is enough to prove that generics work at compile time. The runtime is erased and treated as an Object. This is generic erasure.
conclusion
Generics this thing to understand is really very simple, difficult is how to use it well, this requires strong programming skills, design patterns, my advice is to read the JDK source code, framework source code, see how big cow is used in the framework.