标签:编译 ESS 假设 直接 原因 warning 实现类 语言 限制
前言整理一下Java泛型的相关知识,算是比较基础的,希望大家一起学习进步。
Java 泛型(generics)是 JDK 5 中引入的一个新特性,其本质是参数化类型,解决不确定具体对象类型的问题。其所操作的数据类型被指定为一个参数(type parameter)这种参数类型可以用在类、接口和方法的创建中,分别称为泛型类、泛型接口、泛型方法。
泛型类(generic class) 就是具有一个或多个类型变量的类。一个泛型类的简单例子如下:
//常见的如T、E、K、V等形式的参数常用于表示泛型,编译时无法知道它们类型,实例化时需要指定。
public
class
Pair
<
K
,
V
>{
private
K first
;
private
V second
;
public
Pair
(
K first
,
V second
)
{
this
.
first
=
first
;
this
.
second
=
second
;
}
public
K getFirst
()
{
return
first
;
}
public
void
setFirst
(
K first
)
{
this
.
first
=
first
;
}
public
V getSecond
()
{
return
second
;
}
public
void
setSecond
(
V second
)
{
this
.
second
=
second
;
}
public
static
void
main
(
String
[]
args
)
{
// 此处K传入了Integer,V传入String类型
Pair
<
Integer
,
String
>
pairInteger
=
new
Pair
<>(
1
,
"第二"
);
System
.
out
.
println
(
"泛型测试,first is "
+
pairInteger
.
getFirst
()
+
" ,second is "
+
pairInteger
.
getSecond
());
}
}
运行结果如下:
泛型测试,
first
is
1
,
second
is
第二
泛型也可以应用于接口。
public
interface
Generator
<
T
>
{
T
next
();
}
实现类去实现这个接口的时候,可以指定泛型T的具体类型。
指定具体类型为Integer的实现类:
public
class
NumberGenerator
implements
Generator
<
Integer
>
{
@Override
public
Integer
next
()
{
return
new
Random
().
nextInt
();
}
}
指定具体类型为String的实现类:
public
class
StringGenerator
implements
Generator
<
String
>
{
@Override
public
String
next
()
{
return
"测试泛型接口"
;
}
}
具有一个或多个类型变量的方法,称之为泛型方法。
public
class
GenericMethods
{
public
<
T
>
void
f
(
T x
){
System
.
out
.
println
(
x
.
getClass
().
getName
());
}
public
static
void
main
(
String
[]
args
)
{
GenericMethods
gm
=
new
GenericMethods
();
gm
.
f
(
"字符串"
);
gm
.
f
(
666
);
}
}
运行结果:
java
.
lang
.
String
java
.
lang
.
Integer
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
我们先来看看一个只能持有单个对象的类。
public
class
Holder1
{
private
Automobile
a
;
public
Holder1
(
Automobile
a
)
{
this
.
a
=
a
;
}
public
Automobile
getA
()
{
return
a
;
}
}
我们可以发现,这个类的重用性不怎样。要使它持有其他类型的任何对象,在jdk1.5泛型之前,可以把类型设置为Object,如下:
public
class
Holder2
{
private
Object
a
;
public
Holder2
(
Object
a
)
{
this
.
a
=
a
;
}
public
Object
getA
()
{
return
a
;
}
public
void
setA
(
Object
a
)
{
this
.
a
=
a
;
}
public
static
void
main
(
String
[]
args
)
{
Holder2
holder2
=
new
Holder2
(
new
Automobile
());
//强制转换
Automobile
automobile
=
(
Automobile
)
holder2
.
getA
();
holder2
.
setA
(
"测试泛型"
);
String
s
=
(
String
)
holder2
.
getA
();
}
}
我们引入泛型,实现功能那个跟Holder2类一致的Holder3,如下:
public
class
Holder3
<
T
>
{
private
T a
;
public
T getA
()
{
return
a
;
}
public
void
setA
(
T a
)
{
this
.
a
=
a
;
}
public
Holder3
(
T a
)
{
this
.
a
=
a
;
}
public
static
void
main
(
String
[]
args
)
{
Holder3
<
Automobile
>
holder3
=
new
Holder3
<>(
new
Automobile
());
holder3
.
setA
(
"测试泛型"
);
Automobile
automobile
=
holder3
.
getA
();
}
}
因此,泛型的好处很明显了:
我们定义泛型时,经常碰见T,E,K,V,?等通配符。本质上这些都是通配符,是编码时一种约定俗成的东西。当然,你换个A-Z中另一个字母表示没有关系,但是为了可读性,一般有以下定义:
为什么需要引入通配符呢,我们先来看一个例子:
class
Fruit
{
public
int
getWeigth
(){
return
0
;
}
}
//Apple是水果Fruit类的子类
class
Apple
extends
Fruit
{
public
int
getWeigth
(){
return
5
;
}
}
public
class
GenericTest
{
//数组的传参
static
int
sumWeigth
(
Fruit
[]
fruits
)
{
int
weight
=
0
;
for
(
Fruit
fruit
:
fruits
)
{
weight
+=
fruit
.
getWeigth
();
}
return
weight
;
}
static
int
sumWeight1
(
List
<?
extends
Fruit
>
fruits
)
{
int
weight
=
0
;
for
(
Fruit
fruit
:
fruits
)
{
weight
+=
fruit
.
getWeigth
();
}
return
weight
;
}
static
int
sumWeigth2
(
List
<
Fruit
>
fruits
){
int
weight
=
0
;
for
(
Fruit
fruit
:
fruits
)
{
weight
+=
fruit
.
getWeigth
();
}
return
weight
;
}
public
static
void
main
(
String
[]
args
)
{
Fruit
[]
fruits
=
new
Apple
[
10
];
sumWeigth
(
fruits
);
List
<
Apple
>
apples
=
new
ArrayList
<>();
sumWeight1
(
apples
);
//报错
sumWeigth2
(
apples
);
}
}
我们可以发现,Fruit[]与Apple[]是兼容的。 List<Fruit>与 List<Apple>不兼容的,集合List是不能协变的,会报错,而 List<Fruit>与 List<?extendsFruits> 是OK的,这就是通配符的魅力所在。通配符通常分三类:
无边界通配符,它的使用形式是一个单独的问号: List<?>,也就是没有任何限定。
看个例子:
public
class
GenericTest
{
public
static
void
printList
(
List
<?>
list
)
{
for
(
Object
object
:
list
)
{
System
.
out
.
println
(
object
);
}
}
public
static
void
main
(
String
[]
args
)
{
List
<
String
>
list1
=
new
ArrayList
<>();
list1
.
add
(
"A"
);
list1
.
add
(
"B"
);
List
<
Integer
>
list2
=
new
ArrayList
<>();
list2
.
add
(
100
);
list2
.
add
(
666
);
//报错,List<?>不能添加任何类型
List
<?>
list3
=
new
ArrayList
<>();
list3
.
add
(
666
);
}
}
通配符 (<?>)可以适配任何引用类型,看起来与原生类型等价,但与原生类型还是有区别,使用通配符则表明在使用泛型 。同时, List<?>list不可以添加任何类型,因为并不知道实际是哪种类型。但是List list因为持有的是Object类型对象,所以可以add任何类型的对象。
使用 <?extendsFruit> 形式的通配符,就是上边界限定通配符。 extends关键字表示这个泛型中的参数必须是 E 或者 E 的子类,请看demo:
class
apple
extends
Fruit
{}
static
int
sumWeight1
(
List
<?
extends
Fruit
>
fruits
)
{
int
weight
=
0
;
for
(
Fruit
fruit
:
fruits
)
{
weight
+=
fruit
.
getWeigth
();
}
return
weight
;
}
public
static
void
main
(
String
[]
args
)
{
List
<
Apple
>
apples
=
new
ArrayList
<>();
sumWeight1
(
apples
);
}
但是,以下这段代码是不可行的:
static
int
sumWeight1
(
List
<?
extends
Fruit
>
fruits
){
//报错
fruits
.
add
(
new
Fruit
());
//报错
fruits
.
add
(
new
Apple
());
}
使用 <?superE>形式的通配符,就是下边界限定通配符。 super关键字表示这个泛型中的参数必须是所指定的类型E,或者是此类型的父类型,直至 Object。
public
class
GenericTest
{
private
static
<
T
>
void
test
(
List
<?
super
T
>
dst
,
List
<
T
>
src
){
for
(
T t
:
src
)
{
dst
.
add
(
t
);
}
}
public
static
void
main
(
String
[]
args
)
{
List
<
Apple
>
apples
=
new
ArrayList
<>();
List
<
Fruit
>
fruits
=
new
ArrayList
<>();
test
(
fruits
,
apples
);
}
}
可以发现, List<?superE>添加是没有问题的,因为子类是可以指向父类的,它添加并不像 List<?extendsE>会出现安全性问题,所以可行。
什么是Java泛型擦除呢? 先来看demo:
Class
c1
=
new
ArrayList
<
Integer
>().
getClass
();
Class
c2
=
new
ArrayList
<
String
>().
getClass
();
System
.
out
.
println
(
c1
==
c2
);
/* Output
true
*/
日常开发中, ArrayList<Integer> 和 ArrayList<String> 很容易被认为是不同的类型。但是这里输出结果是true,这是因为Java泛型是使用擦除实现的,不管是 ArrayList<Integer>() 还是 newArrayList<String>(),在编译生成的字节码中都不包含泛型中的类型参数,即都擦除成了ArrayList,也就是被擦除成“原生类型”,这就是泛型擦除。
Java泛型在编译期完成,它是依赖编译器实现的。其实,编译器主要做了这些工作:
再看个例子:
public
class
GenericTest
<
T
>
{
private
T t
;
public
T
get
()
{
return
t
;
}
public
void
set
(
T t
)
{
this
.
t
=
t
;
}
public
static
void
main
(
String
[]
args
)
{
GenericTest
<
String
>
test
=
new
GenericTest
<
String
>();
test
.
set
(
"jay@huaxiao"
);
String
s
=
test
.
get
();
System
.
out
.
println
(
s
);
}
}
/* Output
jay@huaxiao
*/
javap -c GenericTest.class反编译GenericTest类可得
public
class
generic
.
GenericTest
<
T
>
{
public
generic
.
GenericTest
();
Code
:
0
:
aload_0
1
:
invokespecial
#1 // Method java/lang/Object."<init>":()V
4
:
return
public
T
get
();
Code
:
0
:
aload_0
1
:
getfield
#2 // Field t:Ljava/lang/Object;
4
:
areturn
public
void
set
(
T
);
Code
:
0
:
aload_0
1
:
aload_1
2
:
putfield
#2 // Field t:Ljava/lang/Object;
5
:
return
public
static
void
main
(
java
.
lang
.
String
[]);
Code
:
0
:
new
#3 // class generic/GenericTest
3
:
dup
4
:
invokespecial
#4 // Method "<init>":()V
7
:
astore_1
8
:
aload_1
9
:
ldc
#5 // String jay@huaxiao
11
:
invokevirtual
#6 // Method set:(Ljava/lang/Object;)V
14
:
aload_1
15
:
invokevirtual
#7 // Method get:()Ljava/lang/Object;
18
:
checkcast
#8 // class java/lang/String
21
:
astore_2
22
:
getstatic
#9 // Field java/lang/System.out:Ljava/io/PrintStream;
25
:
aload_2
26
:
invokevirtual
#10 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
29
:
return
}
使用Java泛型需要考虑以下一些约束与限制,其实几乎都跟泛型擦除有关。
不能用类型参数代替基本类型。因此, 没有 Pair<double>, 只 有 Pair<Double>。 当然, 其原因是类型擦除。擦除之后, Pair 类含有 Object 类型的域, 而 Object 不能存储 double值。
如,getClass()方法等只返回原始类型,因为JVM根本就不知道泛型这回事,它只知道原始类型。
if
(
a
instanceof
Pair
<
String
>)
//ERROR,仅测试了a是否是任意类型的一个Pair,会看到编译器ERROR警告
if
(
a
instanceof
Pair
<
T
>)
//ERROR
Pair
<
String
>
p
=
(
Pair
<
String
>)
a
;
//WARNING,仅测试a是否是一个Pair
Pair
<
String
>
stringPair
=
...;
Pair
<
Employee
>
employeePair
=
...;
if
(
stringPair
.
getClass
()
==
employeePair
.
getClass
())
//会得到true,因为两次调用getClass都将返回Pair.class
不能实例化参数化类型的数组, 例如:
Pair
<
String
>[]
table
=
new
Pair
<
String
>[
10
];
// Error
不能使用像 new T(...),newT[...] 或 T.class 这样的表达式中的类型变量。例如, 下面的 Pair<T> 构造器就是非法的:
public
Pair
()
{
first
=
new
T
();
second
=
new
T
();
}
// Error
interface
Swim
<
T
>
{}
class
Duck
implements
Swim
<
Duck
>
{}
class
UglyDuck
extends
Duck
implements
Swim
<
UglyDuck
>
{}
@SuppressWamings
(
"unchecked"
)
public
static
<
T
extends
Throwable
〉
void
throwAs
(
Throwable
e
)
throws
T
{
throw
(
T
)
e
;
}
public
class
Response
<
T
>
extends
BaseResponse
{
private
static
final
long
serialVersionUID
=
-
xxx
;
private
T data
;
private
String
code
;
public
Response
()
{
}
public
T getData
()
{
return
this
.
data
;
}
public
void
setData
(
T data
,
String
code
)
{
this
.
data
=
data
;
this
.
code
=
code
;
}
}
Java泛型常见几道面试题
标签:编译 ESS 假设 直接 原因 warning 实现类 语言 限制
原文地址:https://blog.51cto.com/14989534/2547475