`
Yinny
  • 浏览: 292537 次
  • 性别: Icon_minigender_2
  • 来自: 杭州
社区版块
存档分类
最新评论

泛型的几个注意点!

阅读更多
[size=medium]上周代码里碰到了泛型,使用到了类型参数通配符,平时使用到不多,但是一用到还是会有些模糊,于是想再学习下泛型,网上的文章一抓一大把,但都是你抄我我抄你的,好不容易找到两篇好文,于是整理了些过来。

未经处理的类型和不被检查的警告

即使被重写的Java集合类带来了泛型的好处,在使用他们的时候你也不被要求说明类型变量。一个不带类型变量的泛型类型被认为是一个未经处理的类型(raw type)。这样,5.0版本以前的java代码仍然能够运行:显式的编写所有类型转换就像已经写的一样,你可能会被一些来自编译器的麻烦所困扰。查看下列存储不同类型的对象到一个未经处理的List:
List l = new ArrayList();
l.add("hello"); 
l.add(new Integer(123));
Object o = l.get(0);

这段代码在java1.4下运行得很好。如果您用java5.0来编译它,javac编译了,但是会打印出这样的“抱怨”:
Note: Test.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
如果我们加入-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));
编译在add()方法的调用上给出了警告,因为它不能够确信加入到list中的值具有正确的类型。它告诉我们说我们使用了一个未经处理的类型,它不能验证我们的代码是类型安全的。注意,get()方法的调用是没有问题的,因为能够被获得的元素已经安全的存在于list中了。
如果您不想使用任何的java5.0的新特性,您可以简单的通过带-source1.4标记来编译他们,这样编译器就不会再“抱怨”了。如果您不能这样做,您可以忽略这些警告,通过使用一个“@SuppressWarnings("unchecked")”注解(查看本章的4.3节)隐瞒这些警告信息或者升级您的代码,加入类型变量描述。[2]下列示例代码,编译的时候不再会有警告但仍然允许您往list中放入不同的类型的对象。
List<Object> l = new ArrayList<Object>();
l.add("hello"); 
l.add(123);              // autoboxing
Object o = l.get(0);



参数化类型的体系

参数化类型有类型体系,就像一般的类型一样。这个体系基于对象的类型,而不是变量的类型。这里有些例子您可以尝试:
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<Integer>是一个Collection<Integer>,但不是一个List<Object>。这句话不容易理解,如果您想理解为什么泛型这样做,这段值得看一下。考察这段代码:
List<Integer> li = new ArrayList<Integer>();
li.add(123);
// The line below will not compile.  But for the purposes of this
// thought-experiment, assume that it does compile and see how much
// trouble we get ourselves into.
List<Object> lo = li; 
// Now we can retrieve elements of the list as Object instead of Integer
Object number = lo.get(0);
// But what about this?
lo.add("hello world");
// If the line above is allowed then the line below throws ClassCastException
Integer i = li.get(1);  // Can't cast a String to Integer!

这就是为什么List<Integer>不是一个List<Object>的原因,虽然List<Integer>中所有的元素事实上是一个Object的实例。如果允许转换成List<Object>,那么转换后,理论上非整型的对象也将被允许添加到list中。


运行时类型安全

就像我们所见到的,一个List<X>不允许被转换为一个List<Y>,即使这个X能够被转换为Y。然而,一个List<X>能够被转换为一个List,这样您就可以通过继承的方法来做这样的事情。
这种将参数化类型转换为非参数化类型的能力对于向下兼容是必要的,但是它会在泛型所带来的类型安全体系上凿个漏洞:
Java容器的插入方法并不检查类型,所以很可能会插入错误的类型:
List l_raw = new ArrayList<Integer>();
l_raw.add(new Double(3)); // success

Collections提供了检查类型的方法,比如checkedList返回一个在插入时检查类型的List:
List<Integer> l = Collections.checkedList(new ArrayList<Integer>(), Integer.class);
List l_raw = l;
l_raw.add(new Double(3)); // throws exception!

在checked容器里试图插入不合法的类型会抛出异常。
运行了下,结果如下:
Exception in thread "main" java.lang.ClassCastException: Attempt to insert class java.lang.Double element into collection with element type class java.lang.Integer
at java.util.Collections$CheckedCollection.typeCheck(Collections.java:2202)
at java.util.Collections$CheckedCollection.add(Collections.java:2243)
at generic.TestType.main(TestType.java:12)





参数化类型的数组

在使用泛型类型的时候,数组需要特别的考虑。回忆一下,如果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[]数组,非常正确。既然编译器不能阻止您通过这个方法来战胜类型安全,那么它转而阻止您创建一个参数化类型的数组。所以上述情节永远不会发生,编译器在第一行就开始拒绝编译了。
注意这并不是一个在使用数组时使用泛型的全部的约束,这仅仅是一个创建一个参数化类型数组的约束。我们将在学习如何写泛型方法时再来讨论这个话题。


类型参数通配符

假设我们需要写一个方法来显示一个List中的元素。[3]在以前,我们只需要象这样写段代码:
public static void printList(PrintWriter out, List list) {
for(int i=0, n=list.size(); i < n; i++) {
     if (i > 0)
         out.print(", ");
     out.print(list.get(i).toString());
 }
}

在Java5.0中,List是一个泛型类型,如果我们试图编译这个方法,我们将会得到unchecked警告。为了解决这些警告,您可能需要这样来修改这个方法:
public static void printList(PrintWriter out, List<Object> list) {
for(int i=0, n=list.size(); i < n; i++) {
   if (i > 0)
       out.print(", ");
   out.print(list.get(i).toString());
    }
}

这段代码能够编译通过同时不会有警告,但是它并不是非常地有效,因为只有那些被声明为List<Object>的list才会被允许使用这个方法。还记得么,类似于List<String>和List<Integer>这样的List并不能被转型为List<Object>。事实上我们需要一个类型安全的printList()方法,它能够接受我们传入的任何List,而不关心它被参数化为什么。解决办法是使用类型参数通配符。方法可以被修改成这样:
public static void printList(PrintWriter out, List<?> list) {
for(int i=0, n=list.size(); i < n; i++) {
    if (i > 0)
        out.print(", ");
        Object o = list.get(i);
        out.print(o.toString());
}
}

这个版本的方法能够被编译过,没有警告,而且能够在任何我们希望使用的地方使用。通配符“?”表示一个未知类型,类型List<?>被读作“List of unknown”
作为一般原则,如果类型是泛型的,同时您并不知道或者并不关心值的类型,您应该使用“?”通配符来代替一个未经处理的类型。未经处理的类型被允许仅是为了向下兼容,而且应该只能够被允许出现在老的代码中。注意,无论如何,您不能在调用构造器时使用通配符。下面的代码是非法的:
List<?> l = new ArrayList<?>();
创建一个不知道类型的List是毫无道理的。如果您创建了它,那么您必须知道它将保持的元素是什么类型的。您可以在随后的方法中不关心元素类型而去遍历这里list,但是您需要在您创建它的时候描述元素的类型。如果你确实需要一个List来保持任何类型,那么您只能这么写:
List<Object> l = new ArrayList<Object>();
从上面的printList()例子中,必须要搞清楚List<?>既不是List<Object>也不是一个未经处理的List。一个使用通配符的List<?>有两个重要的特性。第一,考察类似于get()的方法,他们被声明返回一个值,这个值的类型是类型参数中指定的。在这个例子中,类型是“unknown”,所以这些方法返回一个Object。既然我们期望的是调用这个object的toString()方法,程序能够很好的满足我们的意愿。
第二,考察List的类似add()的方法,他们被声明为接受一个参数,这个参数被类型参数所定义。出人意料的是,当类型参数是未确定的,编译器不允许您调用任何有不确定参数类型的方法——因为它不能确认您传入了一个恰当的值。一个List(?)实际上是只读的——既然编译器不允许我们调用类似于add(),set(),addAll()这类的方法。


界定通配符

让我们在我们原来的例子上作些小小的稍微复杂一点的改动。假设我们希望写一个sumList()方法来计算list中Number类型的值的合计。在以前,我们使用未经处理的List,但是我们不想放弃类型安全,同时不得不处理来自编译器的unchecked警告。或者我们可以使用List<Number>,那样的话我们就不能调用List<Integer>、List<Double>中的方法了,而事实上我们需要调用。如果我们使用通配符,那么我们实际上不能得到我们期望的类型安全,我们不能确定我们的方法被什么样的List所调用,Number?还是Number的子类?甚至,String?这样的一个方法也许会被写成这样:
public static double sumList(List<?> list) {
double total = 0.0;
for(Object o : list) {
    Number n = (Number) o;  // A cast is required and may fail
    total += n.doubleValue();
}
return total;
}

要修改这个方法让它变得真正的类型安全,我们需要使用界定通配符(bounded wildcard),能够确保List的类型参数是未知的,但又是Number或者Number的子类。下面的代码才是我们想要的:
public static double sumList(List<? extends Number> list) {
double total = 0.0;
for(Number n : list) total += n.doubleValue();
return total;
}

类型List<? extends Number>可以被理解为“Number未知子类的List”。理解这点非常重要,在这段文字中,Number被认为是其自身的子类。
注意,这样的话,那些类型转换已经不再需要了。我们并不知道list中元素的具体类型,但是我们知道他们能够向上转型为Number,因此我们可以把他们从list中把他们当作一个Number对象取出。使用一个for/in循环能够稍微封装一下从list中取出元素的过程。普遍性的原则是当您使用一个界定通配符时,类似于List中的get()方法的那些方法将返回一个类型为上界的值。因此如果我们在for/in循环中调用list.get(),我们将得到一个Number。在前一节说到使用通配符时类似于list.add()这种方法中的限制依然有效:举个例子来说,如果编译器允许我们调用这类方法,我们就可以将一个Integer放到一个声明为仅保持Short值的list中去。
[/size]
分享到:
评论

相关推荐

    泛型实例详解

    通过7个实例详细介绍泛型的使用,包括普通泛型、通配符、泛型方法、泛型接口、受限泛型、泛型的转型、泛型数组。相信看完这几个泛型的例子就会使用泛型了。注意,这里并不介绍泛型的具体语法,只介绍泛型的使用。

    C#泛型设计需要注意的一个小陷阱

    泛型的出现就是专门解决这个问题的。 但泛型就简单吗?当然不是,继续往下看.. 背景 最近一直在对于公司一个网络通信服务程序使用.net core 进行重构.重构的目的有两个:一是让程序能够跨平台运

    程序员在运用C++语言写代码的时候需要注意的五个方面.docx

    当编写C ++代码时,有几个方面应该特别注意,以确保代码的质量和可维护性。下面是程序员在使用C ++编写代码时应该注意的五个方面: 1.内存泄漏问题 - 内存泄漏可能会导致程序崩溃或性能下降。在编写C ++代码时,需要...

    \java超强笔记(超级经典)

    Callable和Runnable有几点不同: Callable规定的方法是call(),而Runnable规定的方法是run(). Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。 call()方法可抛出异常,而run()方法是不...

    .net C# 分页控件

    一般C#分页都写在页面类里,每个页面...使用时,动态引用控件,并给把分页需要的几个值从持久层取出来,通过控件泛型,传到控件里即可。 注意:必须动态调用控件,不然会导致页面不刷新。查询条件变化,但控件不变化。

    习----题-Java-Web程序设计教程-[共2页].pdf

    Java Web 程序设计教程 30 Student s=list.get(i);// System.out.println(s.getSno()+"," +s.getSname()+","+s.getScore());...实现该类并包含添加、删除、修改、按姓名查等几个方法。编写 主程序测试。 参考如下。

    C++ Template 基础篇(一):函数模板详解

    Template所代表的泛型编程是C++语言中的重要的组成部分,我将通过几篇blog对这半年以来的学习做一个系统的总结,本文是基础篇的第一部分。 为什么要有泛型编程 C++是一门强类型语言,所以无法做到像动态语言(python...

    codeSmith模板(自动生成C#和.net项目和后台)--vs05版

    只需要一个数据库 生成网站后台(带功能) 生成三层结构 包括一些常用的方法(直接调用) 反射工厂 泛型接口 要求是每张表必须有主键才能生成 这点...另外我做的几个网站全是用这个生成框架的 现在完美运行 完全没问题

    c# WinForm Excel考勤数据融合系统 Excel导入导出源代码

    用的时候需要注意以下几点, 实例化Excelheper类的时候需要传入模板所在路径 System.AppDomain.CurrentDomain.BaseDirectory.Replace(@"bin\Debug\", @"Template\excel模板.xlsx") 这句代码就是为了获得Excel模板...

    java服务器 java机器人 unity3D客户端.rar

    输入几个字屏幕就被换行,肯定玩玩。不过如果是画面形式,当然就无所谓了。 修改比较方式。修改数组的初始化的方式(建议改为ArrayList方式作为容器),并调用Lists的排序方法;最后提个建议:注意代码编写规范,...

    asp.net知识库

    与正则表达式相关的几个小工具 你真的了解.NET中的String吗? .NET中的方法及其调用(一) 如何判断ArrayList,Hashtable,SortedList 这类对象是否相等 帮助解决网页和JS文件中的中文编码问题的小工具 慎用const...

    @SuppressWarnings

    一点背景:J2SE 5.0 为 Java 语言增加了几个新的特性,并且和它们一起增加了许多新的警告并承诺在将来增加更多的警告。您可以为 "javac" 增加 -Xlint 参数来控制是否报告这些警告(如上面的 @Deprecated 部分所示)...

    DWR.xml配置文件说明书(含源码)

    有几个术语有必要理解,参数叫做converted,远程Bean叫做created.如果远程Bean A有个方法A.blah(B),那么你需要为A建立一个created,为B建立一个converted. 配置文件init部分声明那些用于建立远程bean和在方法调用中转...

    java命名规范 开发规范

    5. 响应一个请求的分层结构约定,列举几个示例(常规调用、Ajax调用、WebService调用、提供WebService暴露、硬件设备接口调用); 6. 验证代码质量的约定,如JUnit、EMMA、FindBugs、CheckStyle、PMD的使用;Hudson...

    一个为RecyclerView打造的轻量级快速的适配器

    我们使用RecyclerView的Adapter无非就几个目的:1. 基本使用1. 显示多种类视图1. 添加header和footer1. 滑到底部自动加载更多(加载失败,重新加载等多种状态)至于下拉刷新,Google已经提供了SwipeRefreshLayout,...

    djangocon2015-views:“ Django视图的代码

    Django视图:函数,类和泛型 该存储库包含我的DjangoCon 2015演示文稿中使用的代码。 其他所有资料均可在访问。 注意:为了简化此演示的设置,尚未从settings.py文件中删除Django秘钥。 因此,不得将本网站用于生产...

    整理后java开发全套达内学习笔记(含练习)

    以“%”开头,[第几个数值$][flags][宽度][.精确度][格式] printf()的引入是为了照顾c语言程序员的感情需要 格式化输出 Formatter;格式化输入 Scanner;正则表达式 输出格式控制: 转义符: \ddd 1到3位8...

    javaSE代码实例

    1.4 第一个Java程序 8 1.4.1 开发源代码 8 1.4.2 编译运行 9 1.5 小结 11 第2章 基本数据类型——构建Java 大厦的基础 12 2.1 源代码注释 12 2.1.1 单行注释 12 2.1.2 区域注释 12 2.1.3 文档...

    传智播客扫地僧视频讲义源码

    20_信息系统框架集成第三方产品案例_几个重要的面向对象思想_传智扫地僧 21_作业 文档和源码 01_上一次课程回顾 02_数组指针语法梳理 03_函数指针语法梳理_传智扫地僧 04_函数指针做函数参数思想剖析_传智扫地僧 05_...

    领域驱动设计与模式实战

    第1章 应重视的价值,也是对过去几年的沉重反思 1.1 总体价值 1.2 应重视的架构风格 1.2.1 焦点之一:模型 1.2.2 焦点之二:用例 1.2.3 如果重视模型,就可以使用领域模型模式 1.2.4 慎重处理数据库 1.2.5 领域模型...

Global site tag (gtag.js) - Google Analytics