笔记

way

Java Puzzlers(三-一 循环)

24     
  for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++) {
        if (b == 0x90)
            System.out.print("Joy!");
    }
Ox90 超过了byte的取值范围-128到127。byte和int比较是一种混合类型比较。考虑个表达式((byte)0x90 == 0x90)得到的是false。byte和int做比较的时候,Java先对byte进行了widening primitive conversion 再比较两个int值。因为byte是有符号类型,转变做了符号扩展,把负的byte值转换为相应的int值。这个例子中(byte)0x90被转变为-112,当然不等于int值 0x90或者说+144。混合比较总让人迷惑,因为总是强迫系统去提升一个操作数来和另一种类型匹配。有几种方式可避免混合比较。可以把int映射为byte,之后比较两个byte值:
if (b == (byte)0x90)
    System.out.println("Joy!");
另外,可用mask抑制符号扩展,把byte转换为int,之后比较两个int值:
if ((b & 0xff) == 0x90)
    System.out.println("Joy!");
但最好的方法是把常量值移出循环放到常量声明中。
    private static final byte TARGET = 0x90; // Broken!
    public static void main(String[] args) {
        for (byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++)
            if (b == TARGET)
                System.out.print("Joy!");
    }
不幸的是,上面编译通不过:0x90对于byte类型来说不是一个有效值。这样修改即可:
private static final byte TARGET = (byte)0x90;

To summarize: Avoid mixed-type comparisons, because they are inherently confusing (Puzzle 5). To help achieve this goal, use declared constants in place of "magic numbers." You already knew that this was a good idea; it documents the meanings of constants, centralizes their definitions, and eliminates duplicate definitions.现在你知道他还可以强制你为每一个常量定义适用的类型,避免一种混合类型比较的来源。

25
int j = 0;
for (int i = 0; i < 100; i++)
  j = j++;
System.out.println(j); //
打印出的是0

问题出在 j = j++; 等同于下列操作:

int tmp = j; j = j + 1; j = tmp;
这次的教训和难题7一样:在一个表达式中不要给同一个变量赋值超过一次。

26

public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
    int count = 0;
    for (int i = START; i <= END; i++)
       count++;
     System.out.println(count);
}
看起来像100,再仔细看循环是小于等于,应该是101?结果是程序没有输出任何值,陷入一个死循环。问题出在Integer.MAX_VALUE,当继续增加的时候,他悄悄变为Integer.MIN_VALUE。如果你需要循环int值边界,最好用long变量做索引:
for (long i = START; i <= END; i++)  //输出101
教训是:ints are not integers。无论何时用基本类型,注意边界值。上溢或下溢会出现什么情况?一般来说最好用大一点的类型(基本类型是byte, char, short, int, and long)。也可以不用long:

int i = START;
do {
  count++;
} while (i++ != END);
考虑到清晰和简单,总是用long索引,除了一种特殊情况:如果要遍历所有int值,这样用int索引的话会快两倍。
一个循环四十亿int值,调用方法的常规用法:

// Apply the function f to all four billion int values
int i = Integer.MIN_VALUE;
do {
f(i);
} while (i++ != Integer.MAX_VALUE);

27  位移
记住java是使用二进制补码计算,在任何有符号基本类型中(byte, short, int, or long)都是用所有位置1来表示-1。
        int i = 0;
        while (-1 << i != 0)       //左位移
            i++;
        System.out.println(i);
int型的-1用0xffffffff 表示。不断左移,右边由0补位。移位32次,变为全0,跳出循环打印32?实际上程序会死循环。问题出在-1<<32不等于0而是等于-1,因为位移符号只用右边操作数的低五位作为移动距离,如果左操作数是long的话用六位。三个位移操作符:<<,>>,>>>都是这样。移动距离总是0到31,左边操作数是long的话0到63。位移距离用32取模,左边是long则用64取模。给int值位移32位或给long值位移64位只会返回本身。所以不可能用位移完全移除一个数据。幸运的是,有一个简单的办法解决这个问题。保存上一次的位移结果,每一次迭代多移动一位。
        int distance = 0;
        for (int val = -1; val != 0; val <<= 1)
            distance++;
        System.out.println(distance);
修改后的程序说明了一个原则:位移距离如果可能的话,用常量
另外一个问题,许多程序员认为右移一个负的移动距离,就和左移一样,反之亦然。事实上不是这样,左移是左移,右移就是右移。负数距离只留下低五位(long留六位),其余的置0就变为了正数距离。比如,左移一个int值-1的距离,实际上是左移31位。

28 无穷的表示
for (int i = start; i <= start + 1; i++) {
}
看起来循环两次就会结束,如果这样定义呢:
int start = Integer.MAX_VALUE - 1;//死循环
while (i == i + 1) {
}
这个不可能死循环?如果i是无穷呢?Java采用IEEE 754浮点数算术,用double或float来表示无穷。所以可以用任何浮点数计算表达式得出无穷来初始化i。比如:double i = 1.0 / 0.0;
更好的是可以利用标准库提供的常量:double i = Double.POSITIVE_INFINITY;
事实上,根本用不着用无穷初始化i来引起死循环。只要足够大的浮点数就够了:double i = 1.0e40;
这是因为浮点数越大,他的值和下一个数的值距离也就越大。distribution of floating-point values is a consequence of their representation with a fixed number of significant bits. 给足够大的浮点数加一不会改变值,因为他不能填充这个数和下一个数之间的距离。浮点数操作返回最接近准确数学结果的浮点值。一旦两个相邻浮点值之间的距离大于2,加1就不会有效果。float类型来说,超过225(或33,554,432)再加1就无效;对double来说超过254(接近1.8 x 1016)再加1就无效。
     相邻浮点数之间的距离称为ulp(unit in the last place的缩写)。在Java 5里 Math.ulp方法被引入来计算float或double值的ulp。

总结:不可能用float或double来表示无穷。另外,在一个大的浮点数上加一个小的浮点数,值不会改变。有点不合常理,实数并不是这样。记住二进制浮点数计算只是近似于实数计算。

29 NaN

while (i != i) { }
IEEE 754 浮点数计算保留了一个特殊值来来表示不是数字的数量。NaN是浮点计算不能很好定义的数,比如0.0 / 0.0。规范定义NaN不等于任何数包括自己。因此double i = 0.0 / 0.0; 可让开始的等式不成立。也有标准库定义的常量:double i = Double.NaN; 如果一个或多个操作数为NaN,那么浮点数计算就会等于NaN。

总结:float和doule存在特殊的值NaN,小心处理。

30
while (i != i + 0) { } 这一次不能使用float或者double。
+操作符除了数字,就只能处理String。+操作符会被重载:对于String类型,他做的是连接操作。如果操作数有非String类型,会先做转换变为String之后再做连接。i一般用作数字,要是对String型变量这么命名容易引起误解。

总结:操作符重载非常容易误导人。好的变量名,方法名,类名和好的注释对于程序的可读性一样重要。

posted on 2010-11-15 16:39 yuxh 阅读(365) 评论(0)  编辑  收藏 所属分类: jdk


只有注册用户登录后才能发表评论。


网站导航:
 

导航

<2010年11月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

统计

常用链接

留言簿

随笔分类

随笔档案

收藏夹

博客

搜索

最新评论

阅读排行榜

评论排行榜