首页 > 编程语言 >《java解惑》——循环谜题

《java解惑》——循环谜题

时间:2023-11-09 12:03:57浏览次数:46  
标签:java int byte 数值 VALUE static 谜题 解惑 public


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){  
"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){  
"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){  
"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){  
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的相反数都是自身。

标签:java,int,byte,数值,VALUE,static,谜题,解惑,public
From: https://blog.51cto.com/u_809530/8275150

相关文章

  • 《java解惑》——异常谜题
    1.finally语句块中的return:问题:下面的小程序运行结果是什么:publicclassTest{publicstaticvoidmain(String[]args){System.out.println(decision());}staticbooleandecision(){try{returntrue;finally{returnfalse;......
  • 《java解惑》——类谜题
    1.方法重载:问题:下面的程序演示方法重载,代码如下:publicclassTest{publicstaticvoidmain(String[]args){newTest(null);}privateTest(Objecto){"Object");}privateTest(double[]doubleArray){"Doublearray&......
  • 《java解惑》--字符串之谜
    1.字符拼接:问题:程序员几乎在每天编程中都遇到和处理字符串拼接的问题,但是是否对其了解的足够深入,且看下面的程序:publicstaticvoidmain(String[]args){"H"+"a");'H'+'a');}很多人觉得输出结果应是:HaHa,但是真实的程序运行结果是:Ha169。原因:程序第一......
  • JavaScript实现完整的表单验证对邮箱用户名和密码一致性检测并拦截提交-----前端
    完整的表单验证HTML网页使用JS完成用户名密码一致性和邮箱验证<!DOCTYPEhtml><!--这是HTML的注释--><htmllang="en"id="myHtml"> <head> <!--这里不是设置了编码,而是告诉浏览器,用什么编码方式打开文件避免乱码--> <metacharset="UTF-8"> <metaname......
  • 《java解惑》——库谜题
    1.不可变类:问题:下面的程序计算5000+50000+500000值,代码如下:importjava.math.BigInteger;publicclassTest{publicstaticvoidmain(String[]args){newBigInteger("5000");newBigInteger("50000");newBigInteger("500000");......
  • Java-Script 编程
    Java-Script编程目录Java-Script编程一.Js概念1.1简介1.2语法结构二.变量使用2.1定义变量2.2定义常量三.数据类型3.1数值类型(number)3.2字符类型(string)3.3.字符类型常用方法3.4布尔值(boolean)3.5null与undefined3.6数组3.7数组常用的方法3.8运算符四.流程......
  • java ip地址转换成int
    数据库存放,提高数据库使用性能我们将字符串IP转换成int类型保存!java代码如下://将127.0.0.1形式的IP地址转换成十进制整数,这里没有进行任何错误处理publicstaticlongipToLong(StringstrIp){long[]ip=newlong[4];//先找到IP地址字符串中.的......
  • Java类加载机制
    类加载机制将class文件中的二进制数据读取到内存中,并对其进行校验,解析和初始化,将类型数据存放在方法区,实例对象存放在堆,作为方法区该类的数据访问接口。这就是类加载。加载通过全限定名获取二进制字节流将字节流代表的静态存储结构转化为方法区的运行时数据结构在堆中生成......
  • 【深入理解Java虚拟机】内存分配策略
    本文内容来源于《深入理解Java虚拟机》一书,非常推荐大家去看一下这本书。本系列其他文章:【深入理解Java虚拟机】Java内存区域模型、对象创建过程、常见OOM【深入理解Java虚拟机】垃圾回收机制垃圾收集器与内存分配策略Java技术体系中所提倡的自动内存管理最终可以归结为自动......
  • 【JAVA】智慧工地信息管理系统源码 智慧大屏、手机APP、SaaS模式
    一、智慧工地可以通过安全八要素来提升安全保障,具体措施包括:  1.安全管理制度:建立科学完善的安全管理制度,包括安全标准规范、安全生产手册等,明确各项安全管理职责和要求。  2.安全培训教育:对工地人员进行安全培训和教育,提高他们的安全意识和安全素质,使其掌握必要的安全知......