简介 泛型其实并不是一种新的语言元素C++中早就就有但是在C++之后的java却没有吸收这个特性现在Java也有了泛型的特性大概也和Net的竞争有关系吧 首先看泛型的一个应用 在过去我们可能经常要写一些类似这样的代码 List stringList=new LinkedList(); stringListadd(firstString); stringListadd(secondString); String str=(String)erator()next(); 实际上第三行对String的类型转换意义并不大因为通常我们如果在操作一个List都是知道这个List里面放的是什么类型对象的但是我们如果不这样写又通不过语法检查 利用java的泛型机制我们可以这么写 List<String> stringList=new LinkedList<String>(); stringListadd(firstString); stringListadd(secondString); String str=erator()next(); 这样做的好处是在定义容器的时候就指明了容器中的类型一方面我们不再需要取一个元素时候做强制类型转换另外一方面如果在这个容器中放入的对象类型不符合要求那么会在编译时候产生一个错误而不是在运行时候才抛出一个异常 另外这样也提高了程序的可读性 泛型类型的定义 下面是一个简单的使用泛型类的定义 public class MyGenericClass<T> { private T value; public T getValue() { return value; } public void setValue(T value) { thisvalue = value; } } 值得注意的一点是静态变量不能够使用泛型定义也就是说类似下面的语句是非法的 public class MyGenericClass<T> { public static T value;//错误的定义 } 此外泛型的定义不会被继承举个例子来说如果A是B的子类而C是一个声明了泛型定义的类型的话C<A>不是C<B>的子类为了更好的说明可以看下面的代码这段代码是错误的 List<String> strList =new ArrayList<String>(); List<Object> objList=strList; //错误的赋值 不过这样一段代码是正确的 List<Object> strList =new ArrayList<Object>(); strListadd(a string); 统配类型 假设我们需要这样一个函数使用它可以把一个集合中所有的元素打印出来在以前我们可能这样定义 void printCollection(Collection c) { Iterator i = erator(); for (k = ; k < csize(); k++) { Systemoutprintln(inext()); } } 使用新的泛型特性我们可以这样写 void printCollection(Collection<Object> c) { for (Object e : c) { Systemoutprintln(e); } } 但是这样有一个问题假如我们现在有个对象类型是Collection<String>那么我们不能够将它作为参数传给printCollection因为Collection<String>并不是Collection<Object>的子类 为了解决这个问题我们可以使用统配类型?也就是定义成下面这个样子 void printCollection(Collection<?> c) { for (Object e : c) { Systemoutprintln(e); } } 可以说Collection<?>是所有Collection的父类 再来看一段下面的代码 private void clearAllMaps(Collection<Map> c) { for(Map m:c) { mclear(); } } 毫无疑问它也存在上面我们所说的问题也就是对HashMap之类Map的子类无法进行操作但是如果我们将参数改成Collection<?>又不大合理因为我们只希望对父类为Map的子类进行操作那么我们可以这样改写 private void clearAllMaps(Collection<? extends Map> c) { for(Map m:c) { mclear(); } } 类似于? extends Map之类的统配符称为限定统配类型 假设一个对象h类型为Collection<HashMap>那么我们将h作为参数传给clearAllMaps如下面一段代码所示 List<HashMap<StringString>> h=new ArrayList<HashMap<StringString>>(); HashMap<StringString> m=new HashMap<StringString>(); mput(keyvalue); hadd(m); clearAllMaps(h); 对于在类似于上面所说使用了? extend XXX的方法值得注意的一点是不能够在方法体内用XXX的子类对象作为代替如下面一段代码是错误的 public void addRectangle(List<? extends Shape> shapes) { shapesadd( new Rectangle()); // 错误用法! } 这里我们假设Rectangle是Shape的一个子类 不允许这样写的原因比较简单因为调用该方法时候参数类型可能是Shape的另外一个子类假如说Shape除了Rectangle这个子类以外还有另外一个子类Circle那么我们可以把一个List<Circle>类型的对象作为参数传给这个方法(注意这样是合法的)而在方法体内却把一个Rectangle对象放到了shapes里面这显然是不合理的 除了extends在泛型参数类型中还可以使用super关键字参照下面一段程序 private void addString(Collection <? super String> c){ cadd(a String); } 泛型函数 我们在前面提到了统配类型现在让我们来设想一个函数它实现这样的功能将一个数组中的元素添加到一个Collection中为了保证程序的通用性我们可能会写出另外一段错误的代码 private void fromArrayToCollection(Object[] a Collection<?> c) { for (Object o : a) { cadd(o); // 错误的代码 } } 那么这个函数应该怎么写呢?我们可以通过对函数添加泛型参数的方法实现如下面所示 private <T> void exfromArrayToCollection(T[] a Collection<T> c) { for (T o : a) { cadd(o); //这样是正确的 } } 那么在什么时候我们应该使用统配类型什么时候我们应该使用泛型函数呢?答案是取决于函数参数之间函数参数和返回值之间的类型依赖性 如果一个函数的参数类型与函数返回的参数没有必然关联同时对于该函数其他的参数的类型也没有依赖关系那么我们就应该使用统配符否则就应该使用泛型函数 为了更清楚地说明这一点我们可以看一下javautil包中Collections类型几个方法的定义 class Collections { static void swap(List<?> list int i int j) {} static <T> void copy (List<? super T> dest List<? extends T> src) {} } 其中swap函数实际上也可以这样定义 static <T>void swap(List<T> list int i int j) {} 但是注意到这里泛型类型参数T只在参数中用到了一次也就是说它和函数其他部分没有依赖性这可以看作是我们应该使用?的一个标志 copy方法中拷贝源src中的元素必须是dest所能够接受的src中的元素必须是T的一个子类但是具体它是哪种子类我们又不必关心所以方法中使用了泛型作为一个类型参数同时也用了统配类型作为第二类型参数 |