1.泛型,就是“适用于许多许多类型”的意思。其最初的目的是希望类或方法能够具备最广泛的表达能力。
2.类型参数就是用尖括号括住,放在类名后面。在使用这个类的时候,再用实际的类型替换该类型参数,例如:
public class Holder3<T> {
private T a;
public Holder3(T a) { this.a = a; }
public void set(T a) { this.a = a; }
public T get() { return a; }
public static void main(String[] args) {
Holder3<Automobile> h3 =
new Holder3<Automobile>(new Automobile());
Automobile a = h3.get(); // No cast needed这里不需要转型,编译器会帮你处理
// h3.set("Not an Automobile"); // Error
// h3.set(1); // Error
}
}
3.public class TwoTuple<A,B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) { first = a; second = b; }
public String toString() {
return "(" + first + ", " + second + ")";
}
}
上面这段代码是不是违反了java编程的安全性原则呢?字段应该设置为private,然后通过提供get或set来操作相应的字段。在这里,不需要。因为每个字段都是final,只有创建,后面只能读取,不能修改。如果客户端程序员想修改字段的值,必须重新创建对象。
4.元组
仅一次方法调用就能返回多个对象,你应该经常需要这样的功能吧。可是return语句只允许返回单个对象,因此,解决办法就是创建一个对象,用它来持有想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。可是有了泛型,我们就能够一次性地解决该问题,以后再也不用在这个问题上浪费时间了。同时,我们在编译期就能确保类型安全。这个概念称为元组(tuple),它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。(这个概念也称为数据传送对象,或信使。)
通常,元组可以具有任意长度,同时,元组中的对象可以是任意不同的类型。不过,我们希望能够为每一个对象指明其类型,并且从容器中读取出来时,能够得到正确的类型。要处理不同长度的问题,我们需要创建多个不同的元组。上面第3个程序是一个2维元组,它能够持有两个对象:
5.我们可以利用继承机制实现长度更长的元组。从下面的例子中可以看到
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}
public String toString() {
return "(" + first + ", " + second + ", " + third +")";
}
}
6.堆栈类的实现,利用泛型和内部链式存储机制
public class LinkedStack<T> {
private static class Node<U> {
U item;
Node<U> next;
Node() {
item = null;
next = null;
}
Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
boolean end() {
return item == null && next == null;
}
}
private Node<T> top = new Node<T>(); // End sentinel
public void push(T item) {
top = new Node<T>(item, top);
}
public T pop() {
T result = top.item;
if (!top.end())
top = top.next;
return result;
}
public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<String>();
for (String s : "Phasers on stun!".split(" "))
lss.push(s);
String s;
while ((s = lss.pop()) != null)
System.out.println(s);
}
}
内部类Node也是一个泛型,它拥有自己的类型参数。这个例子使用了一个末端哨兵(end sentinel)来判断堆栈何时为空。这个末端哨兵是在构造LinkedStack时创建的。然后,每调用一次push()方法,就会创建一个Node对象,并将其链接到前一个Node对象。当你调用pop()方法时,总是返回top.item,然后丢弃当前top所指的Node,并将top转移到下一个Node,除非你已经碰到了末端哨兵,这时候就不再移动top了。如果已经到了末端,客户端程序还继续调用pop()方法,它只能得到null,说明堆栈已经空了。练习5:(2) 移除Node类上的类型参数,并修改LinkedStack.java的代码,证明内部类可以访问其外部类的类型参数
7.作为容器的另一个例子,假设我们需要一个持有特定类型对象的列表,每次调用其上的select()方法时,它可以随机地选取一个元素。如果我们希望以此构建一个可以应用于各种类型的对象的工具,就需要使用泛型:
public class RandomList<T> {
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(47);
public void add(T item) { storage.add(item); }
public T select() {
return storage.get(rand.nextInt(storage.size()));
}
public static void main(String[] args) {
RandomList<String> rs = new RandomList<String>();
for(String s: ("The quick brown fox jumped over " +
"the lazy brown dog").split(" "))
rs.add(s);
for(int i = 0; i < 11; i++)
System.out.print(rs.select() + " ");
}
}
8.泛型也可以应用于接口。例如生成器(generator),这是一种专门负责创建对象的类。实际上,这是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。也就是说,生成器无需额外的信息就知道如何创建新对象。一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。在这里,就是next()方法。我将它收录在我的标准工具类库中:
public interface Generator<T> { T next(); }
现在,我们可以编写一个类,实现Generator<Coffee>接口,它能够随机生成不同类型的Coffee对象:
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
private Class[] types = { Latte.class, Mocha.class, Cappuccino.class,
Americano.class, Breve.class, };
private static Random rand = new Random(47);
public CoffeeGenerator() {
}
// For iteration:
private int size = 0;
public CoffeeGenerator(int sz) {
size = sz;
}
public Coffee next() {
try {
return (Coffee) types[rand.nextInt(types.length)].newInstance();
// Report programmer errors at run time:
} catch (Exception e) {
throw new RuntimeException(e);
}
}
class CoffeeIterator implements Iterator<Coffee> {
int count = size;
public boolean hasNext() {
return count > 0;
}
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator();
for (int i = 0; i < 5; i++)
System.out.println(gen.next());
for (Coffee c : new CoffeeGenerator(5))
System.out.println(c);
}
}
注意:怎么去实现参数化类型的接口
9.下面的类是Generator<T>接口的另一个实现,它负责生成Fibonacci数列:
public class Fibonacci implements Generator<Integer> {
private int count = 0;
public Integer next() { return fib(count++); }
private int fib(int n) {
if(n < 2) return 1;
return fib(n-2) + fib(n-1);
}
public static void main(String[] args) {
Fibonacci gen = new Fibonacci();
for(int i = 0; i < 18; i++)
System.out.print(gen.next() + " ");
}
}
虽然我们在Fibonacci类的里里外外使用的都是int类型,但是其类型参数却是Integer。这个例子引出了Java泛型的一个局限性:基本类型无法作为类型参数。不过,Java SE5具备了自动打包和自动拆包的功能,可以很方便地在基本类型和其相应的包装器类型之间进行转换。通过这个例子中Fibonacci类对int的使用,我们已经看到了这种效果。
注:斐波纳契数列(Fibonacci Sequence),又称黄金分割数列。这个数列有个十分明显的特点:前面相邻两项之和,构成了后一项。斐波那契数列还有两个有趣的性质:
(1).斐波那契数列中任一项的平方数都等于跟它相邻的前后两项的乘积加1或减1;
(2).任取相邻的四个斐波那契数,中间两数之积(内积)与两边两数之积(外积)相差1.
(3).两个连续的“斐波纳契数”的序列相互分割(除法运算)将接近黄金比例(1.618:1或1:0.618)。
10.
到目前为止,我们看到的泛型,都是应用于整个类上。但同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系。
以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。
public class GenericMethods {
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("");
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f('c');
gm.f(gm);
}
}
GenericMethods并不是参数化的,尽管这个类和其内部的方法可以被同时参数化,但是在这个例子中,只有方法f()拥有类型参数。这是由该方法的返回类型前面的类型参数列表指明的。
注意,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。因此,我们可以像调用普通方法一样调用f(),而且就好像是f()被无限次地重载过。它甚至可以接受GenericMethods 作为其类型参数如果调用f()时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。事实上,泛型方法与自动打包避免了许多以前我们不得不自己编写出来的代码。
11.利用类型参数推断
人们对泛型有一个抱怨,使用泛型有时候需要向程序中加入更多的代码,就要像下面这样:
Map<Person,List<? extends Pet>> petPeople=new HashMap<Person,List<? extends Pet>>();
编译器本来应该能够从泛型参数列表中的一个参数推断出另一个参数。唉,可惜的是,编译器暂时还做不到。然而,在泛型方法中,类型参数推断可以为我
们简化一部分工作
public class New {
public static <K, V> Map<K, V> map() {
return new HashMap<K, V>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> LinkedList<T> lList() {
return new LinkedList<T>();
}
public static <T> Set<T> set() {
return new HashSet<T>();
}
public static <T> Queue<T> queue() {
return new LinkedList<T>();
}
// Examples:
public static void main(String[] args) {
Map<String, List<String>> sls = New.map();
List<String> ls = New.list();
LinkedList<String> lls = New.lList();
Set<String> ss = New.set();
Queue<String> qs = New.queue();
}
}
对于类型参数推断而言,这是一个有趣的例子。不过,很难说它为我们带来了多少好处。如果某人阅读以上代码,他必须分析理解工具类New,以及New所隐
含的功能。而这似乎与不使用New时(具有重复的类型参数列表的定义)的工作效率差不多。这真够讽刺的,要知道,我们引入New工具类的目的,正是为了
使代码简单易读。不过,如果标准Java类库要是能添加类似New.java这样的工具类的话,我们还是应该使用这样的工具类。
类型推断只对赋值操作有效,其他时候并不起作用。如果你将一个泛型方法调用的结果(例如New.map())作为参数,传递给另一个方法,这时编译器并不
会执行类型推断。在这种情况下,编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。下面的例子证明了这一点:
public class LimitsOfInference {
static void
f(Map<Person, List<? extends Pet>> petPeople) {}
public static void main(String[] args) {
// f(New.map()); // Does not compile
}
}
12.在泛型方法中,可以显式地指明类型,不过这种语法很少使用。要显式地指明类型,必须在点操作符与方法名之间插入尖括号,然后把类型置于尖括号
内。如果是在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用static的方法,必须在点操作符之前加上类名。使用这种语法,可
以解决 LimitsOfInference.java中的问题:
public class ExplicitTypeSpecification {
static void f(Map<Person, List<Pet>> petPeople) {}
public static void main(String[] args) {
f(New.<Person, List<Pet>>map());
}
}
class Person{
}
class Pet{
}
当然,这种语法抵消了New类为我们带来的好处(即省去了大量的类型说明),不过,只有在编写非赋值语句时,我们才需要这样的额外说明。
13.泛型方法与可变参数列表能够很好地共存
public class GenericVarargs {
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
for(T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
14.利用生成器,我们可以很方便地填充一个Collection
public class Generators {
public static <T> Collection<T>
fill(Collection<T> coll, Generator<T> gen, int n) {
for(int i = 0; i < n; i++)
coll.add(gen.next());
return coll;
}
public static void main(String[] args) {
Collection<Coffee> coffee = fill(
new ArrayList<Coffee>(), new CoffeeGenerator(), 4);
for(Coffee c : coffee)
System.out.println(c);
Collection<Integer> fnumbers = fill(
new ArrayList<Integer>(), new Fibonacci(), 12);
for(int i : fnumbers)
System.out.print(i + ", ");
}
}
一个通用的Generator,下面的类可以为任何类构造一个Generator,只有该类具有默认的构造器。
public class BasicGenerator<T> implements Generator<T> {
private Class<T> type;
public BasicGenerator(Class<T> type){ this.type = type; }
public T next() {
try {
// Assumes type is a public class:
return type.newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
// Produce a Default generator given a type token:
public static <T> Generator<T> create(Class<T> type) {
return new BasicGenerator<T>(type);
}
}
public class CountedObject {
private static long counter = 0;
private final long id = counter++;
public long id() { return id; }
public String toString() { return "CountedObject " + id;}
}
public class BasicGeneratorDemo {
public static void main(String[] args) {
Generator<CountedObject> gen =
BasicGenerator.create(CountedObject.class);
for(int i = 0; i < 5; i++)
System.out.println(gen.next());
}
}
可以看到,使用泛型方法创建Generator对象,大大减少了我们要编写的代码。
简化元组的使用
public class Tuple {
public static <A,B> TwoTuple<A,B> tuple(A a, B b) {
return new TwoTuple<A,B>(a, b);
}
public static <A,B,C> ThreeTuple<A,B,C>
tuple(A a, B b, C c) {
return new ThreeTuple<A,B,C>(a, b, c);
}
public static <A,B,C,D> FourTuple<A,B,C,D>
tuple(A a, B b, C c, D d) {
return new FourTuple<A,B,C,D>(a, b, c, d);
}
public static <A,B,C,D,E>
FiveTuple<A,B,C,D,E> tuple(A a, B b, C c, D d, E e) {
return new FiveTuple<A,B,C,D,E>(a, b, c, d, e);
}
}
import net.mindview.util.*;
import static net.mindview.util.Tuple.*;
public class TupleTest2 {
static TwoTuple<String,Integer> f() {
return tuple("hi", 47);
}
static TwoTuple f2() { return tuple("hi", 47); }
static ThreeTuple<Amphibian,String,Integer> g() {
return tuple(new Amphibian(), "hi", 47);
}
static
FourTuple<Vehicle,Amphibian,String,Integer> h() {
return tuple(new Vehicle(), new Amphibian(), "hi", 47);
}
static
FiveTuple<Vehicle,Amphibian,String,Integer,Double> k() {
return tuple(new Vehicle(), new Amphibian(),
"hi", 47, 11.1);
}
public static void main(String[] args) {
TwoTuple<String,Integer> ttsi = f();
System.out.println(ttsi);
System.out.println(f2());
System.out.println(g());
System.out.println(h());
System.out.println(k());
}
} /* Output: (80% match)
(hi, 47)
(hi, 47)
(Amphibian@7d772e, hi, 47)
(Vehicle@757aef, Amphibian@d9f9c3, hi, 47)
(Vehicle@1a46e30, Amphibian@3e25a5, hi, 47, 11.1)
*///:~
f2方法放回的是非参数化的对象,可以看到它被向上转型为一个非参数化的TwoTuple。
public static void main(String[] args) {
TwoTuple<String, Integer> ttsi = f();
TwoTuple ttsi2=f2();
//String s=ttsi2.first;编译不通,必须强制类型转换
String s=ttsi1.first//可以,因为返回的就是参数的对象
}
一个有用的Set工具
public class Sets {
public static <T> Set<T> union(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<T>(a);
result.addAll(b);
return result;
}
public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<T>(a);
result.retainAll(b);
return result;
}
// Subtract subset from superset:
public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
Set<T> result = new HashSet<T>(superset);
result.removeAll(subset);
return result;
}
// Reflexive--everything not in the intersection:
public static <T> Set<T> complement(Set<T> a, Set<T> b) {
return difference(union(a, b), intersection(a, b));
}
}
public class WatercolorSets {
public static void main(String[] args) {
Set<Watercolors> set1 = EnumSet.range(BRILLIANT_RED, VIRIDIAN_HUE);
Set<Watercolors> set2 = EnumSet.range(CERULEAN_BLUE_HUE, BURNT_UMBER);
System.out.println("set1: " + set1);
System.out.println("set2: " + set2);
System.out.println("union(set1, set2): " + union(set1, set2));
Set<Watercolors> subset = intersection(set1, set2);
System.out.println("intersection(set1, set2): " + subset);
System.out.println("difference(set1, subset): "
+ difference(set1, subset));
// System.out.println("difference(set2, subset): "
// + difference(set2, subset));
// System.out.println("complement(set1, set2): " + complement(set1, set2));
}
}
利用Set来表达数学中的关系式,通过泛型方法,可以很容易地做到这一点,而且可以应用于多种类型。
15.匿名内部类的泛化
class Customer {
private static long counter = 1;
private final long id = counter++;
private Customer() {}
public String toString() { return "Customer " + id; }
// A method to produce Generator objects:
public static Generator<Customer> generator() {
return new Generator<Customer>() {
public Customer next() { return new Customer(); }
};
}
}
class Teller {
private static long counter = 1;
private final long id = counter++;
private Teller() {}
public String toString() { return "Teller " + id; }
// A single Generator object:
public static Generator<Teller> generator =
new Generator<Teller>() {
public Teller next() { return new Teller(); }
};
}
public class BankTeller {
public static void serve(Teller t, Customer c) {
System.out.println(t + " serves " + c);
}
public static void main(String[] args) {
Random rand = new Random(47);
Queue<Customer> line = new LinkedList<Customer>();
Generators.fill(line, Customer.generator(), 15);
List<Teller> tellers = new ArrayList<Teller>();
Generators.fill(tellers, Teller.generator, 4);
for(Customer c : line)
serve(tellers.get(rand.nextInt(tellers.size())), c);
}
} /* Output:
Teller 3 serves Customer 1
Teller 2 serves Customer 2
Teller 3 serves Customer 3
Teller 1 serves Customer 4
Teller 1 serves Customer 5
Teller 3 serves Customer 6
Teller 1 serves Customer 7
Teller 2 serves Customer 8
Teller 3 serves Customer 9
Teller 3 serves Customer 10
Teller 2 serves Customer 11
Teller 4 serves Customer 12
Teller 2 serves Customer 13
Teller 1 serves Customer 14
Teller 1 serves Customer 15
16.创建一个list元组
public class TupleList<A,B,C,D>
extends ArrayList<FourTuple<A,B,C,D>> {
public static void main(String[] args) {
TupleList<Vehicle, Amphibian, String, Integer> tl =
new TupleList<Vehicle, Amphibian, String, Integer>();
tl.add(TupleTest.h());
tl.add(TupleTest.h());
for(FourTuple<Vehicle,Amphibian,String,Integer> i: tl)
System.out.println(i);
}
}
17.
下面的例子说明了利用泛型来构建复杂的模型是多么的简单。这个模型是个零售店,报告走廊、货架和商品。
class Product {
private final int id;
private String description;
private double price;
public Product(int IDnumber, String descr, double price){
id = IDnumber;
description = descr;
this.price = price;
System.out.println(toString());
}
public String toString() {
return id + ": " + description + ", price: $" + price;
}
public void priceChange(double change) {
price += change;
}
public static Generator<Product> generator =
new Generator<Product>() {
private Random rand = new Random(47);
public Product next() {
return new Product(rand.nextInt(1000), "Test",
Math.round(rand.nextDouble() * 1000.0) + 0.99);
}
};
}
class Shelf extends ArrayList<Product> {
public Shelf(int nProducts) {
Generators.fill(this, Product.generator, nProducts);
}
}
class Aisle extends ArrayList<Shelf> {
public Aisle(int nShelves, int nProducts) {
for(int i = 0; i < nShelves; i++)
add(new Shelf(nProducts));
}
}
class CheckoutStand {}
class Office {}
public class Store extends ArrayList<Aisle> {
private ArrayList<CheckoutStand> checkouts =
new ArrayList<CheckoutStand>();
private Office office = new Office();
public Store(int nAisles, int nShelves, int nProducts) {
for(int i = 0; i < nAisles; i++)
add(new Aisle(nShelves, nProducts));
}
public String toString() {
StringBuilder result = new StringBuilder();
for(Aisle a : this)
for(Shelf s : a)
for(Product p : s) {
result.append(p);
result.append("\n");
}
return result.toString();
}
public static void main(String[] args) {
System.out.println(new Store(14, 5, 10));
}
} /* Output:
258: Test, price: $400.99
861: Test, price: $160.99
868: Test, price: $417.99
207: Test, price: $268.99
551: Test, price: $114.99
278: Test, price: $804.99
520: Test, price: $554.99
140: Test, price: $530.99
18.
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
} /* Output:
true
*///:~
public class LostInformation {
public static void main(String[] args) {
List<Frob> list = new ArrayList<Frob>();
Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
Quark<Fnorkle> quark = new Quark<Fnorkle>();
Particle<Long,Double> p = new Particle<Long,Double>();
System.out.println(Arrays.toString(
list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
p.getClass().getTypeParameters()));
}
} /* Output:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
*///:~
从上面程序的输出结果可以看出:在泛型代码内部,无法获得任何泛型参数类型的信息。即,java泛型是使用擦除来实现的,这意味着当你在使用泛型时,
任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。因此List<String>和List<Integer>在运行时事实上是相同的类型,这两种形式都
被擦除成它们的原生类型即List。
19.有了擦除下面的代码就无法编译通过
class HasF{
public void f(){
System.out.println("Hash.f()");
}
}
class Manipulator<T> {
private T obj;
public Manipulator(T x) { obj = x; }
// Error: cannot find symbol: method f():
public void manipulate() { obj.f(); }
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator =
new Manipulator<HasF>(hf);
manipulator.manipulate();
}
}
必须使用边界才可以
class Manipulator<T extends HasF> { // 表示T类型必须是HasF类或者其子类才可以
private T obj;
public Manipulator(T x) { obj = x; }
public void manipulate() { obj.f(); }
}
当希望代码跨越多个类工作时,使用泛型才有帮助,一般情况下不要使用泛型,它还是比较复杂的。
20.擦除的原因是从非泛型代码到泛型代码的转变过程,它使得现有的非泛型客户端代码能够在不改变的情况下继续使用。
21.使用泛型并不是强制的,例如
Map m=new HashMap();
或者
class Test extends List{
}
22.
java.lang.reflect.Array类的方法,
newInstance
public static Object newInstance(Class<?> componentType,
int length)
throws NegativeArraySizeException
Creates a new array with the specified component type and length. Invoking this method is equivalent to creating an array as
follows:
int[] x = {length};
Array.newInstance(componentType, x);
23.在java中不能创建泛型数组,任何想要创建泛型数组的地方都可以使用ArrayList替代。
24.泛型通配符的应用
Example2.1
List<Number> listNums = new ArrayList<Number>();
nums.add(10);
nums.add(8.88);
在example2.1 中,可以看出替换原则被很好地应用在这里,ArrayList是List的子类,我们提供给listNums变量的类型参数为Number,往 listNums中添加元素时,10被封箱为Integer类型,而Integer是Number的子类,第三行的情况类似。
Example2.2
List<Integer> intList = new ArrayList<Integer>();
List<Number> numList = intList; //compile error
…
numList.add(1.35); // can‘t do that
根据替换原则,我们会很容易想到,既然Integer是Number的子类,则我们应该可以将List<Integer>的变量赋给 List<Number>的变量,但从实际情况表明,List<Integer>并不是List<Number>的子类。
我们不妨试想想,若果List<Integer>类型的变量可以成功赋值给List<Number>类型的变量,会出现什么情况?我们可能在程序的某个位置添加一个double类型的元素进去numList中,而实质上在numList中其它元素都是Integer 的类型的元素,这样就违背了泛型的初衷了。
有时,我们确实希望将形如List<Integer>的List对象赋给List<Number>的变量,这时就要使用extends关键字的通配符。
(2) 使用extends关键字的通配符
Example2.3
List<Integer> intList = new ArrayList<Integer>();
List<? extends Number> numList = intList;
…
numList.add(1.35); //compile error (can‘t do that)
从Example2.3看到numList这个变量,我们可以将类型参数为Number及其Number子类的List赋给它。
记住一条规则如果你使用了“? extends T”,一般情况下,你不能往该数据结构中put元素,而你可以做的就是get元素。
如果要往内put元素,就需要使用下面提到的super关键字的通配符。
(3) 使用super关键字的通配符
Example2.4
List<Integer> intList = new ArrayList<Integer>();
List<? super Integer> numList = intList;
numList.add(3); //can put integer or null
在example2.4 我们可以看到<? super Integer>的意思为,我们可以将类型参数为Integer或Integer超类的List赋给 numList变量,并且可以put元素到列表中(注意:在该例子中put进的元素只能为Integer或null类型)。
一条比较通用的规则:如果要往List中put元素则用<? super T>,如果要从List中get元素则用<? extends T>,如果既要get又要put则不使用通配符。
<? super T>意思是一个T类或者是T类的父类,而add的时候加入的是属于T类的类型,而不是加入T类的未知父类类型。所以加入的元素必然要属于T类,明显这里的T类是B类的映射,而加入的只能是B类或者其子类。符合要求的只有D类,所以list.add(new B())是正确的。
数组问题:
public class Test{
public static void main(String[] args) {
B[] b=new B[5];
A[] a=b;
a[0]=new A();
}
}
class A{
}
class B extends A{
}
编译通过,运行时报错。数组不能存储向上转型的类型,只能存储定义时的确切类型
无限制通配符List<?>和原生类型List的区别:
无限制通配符类型是安全的,原生类型是类型不安全的,由于可以将任何元素放到原生类型的集合中,因此很容易破坏该集合的类型约束条件。但不能将任何元素(除null)放到
无限制通配符类型中。
List<?>是类型安全的,只能get,不能put;