泛型-基本概念(一)

么是泛型
        泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部份,那些部份在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。
        一个泛型类型通过使用一个或多个类型变量来定义,并拥有一个或多个使用一个类型变量作为一个参数或者返回值的占位符。例如,类型 java.util.List<E>是一个泛型类型:一个list,其元素的类型被占位符E描述。这个类型有一个名为add()的方法,被声明为有一个类型为E的参数,同时,有一个get()方法,返回值被声明为E类型。
        泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。
为什么要用泛型
       如果没有泛型,对于集合类的使用我们需要记住每个集合中元素的类型。当在java1.4种创建了一个集合,我们知道要放入集合中的对象类型,但是编译器不知道。因此必须确保加入的对象类型是我们需要的类型,当需要从集合中获取元素时,则必须显式的写强制类型转换以将他们从Object转换为他们真实的类型。这样带来的问题是:我们的list中需要String,但是add的时候放入了其他类型的对象,那么在get()的时候,强制类型转换(String)会抛出 ClassCastException ,而这样的问题也只有在运行时才能发现。
        在java5.0中引入的泛型类型很好的解决了这个问题,当我们申明一个List或者创建一个ArrayList的实例的时候,我们需要在泛型类型的名字后面紧跟一对“<>”,尖括号中写入我们需要的实际的类型。在声明好变量类型后,编译器据此能够在编译期为我们提供很强的类型检查,增强程序的类型安全性-以前只能在运行起才能发现的错误现在能够在编译时就被发现。举个例子来说,有一个只能保持String对象的List,那么这种类型检查就能够阻止我们往里面加入String[]对象。同样的,增加的类型信息使编译器能够为我们做一些类型转换的事情。比如,编译器知道了一个List<String>有个get()方法,其返回值是一个String对象,因此我们不再需要去将返回值由一个Object强制转换为String。
        Java.util中的List或是其他集合类已经使用泛型重写过了。就像前面提到的, List被重新定义为一个list,它中间的元素类型被一个类型可变的名称为E的占位符描述。Add()方法被重新定义为期望一个类型为E的参数,用于替换以前的Object,get()方法被重新定义为返回一个E,替换了以前的Object。
       简言之,使用泛型有两个好处:一、增强了类型安全;二、省去了手动转换类型的麻烦

基本的泛型用法

         为了使用泛型类型,我们需要为类型变量详细指明实际的类型,形成一个就像List<String>类似的参数化类型。
         例:

List<String> list = new ArrayList<String>;
 

使用“(String)”这样的类型转换被替换成了类型参数“<String>”。 不同的是类型参数需要且仅需要声明一次,而list能够被使用任何多次,不需要类型转换。
        注意,Java.util的集合类中的元素必须是对象化的,他们不能是基本类型。泛型的引入并没有改变这点。泛型不能使用基本类型:
我们不能这样来申明—— Set<char>或者List<int>
         就像一个方法可以使用任意数量的参数一样,类允许使用多个类型参数。接口Java.util.Map就是一个例子。一个Map体现了从一个key的对象到一个value的对象的映射关系。接口Map申明了一个类型变量来描述key的类型而另一个类型变量来描述value的类型

使用泛型类型的复杂细节
    1.不带类型参数的使用泛型的后果

