1. 泛型简介 & 与 C++比较
泛型实现了参数化类型的概念,使代码可以应用于多种类型。“泛型”这个术语的意思是:“适用于许多许多的类型”。泛型最初的目的是希望类或方法能够具备最广泛的表达能力。如何做到这点呢?正是通过解耦类或方法与所使用的类型之间的约束。
因为 Java 的设计者曾经说过:设计 Java 的灵感主要来自 C++。所以我们就拿 Java 和 C++做一下对比吧:
首先,了解 C++模板的某些方面,有助于理解泛型的基础。 最终的目的是帮助我们理解 Java 泛型的边界在哪里。理解了边界所在才能成为程序高手,因为只有知道了某个技术不能做什么,才能更好的做到所能做的
第二点,在 Java 社区中,人们普遍对 C++模板有一种误解,令你有可能会在理解泛型的意图时产生偏差。
2. 简单泛型**
有许多原因促成了泛型的出现,而最引人注目的一个原因,就是为了创造容器类。容器,就是存放使用对象的地方,数组也是如此。事实上,所有的程序,在运行时都要求你持有一大堆对象,所以,容器类算得上最具重用性的类库之一。下面来看一个只能持有单个对象的类。当然,这个类可以明确指定其特有的对象的类型:
public class Automobile {}
class Holder{
private Automobile automobile;
public Holder(Automobile automobile){
this.automobile = automobile;
}
Automobile get(){
return automobile;
}
}
不过这个类无法持有其他类型的任何对象。我们不希望为碰到的每个类型都编写一个新的类。所以让这个类直接持有Object类型的对象:
public class Holder2 {
private Object a;
public Holder2 (Object object){
this.a = object;
}
public void set(Object a){
this.a = a;
}
public Object get() {
return a;
}
public static void main(String[] args) {
Holder2 holder2 = new Holder2(new Automobile());
Automobile automobile = (Automobile) holder2.get();
holder2.set("Not an Automobile");
String string = (String) holder2.get();
holder2.set(1);
Integer x = (Integer) holder2.get();
}
}
现在,Holder2可以存储任何类型的对象,在这个例子中,只用了一个Holder2对象,却先后存储了三种不同的类型的对象。
有些情况下,我们确实希望容器能够同时持有多种类型的对象。但是,我们只使用容器来存储一种类型的对象。泛型的主要目的之一就是用来指定容器要持有什么类型的对象,而且由编译器来保证类型的正确性。下面举个例子来使用泛型:
public class Holder3<T> {
private T a;
public Holder3(T a) {
this.a = a;
}
private void set(T a) {
this.a = a;
}
private T get() {
return a;
}
public static void main(String[] args) {
Holder3<Automobile> holder3 = new Holder3<Automobile>(new Automobile());
Automobile automobile = holder3.get();
}
这个例子显示是使用T作为类型参数,所以现在如果你要创建Holder3对象时,你就必须要指明想要持有什么类型的对象,将其置于尖括号内。并且,在你从Holder3中取出它持有的对象时,自动地就是正确的类型了。一般而言,你可以认为泛型与其他的类型差不多,只不过它们碰巧有类型参数罢了。然而,在使用泛型时,我们只需要指定它们的名称以及类型参数列表即可。
3.泛型接口
泛型也可以应用于接口。例如生成器,这是一种专门负责创建对象的类。不过,当使用生成器创建新的对象时,它不需要任何参数。也就是说,生成器无需额外的信息就知道如何创建新的对象。
一般而言,一个生成器只定义一个方法,该方法用以产生新的对象。在这里,就是next()方法。如:
public interface Generator{T next();}
方法next()的返回类型是参数化的T。下面使用一个例子来实现Generator接口:
public class Coffee {
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getSimpleName() + " " +id;
}
}
public class Mocha extends Coffee{}
public class Americano extends Coffee{}
public class CoffeeGenerator implements Generator<Coffee>,Iterable<Coffee>{
private Class[] types = {Mocha.class,Americano.class};
private static Random random = new Random(47);
public CoffeeGenerator() {
}
private int size = 0;
public CoffeeGenerator(int sz) {
this.size = sz;
}
@Override
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
@Override
public Coffee next() {
try {
return (Coffee)types[(random.nextInt(types.length))].newInstance();
} 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(){
throw new UnsupportedOperationException();
}
}
public static void main(String[] args) {
CoffeeGenerator generator = new CoffeeGenerator();
for (int i = 0; i < 5; i++) {
System.out.println(generator.next());
}
for (Coffee coffee : new CoffeeGenerator(5)) {
System.out.println(coffee);
}
}
}
ouput:
Americano 0
Mocha 1
Americano 2
Mocha 3
Mocha 4
Americano 5
Mocha 6
Mocha 7
Americano 8
Americano 9
参数化的Generator接口确保next()的返回值是参数的类型。CoffeeGenerator同时还实现了Iterable接口,所以它可以在循环语句中使用。不过,它还需要一个“末端哨兵”来判断合适停止,这正是第二个构造器的功能。