没有合适的资源?快使用搜索试试~ 我知道了~
首页Java编程陷阱:95个实战谜题与深度解析
《Java Pazzlers》是一本专门针对Java程序员设计的书籍,作者通过精心编排的95个谜题,揭示了Java编程中的陷阱和常见缺陷。这些谜题通常是以简短的程序形式呈现,看似直观易懂,但实际上它们的行为往往与预期大相径庭。每个谜题后面都附有详尽的解答,不仅解释程序为何出现异常,还深入剖析了问题的本质,教给读者如何避免这些底层的陷阱和优化解决方案。 其中的一个重点是"奇数性"谜题,它涉及到Java中的取余操作符(%)。作者指出,尽管我们习惯于认为奇数是能被2整除余数为1的整数,但在Java中,取余操作符对于负数并不总是按照预期工作。例如,当处理负奇数时,i%2可能会返回-1,而非1,导致`isOdd`方法错误地判断为偶数。解决这个问题的关键在于理解Java中取余操作符的特性,即当结果为负时,它会保持左操作数的正负符号。 作者建议测试isOdd方法的各种输入情况,包括负数、零和正数,以确保其正确性。此外,还提供了一个更高效的改进版本,使用位操作符AND(&)替代取余,这对于性能敏感的场景尤其适用,因为位操作通常比取余更快。 这本书不仅关注于代码的正确性,也注重编程技巧和最佳实践,帮助读者提升对Java语言的理解深度,提高代码质量。通过解决这些谜题,读者不仅可以掌握Java语言的细节,还能学会如何避免常见的编程陷阱,从而成为一名更加熟练和经验丰富的Java开发者。
资源详情
资源推荐
![](https://csdnimg.cn/release/download_crawler_static/10233963/bg10.jpg)
尽管 char 是一个整数类型,但是许多类库都对其进行了特殊处理,因为 char
数值通常表示的是字符而不是整数。例如,将一个 char 数值传递给 println 方
法会打印出一个 Unicode 字符而不是它的数字代码。字符数组受到了相同的特殊
处理:println 的 char[]重载版本会打印出数组所包含的所有字符,而
String.valueOf 和 StringBuffer.append 的 char[]重载版本的行为也是类似的。
然而,字符串连接操作符在这些方法中没有被定义。该操作符被定义为先对它的
两个操作数执行字符串转换,然后将产生的两个字符串连接到一起。对包括数组
在内的对象引用的字符串转换定义如下[JLS 15.18.1.1]:
如果引用为 null,它将被转换成字符串"null"。否则,该转换的执行就像是不
用任何参数调用该引用对象的 toString 方法一样;但是如果调用 toString 方法
的结果是 null,那么就用字符串"null"来代替。
那么,在一个非空 char 数组上面调用 toString 方法会产生什么样的行为呢?数
组是从 Object 那里继承的 toString 方法[JLS 10.7],规范中描述到:“返回一
个字符串,它包含了该对象所属类的名字,'@'符号,以及表示对象散列码的一
个无符号十六进制整数”[Java-API]。有关 Class.getName 的规范描述到:在
char[]类型的类对象上调用该方法的结果为字符串"[C"。将它们连接到一起就形
成了在我们的程序中打印出来的那个丑陋的字符串。
有两种方法可以订正这个程序。你可以在调用字符串连接操作之前,显式地将一
个数组转换成一个字符串:
Sys
tem.out.println(letters + " easy as " +
String.valueOf(numbers));
或者,你可以将 Sy
stem.out.println 调用分解为两个调用,以利用 println 的
char[]重载版本:
System.out.print(letters + " easy as ");
System.out.println(numbers);
请注意,这些订正只有在你调用了 valueOf 和 println 方法正确的重载版本
的情
况下,才能正常运行。换句话说,它们严格依赖于数组引用的编译期类型。
下面的程序说明了这种依赖性。看起来它像是所描述的第二种订正方式的具体实
现,但是它产生的输出却与最初的程序所产生的输出一样丑陋,因为它调用的是
println 的 Object 重载版本,而不是 char[]重载版本。
class ABC2{
public static void main(
String[] args){
String letters = "ABC";
Object numbers = new char[] { '1', '2', '3' };
System.out.print(letters + " easy as ");
System.out.println(numbers);
}
}
iTePub.Net-Collect
更多电子书教程下载请登陆http://down.zzbaike.com/ebook
本站提供的电子书教程均为网上搜集,如果该教程涉及或侵害到您的版权请联系我们。
![](https://csdnimg.cn/release/download_crawler_static/10233963/bg11.jpg)
总之,char 数组不是字符串。要想将一个 char 数组转换成一个字符串,就要调
用 String.valueOf(char[])方法。某些类库中的方法提供了对 char 数组的类似
字符串的支持,通常是提供一个 Object 版本的重载方法和一个 char[]版本的重
载方法,而之后后者才能产生我们想要的行为。
对语言设计者的教训是:char[]类型可能应该覆写 toString 方法,使其返回数
组中包含的字符。更一般地讲,数组类型可能都应该覆写 toString 方法,使其
返回数组内容的一个字符串表示。
谜题
谜
题谜题
谜题 13
1313
13:
::
:畜牧场
畜牧场畜牧场
畜牧场
George Orwell 的《畜牧场(Animal Farm)》一书的读者可能还记得老上校的
宣
言:“所有的动物都是平等的。”下面的 Java 程序试图要测试这项宣言。那
么,它将打印出什么呢?
public class AnimalFarm{
public static void main(String[] args){
final String pig = "length: 10";
final String dog = "length: " + pig.length();
System.out. println("Animals are equal: "
+ pig == dog);
}
}
对该程序的表面分析可能会
认为它应该打印出 Animal are equal: true。毕竟,
pig 和 dog 都是 final 的 string 类型变量,它们都被初始化为字符序列“length:
10”。换句话说,被 pig 和 dog 引用的字符串是且永远是彼此相等的。然而,==
操作符测试的是这两个对象引用是否正好引用到了相同的对象上。在本例中,它
们并非引用到了相同的对象上。
你可能知道 String 类型的编译期常量是内存限定的。换句话说,任何两个 String
类型的常量表达式,如果标明的是相同的字符序列,那么它们就用相同的对象引
用来表示。如果用常量表达式来初始化 pig 和 dog,那么它们确实会指向相同的
对象,但是 dog 并不是用常量表达式初始化的。既然语言已经对在常量表达式中
允许出现的操作作出了限制,而方法调用又不在其中,那么,这个程序就应该打
印 Animal are equal: false,对吗?
嗯,实际上不对。如果你运行该程序,你就会发现它打印的只是 false,并没有
其它的任何东西。它没有打印 Animal are equal: 。它怎么会不打印这个字符
串字面常量呢?毕竟打印它才是正确的呀!谜题 11 的解谜方案包含了一条暗示:
+ 操作符,不论是用作加法还是字符串连接操作,它都比 == 操作符的优先级高。
因此,println 方法的参数是按照下面的方式计算的:
Sys
tem.out.println(("Animals are equal: " + pig) == dog);
这个
布尔表达式的值当然是 false,它正是该程序的所打印的输出。
iTePub.Net-Collect
更多电子书教程下载请登陆http://down.zzbaike.com/ebook
本站提供的电子书教程均为网上搜集,如果该教程涉及或侵害到您的版权请联系我们。
![](https://csdnimg.cn/release/download_crawler_static/10233963/bg12.jpg)
有一个肯定能够避免此类窘境的方法:在使用字符串连接操作符时,总是将非平
凡的操作数用括号括起来。更一般地讲,当你不能确定你是否需要括号时,应该
选择稳妥地做法,将它们括起来。如果你在 println 语句中像下面这样把比较部
分括起来,它将产生所期望的输出 Animals are equal: false :
System.out.println("Animals are equal: " + (pig == dog));
可以论
证,该程序仍然有问题。
如果可以的话,你的代码不应该依赖于字符串常量的内存限定机制。内存限定机
制只是设计用来减少虚拟机内存占有量的,它并不是作为程序员可以使用的一种
工具而设计的。就像这个谜题所展示的,哪一个表达式会产生字符串常量并非总
是很显而易见。
更糟的是,如果你的代码依赖于内存限定机制实现操作的正确性,那么你就必须
仔细地了解哪些域和参数必定是内存限定的。编译器不会帮你去检查这些不变
量,因为内存限定的和不限定的字符串使用相同的类型(String)来表示的。这
些因在内存中限定字符串失败而导致的 bug 是非常难以探测到的。
在比较对象引用时,你应该优先使用 equals 方法而不是 == 操作符,除非你需
要比较的是对象的标识而不是对象的值。通过把这个教训应用到我们的程序中,
我们给出了下面的 println 语句,这才是它应该具有的模样。很明显,在用这种
方式订正了该程序之后,它将打印出 true:
Sys
tem.out.println("Animals are equal: " + pig.equals(dog));
这个
谜题对语言设计者来说有两个教训。
• 字符串连接的优先级不应该和加法一样。这意味着重载 + 操作符来执行
字符串连接是有问题的,就像在谜题 11 中提到的一样。
• 还有就是,对于不可修改的类型,例如 S
tring,其引用的等价性比值的
等价性更加让人感到迷惑。也许 == 操作符在被应用于不可修改的类型时
应该执行值比较。要实现这一点,一种方法是将 == 操作符作为 equals
方法的简便写法,并提供一个单独的类似于 System.identityHashCode
的方法来执行引用标识的比较。
谜题
谜
题谜题
谜题 14
1414
14:
::
:转义字符的溃败
转义字符的溃败转义字符的溃败
转义字符的溃败
下面的程序使用了两个 U
nicode 的转义字符,它们是用其十六进制代码来表示
Unicode 字符。那么,这个程序会打印什么呢?
public class EscapeRout{
public static void main(String[] args){
// \u0022 是双引号的 Unicode 转义字符
System.out.println("a\u0022.length()
+\u0022b".length());
}
iTePub.Net-Collect
更多电子书教程下载请登陆http://down.zzbaike.com/ebook
本站提供的电子书教程均为网上搜集,如果该教程涉及或侵害到您的版权请联系我们。
![](https://csdnimg.cn/release/download_crawler_static/10233963/bg13.jpg)
}
对该程序的一种很肤浅
的分析会认为它应该打印出 26,因为在由两个双引号
"a\u0022.length()+\u0022b"标识的字符串之间总共有 26 个字符。
稍微深入一点的分析会认为该程序应该打印 16,因为两个 Unicode 转义字符每
一个在源文件中都需要用 6 个字符来表示,但是它们只表示字符串中的一个字
符。因此这个字符串应该比它的外表看其来要短 10 个字符。 如果你运行这个程
序,就会发现事情远不是这么回事。它打印的既不是 26 也不是 16,而是 2。
理解这个谜题的关键是要知道:Java 对在字符串字面常量中的 Unicode 转义字
符没有提供任何特殊处理。编译器在将程序解析成各种符号之前,先将 Unicode
转义字符转换成为它们所表示的字符[JLS 3.2]。因此,程序中的第一个 Unicode
转义字符将作为一个单字符字符串字面常量("a")的结束引号,而第二个
Unicode 转义字符将作为另一个单字符字符串字面常量("b")的开始引号。程
序打印的是表达式"a".length()+"b".length(),即 2。
如果该程序的作者确实希望得到这种行为,那么下面的语句将要清楚得多:
Sys
tem.out.println("a".length()+"b".length());
更有可能的
情况是该作者希望将两个双引号字符置于字符串字面常量的内部。使
用 Unicode 转义字符你是不能实现这一点的,但是你可以使用转义字符序列来实
现[JLS 3.10.6]。表示一个双引号的转义字符序列是一个反斜杠后面紧跟着一个
双引号(\”)。如果将最初的程序中的 Unicode 转义字符用转义字符序列来替
换,那么它将打印出所期望的 16:
System.out.println("a\".length()+\"b".length());
许多字
符都有相应的转义字符序列,包括单引号(\')、换行(\n)、制
表符(\t)
和反斜线(\\)。你可以在字符字面常量和字符串字面常量中使用转义字符序列。
实际上,你可以通过使用被称为八进制转义字符的特殊类型的转义字符序列,将
任何 ASCII 字符置于一个字符串字面常量或一个字符字面常量中,但是最好是尽
可能地使用普通的转义字符序列。
普通的转义字符序列和八进制转义字符都比 Unicode 转义字符要好得多,因为与
Unicode 转义字符不同,转义字符序列是在程序被解析为各种符号之后被处理
的。
ASCII 是字符集的最小公共特性集,它只有 128 个字符,但是 Unicode 有超过
65,000 个字符。一个 Unicode 转义字符可以被用来在只使用 ASCII 字符的程序
中插入一个 Unicode 字符。一个 Unicode 转义字符精确地等价于它所表示的字符。
Unicode 转义字符被设计为用于在程序员需要插入一个不能用源文件字符集表
示的字符的情况。它们主要用于将非 ASCII 字符置于标识符、字符串字面常量、
字符字面常量以及注释中。偶尔地,Unicode 转义字符也被用来在看起来颇为相
似的数个字符中明确地标识其中的某一个,从而增加程序的清晰度。
iTePub.Net-Collect
更多电子书教程下载请登陆http://down.zzbaike.com/ebook
本站提供的电子书教程均为网上搜集,如果该教程涉及或侵害到您的版权请联系我们。
![](https://csdnimg.cn/release/download_crawler_static/10233963/bg14.jpg)
总之,在字符串和字符字面常量中要优先选择的是转义字符序列,而不是
Unicode 转义字符。Unicode 转义字符可能会因为它们在编译序列中被处理得过
早而引起混乱。不要使用 Unicode 转义字符来表示 ASCII 字符。在字符串和字符
字面常量中,应该使用转义字符序列;对于除这些字面常量之外的情况,应该直
接将 ASCII 字符插入到源文件中。
谜题
谜
题谜题
谜题 15
1515
15:
::
:令人晕头转向的
令人晕头转向的令人晕头转向的
令人晕头转向的 Hello
HelloHello
Hello
下面的程序是对一个老
生常谈的例子做出了稍许的变化之后的版本。那么,它会
打印出什么呢?
/**
* Generated by the IBM IDL-to-Java compiler, version 1.0
* from F:\TestRoot\apps\a1\units\include\PolicyHome.idl
* Wednesday, June 17, 1998 6:44:40 o’clock AM GMT+00:00
*/
public class Test{
public static void main(String[] args){
System.out.print("Hell");
System.out.println("o world");
}
}
这个谜题
看起来相当简单。该程序包含了两条语句,第一条打印 H
ell,而第二
条在同一行打印 o world,从而将两个字符串有效地连接在了一起。因此,你可
能期望该程序打印出 Hello world。但是很可惜,你犯了错,实际上,它根本就
通不过编译。
问题在于注释的第三行,它包含了字符\units。这些字符以反斜杠(\)以及紧
跟着的字母 u 开头的,而它(\u)表示的是一个 Unicode 转义字符的开始。遗憾
的是,这些字符后面没有紧跟四个十六进制的数字,因此,这个 Unicode 转义字
符是病构的,而编译器则被要求拒绝该程序。Unicode 转义字符必须是良构的,
即使是出现在注释中也是如此。
在注释中插入一个良构的 Unicode 转义字符是合法的,但是我们几乎没有什么理
由去这么做。程序员有时会在 JavaDoc 注释中使用 Unicode 转义字符来在文档中
生成特殊的字符。
//
Unicode 转义字符在 JavaDoc 注释中有问题的用法
/**
* This method calls itself recursively, causing a
* StackOverflowError to be thrown.
* The algorithm is due to Peter von der Ah\u00E9.
*/
这项技术表示了 U
nicode 转义字符的一种没什么用处的用法。在 Javadoc 注释中,
应该使用 HTML 实体转义字符来代替 Unicode 转义字符:
iTePub.Net-Collect
更多电子书教程下载请登陆http://down.zzbaike.com/ebook
本站提供的电子书教程均为网上搜集,如果该教程涉及或侵害到您的版权请联系我们。
剩余161页未读,继续阅读
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://csdnimg.cn/download_wenku/file_type_ask_c1.png)
![](https://profile-avatar.csdnimg.cn/c6727e6da08d4f25971c8689e416bfd2_u010666745.jpg!1)
KAKA53
- 粉丝: 0
- 资源: 4
上传资源 快速赚钱
我的内容管理 展开
我的资源 快来上传第一个资源
我的收益
登录查看自己的收益我的积分 登录查看自己的积分
我的C币 登录后查看C币余额
我的收藏
我的下载
下载帮助
![](https://csdnimg.cn/release/wenkucmsfe/public/img/voice.245cc511.png)
最新资源
- 多模态联合稀疏表示在视频目标跟踪中的应用
- Kubernetes资源管控与Gardener开源软件实践解析
- MPI集群监控与负载平衡策略
- 自动化PHP安全漏洞检测:静态代码分析与数据流方法
- 青苔数据CEO程永:技术生态与阿里云开放创新
- 制造业转型: HyperX引领企业上云策略
- 赵维五分享:航空工业电子采购上云实战与运维策略
- 单片机控制的LED点阵显示屏设计及其实现
- 驻云科技李俊涛:AI驱动的云上服务新趋势与挑战
- 6LoWPAN物联网边界路由器:设计与实现
- 猩便利工程师仲小玉:Terraform云资源管理最佳实践与团队协作
- 类差分度改进的互信息特征选择提升文本分类性能
- VERITAS与阿里云合作的混合云转型与数据保护方案
- 云制造中的生产线仿真模型设计与虚拟化研究
- 汪洋在PostgresChina2018分享:高可用 PostgreSQL 工具与架构设计
- 2018 PostgresChina大会:阿里云时空引擎Ganos在PostgreSQL中的创新应用与多模型存储
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
![](https://img-home.csdnimg.cn/images/20220527035711.png)
![](https://img-home.csdnimg.cn/images/20220527035711.png)
![](https://img-home.csdnimg.cn/images/20220527035111.png)
安全验证
文档复制为VIP权益,开通VIP直接复制
![](https://csdnimg.cn/release/wenkucmsfe/public/img/green-success.6a4acb44.png)