Java 5.0 generic programming

Source: Internet
Author: User
One of the new features of java5.0 is the introduction of generic type and generic method. A generic type is defined by using one or more type variables and has one or more types variables as placeholders for one parameter or return value. For example, java. util. List <E> is a generic type: A list. The element type is described by placeholder E. This type has a method named add (), which is declared as a parameter of the E type, and a get () method. The return value is declared as the E type.

To use generic types, you should specify the actual types for the type variables to form a parameterized type similar to list <string>. [1] The reason for specifying this additional type information is that the compiler can provide you with a strong type check during compilation to enhance the type security of your program. For example, if you have a list that can only keep the string object, this type of check will prevent you from adding the string [] object to it. Similarly, the added type information enables the compiler to perform some type conversion tasks for you. For example, the compiler knows that a list <string> has a get () method, and its return value is a string object. Therefore, you do not need to forcibly convert the return value from an object to a string.

The collection classes in the Java. util package have been made generic in java5.0, and you may frequently use them in your program. A type-safe collection class is a typical case of a generic type. Even if you have never defined your own generic type, you have never used Java. for generic types other than collection classes in util, the benefits of type-safe collection classes are also a significant sign-they prove the complexity of this main new language feature.

Starting from exploring basic generic usage in a collection class with type security, we will study more complex details about using generic types. Then we will discuss the type parameter wildcards and bounded wildcards. After describing how to use generics, We will illustrate how to write our own generic and generic methods. Our discussion of generics will end with an important generic trip to the core of Java APIs. This journey will explore these types and their usage to give you an in-depth understanding of how generics work.

Type security collection class

The Java. util class package contains the Java Collection framework (Java collections framework), which is a batch of set containing objects, list of objects, and map based on key-value. Chapter 5 describes collection classes. Here, we will discuss how to use the type parameter of the Collection class in java5.0 to define the object type in the set. This discussion is not suitable for Java or earlier versions. If no generic type exists, the programmer must remember the element types in each set for the use of the Collection class. When you create a set in java1.4, you know the type of the object you put into the set, but the compiler does not know. You must be careful to add an appropriate type of element to it. When you need to obtain an element from the set, you must explicitly write the forced type conversion to convert them from the object to their true type. Evaluate the Java code below.

public static void main(String[] args) {
    // This list is intended to hold only strings.
    // The compiler doesn't know that so we have to remember ourselves.
    List wordlist = new ArrayList();  

    // Oops! We added a String[] instead of a String.
    // The compiler doesn't know that this is an error.
    wordlist.add(args);

    // Since List can hold arbitrary objects, the get() method returns
    // Object.  Since the list is intended to hold strings, we cast the
    // return value to String but get a ClassCastException because of
    // the error above.
    String word = (String)wordlist.get(0);
}

The generic type solves the type security issues displayed in this Code. List or other collection classes in Java. util have been rewritten using generics. As mentioned above, a list is redefined as a list, and the element type in it is described by a placeholder with a variable name E. The add () method is redefined as an expected parameter of the E type to replace the previous object. The get () method is redefined as returning an E, replacing the previous object.

In java5.0, when we declare a list or create an arraylist instance, we need to keep a pair of "<>" after the name of the generic type ", the actual type required for writing in angle brackets. For example, a string-preserving list should be written as "list <string> ". It is very similar to passing a parameter to a method. The difference is that we use type instead of value, and use angle brackets instead of parentheses.

Elements in the Collection class of Java. util must be object-oriented, and they cannot be basic types. The introduction of generics has not changed this point. The generic type cannot use the basic type: we cannot declare -- set <char> or list <int> as follows. Remember, the automatic packaging and automatic unpacking features in Java make it as convenient to use set <character> or list <integer> as directly using char and INT values. (See Chapter 2 for more details about automatic packaging and automatic unpacking ).

In java5.0, the preceding example will be rewritten as follows:

public static void main(String[] args) {
    // This list can only hold String objects
    List<String> wordlist = new ArrayList<String>();

    // args is a String[], not String, so the compiler won't let us do this
    wordlist.add(args);  // Compilation error!

    // We can do this, though.  
    // Notice the use of the new for/in looping statement
    for(String arg : args) wordlist.add(arg);

    // No cast is required.  List<String>.get() returns a String.
    String word = wordlist.get(0);
}

