Java SE foundation consolidation (X): Generics

Java generics are Java5 launched a powerful feature, what is generic? The following definition is picked from Wikipedia:

Defining Generic mainly in the following two ways:

  1. Program code contained in some type parameter type, i.e. a generic class parameter can represent only, not represent individual objects. (This is more common in today's definitions)
  2. Program code contained in the parameters of some class . Which may be representative of a class or object parameters and the like. (Now most people call this template )

Regardless of which definition of use, generic parameters in real time using generics must make specified.

Some strongly typed programming languages support generics, and its main purpose is to enhance type safety and reduce the number of class switching, but some support generic programming language can only reach part of the purpose.

In Java generic definitions apply to the first, namely: in the program code comprises some type parameters of type, that is only the parameters of the generic class may represent, not represent individual objects.

What is the type parameter? Suppose you have got two identical containers (own imagination, what pots and pans), both are now empty, but you also do not want to mess things are entered, throwing, so do the two small piece of paper , written above "T-shirt", write a "shoes", are respectively attached to the two containers and the containers affixed to only T-shirt loaded T-shirt, shoes affixed to the container only installed shoes. In this small example, the content on the small piece of paper is called "type parameter."

The above example may not be appropriate (it is not a good example), but do not worry, when to see Java generics "like" below, and then recall this example, you will understand.

Use the 1 Java generics

There are three ways to use Java Generics are: introduced one by one generic class, generic interfaces, generic method, the following will these three ways.

1.1 generic class

When generic role in the class definition, the class is a generic class, in the JDK (after 1.5) have many generic class, e.g. ArrayList, HashMap, ThreadLocal etc., as follows:

public class MyList<T> {
   //.....
}
复制代码

The T is a generic identifier may be any character, but generally will use some common single character or a double character, e.g. T, K, V, E and so on. In the preparation of the class definition may be used instead of T type, for example:

//用在方法参数上和返回值上
//合法的
public T method1(T val) {
    //do something
    return (一个T类型的对象);
}

//不合法,不能用在静态方法
public static T method1(T val) {
    //do something
    return (一个T类型的对象);
}


//用在字段声明
private T val; //ok
private static T staticVal; //不合法,不能用在静态字段上
复制代码

As for why not on the static field or method, mentioned later realized generics will be talked about, we put that question here and there.

1.2 Generic Interface

JDK where there are a lot of generic interfaces, such as List, Map, Set, etc., when the generic role in the definition of the interface, this interface is a generic interface, for example:

public interface MyGenericInterface<T> {
	//用在抽象方法上
    T method1(T t);

    //或者默认方法也是可以的
    default T method2(T val) {
        
    }
    //但仍然不能作用在静态方法和静态字段上
    //不合法
    static T method3() {
        
    }
    
    //字段就很好理解了,怎么写都不像合法的
    T message = "MESSAGE"; //语法规定了接口里的字段默认是static final的,所以必须要有初始化值,但T不代表某个具体的类型,所以泛型字段根本不合理。
}
复制代码

Code comments written more clearly, not much to do illustrate, let's look at a generic method.

1.3 generic method

When acting on the generic method is a generic method. Note that method before and generic class or a generic interface in the use of generics here is different, we can either generic method definition in a generic class or a generic interface, you can also ordinary class or interface in generic method definition. More generic method and generic class defines generic interface is slightly more complex, as follows:

public <E>  E method1(E val) {
    return val;
}

//静态方法也是合法的
public static  <E>  E method2(E val) {
    return val;
}
复制代码

Herein after to identify generic modifier value before the return position, is not misplaced, the generic scope where E was limited to the identification method, i.e., the generic identification can simply be a local variable (actual not on). But why this time generics can act on a static method of it? Or before, to stay behind to explain.

2 generic role

The above summary describes three generic classes, generic interface and generic methods, but only describes how to define, does not describe how to use generics, in practice the process, come into contact with it comes the beginning of the article. " the concept of type parameters ", hoping to help the reader.

public class Main {

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        for (Integer integer : integers) {
            System.out.println(integer);
        }
    }
}
复制代码

The code is very simple, use the List interface and the ArrayList implementation class, pay attention to this line:

List<Integer> integers = new ArrayList<>();
复制代码

Integer so-called "type parameter", indicates that the container can be stored List Integer class in its subclass object instance, only the parameter type is a reference type, the type can not be substantially (e.g. int, double, char, etc.), the JVM will compile-time type checking will pass to ensure this. Assignment back number <> called "diamond operator", is a syntactic sugar Java7 provided for ease of use generics, the compiler will automatically infer the type parameters, here, for example, the compiler will automatically infer the type parameter is Integer, rather than the type parameter explicitly specified in the ArrayList, before Java7, above that line had written statement:

List<Integer> integers = new ArrayList<Integer>();
复制代码

After completion of the assignment statement and we went into the container "throw" the two elements 1 and 2, because of the automatic packing, 1 and 2 are packed into an instance of Integre class, and so the type of security problem does not occur, now suppose add the following statement:

integers.add("yeonon");
复制代码

What happens? Compilation error, the error message probably meant type mismatch. why? In fact, just it has been said, the container has a type parameter Integer, which indicates that the container can hold only Integer class with its subclass object instance, the presence of type checking mechanism if forced into instances of other types, because, so type mismatch exception occurs, this is the most important generic characteristics: type of security guarantee. In the absence of a generic mechanism, so we will use the container class:

List integers = new ArrayList();
integers.add(1);
integers.add(2);
integers.add("yeonon");
复制代码

Compiler that compiles happen, but there are some warning of it. This is the type of security issues, and why? For example, now I want to extract the container elements, you have to be cast, as shown below:

Integer i1 = (Integer) integers.get(0);
Integer i2 = (Integer) integers.get(1);
String s1 = (String) integers.get(2);
复制代码

Of course, you can not do type conversion, directly use the Object class to receive elements, but what is it? There is a light Object reference, almost no room for maneuver, eventually do type conversion.

Fortunately, there are only three elements, but clearly know the order of the elements, the first and second one is of type Integer, String is the third type, so you can make accurate casts. If it is below this happen?

public processList(List list) {
    //如何处理元素?
}
复制代码

In processList method, List is passed in from the outside, completely unaware of what is in the List something, if reckless will turn into a strong element of some kind, it is very likely a strong turn abnormal, and the abnormal or run when abnormal, that is not sure what will happen when an exception! You might say, that method to write documentation, instructions stored in the List element is of type Integer, and then ask the client must pass all elements of Integer List, this is not to complete a thing? Indeed, this is a solution, but it is only in the development of "agreement", and this agreement belongs to the "gentlemen's agreement", the client may well be for a variety of reasons in violation of this agreement (such as the client has been compromised , or if the caller did not notice the "agreement"), so it is still possible types of security problems.

Through this example, I think the reader can already feel the benefits brought about by the generics, generic type of error can be found at compile time, and issues an error report, suggesting programmer! This makes the type of security problem does not occur in uncontrolled run-time, but appear in a controlled compile time, this feature allows security Java language greatly improved.

That generics in Java is how to achieve it? The answer is through "erase" to achieve.

3 generic erase

Java implementation of generics often heard in the forums, the community is a pseudo-generics, and C #, C ++ generic implementation is the real generics. So there is a reason, because Java byte code compiler source code in any type parameter does not exist. For example, the conventional following code:

public class Main {

    public static void main(String[] args) {
        List<Integer> integers = new ArrayList<>();
        integers.add(1);
        integers.add(2);
        for (Integer integer : integers) {
            System.out.println(integer);
        }
    }
}
复制代码

