【双语】不为人知的集合异常检查工具:Collections.checkedCollection()

本贴最后更新于 2466 天前,其中的信息可能已经东海扬尘

英文原文链接:https://www.javaspecialists.eu/archive/Issue251.htm

摘要: Java 在 5.0 的时候已经具备了检查集合中的元素类型是否正确的能力,但是只有很少一部分程序员知道这个功能,这个功能在代码调试阶段起到了非常巨大的作用,它可以在早期就把异常抛出。

Dinosaurs roamed the earth. Fred Flintstone wrote Applets with JBuilder. A shaft of lightning split the dark sky and with a flash <> appeared on his amber screen. Fred scratched his head. _"What on flat earth was List<String>?"

在那古老的恐龙时代,原始人弗莱德正在用 Jbuilder 写一个 Applets 程序,一道闪电劈开了昏暗的天空,<> 在弗莱德那琥珀显示器上不停的闪烁。弗莱德抓了抓脑袋说道:“List<String> 是个什么鬼?”

It was the advent of generics.

这就是泛型的降临

Programmers do not like change. Or rather, we do not like change that will break something that was working perfectly well before. Java 1.4 introduced the **assert** keyword. It broke a bunch of our classes. Java 1.5 added **enum** to the relatively short list of reserved words, a popular name for Enumeration instances. And that was the last time a new keyword was added to the Java Programming Language. Sun Microsystems' engineers knew their audience. They also knew that for their generics to be accepted, old code should preferably still compile without any changes necessary.

程序员不喜欢变化,不如说我们不喜欢去接受一些新的改变我们编程方式的东西,因为我们之前的编程方式已经做的很熟练和完美了。Java1.4 加入了“断言” assert 关键字,他改变了我们大量的 java 类,Java1.5 添加了枚举 enum (Enumeration instances)这个流行的关键字,这也是最后一次向 Java 中添加关键字了。Sun 公司(java 的创始公司已被 Oracle 收购)的工程师了解他们的用户,并很肯定他们的用户也能接受泛型,因为使用泛型你不需要修改之前的任何代码。

Generics were designed so we could ignore them if we wanted to. The javac compiler would issue a faint sigh, but would still compile everything as before. How did they do it? Type erasure was the magic ingredient. When they compiled the class ArrayList, the generic type parameter was erased and replaced with Object. Even the E[] was erased to Object[]. In Java 6, they changed the element array in ArrayList to the more honest Object[].

泛型虽然被加入到了 Java 体系总但我们完全可以不去使用它,编译器只是会有一个警告标志,但不影响程序的执行。他们怎么做到的?这就是类型擦除(Java 泛型的内部机制,有兴趣的朋友可以百度一下),当我们编译 ArrayList 这个类时,泛型类型是被擦除掉的,被 Object(对象的祖宗)这个类型取代,甚至异常占位符 E[]也被替换成了 Object[] (百度一下泛型占位符的基本概念),在 Java 1.6 版本,他们把 ArrayList 里装载数据的类型都改变成了 Object[]。

1.  private transient Object[] elementData;

有兴趣的可以去百度一下这一行代码,深入的理解一下 ArrayList 这个使用非常频繁的集合。
(这部分看不明白不影响后面的阅读)

By not distinguishing at runtime between ArrayList<String> and ArrayList<Integer>, we allowed Java programmers to still shoot themselves in the foot like so:

如果不去关心 ArrayList<String> 和 ArrayList<Integer> 会出现的问题,我们依然允许程序员去拿石头砸自己的脚,如下面的代码:

import java.util.*;  
public class  FootShootJava5{  
  public  static  void main(String... args)  {  
	 List<String> names =  new  ArrayList<>();
	 Collections.addAll(names,  "John",  "Anton",  "Heinz");
	 List huh = names;  
	 List<Integer> numbers = huh; 
	 numbers.add(42);
  }  
}  

Sure, javac would emit a warning, but at runtime everything would appear to work. It was only when we retrieved elements from ArrayList that a cast to String was inserted into the client code and then a ClassCastException would jump in our faces. This is an example of an exception that is thrown late. A while after the incorrect object has been inserted into the ArrayList, we discover that it wasn't a String after all, thus if we add the following we see the problem:

这段代码并不会报错,一切运行正常,但是当我们从集合中取出数据并加以操作的时候,异常就会跳到我们脸上,这种异常是一种_后期异常(暂时这么翻译,在本例意思是异常并没有出现在不同类型数据插入时,而是在使用时才出现)_, 下面的例子中解释了这一点:

import java.util.*;  
import  static java.util.stream.Collectors.*;
public  class  FootShootJava8  {  
  public  static  void main(String... args)  {  
	List<String> names =  new  ArrayList<String>();
	Collections.addAll(names,  "John",  "Anton",  "Heinz");
	List huh = names;
	List<Integer> numbers = huh;
	//应该出现异常的位置,因为插入了非String类型的数据,但这一行并没有抛出异常,而是在下一行抛出了
	numbers.add(42);  
	//stream:jdk1.8新特性,可百度一下StreamAPI,下面这行代码意思是将集合中的所有元素连接在一起,以+号分隔开
	//这一行抛出了类型转换异常
	System.out.println(names.stream().collect(joining("+")));  
	}
}  

