泛型的优点
泛型应该说是一种高级的抽象,类型的参数化,加强程序的通用性;
类型安全,防止ClasscastException;
泛型化后,编译器会帮助我们进行类型推导,类型自动转化,消除强制类型转换,由编译器更安全地完成;
大大增强容器类的通用性(说不定引入泛型初衷,就是为了更好的使用容器类)。
泛型自底向上
一般情况下,方法的参数只能为固定的类型(基本类型、特定的类型),没有通用性可言;
把方法的参数设为基类,则该方法可以接受任何基类的子类,所有基类的类型通用;
更高的抽象(接口),方法参数设为接口,接受范围更广泛,所有接口类型通用;
终极,参数化类型,方法参数的类型当做参数,即就是泛型,任何类型通用。
简单泛型
设计一个持有单个对象的容器:
public class Holder1 {
private Object a;
public Holder1(Object a) {
this.a = a;
}
Object get() {
return a;
}
}
这个容器确实能持有多种类型的对象,但通常只会用它来存储一种对象。虽然设计时希望能存储任意类型,但使用时却能够只存储我们想要的确定类型。
泛型也可以实现这样的功能,而且提供编译期检查:
class Automobile {}
public class Holder2<T> {
private T a;
public Holder2(T a) {
this.a = a;
}
public void set(T a) {
this.a = a;
}
public T get() {
return a;
}
public static void main(String[] args) {
Holder2<Automobile> h2 =
new Holder2<Automobile>(new Automobile());
Automobile a = h2.get(); // No cast needed
// h2.set("Not an Automobile"); // Error
// h2.set(1); // Error
}
}
泛型方法
泛型可以应用于方法,只需要将泛型参数列表放在方法返回值之前即可。
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);
}
} /* Output:
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
GenericMethods
如果调用f()时传入了基本数据类型,自动打包机制将会被触发
类型擦除
Java是伪泛型,实际上是进行的泛型擦除,在编译就已经完成泛型的相关转型代码的插入。
ArrayList用来存储数据的数组是这样定义的:
/**
* The elements in this list, followed by nulls.
*/
transient Object[] array;
get方法:
@SuppressWarnings("unchecked") @Override public E get(int index) {
if (index >= size) {
throwIndexOutOfBoundsException(index, size);
}
return (E) array[index];
}
通配符
指定上界List<? extends Fruit>
import java.util.*;
public class GenericsAndCovariance {
public static void main(String[] args) {
// Wildcards allow covariance:
List<? extends Fruit> flist = new ArrayList<Apple>();
// Compile Error: can’t add any type of object:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit:
Fruit f = flist.get(0);
}
} ///:~
flist的类型为List<? extends Fruit>
,读作“任何从Fruit继承而来的类型构成的列表”。但这并不意味着这个List将持有任何类型的Fruit,通配符引用的其实是明确的类型,这个例子中它意味着“某种指定了上界为Fruit的具体类型”。
add方法不可用是因为他的参数变为? extends Fruit
,其接受的参数可以是任意类型,只需满足上界为Fruit即可,而编译器无法验证“任意类型”的类型安全性。
指定下界List<? super Apple> apples
class Jonathan extends Apple {}
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error
}
} ///:~
与上界相反,可以使用add方法。
参数化接口
一个类不能实现同一个泛型接口的两种变体,因为擦除会让它们变成相同的接口:
interface Payable<T> {}
class Employee implements Payable<Employee> {}
class Hourly extends Employee
implements Payable<Hourly> {}
Hourly不能编译。但是,如果从Payable的两种用法中移除掉泛型参数(就像编译器在擦除阶段做的那样),这段代码将能够编译。
重载
类型擦除会让两个方法产生相同的签名:
public class UseList<W,T> {
void f(List<T> v) {}
void f(List<W> v) {}
}
想要其使用就要有不同的返回值(略不优雅)
参考:Generics泛型