Java generic details

  • 2020-05-07 19:35:39
  • OfStack

1. Why -- the reason for the introduction of generics mechanism

If we wanted to implement an String array and require it to be dynamically resized, we would all think of using ArrayList to aggregate String objects. However, after the 1 matrix, we want to implement an array of Date objects of variable size, and of course we want to be able to reuse the ArrayList implementation we wrote earlier for String objects.

      before Java 5, the implementation of ArrayList is roughly as follows:


public class ArrayList {
  public Object get(int i) { ... }
  public void add(Object o) { ... }
  ...
  private Object[] elementData;
}

      we can see from the above code, used to add elements to the ArrayList add function receives a Object model parameters, obtain the specified element from ArrayList get method also returns a Object type of object, Object elementData object array to store the objects in this ArrayList, that is to say, no matter you into the ArrayList into what kind of type, in its interior, are a Object object.

There are two problems with      's inheritance-based generic implementation. The first problem concerns the get method, which returns an Object object every time we call the get method. The second question is about the add method. If we add an File object to ArrayList that aggregates String objects, the compiler will not generate any errors, which is not what we want.

So, starting with Java 5, ArrayList can be used with a type parameter (type parameter) that specifies the element type in ArrayList. The introduction of type parameters solves the two problems mentioned above, as shown in the following code:


ArrayList<String> s = new ArrayList<String>();
s.add("abc");
String s = s.get(0); // There is no need to cast 
s.add(123); // Compilation error, can only be added to it String object 
...

  in the above code, when the compiler "knows" the String type parameter of ArrayList, it does the casting and type checking for us.

2. Generic class

      a generic class (generic class) is a class with one or more type parameters. Such as:


public class Pair<T, U> {
  private T first;
  private U second;

  public Pair(T first, U second) {
    this.first = first;
    this.second = second;
  }

  public T getFirst() {
    return first;
  }

  public U getSecond() {
    return second;
  }

  public void setFirst(T newValue) {
    first = newValue;
  }

  public void setSecond(U newValue) {
    second = newValue;
  }
}

In the code above    , we can see that the type parameters of the generic class Pair are T, U, placed in Angle brackets after the class name. Here T is the first letter of Type, representing the meaning of type, commonly used E (element), K (key), V (value) and so on. It's perfectly fine to refer to type parameters without these letters, of course.

When       instantiates a generic class, we simply replace the type parameter with a specific type, such as instantiating 1 Pair < T, U > Class we can look like this:

Pair < String, Integer > pair = new Pair < String, Integer > ();

3. Generic method

      generic methods are methods with type parameters that can be defined in either a generic class or a normal class. Such as:


public class ArrayAlg {
  public static <T> T getMiddle(T[] a) {
    return a[a.length / 2];
  }
}

The getMiddle method in the       code above is a generic method, defined in such a way that the type variable is placed after the modifier and before the return type. As you can see, the above generic methods can be called for various types of arrays, and while they can be overloaded when the types of those arrays are known to be finite, they are much less efficient to code. The sample code to invoke the above generic method is as follows:

String[] strings = {"aa", "bb", "cc"};
String middle = ArrayAlg.getMiddle(names);

4. Qualification of type variables

      in some cases, generic classes or generic methods want to restrict their type arguments to one step at a time. For example, we want to restrict the type arguments to subclasses of a class or to classes that implement an interface. The relevant syntax is as follows:

      < T extends BoundingType > BoundingType is one class or interface. There may be more than one BoundingType, which can be connected by "&".

5. In-depth understanding of generic implementations of

In fact, from the perspective of a virtual machine, there is no concept of generics. For example, the generic Pair class we defined above looks like this from the virtual machine's point of view (that is, compiled to bytecode) :


public class Pair {
  private Object first;
  private Object second;

  public Pair(Object first, Object second) {
    this.first = first;
    this.second = second;
  }

  public Object getFirst() {
    return first;
  }

  public Object getSecond() {
    return second;
  }

  public void setFirst(Object newValue) {
    first = newValue;
  }

  public void setSecond(Object newValue) {
    second = newValue;
  }
}

The class above       is obtained by type erasers and is the original type corresponding to the Pair generic class (raw type). Type erasure replaces all type arguments with BoundingType (Object if not qualified).

We can simply verify that after compiling Pair.java, type "javap-c-s Pair" to get:

      the line with "descriptor" in the figure above is the signature of the corresponding method. For example, from line 4, we can see that the two parameters of Pair constructor have become Object after type erasers.

Since the generic Pair class becomes its raw type in the virtual machine, the getFirst method returns an Object object, which, from the compiler's point of view, returns the object with the type parameter specified when we instantiate the class. In fact, the compiler does the casting for us. That is, the compiler converts the call to the getFirst method in the Pair generic class into two virtual machine instructions:

Article 1 is a call to the raw type method getFirst, which returns an Object object. The second directive casts the returned Object object to the type parameter type we specified at the time.

      type erasers also occur in generic methods, such as the following generic methods:

public static < T extends Comparable > T min(T[] a)
      compiled by type erasure will look like this:

public static Comparable min(Comparable[] a)
The type erasures of the       method cause some problems. Consider the following code:


class DateInterval extends Pair<Date, Date> {
  public void setSecond(Date second) {
    if (second.compareTo(getFirst()) >= 0) {
      super.setSecond(second);
    }
  }
  ...
}

     


class DateInterval extends Pair {
  public void setSecond(Date second) { ... }
  ...
}

      and in class DateInterval there is an setSecond method inherited from class Pair (after type erasure) as follows:

public void setSecond(Object second)
      now we can see that this method has a different method signature (different parameters) than setSecond method overridden by DateInterval, so it is two different methods, however, the two methods should not be different (because it is override). Consider the following code:


DateInterval interval = new DateInterval(...);
Pair<Date, Date> pair = interval;
Date aDate = new Date(...);
pair.setSecond(aDate);

As you can see from the above code, pair actually refers to DateInterval objects, so you should call setSecond methods of DateInterval. The problem here is that type erasers conflict with polymorphism.

      let's take a look at why this happens: pair was previously declared as type Pair < Date, Date > , this class has only one "setSecond(Object)" method in the eyes of the virtual machine. So at run time, when the virtual machine finds that pair actually refers to DateInterval objects, it calls DateInterval's "setSecond(Object)" method, while DateInterval class has only" setSecond(Date)" method.

The solution to this problem is for the compiler to generate a bridge method in DateInterval:


public void setSecond(Object second) {
  setSecond((Date) second);
}

6. Precautions

(1) type parameters cannot be instantiated with basic types

      that is, the following statement is illegal:

Pair < int, int > pair = new Pair < int, int > ();
  but we can use the corresponding packaging type instead.

(2) generic class instances cannot be thrown or captured

The       generic class extension Throwable is illegal and therefore cannot be thrown or captured as an instance of a generic class. But it is legal to use a type parameter in an exception declaration:


public static <T extends Throwable> void doWork(T t) throws T {
  try {
    ...
  } catch (Throwable realCause) {
    t.initCause(realCause);
    throw t;
  }
}

(3) invalid array of parameterized type

  in Java, the Object[] array can be the parent of any array (because any 1 array can be transformed up to an array of the parent class of the element type it specifies at definition). Consider the following code:


ArrayList<String> s = new ArrayList<String>();
s.add("abc");
String s = s.get(0); // There is no need to cast 
s.add(123); // Compilation error, can only be added to it String object 
...
0

  in the code above, we assign an array element to an object that satisfies the parent class (Object) type, but is different from the original type (Pair), which passes at compile time and throws an ArrayStoreException exception at run time.

For the above reasons,   assumes that Java allows us to declare and initialize a generic array with the following statement:

