`
Mr.Benny
  • 浏览: 28992 次
  • 性别: Icon_minigender_1
  • 来自: 南京
社区版块
存档分类
最新评论

[转]深入理解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()方法。

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

相关推荐

    java泛型深入.pdf

    总之,深入理解Java泛型能够帮助开发者编写更安全、更健壮的代码。通过掌握泛型的原理和细节,开发者可以有效利用泛型来优化代码设计,提升程序的类型安全,同时在维持程序性能的同时简化代码。

    JVM如何理解Java泛型类.doc

    ### JVM如何理解Java泛型类 #### 一、引言 在Java中,泛型是一种强大的功能,它允许程序员编写灵活且类型安全的代码。然而,对于Java虚拟机(JVM)来说,它实际上并不理解泛型的概念。所有的泛型信息在编译阶段就被...

    重新理解Java泛型

    通过深入理解Java泛型,开发者可以编写更安全、更具可读性和可维护性的代码,同时避免不必要的类型转换和潜在的运行时错误。对于Java集合框架的使用,泛型是不可或缺的一部分,它使得集合能够以类型安全的方式存储和...

    java 泛型的使用 详细讲解

    ### Java泛型的使用详细讲解 #### 一、引言 在Java开发中,泛型是一种重要的语言特性,它能够帮助开发者在不增加代码量的情况下处理多种数据类型,同时还能保持代码的...希望本文能帮助你更好地理解和使用Java泛型。

    深入理解java泛型详解

    让我们深入探讨一下Java泛型的各个方面。 首先,泛型(Generic type)是一种在定义类、接口或方法时,使用类型参数的方式。类型参数就像是方法的形参,但用于类型而非值。例如,当我们创建一个泛型列表`List&lt;T&gt;`,`...

    Java泛型应用实例

    Java泛型是Java编程语言中的一个强大特性,它允许我们在定义类、接口和方法时指定类型参数,从而实现代码的重用和类型安全。在Java泛型应用实例中,我们可以看到泛型如何帮助我们提高代码的灵活性和效率,减少运行时...

    步步理解 JAVA 泛型编程 – 共三篇

    对于初学者而言,通过阅读文档、编写示例代码并结合实际项目来深入理解泛型,是掌握Java泛型编程的有效方法。同时,深入研究JDK源代码中泛型的使用,也将极大地提高开发者对泛型的理解和应用能力。

    java泛型技术之发展

    本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展历程** - **早期版本的局限**:在Java泛型出现之前,程序员通常使用Object作为容器类(如ArrayList、HashMap等)的默认类型...

    java 泛型方法使用示例

    下面我们将深入探讨Java泛型方法的概念、语法以及使用示例。 **一、泛型方法概念** 泛型方法是一种具有类型参数的方法,这些类型参数可以在方法声明时指定,并在方法体内部使用。与类的泛型类似,它们提供了编译时...

    java泛型的内部原理及更深应用

    通过学习这些知识点,开发者能更好地理解Java泛型的内部原理,以及如何在实际项目中充分利用泛型的特性,提高代码质量和安全性。在这个视频教程中,张孝祥老师将详细讲解这些概念,并通过实例帮助学员深入掌握Java...

    SUN公司Java泛型编程文档

    SUN公司的Java泛型编程文档,包括英文原版和网络翻译版,为开发者提供了深入理解和熟练运用泛型的重要资源。 首先,泛型的基本概念是允许在定义类、接口和方法时使用类型参数,这样就可以在编译时检查类型安全,...

    java泛型Demo

    在这个"java泛型Demo"中,我们将深入探讨泛型的概念、用法以及它们如何提升Java编程的效率和安全性。 1. **泛型的基本概念**: 泛型允许我们在类、接口和方法中定义参数化类型。这意味着我们可以在不指定具体数据...

    Java泛型技术之发展.pdf

    本篇文章将深入探讨Java泛型的发展历程、核心概念以及其在实际开发中的应用。 1. **发展历程** Java泛型的引入是为了解决早期版本中类型转换频繁和潜在的ClassCastException问题。在Java泛型出现之前,集合框架...

    关于C#、java泛型的看法

    本文将深入探讨C#和Java在泛型实现上的异同,帮助开发者更好地理解和利用这两种语言的泛型功能。 首先,我们来看C#中的泛型。C#自2.0版本开始引入泛型,它允许开发者在类、接口和方法中定义类型参数,从而创建可...

    Java泛型设计及其PECS原则解析

    使用场景及目标:帮助读者深入理解Java泛型的内在原理和高级用法,掌握PECS原则,从而能够在项目中更高效地运用泛型,提高代码质量和安全性。 其他说明:文章不仅提供了理论知识,还通过实例代码演示了泛型的实际...

    Java泛型技术之发展

    Java泛型技术的发展历程及其在JDK1.4中的实现,是IT领域特别是软件开发与设计中的一个重要里程碑。本文将深入探讨泛型技术的概念、历史背景、与其它编程概念的区别,以及其在Java语言中的具体应用。 ### 泛型技术...

    Java泛型类型擦除后的补偿

    本文将深入探讨Java泛型类型擦除的概念,并介绍在类型擦除后,为了保持泛型的安全性和便利性,Java设计者所采取的一些补偿机制。 1. **类型擦除**: - 在编译期间,所有的泛型类型信息都会被替换为它们的实际类型...

Global site tag (gtag.js) - Google Analytics