这篇文章我们来讲一下java的泛型
目录
1.什么是泛型
1.1 泛型的概念
1.2 泛型的好处
1.3 粗看泛型集合的源码
2.泛型类
2.1 泛型类的定义
2.2 从泛型类派生子类
3.泛型接口
4.泛型方法
5.类型通配符
5.1类型通配符上限
5.2 类型通配符的下限
6.类型擦除
6.1无限制类型擦除
6.2有限制类型擦除
6.3桥接方法
7.泛型与数组
8.泛型和反射
1.什么是泛型
首先,我们来讲一下java推出泛型的背景
JAVA推出泛型以前,程序员可以构建一个元素类型为Object的集合,该集合能够存储任意的数据类型对象,而在使用该集合的过程中,需要程序员明确知道存储每个元素的数据类型,否则很容易引发ClassCastException异常。
这样说可能不好理解,下面就用具体的例子来讲一下。
看如下代码:
首先,我们定义一个集合list,因为没有指定类型,所以java就默认为Object类型的,然后我们通过add方法,往里面添加元素,可以看到是可以添加的,因为在添加时进行默认的类型转换。然后我们将其输出打印,通过get方法可以获取到元素,因为里面都是Object类型的数据,所以我们定义一个Object类型的变量来接收。而对于集合中的元素,我们通常都是需要使用的,使用时就要用到明确的类型,所以我们可以将其强转为String类型,可以看到编译时没有报错,然后我们运行看一下结果
报错了,报的是ClassCastException错误,而我们可以明确的看到,在书写代码时,它没有报错,也就是说编译时没有报错,但是在运行时它报错了,这是很可怕的。
通过这个例子,我们就可以看到,我们需要使用泛型
1.1 泛型的概念
Java泛型(generics)是JDK5中引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。
泛型的本质就是参数化类型,也就是所操作的数据类型被指定为一个参数。
下面用具体的例子来说明一下:
此时,我们调用add方法添加时,就指定添加的元素为String类型了
如果我们添加非String类型的数据就会在编译时报错了
下面写完,看一下运行结果:
1.2 泛型的好处
下面说一下泛型的好处:
- 编译期间自动进行类型检查(类型安全)
- 减少了数据类型的转换(消除了强制类型转换)
1.3 粗看泛型集合的源码
下面,我们来粗略的看一下泛型集合的源码
这是开头部分,我们可以看到源码中是<E>,这个E可以理解为一个形参,我们传入泛型的类型就会代替这个E,然后在代码中所有的E都会被我们传入的类型所替换。
2.泛型类
下面,我们来讲解一下泛型类
2.1 泛型类的定义
泛型类的定义语法如下:
class 类名称<类型标识,类型标识,…… >{
private 泛型标识 变量名;
…………
}
常用的泛型标识:T,E,K,V
注意:泛型标识可以理解为一种形参,或者直接点,就理解为一种类型
泛型类的使用:
使用语法:类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
java1.7以后,后面<>中的具体的数据类型可以省略不写,那么使用语法就变为了:
类名<具体的数据类型> 对象名 = new 类名<>();
下面,通过具体实例来看一下:
这个就是很简单的一个泛型类的定义
这个是测试代码
没啥好说的,就是很简单指定类型,然后传入类型而已
注意事项:
- 泛型类,如果没有指定具体的数据类型,此时,操作类型是Object
- 泛型的类型参数只能是类类型,不能是基本数据类型
- 泛型类型在逻辑上可以看出是多个不同的类型,但实际上都是相同类型
2.2 从泛型类派生子类
下面,我们来看一下从泛型类派生子类
如果子类也是泛型类,子类和父类的泛型类型要一致:
class ChileGeneric<T> extends Generic<t>
如果子类不是泛型类,父类要明确泛型的数据类型
class ChildGeneric extends Generic<String>
下面来看一下具体实例:
当子类不是泛型类的时候,和上面差不多
注意:在泛型中,你指定类型了,那就以指定的类型为准,没有指定类型,那就默认为Object类型
3.泛型接口
下面来讲一下泛型接口
泛型接口的定义语法:
interface 接口名称 <泛型标识,泛型标识,……>{
泛型标识 方法名();
……
}
泛型接口的使用:
- 实现类不是泛型类,接口要明确数据类型
- 实现类也是泛型类,实现类和接口的泛型类型要一致
下面通过具体实例来看一下:
接口的测试和普通接口是一样的,这里就不多说了
4.泛型方法
下面来讲一下泛型方法
泛型方法,是在调用方法的时候指明泛型的类型(泛型类,是在实例化类的时候指明泛型类型)
语法格式:
修饰符<T,E,……>返回值类型 方法名(形参列表){
方法体……
}
注意:
- public与返回值中间的<T>非常重要,可以理解为声明此类方法为泛型方法。
- 只有声明了<T>的方法才是泛型方法,泛型类中的使用了返校的成员方法并不是泛型方法。
- <T>声明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T
- 与泛型类的定义一样此处T可以随便写任意标识,常见的如T,E,K,V等形式参数常用于表示泛型
下面通过具体实例来看一下
这就是一个泛型方法。注意,在泛型方法中,如果方法的形参或者方法里面都需要使用到泛型,那么泛型标识必须一致。
下面不写具体代码,就简单的思考一下。上面的泛型方法是写明了泛型标识符,我们知道,泛型就是用来规定类型的,方法的类型是与它的返回值有关的。如果,我们没有返回值,那么就可以不用写泛型标识了,此时只需要写void就可以了。其实就是很简单的一个内容。有返回值,那么必定要写明类型,于是就用到泛型;没有返回值,就没必要写类型,那就直接用void,就没泛型的事了。
5.类型通配符
下面我们来讲一下类型通配符
什么是类型通配符?
类型通配符一般是使用“?”代替具体的类型实参,所以类型通配符是类型实参,而不是类型形参。
下面来看一下具体实例
如图所示,当我们类的泛型改变时,方法就不可以用了(注意:Integer是继承Number的,按照多态的思想,这里是可以通过的,但是在泛型这里,多态是不允许的,因为泛型类的本质是同一个类)
错误的解决方法:
如果再写一个方法,也会报错,因为这不构成方法的重载;如果将方法中的Number改为Object,也不行,因为不支持多态。
正确的解决方法:
使用泛型通配符
即如下图所示:
5.1类型通配符上限
下面,来讲一下通配符的上限
语法格式:
类/接口 <? extends 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的子类型
下面通过具体例子来看一下:
如第13行所示,我们将里面的泛型改为<? extends Number>,意思是我们只能传Number或者Number的子类的类型,所以在第14行,我们就可以直接用Number类型的变量去接收(多态),这时,我们可以看到,当我们传入Integer时,没有报错,所以没有问题。
这就是所谓的类型通配符上限,它规定了泛型所能传入的类型的上限(或者说父类)是什么。
5.2 类型通配符的下限
下面,来讲一下通配符的上限
语法格式:
类/接口 <? super 实参类型>
要求该泛型的类型,只能是实参类型,或实参类型的父类型
下面通过具体例子来看一下:
如第13行所示,我们将里面的泛型改为<? super Integer>,意思是我们只能传Integer或者Integer的父类的类型,所以在第14行,我们就可以用Object类型的变量去接收(多态),这时,我们可以看到,当我们传入Number时,没有报错,所以没有问题。
这就是所谓的类型通配符下限,它规定了泛型所能传入的类型的下限(或者说子类)是什么。
6.类型擦除
下面,我们来讲一下java的类型擦除
概念:
泛型是Java 1.5版本才引进的概念,在这之前是没有泛型的,但是,泛型代码能够很好地和之前版本的代码兼容。那是因为,泛型信息只存在于代码编译阶段,在进入JVM之前,与泛型相关的信息会被擦除掉,我们称之为——类型擦除。、
下面我们来看一下例子:
结果为true,说明list1和list2是同一个类,也就是它两是同一类型,也就是说他们的类型(Integer和String)被擦除了
6.1无限制类型擦除
下面来看一下类型擦除的无限制类型擦除
意思就是我们在编写泛型类的时候,编写完后JVM在编译的时候,里面的泛型标识直接用Object来代替
6.2有限制类型擦除
下面来看一下类型擦除的有限制类型擦除
意思就是我们在编写泛型类的时候,如果用到泛型上限,那么编写完后JVM在编译的时候,里面的泛型标识直接用上限来代替
6.3桥接方法
桥接方法在接口中有用到,它是为了保持类和接口的实现关系
7.泛型与数组
下面我们来讲一下泛型数组
泛型数组的创建
- 可以声明待泛型的数组引用,但是不能直接创建带泛型的数组对象
- 可以通过 java.lang.reflect.Array的newInstance(Class<T>,int)创建T[ ]数组
下面,通过具体例子来看一下:
这个是错误演示:
正确方法:
这就是正确写法,如果再出现上面的错误,它就好显示错误了
另一种方法:
这就是第二种创建泛型数组的方法,可能比较难,但是仔细看是可以理解的
8.泛型和反射
最后我们来说一下泛型和反射
反射常用的泛型类:Class<T>;Constructor<T>
下面用具体实例来看一下:
没什么问题
至此,我们的泛型粗略的讲完了,在java中泛型是一个比较重要的章节,大家要掌握。