1.定义
泛型最精准的定义:==参数化类型==。具体点说就是处理的数据类型不是固定的,而是可以作为参数传入。定义泛型类、泛型接口、泛型方法,这样,同一套代码,可以用于多种数据类型。(看到过有人面试问这个,感觉这个解释泛型最言简意赅)
2.实例
2.1 标识符号的字母规范.
虽然标识符号可以随意取,但为了提高可读性,一般遵循以下规则.
E — Element,常用在java Collection里,如:List,Iterator,Set K,V — Key,Value,代表Map的键值对 N — Number,数字 T — Type,类型,如String,Integer等等
2.2 泛型类
public class Generic<T>{
//key这个成员变量的类型为T,T的类型由外部指定
private T key;
public Generic(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
复制代码
泛型的字母可以随意取,但是自己可以规范点,Key,Value 可以用(K, V)Element 可以用E。
2.3 泛型接口
public interface Generator<T> {
public T next();
}
复制代码
2.4 泛型方法
public < E > void printArray( E[] inputArray )
{
// 输出数组元素
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
复制代码
3.泛型通配符
3.1 上界限定通配符 <? extends E>
接收E类型以及其子类 被该泛型通配符限制的数组==只能取不能存==。因为该泛型为其子类,不能确定究竟几级子类,因此不能加数据。只能取,取出的数据能保证一定能转成E类型。
3.2 下界限定通配符 <?super E>
接收E类型以及其父类型。因此==只能存不能取==。如果要取的话只能是保证为Object类型的数据,这样没意义。而存的话能存E以及E的子类。
3.3 无限通配符 <?>
无界通配符意味着可以使用任何对象,因此使用它类似于使用原生类型。但它是有作用的,原生类型可以持有任何类型,而无界通配符修饰的容器持有的是某种具体的类型。举个例子,在List类型的引用中,不能向其中添加Object, 而List类型的引用就可以添加Object类型的变量。
4.擦除带来的问题
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
复制代码
1 泛型类型不能显式地运用在运行时类型的操作当中,例如:转型、instanceof 和 new。因为在运行时,所有参数的类型信息都丢失了。
public class Erased<T> {
private final int SIZE = 100;
public static void f(Object arg) {
//编译不通过
if (arg instanceof T) {
}
//编译不通过
T var = new T();
//编译不通过
T[] array = new T[SIZE];
//编译不通过
T[] array = (T) new Object[SIZE];
}
}
复制代码
2 泛型声明的对象在类内部不能获取某个类的特定方法。代码如下
class HasF {
public void f() {
System.out.println("HasF.f()");
}
}
public class Manipulator<T> {
private T obj;
public Manipulator(T obj) {
this.obj = obj;
}
public void manipulate() {
obj.f(); //无法编译 找不到符号 f()
}
public static void main(String[] args) {
HasF hasF = new HasF();
Manipulator<HasF> manipulator = new Manipulator<>(hasF);
manipulator.manipulate();
}
复制代码
在这个例子中由于泛型在代码内部是无法知道其确切类型,因而也就不能知道obj 是否有f()方法。上面这个问题解决方法可以给T范型加入上边界即 T extends HasF,这样就解决这个问题了。
5.擦除补偿
5.1 类型判断问题
class Building {}
class House extends Building {}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.print(ctt2.f(new House()));
}
}
复制代码
泛型参数的类型无法用instanceof关键字来做判断。所以我们使用类类型来构造一个类型判断器,判断一个实例是否为特定的类型。
5.2 创建类型实例
Erased.java中不能new T()的原因有两个,一是因为擦除,不能确定类型;而是无法确定T是否包含无参构造函数。
为了避免这两个问题,我们使用显式的工厂模式:
interface IFactory<T> {
T create();
}
class Foo2<T> {
private T x;
public <F extends IFactory<T>> Foo2(F factory) {
x = factory.create();
}
}
class IntegerFactory implements IFactory<Integer> {
@Override
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class Factory implements IFactory<Widget> {
@Override
public Widget create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
}
复制代码
通过特定的工厂类实现特定的类型能够解决实例化类型参数的需求。
5.3 创建泛型数组
一般不建议创建泛型数组。尽量使用ArrayList来代替泛型数组。但是在这里还是给出一种创建泛型数组的方法。
public class GenericArrayWithTypeToken<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int sz) {
array = (T[]) Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>(Integer.class, 10);
Integer[] ia = gai.rep();
}
}
复制代码
这里我们使用的还是传参数类型,利用类型的newInstance方法创建实例的方式。
6.其他注意点
- 任何基本类型都不能作为类型参数
- 静态变量是被泛型类的所有实例所共享的。对于声明为MyClass的类,访问其中的静态变量的方法仍然是 MyClass.myStaticVar。不管是通过new MyClass还是new MyClass创建的对象,都是共享一个静态变量。(擦除导致) 3.泛型的类型参数不能用在Java异常处理的catch语句中。因为异常处理是由JVM在运行时刻来进行的。由于类型信息被擦除,JVM是无法区分两个异常类型MyException(String)和MyException(Integer)的。对于JVM来说,它们都是 MyException类型的。也就无法执行与异常对应的catch语句。
参考文章: