Generic Type learning in JDK 5.0

Source: Internet
Author: User
The generic type added in JDK 5.0 is an important improvement in type security in Java. However, for users who use generics for the first time, some aspects of generics may not seem easy to understand, or even very strange. In this month's "Java theory and practice", Brian Goetz analyzed common traps that constrain users who use generics for the first time. You can share your views on this article with the author and other readers through the Forum. (You can also click the discussion at the top or bottom of this article to visit this forum .)

On the surface, both the syntax and the application environment (such as the container class) and the generic type (or generic type) are similar to the template in C ++. However, this similarity is limited to the surface. in Java, generics are basically fully implemented in the compiler. The Compiler executes the type check and type inference, and then generates common non-generic bytecode. This implementation technology is called Erasure (the compiler uses generic type information to ensure type security and then clears bytecode before it is generated). This technology is somewhat odd, and sometimes it has some confusing consequences. Although the paradigm is a major step towards type security for Java classes, it is almost certainly a headache (sometimes intolerable) when learning to use generics.

Note: This document assumes that you have a basic understanding of the model in JDK 5.0.

Generics are not covariant.

Although it is helpful to regard the set as an array abstraction, the array also has some special properties that the set does not have. Arrays in Java are covariant. That is to say, if integer is extended by number (as is true), not only integer is number, but integer [] is also number []. you can pass the number [] or assign it to integer []. (More formally, if number is an integer supertype, number [] is also an integer [] supertype ). You may think this principle applies to the generic type -- list <number> is a list <integer> supertype, you can pass list <integer> where list <number> is required. Unfortunately, this is not the case.

It is not allowed to do so for a very good reason: doing so will undermine the type security generic type to be provided. If you can grant list <integer> to list <number>. The following code allows non-integer content to be placed in the list <integer>:

List <integer> li = new arraylist <integer> ();
List <number> Ln = Li; // illegal
Ln. Add (New flowat (3.1415 ));

 

Because ln is list <number>, it seems completely legal to add float to it. However, if ln is the alias of Li, this destroys the type security commitment contained in the Li definition-it is an integer list, which is why generic types cannot be changed together.

Other coordination issues

Another consequence of changing arrays while changing generics is that arrays of the generic type cannot be instantiated (new list <string> [3] is invalid ), unless the type parameter is an unbound wildcard (new list <?> [3] is valid ). Let's take a look at the consequences of declaring a Generic Array:

List <string> [] LSA = new list <string> [10]; // illegal
Object [] OA = LSA; // OK because list <string> is a subtype of Object
List <integer> li = new arraylist <integer> ();
Li. Add (New INTEGER (3 ));
OA [0] = Li;
String S = LSA [0]. Get (0 );

 

The last row will throw classcastexception, because this will fill in list <integer> in the location where it should be list <string>. Arrays are not allowed to instantiate arrays of the generic type unless the type parameter is an unbound wildcard, such as list <?> ).

 

Construction Delay

Because it can be erased, list <integer> and list <string> are the same class. The Compiler generates only one class (different from C ++) when compiling list <v> ). Therefore, when compiling the list <v> class, the compiler does not know the type represented by V, therefore, it cannot process the type parameter in the list <v> class definition as it knows the specific type represented by the class (V in list <v> ).

Because list <string> and list <integer> cannot be distinguished during running, the construction of variables identified by generic parameters becomes a problem. There is a lack of type information during the runtime, which makes it difficult for generic containers and generic classes that want to create protective copies.

For example, generic FOO:

Class Foo <t> {
Public void dosomething (t param ){...}
}

 

The generic type added in JDK 5.0 is an important improvement in type security in Java. However, for users who use generics for the first time, some aspects of generics may not seem easy to understand, or even very strange. In this month's "Java theory and practice", Brian Goetz analyzed common traps that constrain users who use generics for the first time. You can share your views on this article with the author and other readers through the Forum. (You can also click the discussion at the top or bottom of this article to visit this forum .)

On the surface, both the syntax and the application environment (such as the container class) and the generic type (or generic type) are similar to the template in C ++. However, this similarity is limited to the surface. in Java, generics are basically fully implemented in the compiler. The Compiler executes the type check and type inference, and then generates common non-generic bytecode. This implementation technology is called Erasure (the compiler uses generic type information to ensure type security and then clears bytecode before it is generated). This technology is somewhat odd, and sometimes it has some confusing consequences. Although the paradigm is a major step towards type security for Java classes, it is almost certainly a headache (sometimes intolerable) when learning to use generics.

Note: This document assumes that you have a basic understanding of the model in JDK 5.0.

Generics are not covariant.

Although it is helpful to regard the set as an array abstraction, the array also has some special properties that the set does not have. Arrays in Java are covariant. That is to say, if integer is extended by number (as is true), not only integer is number, but integer [] is also number []. you can pass the number [] or assign it to integer []. (More formally, if number is an integer supertype, number [] is also an integer [] supertype ). You may think this principle applies to the generic type -- list <number> is a list <integer> supertype, you can pass list <integer> where list <number> is required. Unfortunately, this is not the case.

It is not allowed to do so for a very good reason: doing so will undermine the type security generic type to be provided. If you can grant list <integer> to list <number>. The following code allows non-integer content to be placed in the list <integer>:

List <integer> li = new arraylist <integer> ();
List <number> Ln = Li; // illegal
Ln. Add (New flowat (3.1415 ));

 

Because ln is list <number>, it seems completely legal to add float to it. However, if ln is the alias of Li, this destroys the type security commitment contained in the Li definition-it is an integer list, which is why generic types cannot be changed together.

Other coordination issues

Another consequence of changing arrays while changing generics is that arrays of the generic type cannot be instantiated (new list <string> [3] is invalid ), unless the type parameter is an unbound wildcard (new list <?> [3] is valid ). Let's take a look at the consequences of declaring a Generic Array:

