提高你的Java代码质量吧

来源:互联网 发布:阿里云视频 保利威视 编辑:程序博客网 时间:2024/05/01 22:20

提高你的Java代码质量吧:提防包装类型的null值

分类: 提高Java代码质量吧 116人阅读 评论(0) 收藏 举报
Java自动装箱自动拆箱空指针包装类型

一、分析 

Java引入包装类型(Wrapper Types)是为了解决基本类型的实例化问题,以便一个基本类型也能参与到面向对象的编程世界中  

而在Java5中泛型更是对基本类型说了“不”,如果想把一个整形放到List中,就必须使用Integer包装类型。 

基本类型和包装类型都是可以通过自动装箱(Autoboxing)和自动拆箱(AutoUnboxing)自由转换的。如,整型的拆箱过程是通过调用包装类型的inValue方法来实现,如果包装类型是null,访问其intValue方法报空指针异常也就在所难免了。 

二、场景 

代码如下 

[java] view plaincopy
  1. public static int f(List<Integer> list){   
  2.     int count = 0;   
  3.     for(int i : list){   
  4.         //隐含了自动拆箱操作   
  5.         count += i;   
  6.     }   
  7. }   
  8.    
  9. public static void main(String[] args){   
  10.     List<Integer> list = new ArrayList<Integer>();   
  11.     list.add(1);   
  12.     list.add(2);   
  13.     list.add(null);   
  14.     System.out.println(f(list));   
  15. }   

运行失败,结果 

Exception in thread "main" java.lang.NullPointerException 

我们稍微思考一下,在for循环过程中,隐含了一个拆箱过程,将包装类型转换成基本类型,调用intValue方法。由于存在null值,故调其intValue方法出现空指针异常。 

解决办法:在自动拆箱过程中,剔除null值的情况,该情况不转换成基本类型 

[java] view plaincopy
  1. public static int f(List<Integer> list){   
  2.     int count = 0;   
  3.     for(Integer i : list){   
  4.         count += (i != null) ? i : 0;   
  5.     }   
  6.     return null;   
  7. }   

三、建议 

我们禁忌一点:包装类型参与运算时,要做null值校验

 

提高你的Java代码质量吧:不容忽视的四舍五入细节

分类: 提高Java代码质量吧 303人阅读 评论(1) 收藏 举报
JavaBigDecimalMathRoundingMode四舍五入

一、分析 

在许多数学计算的场景中,会用到近似取值的计算方法。常用的近似取值有四舍五入 

但是在某些金融行业或特殊场景中,四舍五入的方式就不太适宜。目前Java支持一下其中舍入方式 

  • ROUND_UP:远离零方向舍入,向远离0的方向舍入,也就是说,向绝对值最大的方向舍入,只要舍弃位非0即进位。 

  • ROUND_DOWN:趋向零方向的舍入,向0方向靠拢,也就是说,向绝对值最小的方向输入。注意,所有的位都舍弃不存在进位的情况。 

  • ROUND_CEILING:向正无穷方向舍入,向正最大方向靠拢,如果是正数,舍入行为类似于ROUND_UP;如果是负数,则舍入行为类似于ROUND_DOWN。注意,Math.round方法使用的即此模式。 

  • ROUND_FLOOR:向负无穷方向舍入,向负无穷方向靠拢,如果是正数,则舍入行为类似于ROUND_DOWN;如果是负数,则舍入行为类似于ROUND_UP。 

  • HALF_UP:最近数字舍入(5进),这就是我们最经典的四舍五入模式 

  • HALF_DOWN:最近数字舍入(5舍),在四舍五入中,5是进位的,而在HALF_DOWN中却是舍弃不进位。 

  • HALF_EVEN:银行家算法,四舍六入五考虑,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一 

二、场景 

使用Math.round来指定精度的整数或小数 

[java] view plaincopy
  1. public class Client{   
  2.     public static void main(String[] args){   
  3.         System.out.println("10.5的近似值:" + Math.round(10.5));   
  4.         System.out.println("-10.5的近似值:" + Math.round(-10.5));   
  5.     }   
  6. }   

由于Math.round采用的舍入规则是正无穷方向舍入。所以输出结果为 

10.5近似值:11 

-10.5近似值:-10 

 

但是在银行计算利息的场景下,适应四舍五入计算利息就会出现如下情况 

