`

java解惑你知多少(二)

    博客分类:
  • Java
 
阅读更多

8. +=复合赋值问题

x+=i与x=x+i等效吗,许多程序员都会认为第一个表达式x+=i只是第二个表达式x=x+i的简写方式,但这并不准确。

 

Java语言规范中提到:复合赋值 E1 op= E2等价于简单赋值 E1 = (T)((E1) op (E2)),其中T是E1的类型。

复合赋值表达式自动地将所执行计算的结果转型为其左侧变量的类型。如果结果的类型与该变量的类型相同,那么这

个转型不会造成任何影响,然而,如果结果的类型比该变量的类型要宽,那么复合赋值操作符将悄悄地执行一个窄化

原生类型转换,这样就会导致结果不正确:

Java代码  收藏代码
  1. short x=0;  
  2. int i = 123456;  
  3. x +=i;  
  4. System.out.println(x);//-7616  

 

使用简单的赋值方式就不会有这样的问题了,因为宽类型不能自动转换成窄的类型,编译器会报错,这时我们就会注

意到错误:x = x + i;//编译通不过

 

请不要将复合赋值操作符作用于byte、short或char类型的变量;在将复合赋值操作符作用于int类型的变量时,要确

保表达式右侧不是long、float或double类型;在将复合赋值操作符作用于float类型的变量时,要确保表达式右侧不

是double类型。其实一句:不要将让左侧的类型窄于右侧的数字类型。

 

总之,不要在short、byte或char类型的变量之上使用复合赋值操作符,因为这一过程会伴随着计算前类型的提升与计

算后结果的截断,导致最后的计算结果不正确。


9. i =++i;与i=i++;的区别

Java代码  收藏代码
  1. int i = 0;   
  2. i = i++;  
  3. System.out.println(i);  

上面的程序会输出什么?大部分会说是 1,是也,非也。运行时正确结果为0。

 

i=++i;相当于以下二个语句(编译时出现警告,与i=i;警告相同):
i=i+1;
i=i;

 

i = i++;相当于以下三个语句:
int tmp = i;
i = i + 1;
i = tmp;

 

下面看看下面程序片段:

Java代码  收藏代码
  1. int i = 0, j = 0, y = 0;  
  2. i++;//相当于:i=i+1;  
  3. System.out.println("i=" + i);// i=1  
  4. ++i;//相当于:i=i+1;  
  5. System.out.println("i=" + i);// i=2  
  6. i = i++;//相当于:int tmp=i;i=i+1;i=tmp;  
  7. System.out.println("i=" + i);// i=2  
  8. i = ++i;//编译时出现警告,与i=i;警告相同。相当于:i=i+1;i=i;  
  9. System.out.println("i=" + i);// i=3  
  10. j = i++;//相当于:int tmp=i;i=i+1;j=tmp;  
  11. System.out.println("j=" + j);// j=3  
  12. System.out.println("i=" + i);// i=4  
  13. y = ++i;//相当于:i=i+1;y=i;  
  14. System.out.println("y=" + y);// y=5  
  15. System.out.println("i=" + i);// i=5  

 
10. Integer.MAX_VALUE + 1=?

Java代码  收藏代码
  1. System.out.println(Integer.MAX_VALUE + 1);  

上面的程序输出多少?2147483647+1=2147483648?答案为-2147483648。

 

查看源码Integer.MAX_VALUE 为MAX_VALUE = 0x7fffffff;所以加1后为0x80000000,又0x80000000为整型字面常量,满了32位,且最位为1,所以字面上等于 -0,但又由于 -0就是等于0,所以-0这个编码就规定为最小的负数,32位的

最小负数就是-2147483648。


11. -1<<32=?、-1<<65=?

如果左操作数是int(如果是byte、short、char型时会提升至int型再进行位操作)型,移位操作符只使用其右操作数

的低5位作为移位长度(也就是将右操作数除以32取余);如果左操作数是long型,移位操作符只使用其右操作数的低

6位作为移位长度(也就是将右操作数除以64取余);

 

再看看下面程序片段就会知道结果:

Java代码  收藏代码
  1. System.out.println(-1 << 31);// -2147483648 向左移31%32=31位  
  2. System.out.println(-1 << 32);// -1 向左移32%32=0位  
  3. System.out.println(-1 << 33);// -2 向左移33%32=1位  
  4. System.out.println(-1 << 1);// -2 向左移1%32=1位  
  5.   
  6. System.out.println(-1L << 63);// -9223372036854775808 向左移63%64=63位  
  7. System.out.println(-1L << 64);// -1 向左移64%64=0位  
  8. System.out.println(-1L << 65);// -2 向左移65%64=1位  
  9. System.out.println(-1L << 1);// -2 向左移1%64=1位  
  10.   
  11. byte b = -1;// byte型在位操作前类型提升至int  
  12. System.out.println(b << 31);// -2147483648 向左移31%32=31位  
  13. System.out.println(b << 63);// -2147483648 向左移63%32=31位  
  14.   
  15. short s = -1;// short型在位操作前类型提升至int  
  16. System.out.println(s << 31);// -2147483648 向左移31%32=31位  
  17. System.out.println(s << 63);// -2147483648 向左移63%32=31位  
  18.   
  19. char c = 1;// char型在位操作前类型提升至int  
  20. System.out.println(c << 31);// -2147483648 向左移31%32=31位  
  21. System.out.println(c << 63);// -2147483648 向左移63%32=31位  

 

12. 一个数永远不会等于它自己加1吗?i==i+1

一个数永远不会等于它自己加1,对吗?如果数字是整型,则对;如果这个数字是无穷大或都是浮点型足够大(如

1.0e40),等式就可能成立了。

 

Java强制要求使用IEEE 754浮点数算术运算,它可以让你用一个double或float来表示无穷大。

 

浮点型分为double型、float型。

 

无穷分为正无穷与负无穷。

 

无穷大加1还是无穷大。

 

一个浮点数值越大,它和其后继数值之间的间隔就越大。

 

对一个足够大的浮点数加1不会改变它的值,因为1不足以“填补它与其后者之间的空隙”。

 

浮点数操作返回的是最接近其精确数学结果的浮点数值。

 

一旦毗邻的浮点数值之间的距离大于2,那么对其中的一个浮点数值加1将不会产生任何效果,因为其结果没有达到两

个数值之间的一半。对于float类型,加1不会产生任何效果的最小数是2^25,即33554432;而对于double类型,最小

数是2^54,大约是1.8*10^16。

 

33554432F转二进制过程:
33554432的二进制为:10000000000000000000000000,将该二进制化成规范的小数二进制,即小数从右向左移25位

1.0000000000000000000000000,化成浮点数二进制0,25+127, 00000000000000000000000 00(丢弃最后两位),即0, 10011000, 00000000000000000000000,最后的结果为1.00000000000000000000000*2^25


毗邻的浮点数值之间的距离被称为一个ulp,它是最小单位(unit in the last place)的首字母缩写。在5.0版本中,引入了Math.ulp方法来计算float或double数值的ulp。

 

二进制浮点算术只是对实际算术的一种近似。

Java代码  收藏代码
  1. // 注,整型数不能被 0 除,即(int)XX/0运行时抛异常  
  2. double i = 1.0 / 0.0;// 正无穷大  
  3. double j = -1.0 / 0.0;// 负无穷大  
  4. // Double.POSITIVE_INFINITY定义为:POSITIVE_INFINITY = 1.0 / 0.0;  
  5. System.out.println(i + " " + (i == Double.POSITIVE_INFINITY));//Infinity true  
  6. // Double.NEGATIVE_INFINITY定义为:NEGATIVE_INFINITY = -1.0 / 0.0;  
  7. System.out.println(j + " " + (j == Double.NEGATIVE_INFINITY));//-Infinity true  
  8. System.out.println(i == (i + 1));// true  
  9. System.out.println(0.1f == 0.1);// false  
  10. float f = 33554432;  
  11. System.out.println(f + " " + (f==(f+1)));//3.3554432E7 true  

 
13. 自己不等于自己吗?i!=i

NaN(Not a Number)不等于任何数,包括它自身在内。

 

double i = 0.0/0.0;可表示NaN。

 

float和double类型都有一个特殊的NaN值,Double.NaN、Float.NaN表示NaN。

 

如果一个表达式中产生了NaN,则结果为NaN。

Java代码  收藏代码
  1. System.out.println(0.0 / 0.0);// NaN  
  2. System.out.println(Double.NaN + " " + (Double.NaN == (0.0 / 0.0)));//NaN false  

 

14. 自动拆箱

Java代码  收藏代码
  1. // 為了兼容以前版本,1.5不會自動拆箱  
  2. System.out.println(new Integer(0) == new Integer(0));// false  
  3. // 1.4编译非法,1.5会自动拆箱  
  4. System.out.println(new Integer(0) == 0);// true  

 

15. 为什么-0x00000000==0x00000000、-0x80000000== 0x80000000

为了取一个整数类型的负值,要对其每一位取反(如果是对某个十六进制形式整数求负,如:-0x00000000则直接对这

个十六进制数进行各位取反操作——但不包括前面的负号;如果是对某个十进制求负,如-0,则需先求其绝对值的十

六进制的原码后,再各位取反),然后再加1。


注:如果是对某个十进制数求负,如-1(0xffffffff),实质上按照平时求一个负数补码的方式来处理也是一样的,求某个负数的补码规则为:先求这个数绝对值的原码,然后从该二进制的右边开始向左找第一个为1的位置,最后将这个1前的各位取反(包括最高位符号位,即最高位0取反后为1),其他位不变,最终所得的二进制就为这个负数的补码,也就是最终在内存中负数所表示的形式。不过在找这个第一个为1时可能找不到或在最高位,比如-0,其绝对值为0(0x00000000);也有可能最高位为1,比如-2147483648,其绝对值为2147483648(0x80000000),如果遇到绝对值的原码为0x00000000或0x80000000的情况下则不变,即为绝对值的原码本身。

 

-0x00000000的运算过程:对0x00000000先取反得到0xffffffff,再加1,-0x00000000的最后结果就为 0xffffffff+1

,其最后的结果还是0x00000000,所以-0x00000000 == 0x00000000。前面是对0x00000000求负的过程,如果是对0求负呢?先求0的十六进制形式0x00000000,再按前面的过程来即可。或者根据前面规则对0x00000000求负不变,即最后

结果还是0x00000000。

 

-0x80000000的运算过程:对0x80000000先取反得到0x7fffffff,再加1,-0x80000000的最后结果就为 0x7fffffff+1

,其最后的结果还是0x80000000,即-0x80000000 == 0x80000000。前面是对0x80000000求负的过程,如果是对

2147483648求负呢?先求2147483648的十六进制形式0x80000000,再按前面的过程来即可。或者根据前面规则对0x80000000求负不变,即最后结果还是0x80000000。

 

-0x00000001的运算过程,实质上就是求-1的补码过程,即对其绝对值的十六进制0x00000001求补码,即为0xffffffff

,即-1的补码为0xffffffff。

 

Java代码  收藏代码
  1. System.out.println(Integer.MIN_VALUE == -Integer.MIN_VALUE);// true   
  2. /* 
  3.  *  0x80000000取反得0x7fffffff,再加1得0x80000000,因为负数是 
  4.  *  以补码形式存储于内存中的,所以推导出结果原码为:0x80000000, 
  5.  *  即为-0,又因为-0是等于0的,所以不需要-0这个编码位,那就多了 
  6.  *  一个0x80000000编码位了,所以最后就规定0x80000000为最小负数  
  7.  */  
  8. System.out.println(-0x80000000);// -2147483648  
  9. /* 
  10.  *  0x7fffffff取反得0x80000000,再加1得0x80000001,因为负数是 
  11.  *  以补码形式存储于内存中的,所以推导出结果原码为:0xffffffff, 
  12. *  第一位为符号位,所以最后的结果就为 -0x7fffffff = -2147483647 
  13.  */  
  14. System.out.println(-0x7fffffff);// -2147483647  

 
另外,还发现有趣现象:最大整数加1后会等于最小整数:

Java代码  收藏代码
  1. // MAX_VALUE = 0x7fffffff; MIN_VALUE = 0x80000000;  
  2. System.out.println((Integer.MAX_VALUE + 1) == Integer.MIN_VALUE);// true  
  3. // MIN_VALUE = 0x8000000000000000L; MIN_VALUE = 0x8000000000000000L;  
  4. System.out.println((Long.MAX_VALUE + 1) == Long.MIN_VALUE);// true  

当然,-Byte. MIN_VALUE==Byte.MIN_VALUE、-Short.MIN_VALUE== Short.MIN_VALUE、-Long.MIN_VALUE== Long.MIN_VALUE,也是成立的。


16. Math.abs结果一定为非负数吗?

Java代码  收藏代码
  1. System.out.println(Math.abs(Integer.MIN_VALUE));// -2147483648  

上面的程序不会输出2147483648,而是-2147483648,为什么?

 

其实我们看一下Math.abs源码就知道为什么了,源码:(a < 0) ? -a : a;,结合上面那个迷题,我们就发现-Integer.MIN_VALUE= Integer.MIN_VALUE,所以上面的答案就是最小整数自己。

 

另外我们也可以从API文档看到对Math.abs()方法的解释:如果参数等于 Integer.MIN_VALUE 的值(即能够表示的最

小负 int 值),则结果与该值相同且为负。

 

所以Math.abs不能保证一定会返回非负结果。

 

当然,Long.MIN_VALUE也是这样的。

分享到:
评论

相关推荐

    java解惑(+Java 解惑你知多少)

    你认为自己了解Java多少?你是个爱琢磨的代码侦探吗?你是否曾经花费数天时间去追踪一个由Java或其类库的陷阱和缺陷而导致的bug?你喜欢智力测验吗?本书正好适合你!.. Bloch和Gafter继承了Effective Jaya一书的传统,...

    java解惑java解惑java解惑

    java解惑java解惑java解惑java解惑java解惑java解惑

    Java解惑Java解惑

    Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑Java解惑

    Java解惑.pdf

    Java解惑.pdf Java解惑.pdf Java解惑.pdf Java解惑.pdf

    JAVA 解惑 java经典

    JAVA解惑,你面包括一些java经典的问题。

    Java解惑 中文版

    Java解惑中文版 Java解惑 java健壮程序

    JAVA解惑.pdf

    JAVA解惑.pdf JAVA解惑.pdf JAVA解惑.pdf

    Java PUZZLE Java 解惑

    Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑Java PUZZLE Java 解惑 Java PUZZLE Java 解惑 Java PUZZLE Java 解惑

    最新版的Java-解惑

    《Java解惑》《Java解惑》《Java解惑》《Java解惑》《Java解惑》《Java解惑》

    Java解惑(中文版)_java_java解惑_solve65p_

    与java相关的的学习,适合初学者,可以看看

    Java解惑 布洛克 著;陈昊鹏 译

    《Java解惑》 布洛克 著;陈昊鹏 译 扫描清晰带目录,仅供参阅,请支持正版

    "java解惑" PDF版本

    "java解惑" PDF版本

    java解惑 for all javaer

    讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java程序员阅读。

    Java解惑

    Java解惑,罗列你意想不到的100道java疑惑

    JAVA解惑(JAVA谜题) 中文版(PDF)

    Java解惑,是一本以大量java实例,讲述如何在程序中避免程序缺陷和程序陷阱的,解惑的过程中,介绍了一些Java编程语言中许多不易被掌握的知识点,其阅读价值非常高,适合具有Java知识的学习者和有编程经验的Java...

    Java解惑(中文).pdf

    Java解惑(中文).pdf 给大家介绍java中容易迷惑用错的实例

    4,JAVA解惑 高清PDF 下载

    Java四大名著之一:4,JAVA解惑 高清PDF 下载

    《Java Pazzlers》Java解惑.pdf 书签齐全

    该书特写了95个有关Java或其类库的陷阱和缺陷的谜题,其中大多数谜题都采用了短程序的方式,这些程序的行为与其看似的大相径庭。在每个谜题之后都给出了详细的解惑方案,这些解惑方案超越了对程序行为的简单解释,向...

    java解惑中文版

    java解惑中文版 这本书吊的不行不行的 看完这本书你对java的理解能上一个档次

Global site tag (gtag.js) - Google Analytics