Generic learning environment

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,Java Theory and PracticeBrian Goetz analyzes 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 clickDiscussionTo access 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 calledErasure)(The compiler uses generic type information to ensure type security and then clears bytecode before it is generated.) This technology is somewhat strange and sometimes has 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, ifIntegerExtendedNumber(The same is true ).IntegerYesNumberAndInteger[]YesNumber[], In the requirementNumber[]Can be passed or assignedInteger[]. (More formally, ifNumberYesIntegerSuper type, thenNumber[]YesInteger[]). You may think this principle applies to generic types as well --List<Number>YesList<Integer>Super type, you canList<Number>Place TransferList<Integer>. 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 canList<Integer>AssignedList<Number>. The following code allowsIntegerContent inList<Integer>:

List<Integer> li = new ArrayList<Integer>();List<Number> ln = li; // illegalln.add(new Float(3.1415));

BecauselnYesList<Number>, So addFloatIt seems completely legal. However, iflnYesliThis destroysliType security commitment in 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]; // illegalObject[] oa = lsa;  // OK because List<String> is a subtype of ObjectList<Integer> li = new ArrayList<Integer>();li.add(new Integer(3));oa[0] = li; String s = lsa[0].get(0); 

The last row will throwClassCastExceptionBecauseList<Integer>EnterList<String>. Because the array co-variation will undermine the security of the generic type, it is not allowed to instantiate an array of the generic type (unless the type parameter is an unbound wildcard, suchList<?>).


Back to Top

Construction Delay

Because it can be erasedList<Integer>AndList<String>Is the same class, the compiler is compilingList<V>Only one class is generated (different from C ++ ). ThereforeList<V>Class, the compiler does not knowVSo it cannot be processed as it knows the specific type represented by the class.List<V>Type parameters in the class definition (List<V>InV).

Because the runtime cannot be distinguishedList<String>AndList<Integer>(BothList), Using generic type parameters to identify the structure of the type variable 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 classesFoo:

class Foo<T> {   public void doSomething(T param) { ... }}

HypothesisdoSomething()Method to copy the inputparamWhat happens to parameters? There are not many options. You may want to implementdoSomething():