It is worth noting that the amount of code is not much less than the original example without generics. The type conversion using "(string)" is replaced with the type parameter "<string> ". The difference is that the type parameter only needs to be declared once, and the list can be used multiple times without type conversion. In the example code with a longer point, this is more obvious. Even in those examples where generic syntax looks longer than non-generic syntax, the use of generics is still very valuable-Additional type information allows the compiler to perform stronger error checks in your code. Errors that can only be detected before they can only be found at runtime can now be found at compilation. In addition, to handle type conversion exceptions, we need to add additional code lines. If no generic type exists, a classcastexception will be thrown from the actual code when a type conversion exception occurs.

Just as a method can use any number of parameters, the class allows multiple types of variables. The Java. util. map interface is an example. A Map reflects the ing between an object of a key and an object of a value. Interface map declares a type variable to describe the key type and another type variable to describe the value type. For example, assume that you want to map a String object to an integer object:

public static void main(String[] args) {
    // A map from strings to their position in the args[] array
    Map<String,Integer> map = new HashMap<String,Integer>();

    // Note that we use autoboxing to wrap i in an Integer object.
    for(int i=0; i < args.length; i++) map.put(args[i], i);  

    // Find the array index of a word.  Note no cast is required!
    Integer position = map.get("hello");

    // We can also rely on autounboxing to convert directly to an int,
    // but this throws a NullPointerException if the key does not exist
    // in the map
    int pos = map.get("world");
}

Like list <string>, a parameter type is also a type and can be used as a type variable value of another type. You may see the following code:

// Look at all those nested angle brackets!
Map<String, List<List<int[]>>> map = getWeirdMap();

// The compiler knows all the types and we can write expressions
// like this without casting.  We might still get NullPointerException
// or ArrayIndexOutOfBounds at runtime, of course.
int value = map.get(key).get(0).get(0)[0];

// Here's how we break that expression down step by step.
List<List<int[]>> listOfLists = map.get(key);
List<int[]> listOfIntArrays = listOfLists.get(0);
int[] array = listOfIntArrays.get(0);
int element = array[0];

In the above Code, Java. util. list <E> and Java. util. the get () method of Map <K, V> Returns a list element of the E type or a map element of the V type. Note that in any case, generic types can use their variables more precisely. In the reference section of this book, View list <E>. You will see that its iterator () method is declared as returning an iterator <E>. This means that this method returns an instance of the same parameter type as the actual parameter type of the list. The following example provides a method for obtaining the first element of a list <string> without the get (0) method.

List<String> words = // ...initialized elsewhere...
Iterator<String> iterator = words.iterator();
String firstword = iterator.next();
II,
Understanding generic types

This section will further discuss the Usage Details of generic types to explain the following issues:

The consequence of Using Generics without a type parameter

Parameterized type system

A vulnerability about generic type security during compilation and a patch for ensuring runtime type security

Why is the array of parameterized types not of type-safe?

Unprocessed type and non-inspected warning

Even if the rewritten Java Collection classes bring the benefits of generics, you are not required to describe type variables when using them. A generic type without a type variable is considered as an unprocessed type (raw type ). In this way, Java code earlier than 5.0 can still run: You explicitly write all types of conversions, just as you have already written them, you may be troubled by some troubles from compilers. View the following objects that store different types of objects to an unprocessed list:

List l = new ArrayList();
l.add("hello");  
l.add(new Integer(123));
Object o = l.get(0);

This code runs well in java1.4. If you use java5.0 to compile it, javac will compile it, but the "complaint" will be printed ":

Note: test. Java uses unchecked or unsafe
Operations.
Note: recompile with-xlint: unchecked
Details.

If we add the-xlint parameter and re-compile it, we will see these warnings:

Test. Java: 6: Warning: [unchecked]
Unchecked call
Add (e) as a member of the raw type
Java. util. List
L. Add ("hello ");
^
Test. Java: 7:
Warning: [unchecked]
Unchecked call to add (e) as a member of the raw type
Java. util. List
L. Add (New INTEGER (123 ));
^

The compilation gives a warning on the call to the add () method, because it cannot be sure that the value added to the List has the correct type. It tells us that we use an unprocessed type, and it cannot verify that our code is type-safe. Note that the call to the get () method is correct, because the elements that can be obtained are stored in the list safely.

If you do not want to use any new features of java5.0, you can simply compile them by using the-source1.4 mark, so that the compiler will not "complain. If you cannot do this, ignore these warnings by using a "@ suppresswarnings (" unchecked ")" annotation (see section 4.3 of this chapter) conceal the warning information or upgrade your code, and add the type variable description. [2] The following sample code does not provide warnings during compilation, but allows you to add different types of objects to the list.

List<Object> l = new ArrayList<Object>();
l.add("hello");  
l.add(123);              // autoboxing
Object o = l.get(0);

Parameterized type system

