设为首页 收藏本站
查看: 1168|回复: 0

[经验分享] 读 IBM中国 《Java 理论和实践: 了解泛型》

[复制链接]

尚未签到

发表于 2017-5-26 11:32:05 | 显示全部楼层 |阅读模式
了解Java泛型
  参考于IBM Developer 中国 《Java 理论和实践: 了解泛型
  Java语言的泛型类似于C++中的模板. 但是这仅仅是基于表面的现象。   Java语言的泛型基本上完全在编译器中实现的,由编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码。 这种实现称为"擦除" 
 (编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除)
  

  泛型不是协变的
    协变:

    Java 语言中的数组是协变的(covariant),也就是说,
    如果 Integer 扩展了 Number(事 实也是如此),那么不仅 Integer 是 Number,而且 Integer[] 也是 Number[],在要求Number[] 的地方完全可以传递或者赋予 Integer[]。
    (更 正式地说,如果 Number 是 Integer 的超类型,那么 Number[] 也是 Integer[] 的超类型)
    
    但是,泛型并不是协变的。  如果,List<Number> 是List<Integer> 的超类型,但是,如果需要List<Integer>的时候, 并不容许传递List<Number>,它们并不等价。

    不允许的理由很简单,这样会破坏要提供的类型安全泛型. 如下代码:

        
public static void main(String[] args) {
    List<Integer> list=new ArrayList<Integer>();
    List<Number> list2=list;//编译错误.
    list2.add(new Float(19.0f));
}


其他协变问题
数组能够协变而泛型不能协变的另外一个问题是:不能实例化泛型类型的数组: (new List<String>[3]是不合法的),除非类型参数是一个未绑定类型的通配符(new List<?>[3]是合法的).

    延迟构造
 因为可以擦除功能,所以List<Integer>和List<String>是同一个类,编译器在编译List<V>的时候,只生成一个类。
     所以,运行时,不能区分List<Integer>和List<String>(实际上,运行时都是List,类型被擦除了),用泛型类型参数标识类型的变量的构造就成了问题。运行时缺乏类型信息,这给泛型容器类和希望创建保护性副本的泛型类提出了难题。
    比如泛型类Foo:
    
class Foo<T>{
    public void dosomething(T param){
         T copy=new T(param);//语法错误.
    }
}
      因为编译的时候,还不知道要构造T的具体类型,所以也无法调用T的构造函数。
    那么,我们是否可以使用克隆来构建呢?

class Foo<T extends Cloneable>{
public void dosomething(T param){
    //编译错误,

    T copy=(T)param.clone();
}
}
     为什么会报错呢?  因为clone()在Object类中是受保护的。

    所以,不能复制在编译时根本不知道是什么类的类型引用。

    构造通配符引用
那么使用通配符类型怎么样呢? 假设要创建类型为Set<?>的参数的保护性副本。 我们来看看:
    
class Foo{
public void doSomething(Set<?> set){
    //编译出错,你不能用通配符类型的参数调用泛型构造函数
    Set<?> copy=new HashSet<?>(set);
}
}

    但是下面的方法可以实现:

    
class Foo{
public void doSomething(Set<?> set){
    Set<?> copy=new HashSet<Object>(set);
}
}


    构造数组
对于我们常用的ArrayList<V> ,我们需要来探讨一下它的内部实现机制:

    假设它内部管理着一个V数组,那么我们希望能在ArrayList<V>的构造函数中来初始化这个数组:

    
class ArrayList<V>{
V[] content;
private static final int DEFAULT_SIZE=10;
public ArrayList() {
        //编译错误。
        content=new V[DEFAULT_SIZE];
}
}
    但这段代码不能工作,不能实例化用类型参数表示的类型数组。因为编译器不知道V到底是代表什么类型,所以不能实例化V数组。


    Java库中的 Collections提供了一种思路,用于实现,但是非常别扭(设置连Collections的作者都这样说过.) 在Collections类编译时,会产生警告:

class ArrayList<V> {
    private V[] backingArray;
    public ArrayList() {
    backingArray = (V[]) new Object[DEFAULT_SIZE];
    }
}

    因为泛型是通过擦除实现的,backingArray 的类型实际上就是 Object[],因为 Object 代替了 V。
    这意味着:实际上这个类期望 backingArray 是一个 Object 数组,但是编译器要进行额外的类型检查,以确保它包含 V 类型的对象。所以这种方法很奏效,但是非常别扭,因此不值得效仿

    另外有一种方法是:  
        声明backingArray为Object数组,并在使用它的各个地方,强转成V[]


其他方法

    

    最好的方法是: 向构造方法中,传入类对象,这样,在运行时,就可以知道T的值了。不采用这种方法的原因是,它无法与之前版本的Collections框架相兼容。
   比如:
 
public class ArrayList<V> implements List<V> {
private V[] backingArray;
private Class<V> elementType;
public ArrayList(Class<V> elementType) {
this.elementType = elementType;
backingArray = (V[]) Array.newInstance(elementType, DEFAULT_LENGTH);
}
}

    但是,这里还是有地方不是很妥当:  
    调用 Array.newInstance() 时会引起未经检查的类型转换。为什么呢?同样是由于向后兼容性。Array.newInstance() 的签名是:

public static Object newInstance(Class<?> componentType, int length) 
    而不是类型安全的:
public static<T> T[] newInstance(Class<T> componentType, int length) 

 为何 Array 用这种方式进行泛化呢?同样是为了保持向后兼容。要创建基本类型的数组,如 int[], 可以使用适当的包装器类中的 TYPE 字段调用 Array.newInstance()(对于 int,可以传递 Integer.TYPE 作为类文字)。用 Class<T> 参数而不是 Class<?> 泛化 Array.newInstance(),对 于引用类型有更好的类型安全,但是就不能使用 Array.newInstance() 创建基本类型数组的实例了。也许将来会为引用类型提供新的 newInstance() 版本,这样就两者兼顾了。
在这里可以看到一种模式 —— 与泛型有关的很多问题或者折衷并非来自泛型本身,而是保持和已有代码兼容的要求带来的副作用。
        擦除的实现
因为泛型基本上都是在JAVA编译器中而不是运行库中实现的,所以在生成字节码的时候,差不多所有关于泛型类型的类型信息都被“擦除”了, 换句话说,编译器生成的代码与手工编写的不用泛型、检查程序类型安全后进行强制类型转换所得到的代码基本相同。与C++不同,List<Integer>和List<Number>是同一个类(虽然是不同的类型,但是都是List<?>的子类型。)
    擦除意味着,一个类不能同时实现 Comparable<String>和Comparable<Number>,因为事实上,两者都在同一个接口中,指定同一个compareTo()方法。

    擦除也造成了上述问题,即不能创建泛型类型的对象,因为编译器不知道要调用什么构造函数。 如果泛型类需要构造用泛型类型参数来指定类型的对象,那么构造函数应该传入类对象,并将它们保存起来,以便通过反射来创建实例。



PS :
   貌似发布上来格式有问题,
  这里放出我整理的Google文档地址,感觉看不清楚的可以去这里看看。
  http://docs.google.com/View?id=dgmjz37_7gp8mrjvv

运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-381337-1-1.html 上篇帖子: IBM WebSphere Commerce 中的关于catalog 的表设计 下篇帖子: pureQuery:IBM 最新的 Java 数据库应用编程范例
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表