Java generics in-depth explanation

Source: Internet
Author: User
Tags comparable object object

Why introduce generics

Bug is part of programming, we can only do our best to reduce the chance of bugs, but no one can guarantee that the program you write does not have any problems.

Errors can be divided into two types: compile-time errors and run-time errors. Compile-time errors can be found and excluded at compile time, while run-time errors are very uncertain and can only be discovered when the program is running, and the consequences can be catastrophic.

Using generics can cause errors to be detected at compile time, thereby increasing the robustness of the program.

Take a look at an example:

public class box{    private Object object;     public void Set (Object object) {            this.object= object;       }    public object Get () {             return object;       }}

By declaring that the set () method can accept any Java object as a parameter (any object is a subclass of object), if the class is used somewhere, the set () method expects the input object to be an integer type, but the actual input is a string type. A run-time error is thrown, and this error is not detectable during the compile phase. For example:

Box box = new box; Box.set ("abc"); Integer a = (integer) box.get ();  Compile without error, but run-time will be reported classcastexception
Use generics to transform the code above:

public class box<t>{    private T T;     public void set (T t) {            this.t= t;       }    Public T get () {             return T;       }}

When we use this class, we specify the concrete type of T, which can be a class, an interface, an array, and so on, but cannot be a basic type。

Like what:

box<integer> box = new box<integer>; The Type type Integer//box.set ("abc") is specified;  This sentence will be error box.set (new Integer (2)) at compile time; integer a = Box.get ();  No conversion type

As you can see, using generics also removes conversion operations.

Before introducing a generic mechanism, to support multiple data types in a method, you need to overload the method, and after introducing the paradigm, you can solve the problem more succinctly, further defining the relationships between multiple parameters and the return value.

For example

public void Write (Integer i, integer[] ia);p ublic void Write (Double  D, double[] da);p ublic void Write (Long l, long[] LA);

The model version is

Public <T> void Write (T T, t[] ta);
In general, the generic mechanism can use the "type" as a parameter when defining classes, interfaces, and methods, which is somewhat similar to the formal parameters in a method declaration, so that we can implement the reuse of the program through different input parameters. The difference is that the input of the formal parameter is a value, and the input of the generic parameter is the type.


Naming rules

The naming of type parameters has a set of default rules, which are strongly recommended in order to improve the maintainability and readability of the code. The application of these naming conventions is ubiquitous in the JDK.

E -element (usually representing the elements in the collection Class)

K -Key

N -Number

T -Type

V -Value

s,u,v etc.– Second, third, fourth type parameter ...

Note that the type parameters defined by the parent class cannot be inherited by the quilt class.

You can also declare multiple types of variables at the same time, separated by commas, for example:

Public interface Pair<k, v> {public    K GetKey ();    Public V GetValue ();} public class Orderedpair<k, v> implements Pair<k, v> {     private K key;    private V value;     Public Orderedpair (K key, V value) {      This.key = key;      This.value = value;    }     Public K GetKey ()   {return key;}    Public V GetValue () {return value;}}

The following two lines of code create two instances of the Orderedpair object.

pair<string,integer> p1 = new orderedpair<string, integer> ("even", 8); pair<string,string>  p2 = new orderedpair<string, string> ("Hello", "World");//You can also omit the type parameter after new, abbreviated as: pair<string,integer> P1 = new Orderedpair<> ("even", 8); Type variables with type variables can also be used within angle brackets, such as:orderedpair<string,box<integer>> p = new orderedpair<> ("Primes", new Box<integer> (...));
Generics are introduced after JDK 5.0, and for compatibility, it is permissible to not specify generic parameters, but as a result, the compiler cannot perform type checking, and it is best to explicitly specify generic parameters when programming.

Similarly, a generic parameter is used in a method, and the parameter is scoped to the method body only. For example:

