Java谜题9——高级谜题

Java谜题9——高级谜题谜题86:有毒的括号垃圾 | 谜题87:紧张的关系 | 谜题88:原生类型的处理 | 谜题89:泛型迷药 | 谜题90:荒谬痛苦的超类 | 谜题91:序列杀手 | 谜题92:双绞线 | 谜题93:类的战争 | 谜题94:迷失在混乱中 | 谜题95:只是些甜点 谜题86:有毒的括号垃圾你能否举出这样一个合法的Java表达式,只要对它的某个子表达式加上括号就可以使其成为不合法的表达式,而添加的括号只是为了注解未加括号时赋值的顺序? 插入一对用来注解现有赋值顺序的括号对程序的合法性似乎是应该没有任何影响的。事实上,绝大多数情况下确实是没有影响的。但是,在两种情况下,插入一对看上去没有影响的括号可能会令合法的Java程序变得不合法。这种奇怪的情况是由于数值的二进制补码的不对称性引起的,就像在谜题33和谜题64中所讨论的那样。 你可能会联想到,最小的int型负数其绝对值比最大的int型正数大1:Integer.MIN_VALUE是-231,即-2,147,483,648,而Integer.MAX_VALUE是231-1,即2,147,483,647。 Java不支持负的十进制字面常量;int和long类型的负数常量都是由正数十进制字面常量前加一元负操作符(-)构成。这种构成方式是由一条特殊的语言规则所决定的:在int类型的十进制字面常量中,最大的是2147483648。而从0到2147483647的所有十进制字面常量都可以在任何能够使用int类型字面常量的地方出现,但是字面常量2147483648只能作为一元负操作符的操作数来使用[JLS 3.10.1]。 一旦你知道了这个规则,这个谜题就很容易了。符号-2147483648构成了一个合法的Java表达式,它由一元负操作符加上一个int型字面常量2147483648组成。通过添加一对括号来注解(很不重要的)赋值顺序,即写成-(2147483648),就会破坏这条规则。信不信由你,下面这个程序肯定会出现一个编译期错误,如果去掉了括号,那么错误也就没有了: public class PoisonParen { int i = -(2147483648); } 类似地,上述情况也适用于long型字面常量。下面这个程序也会产生一个编译期错误,并且如果你去掉括号错误也会消失: public class PoisonParen { long j = -(9223372036854774808L); } 谜题87:紧张的关系在数学中,等号(=)定义了一种真实的数之间的等价关系(equivalence relation)。这种等价关系将一个集合分成许多等价类(equivalence class),每个等价类由所有相互相等的值组成。其他的等价关系包括有所有三角形集合上的“全等”关系和所有书的集合上的“有相同页数”的关系等。事实上,关系 ~ 是一种等价关系,当且仅当它是自反的、传递的和对称的。这些性质定义如下: 自反性:对于所有x,x ~ x。也就是说,每个值与其自身存在关系 ~ 。 传递性:如果x ~ y 并且y ~ z,那么x ~ z。也就是说,如果第一个值与第二个值存在关系 ~,并且第二个值与第三个值存在关系 ~ ,那么第一个值与第三个值也存在关系 ~ 。 对称性:如果x ~ y,那么y ~ x。也就是说,如果第一个值和第二个值存在关系 ~ ,那么第二个值与第一个值也存在关系 ~ 。 如果你看了谜题29,便可以知道操作符 == 不是自反的,因为表达式( Double.NaN == Double.NaN )值为false,表达式( Float.NaN == Float.NaN )也是如此。但是操作符 == 是否还违反了对称性和传递性呢?事实上它并不违反对称性:对于所有x和y的值,( x == y )意味着( y == x )。 传递性则完全是另一回事。 谜题35为操作符 == 作用于原始类型的数值时不符合传递性的原因提供了线索。当比较两个原始类型数值时,操作符 == 首先进行二进制数据类型提升(binary numeric promotion)[JLS 5.6.2]。这会导致这两个数值中有一个会进行拓宽原始类型转换(widening primitive conversion)。大部分拓宽原始类型转换是不会有问题的,但有三个值得注意的异常情况:将int或long值转换成float值,或long值转换成double值时,均会导致精度丢失。这种精度丢失可以证明 == 操作符的不可传递性。 实现这种不可传递性的窍门就是利用上述三种数值比较中的两种去丢失精度,然后就可以得到与事实相反的结果。可以这样构造例子:选择两个较大的但不相同的long型数值赋给x和z,将一个与前面两个long型数值相近的double型数值赋给y。下面的程序就是其代码,它打印的结果是true true false,这显然证明了操作符 == 作用于原始类型时具有不可传递性。 public class Transitive { public static void main(String[] args) throws Exception { long x = Long.MAX_VALUE; double y = (double) Long.MAX_VALUE; long z = Long.MAX_VALUE - 1; System.out.print((x == y) + “ “); // Imprecise! System.out.print((y == z) + “ “); // Imprecise! System.out.println(x == z); // Precise! } } 本谜题的教训是:要警惕到float和double类型的拓宽原始类型转换所造成的损失。它们是悄无声息的,但却是致命的。它们会违反你的直觉,并且可以造成非常微妙的错误(见谜题34)。更一般地说,要警惕那些混合类型的运算(谜题5、8、24和31)。本谜题给语言设计者的教训和谜题34一样:悄无声息的精度损失把程序员们搞糊涂了。 谜题88:原生类型的处理下面的程序由一个单一的类构成,该类表示一对类型相似的对象。它大量使用了5.0版的特性,包括泛型、自动包装、变长参数(varargs)和for-each循环。关于这些特性的介绍,请查看http://java.sun.com/j2se/5.0/docs/guide/language[Java-5.0]。这个程序的main方法只是执行这个类。那么它会打印什么呢? import java.util.*; public class Pair { private final T first; private final T second; public Pair(T first, T second) { this.first = first; this.second = second; } public T first() { return first; } public T second() { return second; } public List stringList() { return Arrays.asList(String.valueOf(first), String.valueOf(second)); } public static void main(String[] args) { Pair p = new Pair
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值