Deep understanding of Java generics

brief introduction

Generics is a concept introduced by JDK 5. Generics are mainly introduced to ensure the security of types in Java, which is a bit like templates in C + +.

However, in order to ensure downward compatibility, all Java generics are implemented during compilation. The compiler performs type checking and type inference, and then generates ordinary non generic bytecode. This type is called erasure. The compiler performs type checking during compilation to ensure type safety, but erases it before subsequent bytecode generation.

This can lead to confusing results. This article will explain the use of generics in Java in detail to avoid misunderstandings.

Generics and covariance

For detailed description of covariance and inversion, please refer to:

Deep understanding of covariance and inversion

Here, I'll summarize that covariance and inversion are meaningful only in the type parameters in the type declaration, and have no significance for parameterized methods, because the tag affects the subclass inheritance behavior, and the method has no subclasses.

Of course, what is not shown in Java indicates whether the parameter type is covariant or inverse.

Covariance means that if there are two classes a < T > and a < C >, where C is a subclass of T, we can use a < C > instead of a < T >.

Inversion is the opposite relationship.

Arrays in Java are covariant. For example, if integer is a subclass of number, then integer [] is also a subclass of number []. We can pass in integer [] when we need number [].

Next, let's consider the case of generics. Is list < number > the parent of list < integer >? Unfortunately, not.

We conclude that generics are not covariant.

Why? Let's take an example:

List<Integer> integerList = new ArrayList<>();
        List<Number> numberList = integerList; // compile error
        numberList.add(new Float(1.111));

If integerlist can be assigned to numberlist, numberlist can add any number type, such as float, which violates the original intention of generics and adds float to integer list. Therefore, the above operation is not allowed.

We just mentioned that array is covariant. If you bring generics into array, a compilation error will occur. For example, new list < string > [10] is illegal, but new list [10] Yes. Because in generics? Represents an unknown type.


List<?>[] list1 = new List<?>[10];

List<String>[] list2 = new List<String>[10]; //compile error

Problems encountered in the use of generics

Because of type erasure, both list < string > and list < integer > will be treated as lists during operation. So we will encounter some problems when using generics.

If we have a generic class, there is a method in the class, and the parameters of the method are generic, we want to copy the generic parameters in this method.

public class CustUser<T> {

    public void useT(T param){
        T copy = new T(param);  // compile error
    }
}

The above operation will fail to compile because we don't know what t is or whether T has a corresponding constructor.

There is no way to directly clone T. If we want to copy a set, what should we do if the type in the set is undefined?

    public void useTSet(Set<?> set){
        Set<?> copy1 = new HashSet<?>(set);  // compile error
        Set<?> copy2 = new HashSet<>(set);
        Set<?> copy3 = new HashSet<Object>(set);  
    }

Can you see? It cannot be directly used for instantiation. But we can replace it in the following two ways.

Let's look at the use of array:

    public void useArray(){
         T[] typeArray1= new T[20];  //compile error
        T[] typeArray2=(T[]) new Object[20];
        T[] typeArray3 = (T[]) Array.newInstance(String.class,20);
    }

Similarly, t cannot be directly used for instantiation, but we can replace it in the following two ways.

Precautions for type erasure

Because of type erasure, it is meaningless to implement two different types of the same interface in our interface implementation:

public class someClass implements Comparable<Number>,Comparable<String> { ... } // no

Because in the compiled bytecode, the two comparable are the same.

Similarly, it is meaningless for us to use t for type casting:

public <T> T cast(T t,Object o) { return (T) o; }

Because the compiler doesn't know whether the cast is right or wrong.

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>