List l = new ArrayList();
    l.add("hello"); 
    l.add(new Integer(123));
    Object o = l.get(0);
 

    一个不带类型变量的泛型类型被认为是一个未经处理的类型(raw type)。在使用java集合时,并不强制要求说明类型变量,java编译也可以通过,但是java编译器会给出提示
    Note: Test.java uses unchecked or unsafe operations.
    Note: Recompile with -Xlint:unchecked for details.
    编译在add()方法的调用上给出了警告,因为它不能够确信加入到list中的值具有正确的类型。它告诉我们说我们使用了一个未经处理的类型,它不能验证我们的代码是类型安全的。
     如果我们加入-Xlint参数后重新编译,我们会看到这些警告:
 Test.java:6: warning: [unchecked]    unchecked call to add(E) as a member of the raw type java.util.List    l.add("hello");                                                                                                                                                                                      ^
 Test.java:7:warning: [unchecked]    unchecked call to add(E) as a member of the raw type  java.util.List  l.add(new Integer(123));
        如果不想使用任何的java5.0的新特性,可以简单的通过带-source1.4标记来编译他们,这样编译器就不会再“抱怨”了。也可以通过使用一个“SuppressWarnings("unchecked")”注解可以忽略这些警告。这样编译的时候不再会有警告但仍然允许往list中放入不同的类型的对象。
 2.参数化类型的体系
     参数化类型有类型体系,就像一般的类型一样。这个体系基于对象的类型,而不是变量的类型。例子:

  ArrayList<Integer> l = new ArrayList<Integer>();
    List<Integer> m = l;                            // okay
    Collection<Integer> n = l;                      // okay
    ArrayList<Number> o = l;                        // error
    Collection<Object> p = (Collection<Object>)l;   // error, even with cast

           就像我们所见到的,一个List<X>不允许被转换为一个List<Y>,即使这个X能够被转换为Y。然而,一个List<X>能够被转换为一个List,这样我们就可以通过继承的方法来做这样的事情。
          泛型仅提供了编译期的类型安全。如果使用java5.0的编译器来编译代码并且没有任何警告,那么这样的代码在运行期也是类型安全的。相反,如果编译器有警告或者使用了像未经处理的类型那样修改集合的代码,那么我们需要增加一些步骤来确保运行期的类型安全。即使用java.util.Collections中的checkedList()和checkedMap( )方法来做到这一步。这些方法把集合打包成一个wrapper集合,从而在运行时检查确认只有正确类型的值能够被置入集合众。下面是一个能够补上类型安全漏洞的一个例子:

List<Integer> li = new ArrayList<Integer>();
// Wrap it for runtime type safety
List<Integer> cli = Collections.checkedList(li, Integer.class);
// Now widen the checked list to the raw type
List l = cli;  
// This line compiles but fails at runtime with a ClassCastException.
// The exception occurs exactly where the bug is, rather than far away
l.add("hello");

 


 3.为什么参数化类型的数组不是类型安全的
    在使用泛型类型的时候,数组需要特别的考虑。如果T是S的父类(或者接口),那么类型为S的数组S[],同时又是类型为T的数组T[]。正因为如此,每次存放一个对象到数组中时,Java解释器都必须进行检查以确保放入的对象类型与要存放的数组所允许的类型是匹对的。例如,下列代码在运行期会检查失败,抛出一个ArrayStoreException异常:

String[] words = new String[10];
Object[] objs = words;
objs[0] = 1;  // 1 autoboxed to an Integer, throws ArrayStoreException
 

虽然编译时obj是一个Object[],但是在运行时它是一个String[],它不允许被用于存放一个Integer。
当我们使用泛型类型的时候,仅仅依靠运行时的数组存放异常检查是不够的,因为一个运行时进行的检查并不能够获取编译时的类型参数信息。查看下列代码:

List<String>[] wordlists = new ArrayList<String>[10];
ArrayList<Integer> ali = new ArrayList<Integer>();
ali.add(123);
Object[] objs = wordlists;
objs[0] = ali;                       // No ArrayStoreException
String s = wordlists[0].get(0);      // ClassCastException!
 


  如果上面的代码被允许,那么运行时的数组存储检查将会成功:没有编译时的类型参数,代码简单地存储一个ArrayList到一个ArrayList[]数组,非常正确。既然编译器不能阻止我们通过这个方法来战胜类型安全,那么它转而阻止我们创建一个参数化类型的数组。所以上述情节永远不会发生,编译器在第一行就开始拒绝编译了。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值