一、认识泛型
泛型最常用的场景是在集合中使用,想必我们对这样的代码也不陌生
1 2 |
|
可以理解泛型为一个模板可以充当你需要的类型
二、使用泛型
-
泛型类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public
class
Order {
String orderName;
int
orderId;
//类的内部结构就可以使用类的泛型
T orderT;
public
Order(){
//编译不通过,不能new泛型对象
// T[] arr = new T[10];
//编译通过
T[] arr = (T[])
new
Object[
10
];
}
public
Order(String orderName,
int
orderId,T orderT){
this
.orderName = orderName;
this
.orderId = orderId;
this
.orderT = orderT;
}
//如下的个方法都不是泛型方法
public
T getOrderT(){
return
orderT;
}
public
void
setOrderT(T orderT){
this
.orderT = orderT;
}
@Override
public
String toString() {
return
"Order{"
+
"orderName='"
+ orderName + '\
''
+
", orderId="
+ orderId +
", orderT="
+ orderT +
'}'
;
}
//静态方法中不能使用类的泛型。
// public static void show(T orderT){
// System.out.println(orderT);
// }
public
void
show(){
//编译不通过
// try{
//
//
// }catch(T t){
//
// }
}
//泛型方法:在方法中出现了泛型的结构,泛型参数与类的泛型参数没任何关系。
//换句话说,泛型方法所属的类是不是泛型类都没关系。
//泛型方法,可以声明为静态的。原因:泛型参数是在调用方法时确定的。并非在实例化类时确定。
public
static
List copyFromArrayToList(E[] arr){
ArrayList list =
new
ArrayList();
for
(E e : arr){
list.add(e);
}
return
list;
}
}
【SubOrder.java】
1 2 3 4 5 6 7 8 9 |
|
- 泛型接口
1 2 3 4 5 6 7 8 |
|
- 泛型方法
小结:1
2
3
4
5
public
class
GeneTest{
public
E show(E obj){
System.out.println(obj);
}
}
- 泛型类中的泛型对象不能使用new方法
- 静态方法中不要使用泛型参数,因为静态方法在类加载时就已经初始化了这时还编译器无法确定泛型。
- 若父类是泛型,则在继承时子类分为:全继承(将父类的泛型全部继承),部分继承(将父类部分泛型指定具体类型,再将每指定具体类型的泛型继承),全不继承(将父类的泛型全看作object对象)。
- 泛型类及接口的泛型是在new对象的时候确定的,而泛型方法的泛型是在调用方法时才决定而非在new对象时确定
- 异常类没有泛型。
三、擦拭法
其实虚拟机是不认识泛型的,泛型是依赖编辑器完成的,在早期的jdk中,java采用将属性、参数声明为object方式来增加自由度,到jdk1.5后引入了泛型,为了兼容早期的版本,于是在jvm将泛型看作是object。
1 2 3 4 |
|
上述代码等同于
1 2 3 4 |
|
四、通配符
说到通配符,首先我们来聊聊泛型的继承,看下面一段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
有同学会认为,上面的代码是没有问题的,因为Number是Integer的父类,这不就是多态嘛,其实不是这样的,同一个类指定不同类型作为泛型的对象之间是并列关系,并不是继承关系,编译器不会通过这样的语法。
如何让上面代码成立?
1 2 3 |
|
只需要加一个通配符即可,下面我们来讲以下通配符
通配符的关键字为? 我们常用<? extends 类型> 和 <? super 类型>
- 以上面的Data类为例,当我们采用了Data<? extends Number>修饰的时候,该变量可以指向Data<泛型为Number或其子类>的对象,但不能修改其泛型字段指向其他对象。
1
2
3
4
5
6
7
8
Data<?
super
NumSon> dataSon =
new
Data<>(
new
NumSon(
1
,
2
,
3
));
Data<Num> data =
new
Data<>(
new
Num(
4
,
5
));
Data<NumFather> dataFa =
new
Data<>(
new
NumFather(
6
));
//通过编译,super通配符修饰的对象
dataSon = data;
Data<?
extends
NumFather> exFather = data;
//无法通过编译,因为尝试修改其泛型字段指向其他对象
exFather.setData(
new
Num(
1
,
2
));
原因:当我们初始化变量时指向了一个Data<integer>的对象,而在后面的代码中又将该变量指向了一个Data<double>的对象,这是错误的,所以编译器为了防止这种错误的发生,不允许让<? extends 类型>的对象在初始化完成之后又指向其他的地址。
2. 与<? extends 类型>相反,<? super T>允许该泛型修饰的对象可以指向T类型及其父类的对象,但你不方便去读取该泛型对象的泛型字段。以如下代码为例</double></integer>
1 2 3 4 5 6 |
|
小结:PESC原则:extends通配符则只用来读取泛型字段而不修改,super修饰符则只用来修改泛型字段而不读取。
如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。