Parameterized types have a type system, just like general types. This system is based on the object type, not the variable type. Here are some examples you can try:

ArrayList<Integer> l = new ArrayList<Integer>();
List<Integer> m = l;                            // okay
Collection<Integer> n = l;                      // okay
ArrayList<Number> o = l;                        // error
Collection<Object> p = (Collection<Object>)l;   // error, even with cast

A list <integer> is a collection <integer>, but not a list <Object>. This sentence is not easy to understand. If you want to understand why Generics do this, it is worth looking. Evaluate the knowledge of this Code:

List<Integer> li = new ArrayList<Integer>();
li.add(123);

// The line below will not compile.  But for the purposes of this
// thought-experiment, assume that it does compile and see how much
// trouble we get ourselves into.
List<Object> lo = li;  

// Now we can retrieve elements of the list as Object instead of Integer
Object number = lo.get(0);

// But what about this?
lo.add("hello world");

// If the line above is allowed then the line below throws ClassCastException
Integer i = li.get(1);  // Can't cast a String to Integer!

This is why list <integer> is not a list <Object>, although all the elements in list <integer> are actually an object instance. If list <Object> is allowed, theoretically non-integer objects are allowed to be added to list.

III,
Runtime type security

As we can see, a list <x> cannot be converted to a list <Y>, even if this X can be converted to y. However, a list <x> can be converted into a list, so that you can do this through the inherited method.

This ability to convert parameterized types to non-parameterized types is necessary for downward compatibility, but it will create a vulnerability in the generic type security system:

// Here's a basic parameterized list.
List<Integer> li = new ArrayList<Integer>();

// It is legal to assign a parameterized type to a nonparameterized variable
List l = li;  

// This line is a bug, but it compiles and runs.
// The Java 5.0 compiler will issue an unchecked warning about it.
// If it appeared as part of a legacy class compiled with Java 1.4, however,
// then we'd never even get the warning.  
l.add("hello");

// This line compiles without warning but throws ClassCastException at runtime.
// Note that the failure can occur far away from the actual bug.
Integer i = li.get(0);
Generic only provides type security during the compilation period. If you use the Java compiler to compile your code without any warning, these compiler checks ensure that your code is also type-safe at runtime. If you receive a warning or use code to modify your collection as you did for an unprocessed type, you need to add steps to ensure type security during runtime. You can do this by using the checkedlist () and checkedmap () methods in Java. util. Collections. These methods will package your set into a wrapper set, so that you can check at runtime to ensure that only the correct type of value can be placed into the set union. The following is an example of a type of security vulnerability that can be supplemented:

// Here's a basic parameterized list.
List<Integer> li = new ArrayList<Integer>();

// Wrap it for runtime type safety
List<Integer> cli = Collections.checkedList(li, Integer.class);

// Now widen the checked list to the raw type
List l = cli;  

// This line compiles but fails at runtime with a ClassCastException.
// The exception occurs exactly where the bug is, rather than far away
l.add("hello");
Parameterized Array

When using generic types, special consideration is required for arrays. Recall that if T is the parent class (or interface) of S, the array s [] of S type is also the array T [] of T type. Because of this, each time you store an object in an array, the Java interpreter must be checked to ensure that the object type you put is correct to the type allowed by the array to be stored. For example, the following code will check for failures at runtime and throw an arraystoreexception:

String[] words = new String[10];
Object[] objs = words;
objs[0] = 1;  // 1 autoboxed to an Integer, throws ArrayStoreException
Although obj is an object [] during compilation, It is a string [] At runtime and cannot be used to store an integer.

When we use generic types, it is not enough to store exception checks only by the runtime array, because a runtime check cannot obtain the type parameter information during compilation. View the following code:

List<String>[] wordlists = new ArrayList<String>[10];
ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add(123);
Object[] objs = wordlists;
objs[0] = ali;                       // No ArrayStoreException
String s = wordlists[0].get(0);      // ClassCastException!
If the above Code is allowed, the Array Storage check during runtime will be successful: There is no type parameter during compilation, and the code simply stores an arraylist to an arraylist [] array, very correct. Since the compiler cannot prevent you from using this method to overcome type security, it instead prevents you from creating an array of parameterized types. So the above plot will never happen, And the compiler starts to refuse to compile in the first line.

Note that this is not a constraint that uses generics when using arrays. It is just a constraint that creates an array of parameterized types. We will discuss this topic again when learning how to write generic methods.

Type parameter wildcard

Suppose we need to write a method to display the elements in a list. [3] in the past, we only needed to write code segments like this:

