java 泛型 限制_java泛型中的各种限制

java和其他语言一样,都支持泛型,包括泛型类和泛型方法,但是java的泛型比较特殊。因为java的泛型并不是在java诞生之初就加入的,在很长的一段时间里,java是没有泛型的,在需要泛型的地方,统统都采用协变的方式,也就是采用Object,比如ArrayList类,元素的类型就是Object。为了兼容原来的代码,java的设计者希望在加入泛型之后,所有的泛型都可以传给原来的对应的非泛型参数,例如,可以把ArrayList传给原来接收ArrayList参数的方法。为了达到这个目的,java的设计者采取了一种叫做“类型擦除”的实现方式,把泛型拦截在了编译阶段,在虚拟机中,所有的泛型参数最终的类型都会是转换后的Object类型(如果有类型限定,则是限定后的类型,如 ,则泛型T在虚拟机中统一都是Super类型),然后利用协变性而兼容所有类型或限定类型参数的传入,从而实现泛型。

比如用户定义泛型类:

class Super{

T a;publicV mehod(T arv){

a=arv;returna;

}

}

在经过编译阶段的类型擦除之后,虚拟机看到的Super类是这样的:

classSuper{

Object a;publicObject mehod(Object arv){

a=arv;returna;

}

}

而且可以看到,无论有多少个实例化后的泛型,比如Super、Super,在虚拟机中,都只存在一个Super类。

在应用泛型类的时候,如果返回值也是泛型,那么返回的将是Object,但是很显然,我们在使用泛型的过程中,并没有要显示的进行类型转换,比如我们不需要这样 String a=(String) super.method("hahaha"); 而是直接 String a= super.method("hahaha"); 就可以了。为什么呢?

则是因为,为了返回实现类型的自动匹配,java编译器会在“类型擦除”的基础上在进行“转换插入”操作。每当使用泛型的返回值时,编译器会制动插入类型转换,所以原始代码是 String a= super.method("hahaha"); ,编译之后,虚拟机看到的却是 String a= (String)super.method("hahaha"); ,类型转换被“插入”了。

因为类型擦除后,原有的泛型方法的泛型参数都被替换成了Object,因此无法实现方法覆盖,但实际应用中,肯定需要实现方法覆盖从而达到多态的目的。

比如有用户继承了Super这个泛型,实现了子类SubClass:Super:

class SubClass:Super{

String a;publicString mehod(String arv){

a=arv;returna;

}

}

因为用户只实现了方法 public String mehod(String arv) ,而父类的method方法是 public Object mehoed(Object arv) ,因此覆盖失败。为了实现对method方法的覆盖,编译器在进行编译时,自动插入了一个桥接方法,桥接方法形式如下:

publicObject mehoed(Object arv){returnmethod((String) arv);

}

桥接方法的方法签名和返回值和父类的泛型方法 public Object mehoed(Object arv) 一致,因此可以覆盖父类的method方法,实现了多态。通过桥接方法,还实现了对用户定义的method方法的调用,在程序员看来,就好像 public String mehoed(String arv) 成功覆盖了 public Object mehoed(Object arv) 一样。

正因为java是以这种比较奇怪的方式实现了泛型,因此一切new T(..)或者new T[]其实都会是new Object,没有任何意义,所以java干脆在编译阶段就禁止了这种语法,也就是说,泛型只能传入,不能产生。

除了不能产生泛型外,其它场合泛型和具体类型无差别,泛型参数可以用于类型转换、方法参数的类型等各种场合,比如这样 (T)a; 也是合法的。同时我们也可以发现,泛型参数的实例化,其实只是在编译阶段传入了类型信息,这个类型信息被用于“插入转换”和“方法桥接”。

但是需要注意的是,很多看起来像产生泛型的语法,其实并不是真正产生泛型,比如 List=new ArrayList() ,这里产生的只有一个ArrayList,并且这个ArrayList的类型参数被传入了一个String,这个String将用于ArrayList的“桥接方法”、“转换插入”等用处。实际上,在ArrayList<>的内部,是以一个Object[ ]来存放数据的,而不是String[ ],在你add("hahaha")的时候,这个泛型还是被传入的。在get(0)时,这个被传入的类型String在“转换插入”中发挥了作用,每次调用 get(0); 都被转换成了 (String)get(0); 。

上面说泛型在编译阶段就被“擦除”了,其实也不完全对,利用反射,仍然可以获取泛型信息,比如泛型参数、泛型限制、通配符信息等都可以获取到,说明在java的字节码(即.class文件)中,泛型信息仍然存在。因此,更准确的说,是泛型经过类加载器加载到虚拟机之后,泛型比擦除了,但是泛型的信息仍然存在字节码中,并且可以通过反射获取到。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值