Pair < String, String > [] pairs = new Pair < String, String > [10];
  so after the virtual machine erases the types, pairs is actually an Pair[] array, and we can turn it up to an Object[] array. So let's add Pair < Date, Date > Objects can be checked both at compile time and at run time, and the idea is that we just want this array to store Pair < String, String > Object, which causes hard-to-locate errors. Therefore, Java does not allow us to declare and initialize a generic array in the form of the above statement.

    can be declared and initialized with the following statement:

Pair < String, String > [] pairs = (Pair < String, String > []) new Pair[10];

(4) type variables cannot be instantiated

      cannot be written as "new T(...) ", "new T [...]. The form ", "T.class" USES type variables. Java prohibits us from doing this simply because there is type erasure, so it is similar to "new T(...) ". "Such a statement will become" new Object(...) "And that's usually not our intention. We can substitute the following statement for "new T[...] "Of:

arrays = (T[]) new Object[N];
 

(5) type variables cannot be used in the static context of a generic class

Note that here we emphasize generic classes. Because static generic methods can be defined in ordinary classes, such as the getMiddle method in the ArrayAlg class we mentioned above. As to why this is so, consider the following code:


ArrayList<String> s = new ArrayList<String>();
s.add("abc");
String s = s.get(0); // There is no need to cast 
s.add(123); // Compilation error, can only be added to it String object 
...
1

We know that there may be more than one People in memory at the same time < T > The class instance. Let's say I have an People in memory < String > Objects and People < Integer > Object, while the class's static variables and static methods are Shared by all instances of the class. So the question is, is name of type String or Integer? For this reason, type variables are not allowed in Java in the static context of generic classes.

7. Type wildcard

Before introducing type wildcards,       introduces two things:

(1) suppose Student is a subclass of People, Pair < Student, Student > But not Pair < People, People > There is no "is-a" relationship between them.

(2) Pair < T, T > There is an "is-a" relationship with its original type Pair, Pair < T, T > You can convert to Pair type in any case.

      now considers the following method:


ArrayList<String> s = new ArrayList<String>();
s.add("abc");
String s = s.get(0); // There is no need to cast 
s.add(123); // Compilation error, can only be added to it String object 
...
2

In the above method, we want to be able to pass Pair as well < Student, Student > And Pair < People, People > Type, however, there is no "is-a" relationship between the two. In this case, Java offers us one solution: use Pair < ? extends People > As a formal parameter. That is, Pair < Student, Student > And Pair < People, People > Both can be viewed as Pair < ? extends People > The subclass.

     " < ? extends BoundingType > "Is called a subtype qualification of a wildcard. The corresponding supertype qualification of the wildcard character is as follows: < ? super BoundingType > .

      now consider the following code:


Pair<Student> students = new Pair<Student>(student1, student2);
Pair<? extends People> wildchards = students;
wildchards.setFirst(people1); 

The third line of the above code reports an error because wildchards is one Pair < ? extends People > Object whose setFirst and getFirst methods look like this:


ArrayList<String> s = new ArrayList<String>();
s.add("abc");
String s = s.get(0); // There is no need to cast 
s.add(123); // Compilation error, can only be added to it String object 
...
4

For the setFirst method,       makes the compiler not know what type the parameter is (only that it is a subclass of People), and we try to pass in an People object. The compiler cannot determine whether People is related to the parameter type "is-a", so calling setFirst method will report an error. It is legal to call the getFirst method of wildchards, because we know it will return a subclass of People, and a subclass of People "always is a People". (you can always convert subclass objects to superclass objects)

      while it is illegal to call getter methods for the supertype qualification of the wildcard, it is legal to call setter methods.

In addition to subtype and supertype qualifiers,       has a wildcard called unqualified wildcard, which looks like this: < ? > . When are we going to use this? Consider 1 in this scenario, we call 1 and it returns 1 getPairs method, which returns 1 set of Pair < T, T > Object. There is Pair < Student, Student >   and Pair < Teacher, Teacher > Object. (there is no inheritance between the Student and Teacher classes.) obviously, in this case, neither the subtype qualification nor the supertype qualification can be used. Here's how to do it:

Pair < ? > [] pairs = getPairs(...);


Related articles: