在JavaSE 1.5中,Java引入泛型的概念。那么何为泛型呢?
泛型就是将类型参数化,也就是说将所操作的数据类型指定为一个参数,从而使代码可以复用,并且引入泛型意味着更好的类型安全。
泛型可以被使用到类、接口和方法中。
泛型的使用在集合中尤为突出。
一、定义&使用
在定义泛型类、泛型接口或泛型方法使,在<>
中指定一个或多个类型参数的名字。对于类型参数的名字可以使用任何Java标识符(由字符、数字、下划线(_)、美元符($)组成,不能以数字开头),但为了规范化和达到见名知意的目的,建议使用简练的名字,多使用单个字符,并且最好避免小写字母。
在这里建议大家多看看Java API的源码,并不一定要看懂程序,而是看那些大牛的编码风格,命名风格,很值得去学习一下。
比如在API中的类型参数名字:E (Element)元素
K (Key)键
V (Value) 值
就像这样
//泛型接口
public interface Test<E>{}
//单个类型参数的泛型类
public class Test1<E> {
public void test(E e){}
}
//多个类型参数的泛型类
public class Test2<K,V>{}
二、泛型方法
泛型接口和泛型类在声明上相对简单些,看上面的例子就可以了,对于泛型方法,我们看API中的一个例子,在java.util.Collections
有一个对集合按照某种规则进行排序的方法sort()
,方法如下:
public static <T extends Comparable<? super T>> void sort(List<T> list){//...}
泛型方法的声明需要在返回值前面进行泛型的生命,和泛型类和泛型接口一样,可以声明多个泛型,用英文逗号隔开。
泛型方法使我们可以使用未定义在类声明中的类型参数。
三、规则&限制
1.泛型的类型参数只能是类类型,不能是简单类型。
List<int> list = new ArrayList<int>();
/*这样写也是错误,是自己粗心大意,有些想当然了。如果由于自己的失误给大家带来
误解,在这里向大家表示歉意。
*/
2.泛型的类型参数可以使用extends语句,例如:
public class Test<T extends List> {//...}
在以上的类声明中类型参数只能是List的子类,不能为其他类类型 。请注意,在这里extends关键字不能理解成继承的意思,因为在这里extends既适用于类也适用于接口,就如上面那样,所以在这里extends应该理解为“是一个”的意思。
3.泛型的类型参数可以有多个,就像API中的Map一样,
public interface Map<K,V>{//...}
4.同一种泛型对应多个版本,因为参数类型是不确定的,但是不同版本的泛型类实例是不兼容的,包括具有继承关系的类类型。下面看下面这个例子。
public class Test{
public static void test(List<Animal> list){}
static class Animal{}
static class Dog extends Animal{}
static class Cat extends Animal{}
public static void main(String[] args){
//创建Animal类型的集合
List<Animal> animalList = new ArrayList<Animal>();
animalList.add(new Animal());
//由于Dog和Cat类继承自Animal,所以也可以说Dog和Cat也是Animal类型的,所以可以网Animal类型的集合中添加Dog和Cat的实例
animalList.add(new Dog());
animalList.add(new Cat());
//创建Dog类型的集合
List<Dog> dogList = new ArrayList<Dog>();
dogList.add(new Dog());
dogList.add(new Dog());
//调用test方法
test(animalList);
test(dogList);//编译器将会报错
}
}
在上面的实例中我们首先定义了一个接收Animal类型集合作为参数的test方法,然后再Main主方法中分别定义了Animal类型和Dog类型的List集合。
接下来我们调用test方法将animalList
和dogList
作为参数传进去,
这时我们会发现test(dogList)
会报错。
这是因为将子类集合加入到具有声明为父类集合的参数的方法中是不合法的操作,也就是我们上面说的不同版本的泛型类实例是不兼容的。
那么你可能要疑问:那么我该怎样使用多态化集合参数呢?
方法1.使用通配符类型?,就像下面这样
...
public static void test(List<?> list){}
...
public static void main(String[] args){
...
test(animalList);
test(dogList);//这样编译器就不会报错了
}
方法2.在声明泛型时使用extends语句
...
public static <T extends Animal> void test(List<T> list){}
...
public static void main(String[] args){
...
test(animalList);
test(dogList);//这样编译器就不会报错了
}
<b>注意</b>:使用上面两种方式时,编译器会组织任何可能破坏形式参数所指集合的行为,也就是不能在定义的方法中,不能向作为参数的集合中添加任何元素,但可以删除元素。
三、其他
数组也是我们经常使用的用来存储数据的一种集合
对于数组的类型是在运行时期检查的,对于集合的类型检查只会发生在编译时期。
所以因为多态的关系,将子类数组传入具有声明为父类数组的参数的方法中是完全合法的操作。但是如果将子类数组传入到方法中后,对该数组随意进行赋值操作,会报ArrayStoreException异常,就行下面这样。
public class Test {
public static void test2(Animal[] animals){
animals[0] = new Cat();
animals[1] = new Animal();
}
public static void main(String[] args){
Animal[] animals = new Animal[3];
Dog[] dogs = new Dog[3];
test2(animals);
test2(dogs);
}
}
在test2()
方法中对参数数组进行了赋值操作,当将Dog类型的数组传入test2()
方法中,在程序运行时会有ArrayStoreException异常。
因为经验尚浅,所以如果有错误的或者描述不妥的地方,希望各位读者帮忙指正。