1.byte数值比较:
问题:
下面的程序循环遍历byte数值,以查找某个特定值,代码如下:
public class Test {
public static void main(String[] args) {
for(byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++){
if(b == 0x90){
System.out.println("Joy!");
}
}
}
}
该程序期望打印出”Joy!”来,但是程序真实运行结果是什么也没有打印出来。
原因:
上述程序循环在除了Byte.MAX_VALUE之外所有的byte数值中进行迭代,以查找0x90(十进制为144)。这个数值适合用byte表示,并且不等于Byte.MAX_VALUE,因此按理说在循环中应该找到一次,但是真实运行确实没有找到。
原因很简单,java中二进制表示的数值最高位表示符号位,byte表示的十进制数值范围是-128~127,十六进制0x90(十进制数值144),超过了byte的表示范围,因此byte被类型扩展提高为int类型,用一个byte类型与一个int类型进行比较是一个混合类型比较运算时,byte是一个有符号的类型,所以在进行数据类型扩展时符号位要保留,因此(byte)0x90被提升为int数值-112,它确实不等于int数值144。
结论:
修正这个问题有以下三种方法:
方法1:
比较时进行强制数据类型转换,代码如下:
public class Test {
public static void main(String[] args) {
for(byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++){
if(b == (byte)0x90){
System.out.println("Joy!");
}
}
}
}
方法2:
使用屏蔽码消除符合扩展,代码如下:
public class Test {
public static void main(String[] args) {
for(byte b = Byte.MIN_VALUE; b < Byte.MAX_VALUE; b++){
if((b & 0xff) == 0x90){
System.out.println("Joy!");
}
}
}
}
2.变量增量操作:
问题:
下面的程序在循环中,对变量进行增量操作,代码如下:
public class Test {
public static void main(String[] args) {
int j = 0;
for(int i = 0; i < 100; i++){
j = j++;
}
System.out.println(j);
}
}
该程序期望打印输出100,但是程序真实运行打印输出结果为0.
原因:
上述程序问题出现在j= j++;增量语句上。在java中,该增量语句等价于如下的语句序列:
int tmp = j;
j = j + 1;
j = tmp;
从上述分解我们可以看到,虽然经过了100次增量操作,但是j始终被复位到循环之前的值。
结论:
修改改程序非常简单,代码如下:
public class Test {
public static void main(String[] args) {
int j = 0;
for(int i = 0; i < 100; i++){
j++;
}
System.out.println(j);
}
}
只需要移除无关的赋值操作,就可以打印输出我们期望的100次增量操作的结果。
3.循环计数
问题:
下面的程序计算循环迭代的次数,代码如下:
public class Test {
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,因此程序结束循环的条件是小于等于END,该程序真实运行时会产生死循环,根本无法执行结束,更不用说什么也不会打印输出。
原因:
上述程序的for循环是一个无限循环,因为int最大值就是Integer.MAX_VALUE,当int i循环达到Integer.MAX_VALUE时再次执行增量操作时,它就又绕回到了Integer.MIN_VALUE。
结论:
解决该问题有如下几种方法:
方法1:
当需要的循环会迭代到int数值的边境附件时,最好使用一个long类型的变量作为循环索引,因此只需要将循环所以从int类型修改为long类型就可以解决该问题,从而使程序打印出我们期望的101,代码如下:
public class Test {
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(long i = START; i <= END; i++){
count++;
}
System.out.println(count);
}
}
方法2:
不使用long类型也可以解决该问题,使用do-while循环也可以打印输出我们期望的101,代码如下:
public class Test {
public static final int END = Integer.MAX_VALUE;
public static final int START = END - 100;
public static void main(String[] args) {
int count = 0;
int i = START;
do{
count++;
}while(i++ != END);
System.out.println(count);
}
}
在进行循环或者运算时,特别注意数据类型的溢出。
4.循环移位计数:
问题:
下面程序通过移位操作,在循环中记录迭代次数,代码如下:
public class Test {
public static void main(String[] args) {
int i = 0;
while(-1 << i != 0){
i++;
}
System.out.println(i);
}
}
Java使用的是基于2的补码的二进制算术运算,因此-1在任何有符号的整数类型中(byte、short、int或long)的表示都是所有的为被置位,因此常量-1是所有32为都被置位的int数值(oxffffffff),左移位操作符将0移入到最低位,因此表达式(-1 << i)将i最右边的位设置为0,并保持左边的32-i位为1,因此我们期望整个循环在32次循环移位后将-1变为0从而结束循环,因此程序应该打印输出32,但是真实程序运行时同样成了一个无限死循环,没有任何打印输出。
原因:
Java中三个移位操作符:<<,>>和>>>的移位长度总数介于0到数据类型长度(不包括)之间,即int类型移位长度总数介于0~31之间,long类型移位长度总数介于0~63之间,也就是移位长度对数据类型长度取余。如果视图对一个int类型移位32位或者对一个long类型移位64为,都只能返回这个数自身的值。
结论:
解决这个问题很简单,我们不要让-1重复地移位不同的位移长度,而是将前一次的移位结果保存,并且让其在每一次循环迭代时都向做移位恒定的1位,下面的代码可以打印出我们期望的32:
public class Test {
public static void main(String[] args) {
int count = 0;
for(int i = -1; i != 0; i <<= 1){
count++;
}
System.out.println(count);
}
}
如果可能的话,尽可能在移位时移动恒定位数。
5.无限循环:
下面的循环都是无限死循环:
(1).数据类型溢出:
int start = Integer.MAX_VALUE – 1;
for(int i = start; i <= start + 1; i++){}
(2).浮点数之间的最小距离:
double i = 1.0 / 0.0;//或者i = Double.POSITIVE_INFINITY;
while(i == i + 1){}
也可以使用double i =1.0e40;
一个浮点数数值越大,它和其后继数值之间的间隔就越大,浮点数的这种分布是用固定数量的有效位来表示它们的必然结果,对于一个足够大的浮点数加1不会改变他的值,因为1不足以填补它与其后继者之间的空隙。
Java中浮点数操作返回的是最接近其精确的数学结果的浮点数值,一旦毗邻的浮点数值之间的距离大于2,则对于其中的一个浮点数值加1将不会产生任何效果,因为其结果没有达到两个数值之间的一半,对于float类型,加1不会产生任何效果的最小级数是2的35次方;而对于double类型,最小级数是2的54次方。
毗邻的浮点数值之间的距离被称为一个ulp(unitin the last place),JDK1.5中引入了Math。ulp方法来计算float或double数值的ulp。
(3).NaN:
Double i = 0.0 / 0.0;//或者i = Double.NaN;
While(i != i){}
Java浮点数中的NaN表示不是一个数字,对于所有没有数学定义的浮点数都由其来表示,NaN不等于任何浮点数值,包括其自身在内。
(4).类型扩展和类型窄化:
short i = -1;
while(i != 0){
i >>>= 1;
}
short类型的-1是oxffff,i >>>= 1操作分解为一下:
首先,将数据类型扩展为int,即0xffffffff。
然后,进行无符号右移操作变成0x7fffffff。
最后,把int类型窄化为short,截掉高位之后变为0xffff。
因此注意不要在short,byte和char类型的变量上使用复合赋值操作符,因为复合赋值表达式执行的是混合类型算术运算,运算结果可能会自动只想数据类型窄化转换而丢失精度。
(5).自动装箱比较运算符:
Integer i = new Integer(0);
Integer j = new Integer(0);
while(i <= j && j <= i && i != j){}
原始数值类型被自动装箱为对象类型后,相等运算符比较的不再是数值而是对象的引用。
(6).数值的非对称性:
int i = Integer.MIN_VALUE;//或long i = Long.MIN_VALUE;
while(i != 0 && i == -i){}
计算机中有符号数使用的二进制补码算术运算,为了对一个数值取其负值,需要反转其每一位然后加1从而得到结果,二进制补码算术运算的一个优势是0具有唯一的表示形式,但是有个不利之处在于总共存在偶数个int数值(2的32次方),其中一个用来表示0,剩下奇数个数用来表示正负数,因此整数和负数的个数不想等,而Integer.MIN_VALUE是2的负32次方,即0x8000000,其符号位为1,如果对其去负值,得到是0x7fffffff+1,结果还是0x80000000,因此Integer.MIN_VALUE和其相反数是同一个数,Long.MIN_VALUE也一样,更一般的讲java中所有的数值类型都是非对称,他们的MIN_VALUE的相反数都是自身。