初步理解Java的泛型特性

时间:2022-08-31 19:53:19

在Java SE1.5中,增加了一个新的特性:泛型(日本语中的总称型)。何谓泛型呢?通俗的说,就是泛泛的指定对象所操作的类型,而不像常规方式一样使用某种固定的类型去指定。泛型的本质就是将所操作的数据类型参数化,也就是说,该数据类型被指定为一个参数。这种参数类型可以使用在类、接口以及方法定义中。
 
一、为什么使用泛型呢?
     在以往的J2SE中,没有泛型的情况下,通常是使用Object类型来进行多种类型数据的操作。这个时候操作最多的就是针对该Object进行数据的强制转换,而这种转换是基于开发者对该数据类型明确的情况下进行的(比如将Object型转换为String型)。倘若类型不一致,编译器在编译过程中不会报错,但在运行时会出错。
    使用泛型的好处在于,它在编译的时候进行类型安全检查,并且在运行时所有的转换都是强制的,隐式的,大大提高了代码的重用率。
 
二、定义&使用
 类型参数的命名风格为:
 推荐你用简练的名字作为形式类型参数的名字(如果可能,单个字符)。最好避免小写字母,这使它和其他的普通
 的形式参数很容易被区分开来。
 使用T代表类型,无论何时都没有比这更具体的类型来区分它。这经常见于泛型方法。如果有多个类型参数,我们
 可能使用字母表中T的临近的字母,比如S。
 如果一个泛型函数在一个泛型类里面出现,最好避免在方法的类型参数和类的类型参数中使用同样的名字来避免混
 淆。对内部类也是同样。
 
1.定义带类型参数的类
 在定义带类型参数的类时,在紧跟类命之后的<>内,指定一个或多个类型参数的名字,同时也可以对类型参数的取
 值范围进行限定,多个类型参数之间用,号分隔。
 定义完类型参数后,可以在定义位置之后的类的几乎任意地方(静态块,静态属性,静态方法除外)使用类型参数,
 就像使用普通的类型一样。
 注意,父类定义的类型参数不能被子类继承。

?
1
2
3
public class TestClassDefine<T, S extends T> {
  ....
}

 
2.定义待类型参数方法
 在定义带类型参数的方法时,在紧跟可见范围修饰(例如public)之后的<>内,指定一个或多个类型参数的名字,
 同时也可以对类型参数的取值范围进行限定,多个类型参数之间用,号分隔。
 定义完类型参数后,可以在定义位置之后的方法的任意地方使用类型参数,就像使用普通的类型一样。
 例如:

?
1
2
3
public <T, S extends T> T testGenericMethodDefine(T t, S s){
  ...
}

 注意:定义带类型参数的方法,骑主要目的是为了表达多个参数以及返回值之间的关系。例如本例子中T和S的继
 承关系, 返回值的类型和第一个类型参数的值相同。
 如果仅仅是想实现多态,请优先使用通配符解决。通配符的内容见下面章节。

?
1
2
3
public <T> void testGenericMethodDefine2(List<T> s){
  ...
}

 应改为

?
1
2
3
public void testGenericMethodDefine2(List<?> s){
  ...
}

 
3. 类型参数赋值
 当对类或方法的类型参数进行赋值时,要求对所有的类型参数进行赋值。否则,将得到一个编译错误。
 
4.对带类型参数的类进行类型参数赋值
 对带类型参数的类进行类型参数赋值有两种方式
 第一声明类变量或者实例化时。例如

?
1
2
List<String> list;
list = new ArrayList<String>;

 第二继承类或者实现接口时。例如

?
1
public class MyList<E> extends ArrayList<E> implements List<E> {...}

 
5.对带类型参数方法进行赋值

 当调用范型方法时,编译器自动对类型参数进行赋值,当不能成功赋值时报编译错误。例如

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public <T> T testGenericMethodDefine3(T t, List<T> list){
  ...
}
public <T> T testGenericMethodDefine4(List<T> list1, List<T> list2){
  ...
}
 
Number n = null;
Integer i = null;
Object o = null;
testGenericMethodDefine(n, i);//此时T为Number, S为Integer
testGenericMethodDefine(o, i);//T为Object, S为Integer
 
List<Number> list1 = null;
testGenericMethodDefine3(i, list1)//此时T为Number
 
List<Integer> list2 = null;
testGenericMethodDefine4(list1, list2)//编译报错

 
6.通配符
 在上面两小节中,对是类型参数赋予具体的值,除此,还可以对类型参数赋予不确定值。例如

?
1
2
3
List<?> unknownList;
List<? extends Number> unknownNumberList;
List<? super Integer> unknownBaseLineIntgerList;

 注意: 在Java集合框架中,对于参数值是未知类型的容器类,只能读取其中元素,不能像其中添加元素,
 因为,其类型是未知,所以编译器无法识别添加元素的类型和容器的类型是否兼容,唯一的例外是NULL

?
1
2
3
4
List<String> listString;
List<?> unknownList2 = listString;
unknownList = unknownList2;
listString = unknownList;//编译错误

 
7.数组范型

 可以使用带范型参数值的类声明数组,却不可有创建数组

?
1
2
List<Integer>[] iListArray;
new ArrayList<Integer>[10];//编译时错误

 
三、扩展
1、extends语句

使用extends语句将限制泛型参数的适用范围。例如:
<T extends collection> ,则表示该泛型参数的使用范围是所有实现了collection接口的calss。如果传入一个<String>则程序编译出错。
2、super语句
super语句的作用与extends一样,都是限制泛型参数的适用范围。区别在于,super是限制泛型参数只能是指定该class的上层父类。
例如<T super List>,表示该泛型参数只能是List和List的上层父类。
3、通配符
使用通配符的目的是为了解决泛型参数被限制死了不能动态根据实例来确定的缺点。
举个例子:public class SampleClass < T extends S> {…}
假如A,B,C,…Z这26个class都实现了S接口。我们使用时需要使用到这26个class类型的泛型参数。那实例化的时候怎么办呢?依次写下

?
1
2
3
4
SampleClass<A> a = new SampleClass();
SampleClass<B> a = new SampleClass();
SampleClass<Z> a = new SampleClass();

这显然很冗余,还不如使用Object而不使用泛型,呵呵,是吧?
别着急,咱们使用通配符,就OK了。

?
1
SampleClass<? Extends S> sc = new SampleClass();

 
只需要声明一个sc变量,很方便把!