(StreamAPI 相关博文:http://blog.csdn.net/u010425776/article/details/52346644)

esults in a rather grumpy:

结果让人很暴躁

ClassCastException:  Integer cannot be cast to CharSequence 
	at ReduceOps$3ReducingSink.accept()
	at ArrayList$ArrayListSpliterator.forEachRemaining()
	at AbstractPipeline.copyInto()
	at AbstractPipeline.wrapAndCopyInto()
	at ReduceOps$ReduceOp.evaluateSequential()
	at AbstractPipeline.evaluate() 
	at ReferencePipeline.collect() 
	at FootShootJava8.main 

Since the exception is thrown late, it results in wasted programmer effort searching for where the wrong type could have been inserted into the list.

异常在运行时抛出,而且抛出的位置让程序员很难找到问题所在,浪费了大量的时间。

And yet there has always been a better way, even in Java 5. We can wrap our List object with a checkedList. This way, every time we add an element, it is checked that it is of the correct type. The ClassCastException thus happens during the add(42), rather than much later. For example:

但是总有办法来解决这个问题,甚至在 java1.5 我们也能进行集合的检查,有种方法可以在数据插入集合时就可以去检查数据类型是否正确,在 add(42) 这一行执行时就可以抛出异常不就是我们想要的?看下面的代码:

import java.util.*;
import  static java.util.stream.Collectors.*;
public  class  FootShootWithSafetyCatch  {  
	public  static  void main(String... args)  {  
		List<String> names =  Collections.checkedList(new  ArrayList<String>(),  String.class);
		Collections.addAll(names, "John","Anton","Heinz");
		List huh = names;
		List<Integer> numbers = huh;
		numbers.add(42); //异常将会出现在这一行
		System.out.println(names.stream().collect(joining("+")));
	}
}  

We would still get a ClassCastException, but at the place where the damage was done:

我们依然会触发一个类型转换异常,但是这次抛出的位置正是错误出现的位置。

ClassCastException:  Attempt to insert class  Integer element into collection with element type class  String 
	at java.util.Collections$CheckedCollection.typeCheck() 
	at java.util.Collections$CheckedCollection.add() 
	at FootShootWithSafetyCatch.main 

The checked collection would also discover objects that are added via reflection and throw a ClassCastException. It could not safeguard against "deep reflection", but then not much can.

这个集合检查功能同样可以用于反射检查,抛出类型转换异常,有兴趣的可以去查阅资料,但不能去应付“深度反射”(反射层数较多),但这并不是什么大问题。

You might wonder why I am writing about a method that was added in Java 5? The reason is that hardly anybody I speak to has heard of Collections.checkedCollection() and its derivatives. It is useful to make your collections just a bit more robust against accidental or deliberate tomfoolery.

你肯定问为什么我要拿 java 1.5 来讲这个例子,原因是我想强调一下很少有人知道 Collections.checkedCollection() 和他的衍生物,这是在工作中能让我们的代码更加健壮且避免发生一些愚蠢的问题和浪费不必要的时间。

It can also be a quick and easy way to debug any ClassCastException you might discover in your system. Wrap the collection in a checked exception and the guilty party will quickly come to the fore.

这也是一种快速的方式帮助我们找到类型转换异常的方式,把集合包装起来好让问题尽快的暴露出来。

Oh one last thing, completely unrelated to Java, but definitely related to our profession. Today also marks one year since I started my running streak, running at least one mile a day, in snow, rain, lightning and 48C heat. It's been fun and a great way to think about all sorts of things. I've had far more energy, have slept better and have produced more this year than in many previous years. If you're a couch potato, I can only recommend you try Streak Running and join me in the list of people who've run for at least one year, every day. No excuses.

oh. 还有一件很重要的事,跟 Java 没关系,但是对我们的职业很有好处,今天已经是我连续跑步一年的日子,每天至少跑一公里,不管是刮风下雪,闪电还是 48 度的桑拿天,对我们做事还是工作都非常有好处,我觉得我比之前更有能量了,吃的好睡的好,工作状态比前几年要好太多了,如果你是一个懒癌患者(couch potato?)我非常的建议你跟我一样加入跑步的行列,坚持一年你将看到非常显著的效果。

Kind regards from Crete

Heinz

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3186 引用 • 8212 回帖 • 1 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • CloudFoundry

    Cloud Foundry 是 VMware 推出的业界第一个开源 PaaS 云平台,它支持多种框架、语言、运行时环境、云平台及应用服务,使开发人员能够在几秒钟内进行应用程序的部署和扩展,无需担心任何基础架构的问题。

    5 引用 • 18 回帖 • 164 关注
  • jQuery

    jQuery 是一套跨浏览器的 JavaScript 库,强化 HTML 与 JavaScript 之间的操作。由 John Resig 在 2006 年 1 月的 BarCamp NYC 上释出第一个版本。全球约有 28% 的网站使用 jQuery,是非常受欢迎的 JavaScript 库。

    63 引用 • 134 回帖 • 726 关注
  • 前端

    前端技术一般分为前端设计和前端开发,前端设计可以理解为网站的视觉设计,前端开发则是网站的前台代码实现,包括 HTML、CSS 以及 JavaScript 等。

    247 引用 • 1347 回帖
  • 工具

    子曰:“工欲善其事,必先利其器。”

    285 引用 • 728 回帖
  • 音乐

    你听到信仰的声音了么?

    60 引用 • 511 回帖
  • 黑曜石

    黑曜石是一款强大的知识库工具,支持本地 Markdown 文件编辑,支持双向链接和关系图。

    A second brain, for you, forever.

    14 引用 • 106 回帖 • 1 关注
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 461 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 3 关注
  • Node.js

    Node.js 是一个基于 Chrome JavaScript 运行时建立的平台, 用于方便地搭建响应速度快、易于扩展的网络应用。Node.js 使用事件驱动, 非阻塞 I/O 模型而得以轻量和高效。

    139 引用 • 269 回帖 • 47 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖
  • Swift

    Swift 是苹果于 2014 年 WWDC(苹果开发者大会)发布的开发语言,可与 Objective-C 共同运行于 Mac OS 和 iOS 平台,用于搭建基于苹果平台的应用程序。

    36 引用 • 37 回帖 • 534 关注
  • OpenShift

    红帽提供的 PaaS 云,支持多种编程语言,为开发人员提供了更为灵活的框架、存储选择。

    14 引用 • 20 回帖 • 624 关注
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    89 引用 • 345 回帖
  • Markdown

    Markdown 是一种轻量级标记语言,用户可使用纯文本编辑器来排版文档,最终通过 Markdown 引擎将文档转换为所需格式(比如 HTML、PDF 等)。

    167 引用 • 1509 回帖
  • SOHO

    为成为自由职业者在家办公而努力吧!

    7 引用 • 55 回帖 • 18 关注
  • Git

    Git 是 Linux Torvalds 为了帮助管理 Linux 内核开发而开发的一个开放源码的版本控制软件。

    209 引用 • 358 回帖
  • V2EX

    V2EX 是创意工作者们的社区。这里目前汇聚了超过 400,000 名主要来自互联网行业、游戏行业和媒体行业的创意工作者。V2EX 希望能够成为创意工作者们的生活和事业的一部分。

    17 引用 • 236 回帖 • 333 关注
  • 30Seconds

    📙 前端知识精选集,包含 HTML、CSS、JavaScript、React、Node、安全等方面,每天仅需 30 秒。

    • 精选常见面试题,帮助您准备下一次面试
    • 精选常见交互,帮助您拥有简洁酷炫的站点
    • 精选有用的 React 片段,帮助你获取最佳实践
    • 精选常见代码集,帮助您提高打码效率
    • 整理前端界的最新资讯,邀您一同探索新世界
    488 引用 • 383 回帖 • 7 关注
  • App

    App(应用程序,Application 的缩写)一般指手机软件。

    91 引用 • 384 回帖 • 1 关注
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    490 引用 • 916 回帖 • 1 关注
  • 星云链

    星云链是一个开源公链,业内简单的将其称为区块链上的谷歌。其实它不仅仅是区块链搜索引擎,一个公链的所有功能,它基本都有,比如你可以用它来开发部署你的去中心化的 APP,你可以在上面编写智能合约,发送交易等等。3 分钟快速接入星云链 (NAS) 测试网

    3 引用 • 16 回帖 • 1 关注
  • Openfire

    Openfire 是开源的、基于可拓展通讯和表示协议 (XMPP)、采用 Java 编程语言开发的实时协作服务器。Openfire 的效率很高,单台服务器可支持上万并发用户。

    6 引用 • 7 回帖 • 97 关注
  • Hprose

    Hprose 是一款先进的轻量级、跨语言、跨平台、无侵入式、高性能动态远程对象调用引擎库。它不仅简单易用,而且功能强大。你无需专门学习,只需看上几眼,就能用它轻松构建分布式应用系统。

    9 引用 • 17 回帖 • 615 关注
  • Bug

    Bug 本意是指臭虫、缺陷、损坏、犯贫、窃听器、小虫等。现在人们把在程序中一些缺陷或问题统称为 bug(漏洞)。

    75 引用 • 1737 回帖 • 1 关注
  • 单点登录

    单点登录(Single Sign On)是目前比较流行的企业业务整合的解决方案之一。SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。

    9 引用 • 25 回帖
  • 新人

    让我们欢迎这对新人。哦,不好意思说错了,让我们欢迎这位新人!
    新手上路,请谨慎驾驶!

    52 引用 • 228 回帖 • 1 关注
  • 持续集成

    持续集成(Continuous Integration)是一种软件开发实践,即团队开发成员经常集成他们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。

    15 引用 • 7 回帖 • 1 关注