前言作为Java程序员,不知你有没有踩过一些基础知识的坑。有时候,你为了某个bug查了半天,最后发现是一个很低级的错误。有时,对于某些代码,这批数据功能正常,但是当一批数据发生变化时,就会出现异常。有时候,你可能看着某一行代码傻眼了,心里想:这行代码为什么会出错?今天就和大家聊聊99%的Java程序员都踩过或者即将踩过的6个陷阱。1.不知道大家有没有看到项目中用==号比较的坑。有同事用==号来比较Integer类型的两个参数。他们平等吗?不管怎样,我已经看到了,所以这个用法正确吗?我的回答要看具体的场景,不能说是对还是错。一些状态字段,如:orderStatus有:-1(未下单)、0(已下单)、1(已付款)、2(已完成)、3(已取消)、5种状态。这时如果用==判断是否相等:IntegerorderStatus1=newInteger(1);IntegerorderStatus2=newInteger(1);System.out.println(orderStatus1==orderStatus2);返回结果会是真的吗?答:是假的。可能有同学会反驳,Integer范围:-128-127不是有缓存吗?为什么是假的?我们先看看Integer的构造方法:它实际上并没有使用缓存。那么缓存用在什么地方呢?答案在valueOf方法中:如果把上面的判断改成这样:StringorderStatus1=newString("1");StringorderStatus2=newString("1");System.out.println(Integer.valueOf(orderStatus1)==Integer.valueOf(orderStatus2));返回结果会是真的吗?答:这是真的。我们需要养成良好的编码习惯,尽量少用==来判断两个Integer类型是否相等,只有在上述非常特殊的场景下才相等。而是使用equals方法判断:IntegerorderStatus1=newInteger(1);IntegerorderStatus2=newInteger(1);System.out.println(orderStatus1.equals(orderStatus2));运行结果为真。2、Objects.equals的坑假设有这样一个需求:判断当前登录的用户,如果是我们指定的系统管理员,则发送邮件。系统管理员没有特殊字段标识,他的用户id=888,在开发、测试、生产环境中该值相同。这个需求真的很容易实现:UserInfouserInfo=CurrentUser.getUserInfo();if(Objects.isNull(userInfo)){log.info("请先登录");return;}if(Objects.equals(userInfo.getId(),888L)){sendEmail(userInfo):}从当前登录用户的上下文中获取用户信息,进行判断,如果用户信息直接返回是空的。如果获取到的用户信息不为空,则判断用户id是否等于888,若等于888,则发送邮件。如果不等于888,什么也不做。当我们用id=888的系统管理员账号登录,进行相关操作,满怀期待地准备接收邮件时,却发现收件人孤单。后来发现UserInfo类是这样定义的:@DatapublicclassUserInfo{privateIntegerid;私有字符串名称;私人整数年龄;privateStringaddress;}这时候可能有朋友会说:我看不出有什么问题。但我想说的是这段代码确实有问题。有什么问题?让我们关注它的equals方法:publicstaticbooleanequals(Objecta,Objectb){return(a==b)||(a!=null&&a.equals(b));}equals方法判断逻辑如下:该方法首先判断对象a和b的引用是否相等,相等则直接返回true。如果引用不相等,则判断a是否为空,如果a为空则返回false。如果a不为空,调用对象的equals方法进一步判断值是否相等。这要从Integer的equals方法说起。其equals方法的具体代码如下:publicbooleanequals(Objectobj){if(objinstanceofInteger){returnvalue==((Integer)obj).intValue();}returnfalse;}首先判断参数obj是否为Integer类型,如果不是,直接返回false。如果是Integer类型,进一步判断int值是否相等。上面的例子中b是long类型,所以Integer的equals方法直接返回false。也就是说如果调用Integer的equals方法,输入的参数也必须是Integer类型,否则该方法会直接返回false。此外,Byte、Short、Double、Float、Boolean、Character也有类似的equals方法判断逻辑。常见的陷阱包括:Long类型和Integer类型的比较,例如:用户id的场景。Byte类型和Integer类型比较,比如:状态判断的场景。Double类型和Integer类型的比较,例如:金额为0的判断场景。如果想了解更多Objects.equals方法,可以看看我的另一篇文章《??Objects.equals有坑??》。3.BigDecimal的陷阱通常,我们将一些小数类型的字段(如:金额)定义为BigDecimal而不是Double,以避免精度损失。使用Double时,可能会出现这样的场景:doubleamount1=0.02;双倍金额2=0.03;System.out.println(金额2-金额1);正常情况下,预计amount2-amount1应该等于0.01,但是执行结果是:0.009999999999999998实际结果比预期的要少。Double类型的两个参数相减会转换成二进制,因为Double的有效位数是16位,所以会出现十进制位数存储不足,这种情况下会出错。常识告诉我们使用BigDecimal来避免精度损失。但是使用BigDecimal可以避免精度损失吗?答案是否定的。为什么?BigDecimalamount1=newBigDecimal(0.02);BigDecimalamount2=newBigDecimal(0.03);System.out.println(amount2.subtract(amount1));本例定义了两个BigDecimal类型参数,使用构造函数初始化数据,然后打印两个参数相减后的值。结果:0.0099999999999999984734433411404097569175064563751220703125不科学,为什么还是丢精度?Jdk中BigDecimal的构造方法有这样一段描述:大意是说这个构造函数的结果可能是不可预测的,创建的时候可能是0.1,但实际上是0.1000000000000000055511151231257827021181583404541015625。可见使用BigDecimal构造函数初始化对象也会丢失精度。那么,我们如何才能不失去精度呢?BigDecimalamount1=newBigDecimal(Double.toString(0.02));BigDecimalamount2=newBigDecimal(Double.toString(0.03));System.out.println(amount2.subtract(amount1));我们可以使用Double.toString方法,将double类型的小数进行转换,保证精度不丢失。其实还有更好的方法:BigDecimalamount1=BigDecimal.valueOf(0.02);BigDecimalamount2=BigDecimal.valueOf(0.03);System.out.println(amount2.subtract(amount1));使用BigDecimal.valueOf方法初始化BigDecimal类型的参数也可以保证精度不丢失。在新版阿里巴巴开发手册中,也推荐使用该方法创建BigDecimal参数。4.Java8filter的坑对于Java8中Stream的用法,想必大家都不陌生。通过对集合的Stream操作,我们可以实现:遍历集合、过滤数据、排序、判断、转换集合等,N多功能。这里我们重点介绍数据过滤。在Java8之前,我们一般会这样过滤数据:publicList