List <string> [] LSA = new list <string> [10]; // illegal
Object [] OA = LSA; // OK because list <string> is a subtype of Object
List <integer> li = new arraylist <integer> ();
Li. Add (New INTEGER (3 ));
OA [0] = Li;
String S = LSA [0]. Get (0 );

 

The last row will throw classcastexception, because this will fill in list <integer> in the location where it should be list <string>. Arrays are not allowed to instantiate arrays of the generic type unless the type parameter is an unbound wildcard, such as list <?> ).

 

Construction Delay

Because it can be erased, list <integer> and list <string> are the same class. The Compiler generates only one class (different from C ++) when compiling list <v> ). Therefore, when compiling the list <v> class, the compiler does not know the type represented by V, therefore, it cannot process the type parameter in the list <v> class definition as it knows the specific type represented by the class (V in list <v> ).

Because list <string> and list <integer> cannot be distinguished during running, the construction of variables identified by generic parameters becomes a problem. There is a lack of type information during the runtime, which makes it difficult for generic containers and generic classes that want to create protective copies.

For example, generic FOO:

Class Foo <t> {
Public void dosomething (t param ){...}
}

 

Here we can see a pattern-many problems or trade-offs related to Generics do not come from generics themselves, but are side effects of maintaining compatibility with existing code.

 

Generalized existing classes

There is not much skill in converting existing library classes to use generics, but it is the same as normal, and backward compatibility will not come out of thin air. I have discussed two examples, in which backward compatibility limits the generalization of class libraries.

Another general method may not have backward compatibility issues, which is collections. toarray (object []). The array passed in toarray () has two purposes -- if the set is small enough, you can directly put its content in the provided array. Otherwise, use reflection to create a new array of the same type to accept the results. If you rewrite the collections framework from the beginning, it is very likely that the parameter passed to collections. toarray () is not an array, but a class text:

Interface collection <E> {
Public T [] toarray (class <t super E> elementclass );
}

 

Because the collections framework is widely used as an example of a good class design, but its design is subject to backward compatibility constraints, it deserves your attention and should not be blindly followed.

First, an important aspect of the commonly obfuscated generic collections API is the signatures of containsall (), removeall (), and retainall. You may think that the signatures for remove () and removeall () should be:

Interface collection <E> {
Public Boolean remove (E); // not really
Public void removeall (collection <? Extends E> C); // not really
}

 

But it is actually:

Interface collection <E> {
Public Boolean remove (Object O );
Public void removeall (collection <?> C );
}

 

Why? The answer is also because of backward compatibility. The X. Remove (o) interface indicates that "If O is contained in X, delete it; otherwise, nothing will be done ." If X is a generic set, O is not necessarily compatible with X's type parameters. If removeall () is generalized to be called (collection <? Extends E>), so before generalization, legal code sequences become invalid, for example:

// A collection of Integers
Collection c = new hashset ();
// A collection of objects
Collection r = new hashset ();
C. removeall (R );

 

If the preceding snippets are generalized in an intuitive way (set C to collection <integer> and R to collection <Object>), if the removeall () signature requires its parameter to be collection <? Extends E> instead of No-op, the above Code cannot be compiled. One of the main objectives of a generic class library is not to break or change the semantics of existing code. Therefore, you must define remove () with a type constraint that is weaker than the type constraint used to redesign the generic from the beginning (), removeall (), retainall (), and containsall ().

Classes designed before generics may impede the "obvious" generic method. In this case, it is necessary to make a compromise like the previous example. However, if a new generic class is designed from the beginning, it makes sense to understand what is backward compatible in the Java class library, this avoids improper imitation.

 

Implementation of Erasure

Because generics are basically implemented in Java compilers rather than runtime libraries, when bytecode is generated, almost all information about generic types is erased. In other words, the code generated by the compiler is basically the same as the code that you manually write without generics and checks program type security for forced type conversion. Unlike C ++, list <integer> and list <string> are the same class (although they are of different types, they are all list <?> This is a more important difference in JDK 5.0 compared with previous versions ).

Erasure means that a class cannot implement both comparable <string> and comparable <number> at the same time, because both of them are in the same interface and specify the same compareto () method. It seems wise to declare the decimalstring class to compare it with the string and number, but for the Java compiler, this is equivalent to two declarations on the same method:

Public class decimalstring implements comparable <number>, comparable <string> {...} // Nope

Another consequence of erasure is that it makes no sense to use forced type conversion or instanceof for generic type parameters. The following code does not improve the type security of the code at all:

Public <t> T naivecast (T, object O) {return (t) o ;}

The compiler sends only one type without checking the conversion warning because it does not know whether the conversion is safe. The naivecast () method does not actually perform any conversion at all. t is directly replaced with an object. As expected, the passed object is forcibly converted to an object.

Erasure is also the cause of the preceding constructor problem. That is, you cannot create generic objects because the compiler does not know what constructor to call. If a generic class needs to construct an object of the specified type using the generic type parameter, the constructor should accept the class text (FOO. class) and save them to create instances through reflection.

Conclusion

Generics are a major step towards type security in Java, but the design of generic facilities and the generalization of class libraries are not without compromise. Extended virtual machine instruction sets to support generics are considered unacceptable, as this will cause insurmountable obstacles for Java vendors to upgrade their JVM. Therefore, the erasure method that can be fully implemented in the compiler is adopted. Similarly, in generic Java class libraries, maintaining backward compatibility sets many limitations for the class library's generalization methods, resulting in some messy and frustrating structures (such as array. newinstance ()). This is not a problem of generics, but related to language evolution and compatibility. However, this also makes generic learning and application more confusing and difficult.

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.