Generics from "Java Generics and Collections"

 ->

package org.example;

import java.util.ArrayList;
import java.util.List;

public class Lists {
public static <T> List<T> toList(T[] arr) {
List<T> list = new ArrayList<>();
for (T elt : arr)
list.add(elt);
return list;
}
}


->

It may seem reasonable to expect that since Integer is a subtype of Number, it follows

that List<Integer> is a subtype of List<Number>. But this is not the case, because the

Substitution Principle would rapidly get us into trouble. It is not always safe to assign

a value of type List<Integer> to a variable of type List<Number>. Consider the following

code fragment:

package org.example;

import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Integer> ints = new ArrayList<>();
ints.add(1);
ints.add(2);
List<Number> nums = ints;
nums.add(3.12);
}
}

This code assigns variable ints to point at a list of integers, and then assigns nums to

point at the same list of integers; hence the call in the fifth line adds a double to this

list, as shown in the last line. This must not be allowed! The problem is prevented by

observing that here the Substitution Principle does not apply: the assignment on the

fourth line is not allowed because List<Integer> is not a subtype of List<Number>, and

the compiler reports that the fourth line is in error.


->

What about the reverse? Can we take List<Number> to be a subtype of List<Integer>?

No, that doesn’t work either, as shown by the following code:

package org.example;

import java.util.ArrayList;
import java.util.List;

public class Main {
public static void main(String[] args) {
List<Number> nums = new ArrayList<>();
nums.add(2.78);
nums.add(3.14);
List<Integer> ints = nums; // compile-time error
assert ints.toString().equals("[2.78, 3.14]"); // uh oh!
}
}

The problem is prevented by observing that here the Substitution Principle does not

apply: the assignment on the fourth line is not allowed because List<Number> is not a

subtype of List<Integer>, and the compiler reports that the fourth line is in error.

So List<Integer> is not a subtype of List<Number>, nor is List<Number> a subtype of

List<Integer>; all we have is the trivial case, where List<Integer> is a subtype of itself,

and we also have that List<Integer> is a subtype of Collection<Integer>.


->

Arrays behave quite differently; with them, Integer[] is a subtype of Number[]. We will

compare the treatment of lists and arrays later (see Section 2.5).

package org.example;

public class Main {
public static void main(String[] args) {
Integer[] integers = new Integer[10];
Number[] numbers = integers;
numbers[0] = 5.5;
numbers[1] = 4;
System.out.println(numbers[0]);
System.out.println(numbers[1]);

Number[] n = new Number[10];
Integer[] i = n;
}
}


->

Another method in the Collection interface is addAll, which adds all of the members of one collection to another collection:

interface Collection<E> { ...

public boolean addAll(Collection<? extends E> c);

... }

Clearly, given a collection of elements of type E, it is OK to add all members of another collection with elements of type E. The quizzical phrase "? extends E" means that it is also OK to add all members of a collection with elements of any type that is a subtype of E. The question mark is called a wildcard, since it stands for some type that is a subtype of E.


Here is an example. We create an empty list of numbers, and add to it first a list of integers and then a list of doubles:

List<Number> nums = new ArrayList<Number>(); List<Integer> ints = Arrays.asList(1, 2); List<Double> dbls = Arrays.asList(2.78, 3.14); nums.addAll(ints);

nums.addAll(dbls);
assert nums.toString().equals("[1, 2, 2.78, 3.14]");

The first call is permitted because nums has type List<Number>, which is a subtype of Collection<Number>, and ints has type List<Integer>, which is a subtype of Collec tion<? extends Number>.Thesecondcallissimilarlypermitted.Inbothcalls,Eistaken to be Number. If the method signature for addAll had been written without the wildcard, then the calls to add lists of integers and doubles to a list of numbers would not have been permitted; you would only have been able to add a list that was explicitly declared to be a list of numbers.


Ingeneral,ifastructurecontainselementswithatypeoftheform? extends E,wecan get elements out of the structure, but we cannot put elements into the structure. To put elements into the structure we need another kind of wildcard, as explained in the next section.

package org.example;

import java.util.*;

// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class Main {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.add(1);

List<Double> ints = new ArrayList<>();
ints.add(1.1);
ints.add(2.2);
ints.add(3.3);
List<? extends Number> nums = ints; // upper bound -> get
nums.add(1.1);
nums = list;
nums.add(1);
}
}


package org.example;

import java.util.*;

// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class Main {
public static void main(String[] args) {
// upper bound(extends) -> get, doesn't allow put
// lower bound(super) -> put
List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
List<? super Integer> nums = list;
nums.add(1);
System.out.println(nums.get(0));
}
}


package org.example;

import java.util.*;

// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class Main {
public static void main(String[] args) {
List<Object> dst = Arrays.<Object>asList(2, 3.14, "four");
List<Integer> src = Arrays.asList(5, 6);
copy(dst, src);
}

// T -> Object
public static <T> void copy(List<? super T> dst, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dst.set(i, src.get(i));
}
}
}


-> get and put principle

The Get and Put Principle: use an extends wildcard when you only get values out of a structure, use a super wildcard when you only put values into a structure, and don’t use a wildcard when you both get and put.

We already saw this principle at work in the signature of the copy method: public static <T> void copy(List<? super T> dest, List<? extends T> src)

The method gets values out of the source src, so it is declared with an extends wildcard, and it puts values into the destination dst, so it is declared with a super wildcard.


Now the call to add is fine, because it puts a value into the list, but the call to sum is not, because it gets a value from the list. This is just as well, because the sum of a list containing a string makes no sense!

package org.example;

import java.util.*;

// Press Shift twice to open the Search Everywhere dialog and type `show whitespaces`,
// then press Enter. You can now see whitespace characters in your code.
public class Main {
public static void main(String[] args) {
List<Object> objs = new ArrayList<Object>();
objs.add(1);
objs.add("two");
List<? super Integer> ints = objs;
ints.add(3); // ok
double dbl = sum(ints); // compile-time error
}

public static double sum(Collection<? extends Number> nums) {
double s = 0.0;
for (Number num : nums)
s += num.doubleValue();
return s;
}
}


->

Similarly, you cannot get anything out from a type declared with a super wildcard—

except for a value of type Object, which is a supertype of every reference type:

List<Object> objs = Arrays.<Object>asList(1,"two"); List<? super Integer> ints = objs;
String str = "";
for (Object obj : ints) str += obj.toString(); assert str.equals("1two");









Комментарии

Популярные сообщения из этого блога

Lesson1: JDK, JVM, JRE

SE_21_Lesson_11: Inheritance, Polymorphism

SE_21_Lesson_9: Initialization Blocks, Wrapper types, String class