泛型
泛型的本质是为了参数化类型(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类、泛型接口、泛型方法。
泛型类
class Point<T>{
private T t ;
}
泛型接口
interface A<T>{
T getT() ;
}
泛型方法
public class Test{
public <T> T test(Class<T> clazz){
return clazz.newInstance();
}
}
泛型的上下限
<?> 无限制通配符
<? extends E> extends 关键字声明了类型的上界,表示参数化的类型可能是所指定的类型,或者是此类型的子类
<? super E> super 关键字声明了类型的下界,表示参数化的类型可能是指定的类型,或者是此类型的父类
使用原则《Effictive Java》 为了获得最大限度的灵活性,要在表示 生产者或者消费者 的输入参数上使用通配符,使用的规则就是:生产者有上限、消费者有下限
1. 如果参数化类型表示一个 T 的生产者,使用 < ? extends T>;
2. 如果它表示一个 T 的消费者,就使用 < ? super T>;
3. 如果既是生产又是消费,那使用通配符就没什么意义了,因为你需要的是精确的参数类型。
多重限定
类型参数可以使用多重限定,即使用 & 来表示,指定泛型T必须是A和B这两个接口的共同实现类。
public class Test{
public <T extends InterfaceA & InterfaceB> void test(T data){
data.toString();
}
}
泛型中类型擦除
Java泛型这个特性是从JDK 1.5才开始加入的,因此为了兼容之前的版本,Java泛型的实现采取了“伪泛型”的策略,即Java在语法上支持泛型,但是在编译阶段会进行所谓的“类型擦除”(Type Erasure),将所有的泛型表示(尖括号中的内容)都替换为具体的类型(其对应的原生态类型),就像完全没有泛型一样。
泛型的类型擦除原则:
- 消除类型参数声明,即删除
<>及其包围的部分。 - 根据类型参数的上下界推断并替换所有的类型参数为原生态类型:如果类型参数是无限制通配符或没有上下界限定则替换为Object,如果存在上下界限定则根据子类替换原则取类型参数的最左边限定类型(即父类)。
- 为了保证类型安全,必要时插入强制类型转换代码。
- 自动产生“桥接方法”以保证擦除类型后的代码仍然具有泛型的“多态性”。
泛型擦除后的原始类型
原始类型 就是擦除去了泛型信息,最后在字节码中的类型变量的真正类型,无论何时定义一个泛型,相应的原始类型都会被自动提供,类型变量擦除,并使用其限定类型(无限定的变量用Object)替换。
泛型的类型检测
Java编译器是通过先检查代码中泛型的类型,然后在进行类型擦除,再进行编译。
类型检查就是针对引用的,谁是一个引用,用这个引用调用泛型方法,就会对这个引用调用的方法进行类型检测,而无关它真正引用的对象。
泛型的桥接方法
类型擦除会造成多态的冲突,而JVM解决方法就是桥接方法。 下面通过一个例子解释桥接方法:
public class Pair<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
}
public class PairIml extends Pair<Date> {
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
}
根据泛型的擦除原则,父类的的泛型类型全部变为了原始类型Object,所以父类编译之后会变成下面的样子
public class Pair {
private Object value;
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
}
再看子类的两个重写的方法的类型:
@Override
public void setValue(Date value) {
super.setValue(value);
}
@Override
public Date getValue() {
return super.getValue();
}
从上面的代码看出,父类的类型是Object,而子类的类型是Date,参数类型不一样,这如果实在普通的继承关系中,根本就不会是重写,而是重载。
PairIml pair=new PairIml();
pait.setValue(new Date());//编译正常
pait.setValue(new Object());//编译异常
而在实际使用的时候发现,根本就没有这样的一个子类继承自父类的Object类型参数的方法。所以说,是重写,而不是重载。
实际情况是JVM采用了一个桥方法,来完成这项功能。
通过反编译 PairIml类
从编译的结果来看,我们本意重写setValue和getValue方法的子类,有4个方法。最后的两个方法,就是编译器自己生成的桥方法。可以看到桥方法的参数类型都是Object,也就是说,子类中真正覆盖父类两个方法的就是这两个我们看不到的桥方法。而打在我们自己定义的setvalue和getValue方法上面的@Oveerride只不过是假象。而桥方法的内部实现,就只是去调用我们自己重写的那两个方法。
class PairIml extends cPair<java.util.Date> {
DateInter();
Code:
0: aload_0
1: invokespecial #8
4: return
public void setValue(java.util.Date);
Code:
0: aload_0
1: aload_1
2: invokespecial #16
5: return
public java.util.Date getValue();
Code:
0: aload_0
1: invokespecial #23
4: checkcast #26
7: areturn
public java.lang.Object getValue();
Code:
0: aload_0
1: invokevirtual #28
4: areturn
public void setValue(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: checkcast #26
5: invokevirtual #30
8: return
}
一些常见问题
为什么基本类型不能作为泛型类型
因为当类型擦除后,ArrayList的原始类型变为Object,但是Object类型不能存储int值,只能引用Integer的值。
我们能够使用list.add(1)是因为Java基础类型的自动装箱拆箱操作。
为什么泛型类型不能实例化?
因为在 Java 编译时没有办法确定泛型参数化类型,也就找不到对应的类字节码文件,所以自然就不行了,此外由于T 被擦除为 Object,如果可以 new T() 则就变成了 new Object(),失去了本意。
如果我们确实需要实例化一个泛型,可以通过反射实现。
泛型类中的静态方法和静态变量不可以使用泛型类所声明的泛型类型参数
因为泛型类中的泛型参数的实例化是在定义对象的时候指定的,而静态变量和静态方法不需要使用对象来调用。对象都没有创建,如何确定这个泛型参数是何种类型,所以当然是错误的。
在开发中怎么获取泛型的参数类型
可以通过反射(java.lang.reflect.Type)获取泛型。
java.lang.reflect.Type是Java中所有类型的公共高级接口, 代表了Java中的所有类型. Type体系中类型的包括:数组类型(GenericArrayType)、参数化类型(ParameterizedType)、类型变量(TypeVariable)、通配符类型(WildcardType)、原始类型(Class)、基本类型(Class), 以上这些类型都实现Type接口。