泛型

泛型类

泛型通配符<? extends T>来接收返回的数据,此写法的泛型集合不能使用 add 方 法,而<? super T>不能使用 get 方法,作为接口调用赋值时易出错。
说明:扩展说一下 PECS(Producer Extends Consumer Super)原则:第一、频繁往外读取内
容的,适合用<? extends T>。第二、经常往里插入的,适合用<? super T>。

泛型类的最基本写法
1
2
3
4
5
6
class 类名称 <泛型标识:可以随便写任意标识号,标识指定的泛型的类型>{
private 泛型标识 /*(成员变量类型)*/ var;
.....

}
}

一个最普通的泛型类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//此处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,T的类型由外部指定
return key;
}
}

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

//传入的实参类型需与泛型的类型参数类型相同,即为String.
Generic<String> genericString = new Generic<String>("key_vlaue");
Log.d("泛型测试","key is " + genericInteger.getKey());
Log.d("泛型测试","key is " + genericString.getKey());
1
2
3
4
5
6
7
注意:

泛型的类型参数只能是类类型,不能是简单类型。
不能对确切的泛型类型使用instanceof操作。如下面的操作是非法的,编译时会出错。

if(ex_num instanceof Generic<Number>){
}

泛型接口

泛型接口与泛型类的定义及使用基本相同。泛型接口常被用在各种类的生产器中,可以看一个例子:

1
2
3
4
//定义一个泛型接口
public interface Generator<T> {
public T next();
}

当实现泛型接口的类,未传入泛型实参时:

1
2
3
4
5
6
7
8
9
10
11
/**
* 未传入泛型实参时,与泛型类的定义相同,在声明类的时候,需将泛型的声明也一起加到类中
* 即:class FruitGenerator<T> implements Generator<T>{
* 如果不声明泛型,如:class FruitGenerator implements Generator<T>,编译器会报错:"Unknown class"
*/
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
/**
* 传入泛型实参时:
* 定义一个生产器实现这个接口,虽然我们只创建了一个泛型接口Generator<T>
* 但是我们可以为T传入无数个实参,形成无数种类型的Generator接口。
* 在实现类实现泛型接口时,如已将泛型类型传入实参类型,则所有使用泛型的地方都要替换成传入的实参类型
* 即:Generator<T>,public T next();中的的T都要替换成传入的String类型。
*/
public class FruitGenerator implements Generator<String> {

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

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

泛型方法

1
2
3
4
5
首先在public与返回值之间的<T>必不可少,这表明这是一个泛型方法,并且声明了一个泛型T这个T可以出现在这个泛型方法的任意位置.
泛型的数量也可以为任意多个如:
public <T,K> K showKeyName(Generic<T> container){
...
}

泛型方法与可变参数

泛型方法和可变参数的例子:

1
2
3
4
5
6
7
public <T> void printMsg( T... args){
for(T t : args){
Log.d("泛型测试","t is " + t);
}
}

printMsg("111",222,"aaaa","2323.4",55.55);

静态方法与泛型

静态方法有一种情况需要注意一下,那就是在类中的静态方法使用泛型:==静态方法无法访问类上定义的泛型==;
如果静态方法操作的引用数据类型不确定的时候,必须要将泛型定义在方法上。
即:如果静态方法要使用泛型的话,必须将静态方法也定义成泛型方法 。

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

}
}

泛型上下边界

泛型类的例子
1
2
3
4
5
6
7
8
9
10
11
public class Generic<T extends Number>{
private T key;

public Generic(T key) {
this.key = key;
}

public T getKey(){
return key;
}
}
泛型方法的例子:
1
2
3
4
5
6
7
//在泛型方法中添加上下边界限制的时候,必须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明的时候添加
//public <T> T showKeyName(Generic<T extends Number> container),编译器会报错:"Unexpected bound"
public <T extends Number> T showKeyName(Generic<T> container){
System.out.println("container key :" + container.getKey());
T test = container.getKey();
return test;
}

泛型方法的类型推断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 /**不指定泛型的时候*/  
int i=Test.add(1, 2); //这两个参数都是Integer,所以T替换为Integer类型
Number f=Test.add(1, 1.2);//这两个参数一个是Integer,另一个是Float,所以取同一父类的最小级,为Number
Object o=Test.add(1, "asd");//这两个参数一个是Integer,另一个是String,所以取同一父类的最小级,为Object

/**指定泛型的时候*/
int a=Test.<Integer>add(1, 2);//指定了Integer,所以只能为Integer类型或者其子类
int b=Test.<Integer>add(1, 2.2);//编译错误,指定了Integer,不能为Float
Number c=Test.<Number>add(1, 2.2); //指定为Number,所以可以为Integer和Float

//这是一个简单的泛型方法
public static <T> T add(T x,T y){
return y;
}

类型擦除

编译期间,所有的泛型信息都会被擦除,List和List类型,在编译后都会变成List类型(原始类型).

原始类型

原始类型就是泛型类型擦除了泛型信息后,在字节码中真正的类型。无论何时定义一个泛型类型,相应的原始类型都会被自动提供。原始类型的名字就是删去类型参数后的泛型类型的类名。擦除类型变量,并替换为限定类型(T为无限定的类型变量,用Object替换)
因为在Pair中,T是一个无限定的类型变量,所以用Object替换。
如果是Pair,擦除后,类型变量用Number类型替换

1
2
3
4
5
6
7
8
9
ArrayList<Integer> array=new ArrayList<Integer>();  
array.add(1);//这样调用add方法只能存储整形,因为泛型类型的实例为Integer
array.getClass().getMethod("add", Object.class).invoke(array, "asd");
for (int i=0;i<array.size();i++) {
System.out.println(array.get(i));
}
输出:
1
asd

PECS法则

PECS法则:生产者(Producer)使用extends,消费者(Consumer)使用super

1、生产者

如果你需要一个提供E类型元素的集合,使用泛型通配符<? extends E>。它好比一个生产者,可以提供数据。

1
2
3
4
5
6
List<? extends Number> list=new ArrayList();
list.add(new Integer(1));//编译错误
list.add(new Float(1.0));//编译错误

List<? extends Number> list1=new ArrayList<Integer>();
List<? extends Number> list2=new ArrayList<Float>();
2、消费者

如果你需要一个只能装入E类型元素的集合,使用泛型通配符<? super E>。它好比一个消费者,可以消费你提供的数据。

1
2
3
List<? super Number> list=new ArrayList(); 
list.add(new Integer(1));
list.add(new Float(1.1));
3、既是生产者也是消费者

既要存储又要读取,那就别使用泛型通配符。

泛型相关问题

1、泛型类型引用传递问题

在Java中,像下面形式的引用传递是不允许的:

1
2
ArrayList<String> arrayList1=new ArrayList<Object>();//编译错误  
ArrayList<Object> arrayList1=new ArrayList<String>();//编译错误
第一种情况,将第一种情况拓展成下面的形式:
1
2
3
4
ArrayList<Object> arrayList1=new ArrayList<Object>();  
arrayList1.add(new Object());
arrayList1.add(new Object());
ArrayList<String> arrayList2=arrayList1;//编译错误

在第4行代码处,就会有编译错误。那么,先假设它编译没错。那么当我们使用arrayList2引用用get()方法取值的时候,返回的都是String类型的对象,可是它里面实际上已经存放了Object类型的对象,这样,就会有ClassCastException了。

在看第二种情况,将第二种情况拓展成下面的形式:
1
2
3
4
ArrayList<String> arrayList1=new ArrayList<String>();  
arrayList1.add(new String());
arrayList1.add(new String());
ArrayList<Object> arrayList2=arrayList1;//编译错误

这样的情况比第一种情况好的多,最起码,用arrayList2取值的时候不会出现ClassCastException,因为是从String转换为Object。可是,这样做有什么意义呢,泛型出现的原因,就是为了解决类型转换的问题。我们使用了泛型,到头来,还是要自己强转,违背了泛型设计的初衷。所以java不允许这么干。再说,如果又用arrayList2往里面add()新的对象,那么到时候取得时候,我怎么知道我取出来的到底是String类型的,还是Object类型的呢?
所以,要格外注意泛型中引用传递问题。

2、泛型类型变量不能是基本数据类型

比如,没有ArrayList,只有ArrayList。因为当类型擦除后,ArrayList的原始类中的类型变量(T)替换为Object,但Object类型不能存储double值.

3、运行时类型查询

举个例子:

1
ArrayList<String> arrayList=new ArrayList<String>();

因为类型擦除之后,ArrayList只剩下原始类型,泛型信息String不存在了。那么,运行时进行类型查询的时候使用下面的方法是错误的

1
if( arrayList instanceof ArrayList<String>)

java限定了这种类型查询的方式,?为通配符,也即非限定符。

1
if( arrayList instanceof ArrayList<?>)