"Let errors be discovered during compilation as much as possible"
"You must know the boundary to become a master"
--- Thinking in Java
Errors are very desirable when being detected during compilation. Java generics are introduced in JDK1.5 to "let the compiler take on more work and ensure that the types are correct ". However, the developer community came along with a mixed view, as mentioned in exceptions-Java generics and exceptions, which are highly controversial.
The excellent product of Java generics is the collection framework. Compared with the previous Java Collection framework, the collection framework after generics is very simple and secure. The following comes with the various puzzles that Java generics bring to people. Designers once said that the main design inspiration for Java generics is the C ++ template, however, people who know a little about C ++ may be disappointed with Java's generics. It is worth exploring how to use Java generics elegantly for excellent program design.
1. Why is Java generic like this?
Java generics are a feature that emerged almost 10 years after the emergence of Java, and there is already a lot of old code. As a widely used production language, Java has to consider compatibility. To allow new users to gradually migrate to the new platform, the new code must be compatible with the old code, which is a great motivation, it won't destroy all existing code overnight! Therefore, Java designers decided to use the "erasure mechanism" to design generics on a dark evening!
2. Erasure Mechanism
The primary prerequisite for a correct understanding of the generic concept is understanding the type erasure mechanism ). The generics in Java are basically implemented at the compiler level. The generated Java Byte Code does not contain type information in generics (native type (raw type) at runtime )). Any type information during compilation of generic code will be removed by the compiler during compilation. This process is called "type erasure ". In the Code, such as: List And List After compilation, it will become List. The JVM only sees a List, and the type information appended to the generic type is invisible to the JVM (term: Not specific ).
The entire process of erasure is also easy to understand: First, find the specific class used to replace type parameters. If this class is not named, the default Object is used. If the upper bound of the parameter type is specified, this upper bound is used to replace the type parameter. At the same time, the type declaration is removed, that is, the content of <> is removed. For example, if the T get () method declaration is used, the Object get (), List It becomes a List. Next, some bridging methods may be generated.
3. Non-specific types
A non-specific type means that the runtime notation contains less information than the compile-time notation. Generic is a typical non-materialized type. For more information, see ArrayList. Source code when the object is retrieved
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } public E get(int index) { rangeCheck(index); return elementData(index); } @SuppressWarnings("unchecked") EelementData(int index) { return (E) elementData[index]; }
The actual Object array is held during the runtime, but the compilation can effectively determine whether the input is of the String type, so as to avoid ClassCastException.
The opposite is the "materialized type", and an array is an example. Arrays always check the type constraints of their elements at runtime. For example
Object[]father = new Father[10]; father[0]= new Son(); father[1]= new Integer(100);
There is no compilation error here, but the ArrayStoreException will be thrown during the runtime, which is opposite to the characteristics of the generic type. For example, you are familiar with the following code:
ListList = new ArrayList
();
This code is invalid.
The following code is even more confusing:
Father[]father = new Son[10]; father[0]= new Son(); father[1]= new Father();
This code throws an ArrayStoreException in the third row. Yes, the array type is mandatory. It is reasonable to say that the elements in an array should be safe. The parent class array can be an element of the Child type. The subclass array can be successfully transformed to the parent class array (the transformation is successful here), but an exception is thrown during runtime, the final reason is that the array is not the Father type but the Son type during actual operation. There is a conflict between externalization and generics. (If you create a new T and convert it to the Object type, you can put it into any type, during the compilation period, it cannot be discovered by the compiler, but it is likely to throw an ArrayStroeException or ClassCastException during the runtime), so it is a dark evening, the designers of Java decided another one that never ends. They could not create generic arrays.
In Java, you cannot use the seemingly reasonable ArrayList
Asd = new ArrayList
[]; Code, because I can convert it to the Object [] type, and then I can put any type into it, however, the running of JVM has made you say NO, which is a kind of naked ridicule for Java generics!
4. wildcard characters
Wildcard makes Java generics more flexible and powerful (although not so powerful ). Parameterized types are immutable, that is, the following code cannot be compiled:
ListF = new ArrayList <> (); List
F1 = new ArrayList <> (); f = f1;
Error reported by the third line! It seems that f1 should be a subclass of f, but it is contrary to intuition. The reason is still "Erased", because ListAll classes in are replaced by objects, while List
Are replaced by strings. How to coordinate is a problem.
If you want to write more powerful APIs, you should select wildcard characters. The following code:
Public class Seven {publicstatic void main (String [] args) throws InstantiationException, IllegalAccessException {Stack
Stack = new Stack <> (); List
List = new ArrayList <> (); list. add (10); list. add (100); list. add (1000); Iterable
Iter = list; stack. pushAll (iter) ;}} class Stack
{PrivateE e; publicvoid push (E e) {}/*** here is a wildcard to improve API flexibility * @ param I */publicvoid pushAll (Iterable
I) {for (Ee: I) push (e );}}
The Stack class uses the Number type as its type parameter when creating an instance, if pushAll (Iterable I) as a signature. Due to the type erasure mechanism, Java will compile this layer on your SayNo! The PECS principle is introduced here, that is, if the parameterized type represents a T Producer If it is a T consumer (Comsumer), use . This is a good principle for designing excellent and powerful generic code!
Here we need to describe any wildcard ----?, It looks like? It is not much different from Object (it will be erased as an Object in actual situations), but if you List , So no value can be inserted (except null, but this does not make any sense ). Here? Is to tell the compiler, I don't know what type will be accepted, but please use Java generic mechanism to process it! However, in reality, only when appropriate can we make full use of it.
Related to this is ---- type capture. refer to the following code:
// There are any wildcards here. After the function is called, what is captured immediately? Publicstatic void swap (List
List, int I, int j) {swapHelper (list, I, j);} // The privatestatic attribute is secure when the helper knows the E type.
Void swapHelper (List
List, int I, int j) {list. set (I, list. set (j, list. get (I )));}
Java wildcard indeed plays a key role in strengthening Java's generics. In addition to keeping in mind the above PECS principles, you need more experience and insight!
5. Other suggestions
1. Do not use native types in new code
Java makes generics the current situation for compatibility purposes, and will not use native code in the future. The most important thing is: one of the reasons for the emergence of Java generics is to eliminate possible ClassCastException exceptions in the original ecology and achieve security. If you use native, you will waste Java's interest in you.
However, the native type must be used in some places:
1. Text
2. Use of static methods or fields
2. Eliminate warnings
Java warns of your code, indicating that the Code may have problems. The more warnings you give in the Code, the more likely the problem will be. It is best to think of a solution to the warning, if it cannot be eliminated, you can use the annotation @ SuppressWarnings to eliminate it, but you must prove that the warnings you have eliminated are type-safe, so there is no problem in reality.
@ SuppressWarnings should be used in the shortest range. Do not mistakenly extend the scope and remove the originally dangerous warning. Note that @ SuppressWarnings cannot be annotated on the return Statement.
3. The class table is limited to arrays.
Arrays are used to determine information at runtime, which is more prone to problems. Compared with the type security list, it is also a good choice to sacrifice a little performance for more security and convenience.
4. Give priority to generic
One of the advantages of generic code is that templates can be used repeatedly. This is exactly what designers are pursuing, but only for good reasons (when code can work across multiple classes) to use generics. Otherwise, do not use generics! Compared with generic classes, the generic method is more flexible.
5. Prioritize type-safe heterogeneous containers
The core idea is to parameterize the key rather than the container.
The following code stores different key types in Map:
Publicclass Eight {publicstatic void main (String [] args) {YiGouRongQiy = new YiGouRongQi (); y. put (String. class, "leon"); y. put (Integer. class, 20); System. out. println (y. get (String. class) + "is" + y. get (Integer. class) + "years old! ") ;}} Class YiGouRongQi {privateMap
, Object> hm = new HashMap <> (); public
Boolean put (Class
Type, T instance) {if (type = null) {System. out. println ("null pointer exception"); returnfalse;} elseif (hm. containsKey (type) {System. out. println ("key already exists"); returnfalse;} hm. put (type, instance); returntrue;} // @ SuppressWarnings ("unchecked") public
T get (Class
Type) {// The return statement cannot be annotated. // return (T) hm. get (type); returntype. cast (hm. get (type ));}}
The Set API indicates that generic parameters should be issued. Each container must have a fixed number of type parameters. This shows that you can place type parameters on keys instead of containers to avoid this restriction. For this type of secure heterogeneous containers, you can use the Class object as the key.
6. Reflection on Generics
Java generics are often mentioned, and most people always mention their criticism. For the time being, the historical reasons for Java are put aside. What is generics. Just like the generic type in C ++, which is also called a template, the generic type is more generalized for common methods. This generic class is applicable to all classes or partial classes, generally, it is used with a partial classification. Some restrictions apply to these classes. generic classes do not care about the specific types of classes, but whether they can execute a certain method!
This is the "potential type mechanism ". Therefore, generic classes generally require the parameter type to implement a method or have a method. The implementation interface or reflection can fully reflect the characteristics of the generic type, that is, the implementation of "type potential mechanism ". Generic and generic types should not store the specific information of the class internally, because once a generic class is written for a specific class, therefore, there is no need to write a generic class for this generic class. Therefore, although Java generics bring inconvenience to developers, it is a pity that generic arrays are not supported, but in turn, internal parameter types are erased, using Interfaces and reflection to implement the potential type mechanism is also an elegant method.
The simple code is as follows:
Public class Four {publicstatic void main (String [] args) {// customer class, call the generic method, and pass in the class GenericTest that implements the specified interface. doIt (newTestClass (); System. out. println ("------------------------------"); GenericTest. doIt (newTestClass2 ();} interface testInterface {/*** this interface specifies what needs to be done */publicvoid doSomething ();} /*** the following two classes use the features of interfaces. To program interfaces, You need to implement the generic potential type mechanism ---- "I don't care about the types used here, as long as it has these methods, "* can also be implemented using reflection, without relying on the Interface Method * @ author leon **/ Class TestClass implements testInterface {@ Override publicvoid doSomething () {System. out. println ("I 'mleon, I'll do some thing here! ") ;}} Class TestClass2 implements testInterface {@ Override publicvoid doSomething () {System. out. println (" I 'mdiana, I'll do other thing here! ") ;}} Class GenericTest {/*** the generics in this generic class need to execute some methods, but the testInterface interface must be implemented ** @ param t */static
Void doIt (T t) {// once T is determined, the compiler can determine the internal consistency of the types used in the class or method. // In other words, that is, all the T of this class is replaced by System before compilation. out. println ("Dobefore! "); T. doSomething (); System. out. println (" Doafter! ");}}
7. Type System
In Java, you are familiar with the type Architecture generated by the inheritance mechanism. For example, String inheritance
From Object. According to the Liskov replacement principle, child classes can replace parent classes. When the Object class is referenced
If you input a String object, there is no problem. However, in turn, the parent class is used
When you replace subclass references, you need to perform forced type conversion. The compiler cannot guarantee the running time.
Type conversion must be valid. This automatic subclass replaces the type conversion mechanism of the parent class, which is also suitable for arrays.
. String [] can replace Object []. However, the introduction of generics has produced some problems for this type system.
Impact. As mentioned above, List Yes. The List cannot be replaced. .
The type system added two dimensions after the introduction of generics: one is the inheritance architecture of the type parameter,
The other is the inheritance architecture of generic classes or interfaces. The first one refers to the List And
ListIn this case, the type parameter String is inherited from the Object. The second type refers to List.
The interface inherits from the Collection interface. For this type of system, there are some rules as follows:
1. The relationship between generic classes with the same type parameters depends on the inheritance architecture of the generic classes. That is
List Yes Collection Child type, List Replaceable
Collection . This situation also applies to type declarations with upper and lower bounds.
2. When wildcard is used in the type declaration of a generic class, its Subtypes can be divided into two dimensions.
Expand. For example For example, its subtype can be
Expand dimensions, that is, List And Set And so on.
Expand at the Number level, that is, Collection And Collection . So
Loop down, ArrayList And HashSet And so on.
Number>.
3. If a generic class contains multiple type parameters, apply the preceding rules for each type parameter.
After understanding the above rules, you can easily correct the code given in the instance analysis. You only need
ListChange to List You can. List Yes List So the parameter is not passed
An error occurred.
Note: The above reference is from java deep adventure.
8. Gossip Java generics are already an integral part of Java itself. Just like exceptions, the possibility of future changes is very slim, when writing more code on historical issues, you need to think more. The main application of Java generics in JDK is the set framework. It turns out that the set framework is really good, although the generics in Java are not as powerful as imagined. Therefore, like exceptions, writing elegant generics is an art worth pursuing.