Java基础-泛型

概念

泛型,即“参数化类型”,在java中是指把类型明确的工作推迟到创建对象或调用方法的时候再去做。

一个例子

1
2
3
4
5
6
7
8
List arrayList = new ArrayList();
arrayList.add("aaaa");
arrayList.add(100);

for(int i = 0; i< arrayList.size();i++){
String item = (String)arrayList.get(i);
System.out.println("泛型测试","item = " + item);
}

运行上面的代码将抛出异常:java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

ArrayList作为一个容器,可以存放任意类型(Object类型),例子中添加了一个String类型,添加了一个Integer类型,但使用时都当做String类型来使用,所以报错。从这个例子可以看到,因为开发人员可以把多个类型放入容器,从容器中取出时需要转换成实际的类型,但开发人员很难知道容器中所有的元素的实际类型是什么,所以就会导致类型不安全的问题。而泛型的引入,可以让程序在编译阶段就杜绝这种类型安全问题。

将例子中的第一行声明初始化list的代码更改一下,看看会有什么样的结果:

1
2
3
List<String> arrayList = new ArrayList<String>();
...
//arrayList.add(100); 在编译阶段,编译器就会报类型错误

可以看到使用泛型后编译器就能帮助我们做类型安全的检查工作,这就避免了取出元素时可能发生的ClassCastException异常,开发人员无需记住各个对象的类型并担心类型的匹配问题,因为里面的元素都是ArrayList声明的泛型类型,取出时也无需再做强制类型转换。

泛型的使用

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法。

泛型类

泛型类型用于类的定义中,被称为泛型类。通过泛型可以对一种类型对外开放相同的操作,最典型的就是各种容器类,如:List、Set、Map,可以完成各种增删改查操作。
泛型类定义的简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}

public T getKey(){ //泛型方法getKey的返回值类型为T
return key;
}
}

使用这个泛型:

1
2
3
4
5
6
7
8
//泛型的类型参数只能是类类型(包括自定义类),不能是简单类型
//传入的实参类型需与泛型的类型参数类型相同,即为Integer.
Generic<Integer> genericInteger = new Generic<Integer>(123456);

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
System.out.println("Integer泛型测试:key is " + genericInteger.getKey()); //Integer泛型测试:key is 123456
System.out.println("String泛型测试:key is " + genericString.getKey()); //String泛型测试:key is key_vlaue

注意:泛型的类型参数只能是类类型,不能是简单类型。

此外,泛型的<>里也可以放多个参数:

1
2
3
4
5
6
public class Generic<T,S> {

private T t;
private S s;

//...

泛型接口

泛型接口与泛型类的定义及使用基本相同。在实现(或继承)泛型接口(或类)时,可以明确泛型接口(或类)的类型,也可以不明确。
定义一个泛型接口:

1
2
3
public interface Generator<T> {
public T next();
}

明确类型
在实现泛型类时明确父类的类型:

1
2
3
4
5
6
7
8
9
10
public class FruitGenerator implements Generator<String> {

private String[] fruits = new String[]{"Apple", "Banana", "Orange"};

@Override
public String next() {
Random rand = new Random();
return fruits[rand.nextInt(3)];
}
}

不明确类型

1
2
3
4
5
6
class FruitGenerator<T> implements Generator<T>{
@Override
public T next() {
return null;
}
}

泛型方法

泛型方法的一个特征是在方法声明中,带有一对尖括号<>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class GenerateTest<T>{

//这不是一个泛型方法,只是一个泛型类中的普通方法
public void show_1(T t){
System.out.println(t.toString());
}

//在泛型类中声明了一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可以不同。
//由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
System.out.println(t.toString());
}

//在泛型类中声明了一个泛型方法,使用泛型T,注意这个T是一种全新的类型,可以与泛型类中声明的T不是同一种类型。
public <T> void show_2(T t){
System.out.println(t.toString());
}
}

静态泛型方法
如果要在类中的静态方法使用泛型,须注意:①静态方法无法访问类上定义的泛型;②如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class StaticGenerator<T> {
....
....
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
"StaticGenerator cannot be refrenced from static context"
*/
public static <T> void show(T t){

}
}

泛型通配符

看看这个泛型和多态的问题,Dog,Cat是Animal的子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void feddAll(ArrayList<Animal> animals) {
...
};

//Animal容器
ArrayList<Animal> animals=new ArrayList<Animal>();
animals.add(new Dog());a
nimals.add(new Cat());
feddAll(animals);//这里ok

//Dog容器
ArrayList<Dog> dogs=new ArrayList<Dog>();
dogs.add(new Dog());
dogs.add(new Dog());
feddAll(dogs);//这里编译不通过

这个例子中需要使用泛型通配符来解决,将feddAll()方法改成如下,编译就可以通过了。

1
2
3
public void feddAll(ArrayList<T extends Animal> animals) {
...
};

泛型的通配符包括了如下几种。
无界:?
“?”可以用来接收任何类型,例子如下:

1
2
3
4
5
public void processElements(List<?> elements){
for(Object o : elements){
System.out.println(o);
}
}

上界:? extends A
表示泛型可以接收任何A类或A类的子类。

1
2
3
4
5
public void processElements(List<? extends A> elements){
for(A a : elements){
System.out.println(a.getValue());
}
}

下界:? super A
表示泛型可以接收任何A类或A类的超类。

1
2
3
4
5
public static void insertElements(List<? super A> list){
list.add(new A());
list.add(new B());
list.add(new C());
}

几个注意点

  1. 上界通配符主要用于读数据,下界通配符主要用于写数据。
  2. T 的区别。
    1
    2
    3
    4
    5
    //指定集合元素只能是T类型
    List<T> list1 = new ArrayList<T>();

    //集合元素可以是任意类型,基本上不会这么用,因为没有什么意义,这里只是说明用法。
    List<?> list2 = new ArrayList<?>();
------ 本文完 ------