Use Javac compiled, the compiled .class file contents as follows (I'm using IDEA to open, if you use other tools, may be slightly different):

public class Main {
    public Main() {
    }

    public static void main(String[] var0) {
        ArrayList var1 = new ArrayList();
        var1.add(1);
        var1.add(2);
        Iterator var2 = var1.iterator();

        while(var2.hasNext()) {
            Integer var3 = (Integer)var2.next();
            System.out.println(var3);
        }

    }
}
复制代码

Found that indeed there was no similar character, in other words, the type parameter is "erased" the. Instead, when there is a need for type conversion, the compiler help us add a cast of grammar, sentence such as:

Integer var3 = (Integer)var2.next();
复制代码

As can be seen from here, the JVM does not know the information of the type of the parameter (JVM recognize only the byte code), after which it knows to answer two questions can be left above the.

Why generic class and generic interfaces, generics can not be applied to the static method or static field?

Static field or static method is part belong to the class information, and only one memory area in the method, a plurality of different instances of the class can be shared, even if the compiler knows the type of information, special treatment, can not determine the amount of static a certain type. Static assumption allows static field or method, as shown in the following code:

public A<T> {
    public static T val;
}

public static void main(String[] args) {
    A<Integer> a1 = new A<>();
    A<String> a2 = new A<>();
    System.out.println(a1.val);
    System.out.println(a2.val);
}
复制代码

val should be here in the end is what type it? If the program can run normally, then only one possible, is to have a static amount of two different types, but knowledge of the virtual machine tells us, which is obviously non-compliant, so this use is not allowed.

In turn look ordinary instance methods and fields, because ordinary instance methods and fields that can have multiple parts (one for each object), so the compiler can determine the type of object instances in the instance methods and fields of the type parameter . Note that, here, the type information is known to the compiler, the virtual machine is not known, the compiler can do for each instance of an object type checking of different types of parameters, the type of conversion operation. A1 and a2 such as the above objects, the compiler knows the type of their parameters are Integre and String, it can be done at compile time type checking on them, type conversion.

Why Generic methods can make generic action on a static amount of it?

In fact, this is the compiler of the "trick." Look at an example:

public class Main {

    public static void main(String[] args) {
        MyList<Integer> list1 = new MyList<>();
        MyList<String> list2 = new MyList<>();

        MyList.method2(1);
        MyList.method2("String");
    }
}
复制代码

After javac compiler, used to view the bytecode information javap, substantially follows (irrelevant portions are omitted):

   #21 = NameAndType        #28:#29        // method2:(Ljava/lang/Object;)Ljava/lang/Object;
   
 
 		20: invokestatic  #5                  // Method top/yeonon/generic/MyList.method2:(Ljava/lang/Object;)Ljava/lang/Object;
        23: pop
        24: ldc           #6                  // String String
        26: invokestatic  #5                  // Method top/yeonon/generic/MyList.method2:(Ljava/lang/Object;)Ljava/lang/Object;
复制代码

20 and 26 found in the number of calls method2 method from the constant pool # 21 can be seen, the parameter method2 is of type Object, described in a virtual machine, but the actual type of generic parameters of type Object, without departing virtual machine specification. What type of inspection ah, automatic type inference, the type conversion ah are added by the compiler.

More about generics erased, recommendations lot of reference materials, combined with javac, javap and other tools for research.

4 generics wildcards

In generic systems, generally have the following declare a generic way:

  • . The simplest statement, T can represent any type, but when it is finalized type can only represent a certain type, such as a List, T represents the String, can not represent other types.
  • . Unbounded wildcard form, in this case, the type parameter may be any type, e.g. Class, but this form is read-only, i.e., changing the value, the return value is generally used in a method or a method parameter.
  • . Bounded wildcards form, which may be a type parameter T type and its subtypes. For example, the statement List, then this list can be inserted int type, can also be inserted into the long type, subtype, where T is the Number, which is a subtype such as Interger, Long, etc. are of Number. As shown in the following code: `` `java // E is a generic class declaration time. We use the method bounded wildcards, so that a plurality of types of acceptable values ​​public void pushAll (Iterable iterable) {for (E e: iterable) {push (e);}} // test class to create a type parameter Integer and Double as a List, use pushAll method, it can be normal operation, if pushAll method does not use generics wildcards, then it can only be inserted one type of element. public static void main (String [] args) {MyStack myStack = new MyStack <> (); List integers = new ArrayList <> (); integers.add (1); integers.add (2); List doubles = new ArrayList <> (); doubles.add (1.0); doubles.add (2.0); myStack.pushAll (integers); myStack.pushAll (doubles); while {System.out.println (myStack (myStack.isEmpty ()!) .pop ());}} `` `
  • . And similar to that above, but is only adapted to the type of T or T parent class.

Using bounded wildcards can improve the flexibility of generics, generics can make while working for a variety of types, so that we do not need as many types of writing similar code, on the other hand offers reusability of code. But the proper use bounded wildcards would be more difficult, the most troublesome is how to determine there is an upper bound wildcard still lower bound wildcard? "Effective Java" book gives a principle: PECS (Producer-the extends, Consumer-Super) . That is for producers, use wildcard upper bound (extends, the upper bound is T), for consumers, there is a lower bound of use wildcards (super, the lower bound is T).

Now we have a new problem, how to distinguish between consumers and producers. In simple terms, for the collection, the consumer is the use of container elements such List.sort (Comparator <? Super E> c), sort need to use the elements inside the list, so this method is the consumer, based on the principles of PECS, parameter method declaration should be a lower bound wildcard. As another example List.addAll (Collection <? Extends E> c) method, the addAll element is inserted into the container, the producer, according to the principle of PECS, using the method parameters have to be bounded on the wildcard.

Although bounded wildcards can improve the flexibility of the API, but if the method is not a consumer or producer, then do not use bounded wildcards, and can be used directly, try to keep it simple API also our design principles.

In short, using bounded wildcards can greatly API provides flexibility, but in the design of API, should try to keep it simple, and follow the principles of PECS.

5 generic arrays

And an array of generic container class is very different, the JVM the A [] and B array [] array as two different types, and the List and the List as a Type List same, assuming the array to create a generic , the code shown below:

public class Main {

    public static void main(String[] args) {
        List<String>[] stringLists = new List<String>[1]; //1
        List<Integer> integerList = new ArrayList<>();  //2
        integerList.add(0);  //3
        Object[] objects = stringLists; //4
        objects[0] = integerList; //5
        String s = stringLists[0].get(0); //6
    }
}
复制代码

Some of the code around, we analyze line by line:

  1. Line 1 creates a generic array stringLists, the type of array elements is List, legal (our assumptions).
  2. Line 2 creates a List container object integerList.
  3. Line 3, to insert an element in integerList.
  4. Line 4, assigned to the stringLists type Object [] array. Assignment is permitted here, it belongs up type conversion.
  5. Line 5, set the first element of the array is integerList obejcts, this is legal because the List of the top-most parent class is Object, pay attention here integerList type List.
  6. Line 6, the question is, get the first element stringLists List (in fact integerList), and get the first element of the List of the (type of the element is actually Integer), but the compiler that since acquired from stringLists in , which should be stored in a List of String type of element, so here assigned to the String reference type conversion is not necessary. But in fact, there should be Integer type, but the type of conversion will throw an exception at run time.

This is a problem caused by a generic array, the most fundamental reason is because the mechanism of generics erased, the virtual machine can not distinguish between List and List, so in order to avoid this problem is hard to find, simply create a generic array of the ban.

While there are ways to circumvent restrictions create a generic array, but preferably not so dry, because it will lose the benefits of type safety problems found at compile generics brought more harm than good.

6 Summary

This paper briefly describes the generic, also spoke about the generic implementation: erased. To be honest, generics are more complex and difficult knowledge point, want to understand thoroughly, need to have some experience in the use of generics, or a pit had been so true, otherwise it will always feel this thing a bit generic "illusory." As for how to learn, my experience was reading JDK source code, note how the JDK is to use generics.

7 References

"Effective Java" third edition (English version)

Guess you like

Origin juejin.im/post/5d6e808c5188252d43758f8c