1 . 泛型的主要目的之一就是用来指定容器要持有什么类型的对象。
2 . 简单泛型
(1)有许多原因促成了泛型的出现,而最引人注目的一个原因就是为了创造容器类。
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>(new Automobile());
Automobile a = h3.get() ;
})
}
3 .元组:
(1) 仅一次方法调用就能返回多个对象
(2) 它是将一组对象直接打包存储于其中的一个单一对象。这个容器对象允许读取其中元素,但是不允许向其中存放新的对象。
public class TwoTuple<A ,B> {
//二维元组 ,public外部可以随意访问,final无法改变数据
//可以随便使用对象,但无法修改数据
//声明为final的元素便不能被再赋予其他值了
public final A first ;
public final B second ;
public TwoTuple(A a,B b){
first = a;
second = b;
}
public String toString(){
return "("+first+"."+second+")";
}
}
4 . 泛型方法
(1)同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。是否拥有泛型方法,与其所在的类是否是泛型没有关系。泛型方法使得该方法能够独立于类而产生变化。
(2)如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情变得更加清楚明白。
(3)对于一个static的方法而言,无法访问泛型类的类型参数,所以如果static方法需要使用泛型能力,就必须使其成为泛型方法
public class GenericMethods {
//要定义泛型方法,只需将泛型参数列表置于返回值之前
public <T> void f(T x) {
System.out.print(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("");
gm.f(1);
}
}
5 .杠杆利用类型参数推断
//问题:重复自己做过的事情
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>();
}
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();
}
}
(1)类型推断只对赋值操作有效,其他时候不起作用。如果你将一个泛型方法调用(例如New.map())作为参数,传递给另一个方法,这时编译器并不会执行类型推断。例如:
public class TestOne {
static void f(Map<Person,List<? extends Pet>> petPeople) {};
public static void main(String[] args) {
//f(New.map()); //无效
}
}
6.可变参数与泛型方法
public class TestOne {
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","B","C");
System.out.println(ls);
}
}
7.构建复杂模型
(1)泛型的一个重要好处是能够简单而安全地创建复杂的模型
8.类型擦除
(1)在泛型代码内部,无法获得任何有关泛型参数类型的信息。任何具体的类型信息都被擦除了,你唯一知道的就是你在使用一个对象。
(2)例如:List 这样的类型注解将被擦除为List。而普通的类型变量在未指定边界的情况下将被擦除为Object。
(3)边界处的动作:正是因为有了擦除,泛型可以表示没有任何意义的
public class ArrayMaker<T> {
//kind被存储为Class<T>,擦除意味着它实际将被存储为Class,没有任何参数。
//因此,当在使用它时,例如创建数组时,Array.newInstance()实际上并未拥有kind
//所蕴含的类型信息,因此这不会产生具体的结果,所以必须转型
private Class<T> kind ;
public ArrayMaker(Class<T> kind){
this.kind = kind ;
}
@SuppressWarnings("unchecked")
T[] create(int size){
return (T[])Array.newInstance(kind, size);
}
public static void main(String[] args) {
ArrayMaker<String> stringMaker = new ArrayMaker<String>(String.class);
String[] stringArray = stringMaker.create(9);
System.out.println(Arrays.toString(stringArray));
}
}
(4)对于在泛型中创建数组,使用Array.newInstance()是推荐的方式。
9.擦除的补偿
(1)擦除丢失了在泛型代码中执行某些操作的能力。任何在运行时需要知道确切类型信息的操作都将无法工作:
public class Erased<T> {
private final int SIZE = 100 ;
//错误代码
public static void f(Object arg){
if(arg instanceof T ){}; //Error 因为其类型信息已经被擦除了
T var = new T(); //Errot
T[] array = new T[SIZE]; //Errot
T[] array = (T)new Object[SIZE] ; //Unckecked warning
}
}
有时必须通过引入类型标签来对擦除进行补偿。这意味着你需要显式地传递你的类型的Class对象,以便你可以在类型表达式中使用它。
class Building{};
class House extends Building{};
public class Erased<T> {
Class<T> kind;
public Erased(Class<T> kind){
this.kind = kind ;
}
public boolean f(Object arg){
return kind.isInstance(arg);
}
public static void main(String[] args){
Erased<Building> ctt1 = new Erased<Building>(Building.class);
System.out.println(ctt1.f(new Building())); //true
System.out.println(ctt1.f(new House())); //true
}
}
10.不能创建泛型数组,一般的解决方案是在任何想要创建泛型数组的地方都使用ArrayList
public class ListOfGenerics<T> {
private List<T> array = new ArrayList<T>();
public void add(T item){
array.add(item);
}
public T get(int index){
return array.get(index);
}
}
11.边界
(1)Java泛型重用了extends关键字,能够将这个参数限制为某个类型子集
class Colored2<T extends Hascolor> extends HoldItem<T> {}
12.通配符
13.问题
(1).任何基本类型都不能作为类型参数,因此不能创建ArrayList< int >之类的东西,解决之道是使用基本类型的包装类以及Java SE5的自动包装机制。如果创建一个ArrayList< Integer > ,并将基本类型int应用于这个容器。那么你将发现自动包装机制将自动地实现int到Interger的双向转换。
(2)一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。
interface Payable<T> {};
class Employee implements Payable<Employee> {};
class Hourly extends Employee implements Payable<Hourly> {};
Hourly不能编译,因为擦除会将Payable和Payable< Hourly > 简化为相同的类Payable.