在过去的一段时间中,虽然一直在工作,但是也进行过了几次面试,对于面试会询问的问题还是有一些心得的。其中JavaSE的内容会考很多,所以需要个人对于基础十分扎实。在工作中我们使用到泛型的时候并不多,但是基本每次面试都会有这部分的内容,所以今天也来学习一下Java中泛型的作用。
泛型是在Java1.5版本加入的,那么为什么需要有泛型呢?他是用来做什么的呢?
1.泛型的产生原因
在没有泛型之前,我们创建一个ArrayList集合的时候是这样的
集合中的数据都是Object类型的变量
有兴趣的同学可以执行一下如下代码,这里我就不执行了
ArrayList list = new ArrayList();
list.add("helloworld");
list.add(100);
for(Object o : list) {
//假设我们需要打印集合中的元素,强转为字符串
String str = (String) o
System.out.println(str)
}
这段代码执行以后会报ClassCastException异常,也就是类型转换异常。
原因是数字类型不能转换为字符串类型。
这时候就有问题了,当我们向集合中放入多种类型的元素的时候,就会需要每个元素都需要判断类型后才能进行使用,否则很容易出现类型转换异常。
//获取对象的数据类型
对象.getClass().getSimpleName()
所以在Java1.5提出了泛型,来提供编译时的安全监测,当我们编译代码时可以监测到非法的数据类型。
2.泛型的使用
// 集合后面用<类型>,确定集合所用的泛型,限定为字符串类型
ArrayList<String> list = new ArrayList<>();
list.add("helloworld");
list.add(100);//此时添加int类型值就会报错,需要删除此行
list.add("100");
for(String o : list) {
// 此时我们循环打印集合数据,就没有问题了
System.out.println(o)
}
如上图代码,泛型为我们在编写代码时就限定了集合类可放入的元素的类型,避免了工作中很多人用了一个集合,有可能大家放入的数据就出现了多种数据类型,导致运行的时候报错。
3.泛型类的创建
/**
*
* T 泛型标识,代表形参 用于创建对象的时候指定具体的数据类型
*
*/
public class 自定义类名<T> {
private T name;
}
在这里穿插一个面试的问题,上面的泛型类我使用了T的泛型标识,那么其他的泛型标识还有E、T、K、V、N、?。
面试中有一个问题,就是这些泛型标识都代表什么意思,又或者说什么时候用哪个标识符呢?
泛型标识 | 运用场景 |
E | Element 表示元素,在集合中使用 |
T | Type 表示Java类 |
K | Key 表示键,一般用于map集合 |
V | Value 表示值,一般用于map集合 |
N | Number 表示数值类型 |
? | 表示不确定的java类型 |
我们创建了以后使用时需要像如下方式使用
// Java1.7以前,需要前后都写上类型
// 此方法说明这个类的泛型被确定为字符串类型
// 需要注意,泛型不支持基本数据类型,如int、long、boolean、double等
自定义类名<String> data = new 自定义类名<String>();
// 1.7以后可以省略后面的类型,被称为菱形语法
自定义类名<String> data = new 自定义类名<>();
// 如果没有限制泛型,则默认为Object类型
自定义类名 data = new 自定义类名();
4.泛型类的继承与实现
// 子类如果是泛型类,继承或实现泛型的父类时,需要与父类泛型标识一致
// 因为创建子类对象时,需要先创建父类对象,但如果泛型标识不一致,就无法判断父类的类型
// 子类的泛型标识可以放入多个,但是必须要有一个是父类的泛型标识
class child<T> extend parent<T>
class child<T> implements parent<T>
------------------------------------------------------------------------------
// 子类如果非泛型类,继承或实现泛型的父类时,需要确定父类的泛型数据类型
class child extend parent<String>
class child implements parent<String>
5.泛型方法的创建
public <泛型标识符> 返回值类型 方法名(方法参数) {
方法体;
}
// 以泛型T为例的泛型方法
public <T> void test(T t) {
方法体;
}
//可以使用 & 符号来说明T类为A,B的共同子类
//但是不能使用?通配符来说明,因为他不是一个确定的类型
public <T extends classA & classB> void test(T t){
方法体;
}
// 泛型方法对可变参数的支持,加上...说明此类型的参数的个数是不确定的
public <T> void test(T... t) {
方法体;
}
6.类型通配符
类型通配符一般是使用“?”来代替具体的类型实参。
当我们创建方法,需要传入泛型对象时,如果此时我们确定了具体的数据类型,那么就变得很局限。
1.泛型中的类型,不能通过多态来理解,泛型假定定义为String就必须传入String类型。
2.泛型中的类型不同,但是本质上都是同一类型,所以不可以通过泛型类型来重载方法。
此时就需要使用?来代表任意类型的具体类型实参。
// 如下为类型通配符的用法,获取的对象为Object类型
public void test(ArrayList<?> list) {
Object data0 = list.get(0)
}
// 我们可以通过extends关键字来限定类型通配符的 上限
// 来缩小类型通配符可放入的类型种类
// 限定后限制只能放入此类型或此类型的 子类
public void test(ArrayList<? extends String> list) {
String data0 = list.get(0)
}
// 我们可以通过extends关键字来限定类型通配符的 下限
// 来缩小类型通配符可放入的类型种类
// 限定后限制只能放入此类型或此类型的 父类
public void test(ArrayList<? super String> list) {
String data0 = list.get(0)
}
7.T与?的区别
T用来声明泛型类与方法;?用来使用泛型类与泛型方法。
T不能确定下限,?可以用super关键字来确定下限。
T可以用来确定类型的一致性,?由于是不确定的类型,所以不可以用来确定类型的一致性。
T可以使用&来多重确定上限,但是?不能多重确定上限。