按照概率计算克制,被舍入的数字均匀的分布在0到9之间,下面以10笔存款利息计算作为模型,以银行家的身份来思考这个算法: 

  • 四舍:舍弃的数值:0.000、0.001、0.002、0.003、0.004,因为是舍弃的,对于银行家来说,就不用付给储户利息了,那每舍弃一个数字就会赚取相应的金额:0.000、0.001、0.002、0.003、0.004。 

  • 五入:进位的数值:0.005、0.006、0.007、0.008、0.009,因为是进位,对于银行家来说,每进一位就会多付款给储户,也就是亏损了,那亏损的部分就是对应的10进制补数:0.005、0.004、0.003、0.002、0.001。 

因为舍弃和进位的数字在0到9之间是均匀分布的,所以对于银行家来说,每10笔存款利息因采用四舍五入而获得的盈利是: 

0.000+0.001+0.002+0.003+0.004-0.005-0.004-0.003-0.002-0.001=-0.005。 

也就是说没10笔利息的计算中,就损失了0.005元,每笔利息计算损失0.0005元。对于一家银行(上亿储户)来说,这个误差造成的损失也不可小视觉。 

 

这个误差是由美国的银行家发现的,所以提供出了一个修正算法,叫做银行家舍入的近似算法,其规则如下 

  • 舍去的位数小于5时,直接舍去; 

  • 舍去的位数大于等于6时,进位后舍去; 

  • 当舍去的数值等于5时,分两种情况: 

    • 5后面还有其它数字(非0),则进位后舍去 

    • 若5后面是0(即5是最后一个数字),则根据5前一位数的奇偶性来判断是否需要进位,奇数进位,偶数舍去。 

以上规则汇总成一句话:四舍六入考虑五,五后非零就进一,五后为零看奇偶,五前为偶应舍去,五前为奇要进一 

 

在Java5以上的版本中,使用银行家的舍入算法非常简单,直接使用RoundingMode类,提供Round模式即可,示例代码如下: 

[java] view plaincopy
  1. public class Client{   
  2.     public static void main(String[] args){   
  3.         //存款   
  4.         BigDecimal d = new BigDecimal(88888);   
  5.         //月利率,乘3计算季利率   
  6.         BigDecimal r = new BigDecimal(0.001875*3);   
  7.         //计算利息   
  8.         BigDecimal i = d.mutiply(r)setScale(2,RoundingMode.HALF_EVEN);   
  9.         System.out.println("季利息是:" + i);   
  10.     }    
  11. } <span style="background-color: transparent; color: windowtext; font-size: 11pt; line-height: 17px; font-family: Calibri, sans-serif;"> </span>  

三、建议 

根据不同的场景,慎重选择不同的舍入模式,以提高项目的精准度,减少算法损失。在大量与货币数字交互的项目中,一定要选择好近似的计算模式,尽量减少因算法不同而造成的损失。 

提高你的Java代码质量吧:危险的边界

分类: 提高Java代码质量吧 313人阅读 评论(0) 收藏 举报
Java边界越界测试

一、分析 

在单元测试中,有一项测试叫做边界测试(也有叫做临界测试),它能避免出现:数字越界使检验条件失效 

如果一个方法接受的是Int类型的参数,那一些三个值是必须的:0、正最大、负最大、其中正最大和负最大是边界值,如果这三个值都没有问题,方法才是比较安全可靠的。 

二、场景 

某生产的电子产品非常畅销,需要提前30天预订才能抢到手,同时它还规定了一个会员可拥有的最多产品数量,方式囤积压货肆意加价。 

代码如果如下 

[java] view plaincopy
  1. public class Clinet{   
  2.     //一个会员拥有的最多数量   
  3.     pubilc final static int LIMIT = 2000;   
  4.     public static void main(String[] args){   
  5.         //会员当前拥有的产品数量   
  6.         int cur = 1000;   
  7.         Scanner input = new Scanner(System.in);   
  8.         System.out.println("请输入需要预订的数量:");   
  9.         while(input.hasNextInt()){   
  10.             int order = input.nextInt();   
  11.             //当前拥有的与准备订购的产品的数量之和   
  12.             if(order > 0 && order + cur <= LIMIT){   
  13.                 System.out.println("你已经成功预订的" + order + "个产品");   
  14.             }else{   
  15.                 System.out.println("超限额,预订失败!");   
  16.             }   
  17.         }   
  18.     }   
  19. }   

业务逻辑非常简单,但是经过自动化测试两小时后,出现了异常的结果 

请输入要需要预订的数量:2147483647 

你已经成功预订的2147483647个产品! 