public class Util {//This method is used to compare the equality of two pair objects. The generic parameter must be written before the method return type Boolean public    static <k, v> Boolean compare (pair<k,v> P1, pair<k, v> p2) {        Return P1.getkey (). Equals (P2.getkey ()) &&              p1.getvalue (). Equals (P2.getvalue ());}    } pair<integer,string> P1 = new pair<> (1, "Apple"); pair<integer,string> P2 = new Pair<> (2, "pear"); Boolean same = Util.<integer, String>compare (P1, p2); /In fact, the compiler can infer the type that compare needs to use by the type in the pair, so it can be abbreviated as:/boolean same= Util. Compare (P1, p2);
Sometimes we want to limit the type parameter to a certain range, we need to use the Extends keyword (extends can be followed by an interface, where the extends can either represent inheriting a class or implement an interface), for example, we want the parameter to be a numeric type:

Class Box<t extends number>{  //The subclass of type parameter limited to number is the           private T T;           Public Box (T t) {             this.t = t;      }      public void print () {             System.out.println (T.getclass (). GetName ());      }           public static void Main (string[] args) {              box<integer> box1 = new box<integer> (new Integer (2));             Box1.print ();  Print Result: Java.lang.Integer             box<double> box2 = new box<double> (new Double (1.2));             Box2.print ();  Printed result: java.lang.Double                         box<string> box2 = new box<string> (New String ("abc"));//Error, Because the string type is not a subclass of number             box2.print ();}      }
If you add more than one qualification, you can connect with "&", but because Java is a single inheritance, multiple qualified there can be at most one class, and must be placed in the first position。 For example:

Class Box<t extends number & cloneable & comparable >{//the type must be a subclass of number and implements the Cloneable interface and comparable interface. ......}

Inheritance of generic classes

Java is an object-oriented high-level language, and it is permissible to pass in a subclass of a where a Class A parameter is accepted, for example:

Object someobject = new Object (), Integer someinteger = new Integer (ten), Someobject =someinteger;   Because integer is a subclass of object

This feature also applies to type parameters, such as:

box<number> box = new box<number> (); Box.add (new Integer);   Integer is the subclass of number Box.add (new Double (10.1));  Double is also a subclass of number

However, there is a situation that can easily cause confusion, such as:

The method accepts a parameter of type box<number>public void Boxtest (box<number> N) {...} The following two kinds of calls will be error boxtest (box<integer>); Boxtest (box<double>);

Although both Integer and Double are subclasses of number, box<integer> and <Double> are not subclasses of box<number> , and there are no inheritance relationships. The common parent class of box<integer> and Box<double> is object.

Taking the collection class in the JDK as an example,arraylist<e> implements the List<e> interface,list<e> interface inherits the Collection<e> interface, so,arraylist< String> is a subclass of list<string>, not a subclass of list<integer>. The three inherited relationships are as follows:



Type extrapolation (type inference)

Let's look at an example:

public class demo{       static <T> T pick (t A1, T A2) {             return a2;      }}

The static method pick () uses generics in three places and limits the type and return type of two input parameters, respectively. The code to invoke the method is as follows:

The integer ret =demo.<integer> pick (new Integer (1), new Integer (2));//mentioned earlier, the above code can be abbreviated as: Integer ret =demo. Pick (New Integer (1), new Integer (2));

Because the Java compiler infers that the type returned by the method should be an integer based on the type of the parameter within the method, this mechanism is called type inference

So the question is, what type should be returned by adding two input parameters to a different type?

For example:

Pick ("D", New Arraylist<string> ());

The first argument is a string type, the second argument is the ArrayList type, and the Java compiler infers it based on the two parameter types, making the return type the most explicit. In this example, both string and ArrayList implement the same interface--serializable, of course, they are also subclasses of object, and the Serializable type is clearly more explicit than the object type, because its scope is smaller and more granular, So the final return type should be serializable:

Serializable s =pick ("D", New Arraylist<string> ());

This mechanism can also be used to simplify the code when the generic class is instantiated, and it is important to note that the angle brackets "<>" cannot be omitted at this time. For example:

map<string,list<string>> MyMap = new hashmap<> ();//The compiler can infer the following type, so it can be simplified to:map<string,list< string>> MyMap = new hashmap<> ();//However, cannot be simplified to:map<string,list<string>> myMap =new hashmap< > ();//Because HashMap () is the constructor of the HashMap primitive type (raw), not the Hashmap<string,list<string>> constructor, if the "<>" is not added compiler does not perform type checking

Wildcard characters

We have mentioned an example above:

public void Boxtest (box<number> n) {             ...}

This method can only accept box <Number>This type of argument when we enter a box <Double>or box. <Integer>Error, although the integer and double are the subclasses of number. But what if we want the method to accept number and any of its subclasses?

This is the time to use the wildcard character, rewritten as follows:

public void Boxtest (box<? extends number> N) {             ...}


"? Extends number " means that you can accept number and its subclasses as arguments. This declarative approach is called the upper bound wildcard (upper Boundedwildcard).

Conversely, what if we want the method to accept Integer,number and object type parameters? You should use the lower bound wildcard character (lower bounded wildcard):

public void Boxtest (BOX<? super integer> N) {...}


"? Super integer represents the ability to accept an integer and its parent class as arguments.

If the type parameter has neither the extends keyword nor the Super keyword, there is only one?, which represents the unqualified wildcard character (unbounded wildcards).

Unqualified wildcard characters are usually used in two cases:

(1) If you are writing a method, you can use the functionality provided in the object class to implement

(2) The functionality of the code implementation is independent of the type parameters, such as the List.clear () and List.size () methods, as well as the frequently used class<?> methods, which do not have the functionality to implement the type parameters.

Take a look at an example:

public static void Printlist (List<object> List) {for    (Object elem:list)        System.out.println (Elem + ""); 
    system.out.println ();}

This method only accepts parameters of type list<object> and does not accept parameters of any other type. However, the functionality implemented by this method does not have anything to do with the parameter types in the list, so we want it to accept list parameters that contain any type. The code changes are as follows:

public static void Printlist (List<?> List) {for    (Object elem:list)        System.out.println (Elem + "");    System.out.println ();}

It is important to note that,list<?> is not the same as list<object>, no matter what type of,list<a> is a subclass of list<?>, but,list<a> is not a List Sub-class of <Object>.

For example:

list<number> lb = new arraylist<> ();

list<integer> la = lb; The compilation error is reported, although Integer is a subclass of number, but list<integer> is not a subclass of list<number>

The relationship between List<integer> and List<number> is as follows:


So, the following code is correct:

list<? Extends integer> intlist = new arraylist<> (); list<? Extends number>  numlist = intlist;  No error, list<? Extends integer> is list< Subclasses of extends Number>

The following diagram shows the relationship between the upper bound wildcard, the lower bound wildcard, and the unqualified wildcard:

The compiler can determine the type of the wildcard character through the type inference mechanism, which is known as a wildcard capture . Most of the time we don't have to worry about wildcard captures unless the compiler reports an error that contains "capture of". For example:

public class Wildcarderror {     void foo (list<?> ii) {             i.set (0, I.get (0));  will report a compilation error}}

In the example above, when calling the List.set (int,e) method, the compiler cannot infer what type of i.get (0) It is, and it will report an error.

We can resolve this error by using a private helper method that captures wildcards:

public class Wildcardfixed {     void foo (list<?> i) {        foohelper (i);    }      This method ensures that the compiler infers the parameter type through wildcard captures    : private <T> void Foohelper (list<t> l) {        l.set (0, L.get (0));}    }

According to customary practice, the helper method is named "original method" + "helper", in the above example, the original method is "foo", so named "Foohelper"

When to use the upper bound wildcard, when to use the lower bound wildcard, you should follow a few guidelines.

First, the variables are divided into in-variables and out-variables : The in-variable holds the data for the current code service, and the out-variable holds the data that needs to be used elsewhere. For example, the copy (SRC, dest) method implements the ability to copy data from the SRC source to the dest destination, so src is the in-variable, and dest is the out-variable. Of course, in some cases, a variable may be both a in-variable and a out-variable.

(1) The in-variable uses the upper bound wildcard character;

(2) The out-variable uses a lower bound wildcard character;

(3) When a in-variable can be accessed by a method in the object class, the unqualified wildcard is used;

(4) When a variable is both a in-variable and a out-variable, wildcard characters are not used

Note that the above rule does not apply to the return type of the method.


Type erase (Type Erasure)

When the Java compiler handles generics, it does several things:

(1) Replace the non-qualified type parameter with object to ensure that the class file contains only normal classes, interfaces and methods;

(2) When necessary, the type conversion, to ensure the type safety;

(3) using the bridging method (bridge methods) on the inheritance of generics to preserve polymorphism.

Such operations are known as type erasure .

For example:

public class Node<t> {     private T data;    Private node<t> Next;     Public Node (T data, node<t> next)}        this.data = data;        This.next = next;    }     Public T GetData () {return data;}    // ...}

The T in this class is not qualified by extends or super, and is replaced by the compiler with object:

public class Node {     private Object data;    Private Node Next;     Public Node (Object data, node next) {        this.data = data;        This.next = next;    }     Public Object GetData () {return data;}    // ...}

If T is qualified, the compiler will replace it with the appropriate type:

public class Node<t extends comparable<t>> {     private T data;    Private node<t> Next;     Public Node (T data, node<t> next) {        this.data = data;        This.next = next;    }     Public T GetData () {return data;}    // ...}

Changed to:

public class Node {     private comparable data;    Private Node Next;     Public node (comparable data, node next) {        this.data = data;        This.next = next;    }     Public comparable GetData () {return data;}    //...}

The type erasure in the method is similar to that of the.

Sometimes type erasure produces something we don't anticipate, and here's an example of how it's produced.

public class Node<t> {public     T data;     Public Node (T data) {this.data = data;}     public void SetData (T data) {       System.out.println ("Node.setdata");        This.data = data;}    } public class Mynode extends node<integer>{public    mynode (Integer data) {super (data);}     public void SetData (Integer data) {       System.out.println ("Mynode.setdata");        Super.setdata (data);}    }


The above code defines two classes, the Mynode class inherits the node class, and then runs the following code:

Mynode mn = new Mynode (5); Node n =mn;           N.setdata ("Hello");    Integer x =mn.data;    Throw ClassCastException exception

The above code is converted to the following form after the type erase:

Mynode mn = new Mynode (5); Node n = (mynode) mn;        N.setdata ("Hello"); Integer x = (String) mn.data;   Throw ClassCastException exception

Let's take a look at how the code executes:

(1)n.setdata ("Hello") is actually called the setData (Object) method of the Mynode class (inherited from the node class);

(2) n the data field in the referenced object is assigned a string variable;

(3) data in the same object referenced by MN is expected to be of type Integer (MN is node<integer> type);

(4) The fourth line of code attempted to assign a string to a variable of type integer, so a ClassCastException exception was thrown.

When compiling a class or an excuse that inherits a parameterized generic, the compiler creates a bridging method called bridge methods as needed, which is part of the type erasure.

In the example above, Mynode inherits from the Node<integer> class, and after the type erasure, the code becomes:

Class Mynode extends Node {     //Compiler added bridge method public    void SetData (Object data) {        setData ((Integer) data);    }       ///Mynode This method does not overwrite the SetData (Objectdata) method of the parent class, because the parameter type is not the same as public    void SetData (Integer data) {       System.out.println ("Mynode.setdata");        Super.setdata (data);    }     // ...}

Precautions

In order to use generics efficiently, there are a few things to note:

(1) cannot instantiate type parameter with base type

For example

Class Pair<k,v> {     private K key;    private V value;     Public Pair (K key, V value) {        This.key = key;        This.value = value;    }     // ...}

When creating a pair class, you cannot replace the K,V two type parameter with the base type.

pair<int,char> p = new Pair<> (8, ' a '); Compilation error Pair<integer,character> p = new Pair<> (8, ' a '); Correct wording

(2) Non-instantiation of type parameters

For example:

public static <E> void append (list<e> List) {    e elem = new E ();  Compilation error    List.add (elem);}

However, we can instantiate an object with a type parameter by reflection:

public static <E> void append (list<e> List, class<e> cls) throws exception{    E elem = cls.new Instanc E ();   Correct    List.add (elem);} list<string> ls = new arraylist<> (); append (ls,string.class);  Class object passing in the type parameter

(3) cannot use generics on static fields

This is illustrated by a counter-example :

public class MobileDevice <T> {    private static T os;  Suppose we define a static field with a generic type     //...} mobiledevice<smartphone> phone = new mobiledevice<> (); mobiledevice<pager> Pager = new mobiledevice<> (); mobiledevice<tabletpc> pc = new mobiledevice<> ();

Because a static variable is a class variable that is shared by all instances, what is the true type of the static variable OS? Obviously not at the same time is smartphone, Pager, TabletPC.

This is why you cannot use generics on static fields.


(4) You cannot use a class with a parameterized type castOr instanceofMethod

Public static<e> void Rtti (list<e> list) {    if (list instanceof arraylist<integer>) {  //Compile error ' c7/>//...    }}

Passed to the lid the set of parameterized types for this method is:

S = {arraylist<integer>,arraylist<string> linkedlist<character>, ...}

The operating environment does not track the type parameters, so arraylist<integer> and arraylist<string> can not be distinguished, and the most we could do is to use unqualified wildcard characters to verify that the list is ArrayList:

public static void Rtti (List<?> list) {    if (list instanceof arraylist<?>) {  //correct        //...    }}

Similarly, you cannot convert a parameter to an object with a parameterized type, unless it has a parameterized type of unqualified wildcard character (<?>):

list<integer> li = new arraylist<> (); list<number>  ln = (list<number>) li;  Compile error

Of course, if the compiler knows that the parameterized type is definitely valid, this conversion is allowed:

list<string> L1 = ...; arraylist<string> L2 = (arraylist<string>) L1;  Allow change, type parameter unchanged

(5) You cannot create an array with a parameterized type

For example:

list<integer>[] arrayoflists = new list<integer>[2]; Compile error

Here are two pieces of code to explain why not. Let's look at a normal operation:

Object [] strings= new string[2];string s[0] = "HI";   Insert Normal string s[1] =100;    Error because 100 is not a string type

The same operation, if you are using a generic array, you will get a problem:

object[] stringlists = new list<string>[]; The code will actually get an error, but let's assume it can execute String lists[0] =new arraylist<string> ();   Insert Normal string lists[1] =new arraylist<integer> ();  The code should report arraystoreexception exceptions, but the operating environment cannot detect

(6) Cannot create, catch generic exception

Generic classes cannot inherit throwable classes directly or indirectly

Class Mathexception<t> extends Exception {/* ... */}    /Compile error class Queuefullexception<t> extends Throwabl e {/* ... */}//Compilation error

The method cannot catch a generic exception:

Public Static<t extends Exception, j> void execute (list<j> jobs) {    try {for        (J job:jobs)            ///: .    } catch (T e) {   //Compile error        //...    }}

However, we can use the type parameter in the throw clause:

Class Parser<t extends exception> {public    void parse (file file) throws t{     //correct        //...    }}


(7) You cannot overload a method that passes type erase to the same primitive type

Take a look at the code first:

list<string> L1 = new arraylist<string> (); list<integer> L2 = new arraylist<integer> (); System.out.println (l1.getclass () = = L2.getclass ());
The print result may not be the same as we guessed, the print is true, not false, because all instances of a generic class have the same runtime class (class) at run time, regardless of their actual type parameters.

In fact, generics are called generics because they have the same behavior for all of their possible type parameters, and the same classes can be treated as many different types.

Recognizing this, take a look at the following example:

public class Example {public    void print (Set<string> strset) {}  //Compilation error public    void print (set< Integer>intset) {}  //Compilation Error}

Because Set<string> and set<integer> are essentially part of the same runtime class, the above two methods share a method signature, which is equivalent to a method, so an overload error occurs after the type is erased.


Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Java generics in-depth explanation

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.