没有合适的资源?快使用搜索试试~ 我知道了~
首页Java谜题:理解取余操作符的奇偶性陷阱
Java谜题:理解取余操作符的奇偶性陷阱
需积分: 0 0 下载量 130 浏览量
更新于2024-09-23
收藏 1.18MB PDF 举报
Java谜题是一种有效的学习工具,有助于深化对Java编程语言的理解。这些谜题通常包含一些设计巧妙的问题或者挑战,旨在考察对Java语言特性和概念的掌握。在这个案例中,我们聚焦于两个具体谜题。 第一个谜题涉及一个名为`isOdd`的方法,其目标是判断一个整数是否为奇数。原始代码试图通过计算整数除以2的余数来确定奇偶性,即`return i % 2 == 1;`。然而,这个方法存在缺陷,因为它没有考虑到负数的情况。在Java中,整数除以2的余数运算对负数不适用,因为`i % 2`的结果可能是-1(当i是负奇数时),导致错误的判断。正确的实现应为`return i % 2 != 0;`,这样就能处理所有整数的奇偶性。 第二个谜题可能关注更高效的方法,比如在性能关键场景中使用位操作符。在`isOdd`方法中,我们可以使用按位与操作符`&`代替取余操作符,因为`i & 1`会返回i的最低位,1对应奇数,0对应偶数。这样,如果`(i & 1) != 0`,则表示i是奇数,否则是偶数。这种方法在处理大量数据时更高效,因为它避免了除法和取余操作,尤其是在硬件级别,位操作的执行速度更快。 通过解决这些Java谜题,学习者不仅可以检验自己对基础语法和逻辑的理解,还能提升对异常情况的处理能力以及优化代码性能的认识。在实际编程中,理解这些微妙之处至关重要,它们能帮助开发者编写出更健壮、高效的代码。
资源详情
资源推荐
尽管 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"。将它们连接到一起就形
成了在我们的程序中打印出来的那个丑陋的字符串。
有两种方法可以订正这个程序。你可以在调用字符串连接操作之前,显式地将一
个数组转换成一个字符串:
System.out.println(letters + " easy as " +
String.valueOf(numbers));
或者,你可以将 System.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);
}
}
总之,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 方法的参数是按照下面的方式计算的:
System.out.println(("Animals are equal: " + pig) == dog);
这个布尔表达式的值当然是 false,它正是该程序的所打印的输出。
有一个肯定能够避免此类窘境的方法:在使用字符串连接操作符时,总是将非平
凡的操作数用括号括起来。更一般地讲,当你不能确定你是否需要括号时,应该
选择稳妥地做法,将它们括起来。如果你在 println 语句中像下面这样把比较部
分括起来,它将产生所期望的输出 Animals are equal: false :
System.out.println("Animals are equal: " + (pig == dog));
可以论证,该程序仍然有问题。
如果可以的话,你的代码不应该依赖于字符串常量的内存限定机制。内存限定机
制只是设计用来减少虚拟机内存占有量的,它并不是作为程序员可以使用的一种
工具而设计的。就像这个谜题所展示的,哪一个表达式会产生字符串常量并非总
是很显而易见。
更糟的是,如果你的代码依赖于内存限定机制实现操作的正确性,那么你就必须
仔细地了解哪些域和参数必定是内存限定的。编译器不会帮你去检查这些不变
量,因为内存限定的和不限定的字符串使用相同的类型(String)来表示的。这
些因在内存中限定字符串失败而导致的 bug 是非常难以探测到的。
在比较对象引用时,你应该优先使用 equals 方法而不是 == 操作符,除非你需
要比较的是对象的标识而不是对象的值。通过把这个教训应用到我们的程序中,
我们给出了下面的 println 语句,这才是它应该具有的模样。很明显,在用这种
方式订正了该程序之后,它将打印出 true:
System.out.println("Animals are equal: " + pig.equals(dog));
这个谜题对语言设计者来说有两个教训。
•
字符串连接的优先级不应该和加法一样。这意味着重载 + 操作符来执行
字符串连接是有问题的,就像在谜题 11 中提到的一样。
•
还有就是,对于不可修改的类型,例如 String,其引用的等价性比值的
等价性更加让人感到迷惑。也许 == 操作符在被应用于不可修改的类型时
应该执行值比较。要实现这一点,一种方法是将 == 操作符作为 equals
方法的简便写法,并提供一个单独的类似于 System.identityHashCode
的方法来执行引用标识的比较。
谜题
谜题谜题
谜题 14
1414
14:
::
:转义字符的溃败
转义字符的溃败转义字符的溃败
转义字符的溃败
下面的程序使用了两个 Unicode 的转义字符,它们是用其十六进制代码来表示
Unicode 字符。那么,这个程序会打印什么呢?
public class EscapeRout{
public static void main(String[] args){
// \u0022 是双引号的 Unicode 转义字符
System.out.println("a\u0022.length()
+\u0022b".length());
}
}
对该程序的一种很肤浅的分析会认为它应该打印出 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。
如果该程序的作者确实希望得到这种行为,那么下面的语句将要清楚得多:
System.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 转义字符也被用来在看起来颇为相
似的数个字符中明确地标识其中的某一个,从而增加程序的清晰度。
总之,在字符串和字符字面常量中要优先选择的是转义字符序列,而不是
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");
}
}
这个谜题看起来相当简单。该程序包含了两条语句,第一条打印 Hell,而第二
条在同一行打印 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.
*/
这项技术表示了 Unicode 转义字符的一种没什么用处的用法。在 Javadoc 注释中,
应该使用 HTML 实体转义字符来代替 Unicode 转义字符:
剩余161页未读,继续阅读
huchungun
- 粉丝: 14
- 资源: 4
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
最新资源
- 多功能HTML网站模板:手机电脑适配与前端源码
- echarts实战:构建多组与堆叠条形图可视化模板
- openEuler 22.03 LTS专用openssh rpm包安装指南
- H992响应式前端网页模板源码包
- Golang标准库深度解析与实践方案
- C语言版本gRPC框架支持多语言开发教程
- H397响应式前端网站模板源码下载
- 资产配置方案:优化资源与风险管理的关键计划
- PHP宾馆管理系统(毕设)完整项目源码下载
- 中小企业电子发票应用与管理解决方案
- 多设备自适应网页源码模板下载
- 移动端H5模板源码,自适应响应式网页设计
- 探索轻量级可定制软件框架及其Http服务器特性
- Python网站爬虫代码资源压缩包
- iOS App唯一标识符获取方案的策略与实施
- 百度地图SDK2.7开发的找厕所应用源代码分享
资源上传下载、课程学习等过程中有任何疑问或建议,欢迎提出宝贵意见哦~我们会及时处理!
点击此处反馈
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功