文章目录
1、泛型的作用
泛型:允许在定义类、接口时指定类型形参,这个形参健在声明变量、创建变量的、创建对象的时候再确定的(即传入的实际参数类型,也可以称为类型实参)
泛型出现的版本
泛型出现的原因(即为什么要有泛型)
在jdk1.5之前,集合类像ArrayList、LinkList、Set存放的Object类型的,无法对存入的元素进行自检查,在取数据的需要对数据的强制转换,如果一旦集合中存入了不一致的类型,例如存放integer类型的集合中错误存放String类型,就会出现类型转化错误的异常
使用泛型的原因:
当我们从泛型类的对象中取出值的时候,如果不使用泛型需要的步骤是:
- 获取到 Object 类型的值
- 将 Object 类型的强制转换为 对应的类型,才能使用该类型的相关方法
使用泛型的好处:
- 获取到泛型指定的类的值,(这里我们不用进行强转,这一步骤交给了虚拟机,即 JVM 会帮我们转换为指定的类)
从上面的步骤看:我们减少了泛型的强转工作
public static void main(String[] args) {
List objectList = new ArrayList();
// 存数据
objectList .add(5);
objectList .add(6);
objectList .add("我是一个字符");
//取出数据
for(int i=0;i<objectList.size();i++){
// 在取出前两个的时候可是正常
//在获取字符的时候,会出现ClassCastException异常,因为这里把强制转化成了Integer类型的
Integer element = (Integer )objectList.get(i);
System.out.println("正在取出第"+i+"元素:"+element);
}
}
在main方法中执行上面的语句会出现这样的异常
在jdk1.5之后,使用了泛型之后在上面的代码中片段使用泛型声明了intList是Integer类型的,在编写阶段就进行检查,intList.add(“我要插入一个字符串”); 这里插入的是一个字符串,无法通过编译。
public static void main(String[] args){
// 这是jdk7之前的写法
List<Integer> intList = new ArrayList<Integer>();
// 这是jdk7之后的写法,因为jdk7 的新特性类型推断
List<Integer> intList = new ArrayList<>();
// 可以通过编译的
intList.add(5);
intList.add(6);
// 不能通过编译的,在编写阶段就会报错的
intList.add("我要插入一个字符串");
}
上面的方法在编译阶段就报错,说明集合要求的是Integer类型,传入的是字符串类型
上面如何使用泛型
2、泛型的使用
常见的泛型主要作用在普通类
2.1、定义泛型接口、泛型类
定义泛型接口:
常用 T
、K
、V
等大写的字母去表示泛型,就可以在整个类中将 T
、K
、V
当作一种类型去使用,可以用于成员变量的定义、返回值、形参上使用
从List
、Iterator
、Map
的定义来看如何定义一个泛型接口
// 定义接口时指定了一个类型参数,参数名用E表示
// List接口
public inteface List<E>{
void add(E data);
// 这里使用了下面的泛型接口
Iterator<E> iterator();
}
// Iterator接口
interface Iterator<E> {
E next();
boolean hasNext();
}
// Map接口
interface Map<K,V>{
Set<K> keySet();
V put(K key, V value);
}
定义方型类
定义一个自己的泛型类: BaseBean<T>
package learn.demo.generic;
public class BaseBean<T> {
private T value;
public BaseBean() {
}
public BaseBean(T value) {
this.value = value;
}
@Override
public String toString() {
return "BaseBean{" +
"value=" + value +
'}';
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
2.2、从泛型类派生子类
当我们为带泛型申明的接口、类创建实现类的时候(从泛型类派生子类)
下面的这代码是错误的
一般是继承父类的泛型,要么省略不写
,要么指定泛型的类型
:
情况一:省略不写
package learn.demo.generic;
// 情况一:省略不写
public class SubBean extends BaseBean{
// ...
}
情况二:要么指定泛型的类型
package learn.demo.generic;
//
public class SubBean extends BaseBean<String>{
// ...
}
3、类型通配符 ?
当我们使用一个泛型类的时候,应该为该泛型类传入一个实际参数,如果没有的话,就会引起泛型警告。
例如:在我们使用String
的容器ArrayList
的时候:
/**
* 建议传入泛型的类型
* 这样比较清晰知道该ArrayList集合中的类型
*/
ArrayList<String> srtList = new ArrayList<String>();
// 下面的方式不建议
ArrayList list = new ArrayList();
3.1、泛型统配符出现的原因
先看一下这里错误的代码:
我们都知道Object
是String
的父类,但是当我们在写出上面的代码的时候,IDEA却提示我们该语句是无法通过编译的,说明了虽然Object是String的父类
,但是ArrayList<Object> 却不是ArrayList<String>的父类
。
如果我们要使用一个来表示各种ArrayList的父类呢,这种情况就是泛型通配符 ?出现的原因。
3.2、设定类型通配符的上限
使用List<?>
表示的是任意泛型List
的父类,但是如果我们不想然List<?>
是任何泛型List
的父类,只想让它表示某一类泛型
。
例如:只能是java.lang.Number
的子类 对象的泛型List
,这样就需要对List<?>
设定一个上限。
泛型通配符的上限用在变量定义上
@Test
public void testGenericTag(){
/**
* ArrayList<Object> objects = new ArrayList<String>(); 错误
* */
ArrayList<?> objects = new ArrayList<String>();
/**
* ArrayList的泛型只能是Number的子类
* */
ArrayList<? extends Number> numbers = new ArrayList<Integer>();
}
泛型通配符的上限用在形参上
/**
* 只能将Integer集合的元素添加到其父类的集合中
* */
public void addToSuperList(List<Integer> subList,List<? super Integer> superList){
superList.addAll(subList);
}
@Test
public void testAddToSuperList(){
List<Integer> integers = new ArrayList<>();
// Object 是 Integer 的父类
List<Object> objects = new ArrayList<>();
// Number 是 Integer 的父类
List<Number> numbers = new ArrayList<>();
// String 不是 Integer 的父类
List<String> strings = new ArrayList<>();
TestGeneric testGeneric = new TestGeneric();
// 编译通过
testGeneric.addToSuperList(integers,objects);
// 编译通过
testGeneric.addToSuperList(integers,numbers);
// 编译不通过,因为String 不是 Integer 的父类
testGeneric.addToSuperList(integers,strings);
}
3.3、设定类型通配符的下限
泛类型通配符的下限用在变量定义上
@Test
public void testGenericTag(){
// ...
/**
* ArrayList的泛型只能是Integer的父类
* */
ArrayList<? super Integer> integers = new ArrayList<Number>();
}
泛型通配符的上限用在形参上
/**
* 功能:将List<Integer> 和 List<Double> 等 Number 子类的集合中数据中存放到父集合中,
* 但是要求List<String>的集合不能存放
*/
public void addNumberList(List<Number> superList,List<? extends Number> subList){
superList.addAll(subList);
}
@Test
public void testAddNumberList(){
List<Integer> integers = new ArrayList<>();
// Object 是 Integer 的父类
List<Double> doubles = new ArrayList<>();
// Number 是 Integer 的父类
List<Number> numbers = new ArrayList<>();
// String 不是 Integer 的父类
List<String> strings = new ArrayList<>();
TestGeneric testGeneric = new TestGeneric();
// 编译通过
testGeneric.addNumberList(numbers,doubles);
// 编译通过
testGeneric.addNumberList(numbers,integers);
// 编译不通过,因为Number 不是 String 的父类
testGeneric.addNumberList(numbers,strings);
}
4、定义泛型方法
在定义类的方法和属性的时候,在定义类、接口时使用的泛型可以当成普通类型来使用;但是在一些没有携带泛型的时候,我们也可以自己定义泛型的形参。
泛型方法的定义
修饰符 <T,V> 返回值类型 方法名(形参列表){
// 方法体...
}
/**
* 示例:
* 输出该元素在List中第一次出现的索引,如果没有出现就返回-1
*/
public static <T> int printlnIndex(T t ,List<T> tList){
int index = -1;
for(int i; i<tList.size(); i++){
T item = tList.get(i);
if(t.equals(item)){
index = i;
break;
}
}
return index;
}
使用泛型通配符
public void test(List<?> list){
for (int i = 0; i < list.size(); i++ ){
Sysetm.out.printlen(list.get(i));
}
}
泛型方法:
语法:修饰符 <K,V> 返回值类型 方法名(参数列表…){ … }
static <T> void tranArrayToList(T[] arr,List<T> list){
for(int i = 0; i< arr.length,i++){
list.add(arr[i]);
}
}
备注:泛型方法是定义为使用静态的
设置类型通配符的上限
public void test1(List<? extends Number> numList){
for (int i = 0; i < list.size(); i++ ){
Sysetm.out.printlen(list.get(i));
}
}
设置类型通配符的下限
public void test2(List<? super Integer> numList){
for (int i = 0; i < list.size(); i++ ){
Sysetm.out.printlen(list.get(i));
}
}
4、泛型擦除和转换
泛型信息只存在于代码编译阶段,在进⼊ JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除
。
在类中定义的泛型在jvm中执行最终都是Object,即所有泛型在jvm中执行的时候,都是以Object对象存在的,加泛型只是一种代码的规范,避免在可开发过程再次强转。