泛型的概念
泛型程序设计简称泛型,是程序设计语言的一种风格或泛式。
泛型允许程序员在强类型程序设计语言编写中编写代码时使用一些以后才能指定的类型,在实例化时作为参数指明这些类型。
泛型的分类
程序员可以在定义类、接口及方法时使用泛型,因此可以将泛型分为泛型类、泛型方法和泛型接口。
1、泛型接口
定义如下:
public interface 类名称<类型参数> {}
如我们常用的列表的定义:
常用的Map的定义:
需要注意的是,泛型可以有多个,用“,”分隔。
2、泛型类
定义如下:
public interface 类名称<类型参数> {}
如上面提到的列表的一个实现ArrayList的定义:
Map的一个实现HashMap的定义:
3、泛型方法
定义如下:
[public] [static] <类型参数> 返回值类型 方法名(参数类型, 参数列表)
如ArrayList的toArray方法:
泛型的基本原理
类型擦除
泛型是一种编程范式,在不同的语言和编译器中的实现和支持方式都不一样。
如C++中,当编译器对以下代码进行编译的时候
template<typename T>
struct Foo
{T bar;void doSth(T param) {}
};Foo<int> f1;
Foo<float> f2;
当编译器发现要用到Foo<int>和Foo<float>的时候,就会给咩哥泛型类生成一份执行代码,想当于创建了2个新类:
struct FooInt
{int bar;void doSth(int param){}
};struct FooFloat
{float bar;void doSth(float param){}
};
这种方法很简便,但是当多次使用不同类型的模板时会导致代码膨胀。Java在处理泛型的时候则是采用了另外一种方式如下,加入泛型的定义如下:
public class Foo<T> {T bar;void doSth(T param){}
};Foo<String> f1;
Foo<Integer> f2;
在编译后,字节码中的文件会把泛型的信息擦除:
public class Foo {Object bar;void doSth(Object param){}
};
这种类型擦除的过程称为类型擦除。
类型擦除是指通过类型参数合并,将泛型类型实例关联到同一份字节码上。编译器只为泛型类型生成一份字节码,并将其实例关联到这份字节码上。类型擦除的关键在于从泛型中清除类型参数的相关信息,并且在必要时添加类型检查和类型转换的方法。
泛型擦除的原则:
(1)、将所有的泛型用最左边界(顶级的父类型)类型替代。
(2)、移除所有的参数类型。
泛型带来哪些问题
1、泛型不支持基本数据类型
如List<Integer>是可以的,但是List<int>不行。
2、泛型会影响重载
因为泛型在虚拟机编译过程会进行类型擦除,所以List<String>和List<Integre>在类型擦除之后都会变成List,因此自啊一个类中同时定义两个方法
public void test(List<String> list){}public void test(List<Integer> list){}
是无法通过编译的。
3、instanceof不能直接用于泛型比较
因为泛型在编译后会被擦除,所以在程序中不能对这种参数类型进行一些特殊的操作,如instanceof的判断、new一个对象等。
4、List<Integer>不是List<Object>的子类
List<Integer>是Collection<Integer>的子类,但绝对不是List<Object>的子类。因为经过类型擦除以后,List<Object>和List<Integer>都会变为List。
5、泛型异常类型不能被捕获
如果自定义一个繁星异常类GenericException<T>,那么不要轻易尝试用多个catch去匹配不同的异常类型,例如分别捕获GenericException<String>和GenericException<Integer>,因为在上面介绍的异常擦除后,都会变为一个GenericException。
6、泛型中的静态变量只有一份
public class Holder<T> {public static int var = 0;
}public class HolderTest{public static void main(String[] args) {Holder<Integer> holder1 = new Holder<Integer>;holder1.var = 1;Holder<String> holder2 = new Holder<String>;holder2.var = 2;System.out.println(holder1.var);}
}
上面代码执行的结果为2。就是因为类型擦除。
泛型中的K、T、V、E、?等的含义
K、T、V、E、N这些字母没什么区别,都可以互换使用。
一般我们会使用常用的字母,这些字母都是一些类型的缩写,如:
E:Element的缩写,一般在集合中使用,表示集合中的元素类型。
T:Type的缩写,一般表示Java类。
K:Key的缩写,一般用来表示键,如map中的key。
V:Value的缩写,和key是一对,表示值。
N:Number的缩写,通常用来表示数值类型。
除此之外,还有?表示不确定类型,<?>表示不确定的Java类型。
泛型中限定通配符和非限定通配符
1、限定通配符
Java中有两种限定通配符
一种是<? extends T>,保证泛型类型必须是T的子类来设定泛型类型的上边界,即泛型的类型必须是T类型或者T的子类。
另外一种是<? super T>,保证泛型类型必须是T的父类来设定类型的下边界,即类型必须为T类型或者T的父类。
2、非限定通配符
<?>是非限定通配符,表示可以用任意泛型类型来代替它。