public void doSomething(T param) {   T copy = new T(param);  // illegal}

However, you cannot use type parameters to access constructors, because you do not know the classes to be constructed during compilation, so you do not know which constructors to use. Generic expressions cannot be used to express"TYou must have a copy constructor (or even a non-parametric constructor) constraint. Therefore, you cannot use the constructor of the class represented by generic parameters.

clone()How about it? Assume thatFoo,TExtendedCloneable:

class Foo<T extends Cloneable> {   public void doSomething(T param) {    T copy = (T) param.clone();  // illegal   }}

Unfortunately, it still cannot be calledparam.clone(). Why? Becauseclone()InObjectIs to protect access, callclone()You mustclone()Rewrite the class reference for public access. But re-declareclone()Public is unknownTTherefore, cloning does not help.

Create a wildcard reference

Therefore, it is not possible to copy the type reference of the class during compilation. How about the wildcard type? Assume that the type to be created isSet<?>Parameter. You knowSetThere is a copy constructor. Others may have told you that if you do not know the type of the content to be set, it is best to useSet<?>Replace the original typeSet, Because this method causes fewer Non-checked type conversion warnings. Therefore, you can try to write as follows:

class Foo {  public void doSomething(Set<?> set) {    Set<?> copy = new HashSet<?>(set);  // illegal  }}

Unfortunately, you cannot use a wildcard type parameter to call a generic constructor, even if you know that such constructor exists. However, you can do this:

class Foo {  public void doSomething(Set<?> set) {    Set<?> copy = new HashSet<Object>(set);    }}

This construction is not so intuitive, but it is type-safe and can be likenew HashSet<?>(set)Work that way.

Construct an array

How to ImplementArrayList<V>? Hypothesis classArrayListManage oneVArray, you may want to useArrayList<V>Create a constructorVArray:

class ArrayList<V> {  private V[] backingArray;  public ArrayList() {    backingArray = new V[DEFAULT_SIZE]; // illegal  }}

However, this code cannot work-the type array represented by the type parameter cannot be instantiated. The compiler does not knowVIt cannot be instantiated because of the actual type.VArray.

The collections class bypasses this problem through a awkward method. during the compilation of the collections class, a warning that the type conversion is not checked will be generated.ArrayListThe constructor is as follows:

class ArrayList<V> {  private V[] backingArray;  public ArrayList() {    backingArray = (V[]) new Object[DEFAULT_SIZE];   }}

Why are these codes accessed?backingArrayNot GeneratedArrayStoreExceptionWhat about it? HoweverObjectArrayStringArray. Because generics are implemented through erasure,backingArrayIs actuallyObject[]BecauseObjectReplacedV. This means: in fact, this class expectsbackingArrayIsObjectArray, but the compiler needs to perform additional type checks to ensure that it containsVType object. This method works very well, but it is quite awkward, so it is not worth doing the same. (For more information, see references ).

Another way is to declarebackingArrayIsObjectArray, and forcibly convert itV[]. You will still see the type not checked conversion warning (the same as the previous method), but it makes some ambiguous assumptions clearer (suchbackingArrayDo not escapeArrayList).

Other methods

The best way is to pass the class text to the constructor (Foo.class), So that the implementation can be known at runtime.T. The reason for not using this method is backward compatibility-the new generic collection class cannot be compatible with previous versions of the collections framework.

In the following codeArrayListThe following methods are used:

public class ArrayList<V> implements List<V> {  private V[] backingArray;  private Class<V> elementType;  public ArrayList(Class<V> elementType) {    this.elementType = elementType;    backingArray = (V[]) Array.newInstance(elementType, DEFAULT_LENGTH);  }}

But wait! There is still something wrong with callingArray.newInstance()Will cause unchecked type conversion. Why? This is also due to backward compatibility.Array.newInstance()The signature is:

public static Object newInstance(Class<?> componentType, int length)

Instead of type-safe:

public static<T> T[] newInstance(Class<T> componentType, int length)

WhyArrayHow can we use this method for generalization? It is also used to maintain backward compatibility. An array of the basic types to be created, as shown in figureint[], You can useTYPEField callArray.newInstance()(int, Can be passedInteger.TYPEAs a class text ). UseClass<T>Parameter insteadClass<?>GeneralizationArray.newInstance()It provides better type security for the reference type, but cannot be used.Array.newInstance()An instance of the basic type array is created. Maybe a newnewInstance()Version.

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.


Back to Top

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.

There may be no backward compatibility problem in another generalized method, which isCollections.toArray(Object[]). InputtoArray()The array has two purposes-if the set is small enough, you can put its content directly 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 to be passedCollections.toArray()The parameter is not an array, but a text class:

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 generic Collections API that is often obfuscated iscontainsAll(),removeAll()AndretainAll(). You may thinkremove()AndremoveAll()The signature should be:

interface Collection<E> {   public boolean remove(E 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.x.remove(o)Indicates that "IfoInclude inx, Delete it; otherwise, nothing will be done ." IfxIs a generic set, sooNot necessarilyxType parameter compatibility. IfremoveAll()Generalized to be called only when the type is compatible (Collection<? extends E>), So before generalization, legal code sequences become invalid, for example:

// a collection of IntegersCollection c = new HashSet();// a collection of ObjectsCollection r = new HashSet();c.removeAll(r);

If the preceding snippets are generalized in an intuitive way (cSetCollection<Integer>,rSetCollection<Object>), IfremoveAll()The signature must have the following parameters: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, it must be defined by a type constraint that is weaker than the type constraint used to redesign the generic from the beginning.remove(),removeAll(),retainAll()AndcontainsAll().

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.


Back to Top

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>AndList<String>Is the same class.List<?>This is a more important difference in JDK 5.0 compared with previous versions ).

Erasure means that a class cannot be implemented simultaneously.Comparable<String>AndComparable<Number>Because both of them are in the same interface and specify the samecompareTo()Method. StatementDecimalStringClassStringAndNumberIt seems wise, but for the Java compiler, this is equivalent to two declarations for the same method:

public class DecimalString implements Comparable<Number>, Comparable<String> { ... } // nope

Another consequence of erasure is that generic type parameters are forced type conversion orinstanceofMeaningless. The following code does not improve the type security of the code at all:

public <T> T naiveCast(T 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.naiveCast()The method does not actually perform any conversion at all,TDirectly replacedObject, In contrast to the expected, the passed object is forcibly convertedObject.

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.


Back to Top

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 (suchArray.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.

 

Auto-http://www.ibm.com/developerworks/cn/java/j-jtp01255.html

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.