Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ganesh_JavaSE7_Programming_1z0-804_study_guide.pdf
Скачиваний:
94
Добавлен:
02.02.2015
Размер:
5.88 Mб
Скачать

Chapter 6 Generics and Collections

Why doesn’t subtyping work for generic type parameters? Let’s look at what can go wrong if you assume that you can use subtyping for generic type parameters.

// illegal code – assume that the following intialization is allowed List<Number> intList = new ArrayList<Integer>();

intList.add(new Integer(10)); // okay intList.add(new Float(10.0f)); // oops!

The intList of List<Number> type is supposed to hold an ArrayList<Number> object. However, you are storing an ArrayList<Integer>. This looks reasonable since List extends ArrayList and Integer extends Number. However, you can end up inserting a Float value in the intList! Recall that the dynamic type of intList is the

ArrayList<Integer> type—so you are violating type safety here (and thus will get the compiler error of incompatible types). Since generics are designed to avoid type-safety mistakes like this, you cannot assign a derived generic type parameter to a base type parameter.

As you can see, subtyping for generic parameter types is not allowed because it is unsafe—but still it is an inconvenient limitation. Fortunately, Java supports wildcard parameter types in which you can use subtyping. We’ll explore that capability now.

Type parameters for generics have a limitation: generic type parameters should match exactly for ­assignments. To overcome this subtyping problem, you can use wildcard types.

Wildcard Parameters

You saw in the preceding section that subtyping doesn’t work for generic type parameters. So,

List<Number> intList = new ArrayList<Integer>();

gives the compiler error of

WildCardUse.java:6: incompatible types

found : java.util.ArrayList<java.lang.Integer> required: java.util.List<java.lang.Number>

List<Number> numList = new ArrayList<Integer>();

If you slightly change the statement to use wildcard parameter, it will compile

List<?> wildCardList = new ArrayList<Integer>();

What does a wildcard mean? Just like the wildcard you use for substituting for any card in a card game (ah, it’s so fun to play card games!), you can use a wildcard to indicate that it can match for any type. With List<?>, you mean that it is a List of any type—in other words, you can say it is a “list of unknowns!”

But wait a minute . . . when you want a type indicating “any type,” you use the Object class, don’t you? How about the same statement, but using the Object type parameter?

List<Object> numList = new ArrayList<Integer>();

163

Chapter 6 Generics and Collections

No luck—you get the same error you got above using List<Number>!

WildCardUse.java:6: incompatible types

found : java.util.ArrayList<java.lang.Integer> required: java.util.List<java.lang.Object>

List<Object> numList = new ArrayList<Integer>();

In other words, you are still trying to use subtyping for generic parameters—and it still doesn’t work. As you can see, List<Object> is not same as List<?>. In fact, List<?> is a supertype of any List type, which means you can pass

List<Integer>, or List<String>, or even List<Object> where List<?> is expected. Let’s use the wildcard in an example and see whether it’ll work (see Listing 6-12).

Listing 6-12.  WildCardUse.java

// This program demonstrates the usage of wild card parameters class WildCardUse {

static void printList(List<?> list){ for(Object l:list)

System.out.println("[" + l + "]");

}

public static void main(String []args) { List<Integer> list = new ArrayList<>(); list.add(10);

list.add(100);

printList(list);

List<String> strList = new ArrayList<>(); strList.add("10");

strList.add("100");

printList(strList);

}

}

This program prints the following:

[10]

[100]

[10]

[100]

Well, it works, and the list using wildcard can be passed list of integers as well as list of strings. This happens because of the parameter type of printList() method—List<?>. That’s great!

Limitations of Wildcards

Let’s consider the following snippet, which tries to add an element and print the list:

List<?> wildCardList = new ArrayList<Integer>(); wildCardList.add(new Integer(10)); System.out.println(wildCardList);

164

Chapter 6 Generics and Collections

You get the following compiler error:

WildCardUse.java:7: cannot find symbol symbol : method add(java.lang.Integer)

location: interface java.util.List<capture#145 of ? extends java.lang.Number> wildCardList.add(new Integer(10));

Why? You are absolutely sure that the add() method exists in the List interface. Then why doesn’t the compiler find the method?

The problem requires some detailed explanation. When you use wildcard type <?>, you say to the compiler that you are ignoring the type information, so <?> stands for unknown type. Every time you try to pass arguments to a generic type, the java compiler tries to infer the type of the passed argument as well as the type of the generics and to justify the type safety. Now, you are trying to use the add() method to insert an element in the list. Since wildCardList doesn’t know which type of objects it holds, it is risky to add elements to it. You might end up

adding a string—“hello”, for example—instead of an integer value. To avoid this problem (remember, generics was introduced in the language to ensure type safety!), the compiler doesn’t allow you to call methods that modify the object. Since the add method modifies the object, you get an error! The error message also looks confusing, as in