Public static void printlist (printwriter out, list ){
For (INT I = 0, n = List. Size (); I <n; I ++ ){
If (I> 0) Out. Print (",");
Out. Print (list. Get (I). tostring ());
}
}



In java5.0, list is a generic type. If we try to compile this method, we will get the unchecked warning. To solve these warnings, you may need to modify this method as follows:

public static void printList(PrintWriter out, List<Object> list) {
    for(int i=0, n=list.size(); i < n; i++) {
        if (i > 0) out.print(", ");
        out.print(list.get(i).toString());
    }
}
This code can be compiled without warning, but it is not very effective, because only lists declared as list <Object> are allowed to use this method. Remember, a list similar to list <string> and list <integer> cannot be converted to list <Object>. In fact, we need a type-safe printlist () method that can accept any list we pass in, regardless of why it is parameterized. The solution is to use the type parameter wildcard. The method can be modified as follows:

public static void printList(PrintWriter out, List<?> list) {
    for(int i=0, n=list.size(); i < n; i++) {
        if (i > 0) out.print(", ");
        Object o = list.get(i);
        out.print(o.toString());
    }
}
Methods of this version can be compiled without warning and can be used wherever we want. Wildcard "?" Represents an unknown type, type list <?> Read as "List of unknown"
As a general principle, if the type is generic and you do not know or care about the value type, you should use "?" Wildcard to replace an unprocessed type. Unprocessed types are allowed only for backward compatibility and should only be allowed to appear in old code. Note that, in any case, you cannot use wildcards when calling the constructor. The following code is invalid:

List <?> L = new arraylist <?> ();

Creating a list with unknown types is unreasonable. If you have created it, you must know the type of elements it will keep. You can traverse the list here in the subsequent method regardless of the element type, but you need to describe the element type when you create it. If you really need a list to maintain any type, you can only write it like this:

List<Object> l = new ArrayList<Object>();
In the above printlist () example, you must clarify the list <?> It is neither a list <Object> nor an unprocessed list. A list using wildcards <?> There are two important features. First, evaluate the methods similar to get (). They are declared to return a value of the type specified in the type parameter. In this example, the type is "unknown", so these methods return an object. Since we expect to call the tostring () method of this object, the program can well meet our wishes.

Second, evaluate the list method similar to add (). They are declared to accept a parameter, which is defined by the type parameter. Unexpectedly, when the type parameter is uncertain, the compiler does not allow you to call any method with an uncertain parameter type-because it cannot confirm that you have passed in an appropriate value. A list (?) It is actually read-only -- since the compiler does not allow us to call methods like add (), set (), addall.

Define wildcards

Let's make some small and slightly complex changes in our original example. Suppose we want to write a sumlist () method to calculate the total number type values in the list. Previously, we used unprocessed lists, but we didn't want to discard type security and had to handle unchecked warnings from the compiler. Or we can use list <number>, so we cannot call the methods in list <integer> and list <double>. In fact, we need to call them. If we use wildcards, we cannot actually get the expected type security, and we cannot determine what kind of list calls our method, number? Or a subclass of number? Even, String? Such a method may be written as follows:

public static double sumList(List<?> list) {
    double total = 0.0;
    for(Object o : list) {
        Number n = (Number) o;  // A cast is required and may fail
        total += n.doubleValue();
    }
    return total;
}
To modify this method to ensure true type security, we need to use the defined wildcard (bounded wildcard) to ensure that the List type parameter is unknown, but it is also a subclass of number or number. The following code is what we want:

public static double sumList(List<? extends Number> list) {
    double total = 0.0;
    for(Number n : list) total += n.doubleValue();
    return total;
}
Type list <? Extends number> can be interpreted as "List of unknown sub-classes of number ". Understanding this is very important. In this section, number is considered as its own subclass.

Note: In this case, those types are no longer needed for conversion. We do not know the specific types of elements in the list, but we know that they can be transformed up to number, so we can take them from the list as a number object. Using a for/in loop can slightly encapsulate the process of extracting elements from the list. The general principle is that when you use a defined wildcard, methods similar to the get () method in the list will return a value of the upper bound type. Therefore, if we call list. Get () in the for/in loop, we will get a number. In the previous section, when wildcard characters are used, they are similar to list. the limit in add () method is still valid: for example, if the compiler allows us to call such methods, we can put an integer in a list declared to only keep the short value.

It is also feasible to use lower-bound wildcards. The difference is to replace extends with super. This technique has a different effect on the called method. In practice, lower-bound Wildcards are less used than upper-bound wildcards. We will discuss this issue in a later chapter.
Related Article

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.