《Java 的这些坑你踩到了吗?.docx》由会员分享,可在线阅读,更多相关《Java 的这些坑你踩到了吗?.docx(18页珍藏版)》请在金锄头文库上搜索。
1、Java 的这些坑,你踩到了吗?前言中国有句老话叫事不过三,指一个人犯了同样的错误,一次两次还可以原谅,再多就不可原谅了。写代码也是如此,同一个代码“坑”,踩第一次叫长了经验,踩第二次叫加深印象,踩第三次叫不长记性,踩三次以上就叫不可救药。在本文中,笔者总结了一些 Java 坑,描述了问题现象,进行了问题分析,给出了避坑方法。希望大家在日常工作中,遇到了这类 Java 坑,能够提前避让开来。1 对象比较方法JDK 1.7 提供的 Objects.equals 方法,非常方便地实现了对象的比较,有效地避免了繁琐的空指针检查。问题现象在 JDK1.7 之前,在判断一个短整型、整型、长整型包装数据类
2、型与常量是否相等时,我们一般这样写:Short shortValue = (short)12345;System.out.println(shortValue = 12345); / trueInteger intValue = 12345;System.out.println(intValue = 12345); / trueLong longValue = 12345L;System.out.println(longValue = 12345); / true从 JDK1.7 之后,提供了 Objects.equals 方法,并推荐使用函数式编程,更改代码如下:Short shortVal
3、ue = (short)12345;System.out.println(Objects.equals(shortValue, 12345); / falseInteger intValue = 12345;System.out.println(Objects.equals(intValue, 12345); / trueLong longValue = 12345L;System.out.println(Objects.equals(longValue, 12345); / false为什么直接把 = 替换为 Objects.equals 方法就会导致输出结果不一样?问题分析通过反编译第一段
4、代码,我们得到语句 System.out.println(shortValue = 12345); 的字节码指令如下:getstatic java.lang.System.out : java.io.PrintStream 22aload_1 shortValueinvokevirtual java.lang.Short.shortValue() : short 28sipush 12345if_icmpne 24iconst_1goto 25iconst_0invokevirtual java.io.PrintStream.println(boolean) : void 32原来,编译器会判
5、断包装数据类型对应的基本数据类型,并采用这个基本数据类型的指令进行比较(比如上面字节码指令中的 sipush 和 if_icmpne 等),相当于编译器自动对常量进行了数据类型的强制转化。为什么采用 Objects.equals 方法后,编译器不自动对常量进行数据类型的强制转化?通过反编译第二段代码,我们得到语句 System.out.println(Objects.equals(shortValue, 12345); 的字节码指令如下:getstatic java.lang.System.out : java.io.PrintStream 22aload_1 shortValuesipush
6、 12345invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer 28 invokestatic java.util.Objects.equals(java.lang.Object, java.lang.Object) : boolean 33invokevirtual java.io.PrintStream.println(boolean) : void 39原来,编译器根据字面意思,认为常量 12345 默认基本数据类型是 int,所以会自动转化为包装数据类型 Integer。在 Java 语言中,整数的默认数据类型
7、是 int,小数的默认数据类型是 double。通过分析 Objects.equals 方法的源代码可知:语句 System.out.println(Objects.equals(shortValue, 12345),因为 Objects.equals 的两个参数对象类型不一致,一个是包装数据类型 Short,另一个是包装数据类型 Integer,所以最终的比较结果必然是false;而语句 System.out.println(Objects.equals(intValue, 12345),因为 Objects.equals 的两个参数对象类型一致,都是包装数据类型 Integer 且取值相同
8、,所以最终的比较结果必然是 true。避坑方法1)保持良好的编码习惯,避免数据类型的自动转化为了避免数据类型自动转化,更科学的写法是直接声明常量为对应的基本数据类型。第一段代码可以这样写:Short shortValue = (short)12345;System.out.println(shortValue = (short)12345); / trueInteger intValue = 12345;System.out.println(intValue = 12345); / trueLong longValue = 12345L;System.out.println(longValue
9、 = 12345L); / true第二段代码可以这样写:Short shortValue = (short)12345;System.out.println(Objects.equals(shortValue, (short)12345); / trueInteger intValue = 12345;System.out.println(Objects.equals(intValue, 12345); / trueLong longValue = 12345L;System.out.println(Objects.equals(longValue, 12345L); / true2)借助开
10、发工具或插件,及早地发现数据类型不匹配问题在 Eclipse 的问题窗口中,我们会看到这样的提示:Unlikely argument type for equals(): int seems to be unrelated to ShortUnlikely argument type for equals(): int seems to be unrelated to Long3)进行常规性单元测试,尽量把问题发现在研发阶段“勿以善小而不为”,不要因为改动很小就不需要进行单元测试了,往往 Bug 都出现在自己过度自信的代码中。像这种问题,只要进行一次单元测试,是完全可以发现问题的。注意:进行必
11、要单元测试,适用于以下所有案例,所以下文不再累述。2 三元表达式拆包三元表达式是 Java 编码中的一个固定语法格式:条件表达式?表达式1:表达式2三元表达式的逻辑为:如果条件表达式成立,则执行表达式 1,否则执行表达式 2。问题现象boolean condition = false;Double value1 = 1.0D;Double value2 = 2.0D;Double value3 = null;Double result = condition ? value1 * value2 : value3; / 抛出空指针异常当条件表达式 condition 等于 false 时,直接把
12、 Double 对象 value3 赋值给 Double 对象 result,按道理没有任何问题,为什么会抛出空指针异常?问题分析通过反编译代码,我们得到语句:Double result = condition ? value1 * value2 : value3;的字节码指令如下:iload_1 conditionifeq 33aload_2 value1invokevirtual java.lang.Double.doubleValue() : double 24aload_3 value2invokevirtual java.lang.Double.doubleValue() : dou
13、ble 24dmulgoto 38aload 4 value3invokevirtual java.lang.Double.doubleValue() : double 24invokestatic java.lang.Double.valueOf(double) : java.lang.Double 16astore 5 result在第 9 行,加载 Double 对象 value 3 到操作数栈中;在第 10 行,调用 Double 对象 value 3 的 doubleValue 方法。这个时候,由于 value 3 是空对象 null,调用 doubleValue 方法必然抛出抛出空
14、指针异常。但是,为什么要把空对象 value 3 转化为基础数据类型 double 呢?查阅相关资料,得到三元表达式的类型转化规则:1)若两个表达式类型相同,返回值类型为该类型;2)若两个表达式类型不同,但类型不可转换,返回值类型为 Object 类型;3)若两个表达式类型不同,但类型可以转化,先把包装数据类型转化为基本数据类型,然后按照基本数据类型的转换规则 (byte short(char) int long float double) 来转化,返回值类型为优先级最高的基本数据类型。根据规则分析,表达式 1(value1 * value2)的类型为基础数据类型 double,表达式 2(v
15、alue 3)的类型为包装数据类型 Double,根据三元表达式的类型转化规则判断,最终的表达式类型为基础数据类型 double。所以,当条件表达式 condition 为 false 时,需要把空 Double 对象 value 3 转化为基础数据类型 double,于是就调用了 value 3 的 doubleValue 方法进行拆包,当然会抛出空指针异常。避坑方法1)尽量避免使用三元表达式,可以采用 if-else 语句代替如果三元表达式中有包装数据类型的算术计算,可以考虑利用 if-else 语句代替。改写代码如下:if (condition) result = value1 * value2; else result = value3;2)尽量使用基本数据类型,避免包装数据类型的拆装包如果在三元表达式中有算术计算,尽量使用基本数据类型,避免包装数据类型的拆装包。改写代码如下:boolean condition = false;double value1 = 1.0D;double value2 = 2.0D;double value3