<capture#145 of ? extends java.lang.Number>.

In general, when you use wildcard parameters, you cannot call methods that modify the object. If you try to modify, the compiler will give you confusing error messages. However, you can call methods that access the object.

Bounded Wildcards

Here is a quick recap on wildcards to understand why you need bounded wildcards. You get a compiler error when you try generic types differing in their parameter types, as in

// compiler error:

List<Number> numList = new ArrayList<Integer>();

You use wildcard types to avoid this compiler error:

// now works:

List<?> numList = new ArrayList<Integer>();

Assume you want to be able to store only the list of numbers in the numList. However, you might end up storing a list of any type with wildcards, as in

List<?> numList = new ArrayList<Integer>(); numList = new ArrayList<String>();

Yes, it compiles without any errors. How do you restrict numList to refer to only to Number and its derived classes like Integer, Double, etc.? You do it by using bounded wildcards, like so:

List<? extends Number> numList = new ArrayList<Integer>(); numList = new ArrayList<String>();

165

Chapter 6 Generics and Collections

You get the following compiler error:

BoundedWildCardUse.java:7: incompatible types found : java.util.ArrayList<java.lang.String>

required: java.util.List<? extends java.lang.Number> numList = new ArrayList<String>();

How about this code?

List<? extends Number> numList = new ArrayList<Integer>(); numList = new ArrayList<Double>();

Yes, it compiles fine! What is going on here? In List<? extends Number>, the wildcard (?) is bounded with extends Number. This means that any type you substitute for wildcard (?) should satisfy the condition extends Number. For example, in ? extends Number, if you substitute ? with type Integer, you get

Integer extends Number—which is logically true. So the compilation will succeed for such substitution. But, in ? extends Number, if you substitute ? with type String, you get String extends Number, which is logically false

(remember that String is not a Number). So, you get a compiler error. In other words, you limit or bound the wildcard so that the substituted type must be of the extend Number class.

You can use bounded wildcards in method arguments, return types, etc. Here’s a simple method that uses bounded wildcards:

public static Double sum(List<? extends Number> numList) { Double result = 0.0;

for(Number num : numList) {

result += num.doubleValue();

}

return result;

}

Here is a step-by-step description of this method:

1.The method sum() is meant for taking a list of Numbers and returning the sum of the elements in that list.

2.Since the List is to be limited (bounded) by Number, you declare List as List<? Extends Number>.

3.Since you don’t know the exact type of the list elements (Integer, Double, etc.), you want to use double as the return type for sum. Since primitive types like int and double are implicitly boxed/unboxed when used with collections, you declare the return type as Double, which is more convenient than using the primitive type double.

4.Coming to the body of the method, since the sum of elements is going to be a Double value, you declare the result variable Double and initialize it with zero.

5.In the for-each loop, you use Number as the loop type. Since the wildcard is bounded by Number, you know that (no matter which List object is actually passed as argument) the element type is going to be a Number.

6.You get the double value from the Number type using the doubleValue method.

7.You return the sum of the elements once you are done.

166

Chapter 6 Generics and Collections

Listing 6-13 contains the main() method to test the sum() method.

Listing 6-13.  BoundedWildCardUse.java

// This program demonstrates the usage of bounded wild cards

import java.util.*;

class BoundedWildCardUse {

public static Double sum(List<? extends Number> numList) { Double result = 0.0;

for(Number num : numList) {

result += num.doubleValue();

}

return result;

}

public static void main(String []args) {

List<Integer> intList = new ArrayList<Integer>(); List<Double> doubleList = new ArrayList<Double>();

for(int i = 0; i < 5; i++) { intList.add(i); doubleList.add((double) (i * i));

}

System.out.println("The intList is: " + intList); System.out.println("The sum of elements in intList is: " + sum(intList));

System.out.println("The doubleList is: " + doubleList); System.out.println("The sum of elements in doubleList is: " + sum(doubleList));

}

}

It prints the following:

The intList is: [0, 1, 2, 3, 4]

The sum of elements in intList is: 10.0

The doubleList is: [0.0, 1.0, 4.0, 9.0, 16.0] The sum of elements in doubleList is: 30.0

Let’s go over the code step-by-step:

1.You create two ArrayLists, one of type Integer and another of type Double.

2.In a for loop, you insert five elements each into the lists. For intList, you insert the values 0 to 4. For doubleList, you insert the square of the values 0 to 4 (0 to 16). Since the doubleList expects the value to be double values, to make it explicit, you use an explicit cast—((double) (i * i)); if you want, you can remove that explicit cast.

3.You print the contents of the intList and doubleList and also print the sum of elements by calling the sum() method you wrote; from the output you can see that the sum() method worked correctly for both types Integer and Double.

167

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]