看着2147483647这个数字很眼熟,它是int类型的最大值。当如数order是2147483647时,order+cur就超越了int的范围,结果是-2147482649,orfer+cur<2000与order>0的条件就成立了。故由于:数字越界使检验条件失效。 

三、建议 

在测试过程中,必须要注意临界测试的重要性。避免越界导致的问题,保证程序的的安全可靠

提高你的Java代码质量吧:不要让类型默默转换

分类: 提高Java代码质量吧 183人阅读 评论(0) 收藏 举报
Java类型转换越界基本类型

一、分析 

在Java运算中的类型转换,是先运算在进行类型转换的具体场景如下

二、场景 

在如下程序中 

[java] view plaincopy
  1. public class Client{   
  2.     public static final int LIGHT_SPEED = 30 * 10000 * 1000;   
  3.     public static void main(String[] args){   
  4.         System.out.println("月亮照射到地球需要1秒,计算月亮到地球的距离。");   
  5.         long dis1 = LIGHT_SPEED * 1;   
  6.         System.out.println("月亮与地球的距离是:" + dis1 + "米");   
  7.         System.out.println("----------------------------------------------------");   
  8.         System.out.println("太阳照射到地球上需要8分钟,计算太阳到地球的距离。");   
  9.         //可能超出整数范围,使用long型   
  10.         long dis2 = LIGHT_SPEED * 60 * 8;   
  11.         System.out.println("太阳与地球的距离是:" + dis2 + "米");   
  12.     }   
  13. }   

运行结果是 

月亮照射到地球需要1秒,计算月亮到地球的距离。 

月亮与地球的距离是:300000000米 

---------------------------------------------------- 

太阳照射到地球上需要8分钟,计算太阳到地球的距离 

太阳与地球的距离是:-2028888064米 


太阳和地球的距离竟然是负的!那是因为Java是运算然后再进行类型的转换,具体地说是因为dis2的三个运算参数都是int类型,三者相乘的结果虽然也是int类型,但是已经超过了int的最大值,所以其值就是负值了,在转换成long型,结果是负值。 


解决办法,只要加个L即可,如下 

long dis2 = LIGHT_SPEED * 60 L * 8; 

60L是long型,乘出来的结果也是long型,在还没有超过int类型的范围时就已经转换成long型了。 

三、建议 

在实际开发中,基本类型的转换时,通用的做法是主动声明式类型转换(注意不是强制类型转换)。代码如下 

long dis2 = 1L * LIGHT_SPEED * 60 * 8; 

 

提高你的Java代码质量吧:三元操作符的类型陷阱

分类: 提高Java代码质量吧 469人阅读 评论(2) 收藏 举报
Java三元运算符返回值类型转换

一、分析 

当你使用三元运算符,两边的操作数的类型不一致的时候,这就涉及到三元操作符的转换规则 

1.若果两个操作数不可转换,则不做转换,返回值为Object类型。 

2.若两个操作数是明确类型的表达式(比如变量),则按照正常的二进制数字来转换。int类型转换为long类型,long类型转换成float类型 

3.若两个操作数中有一个是数字S,另外一个是表达式,且其类型为T,那么,若数字S在T的范围内,则转换为T类型;若S超过了T的范围,则T转换为S类型。 

4.若两个操作数字都是直接数字。则返回值类型为范围较大者。 

二、场景 

[plain] view plaincopy
  1. public class Client{   
  2.     public static void main(String[] args){   
  3.         int i = 80;   
  4.         String s = String.valueOf(i < 100? 90 : 100);   
  5.         String s1 = String.valueOf(i < 100? 90 : 100.0);   
  6.         System.out.println("两者是否相等:" + s.equals(s1)):   
  7.     }   
  8. }   

分析,两个三元操运算,条件都为真,返回第一个值,结果“两者相等:true”。结果果真如此吗?结果“两者相等:false”! 


问题出在于100和100.0这两个数字上: 

在变量s中,第一个操作数(90)和第二个操作数(100)都是int类型,类型相同,返回的是int型的90; 

在变量s1中,第一个操作数类型为(90)int类型,第二个操作数是(100.0)浮点型。  

可是三元操作符必须返回同一个数据,而且类型要确定,不可能条件为真返回int类型,条件为假返回float类型,编译器是不会允许的,所以进行类型转换了。int转换成90.0,也就是所返回值是90.0当然和90不相等了。  

三、建议 

保证三元操作符中的两个操作类型一致,即可减少错误的发生


原创粉丝点击