当前位置: 首页 > 后端技术 > Java

Java程序员线上吃了不少苦头,想必大家每年都遇到过

时间:2023-04-01 16:43:23 Java

的线上问题,尤其是今年。记下几个惨痛的上网陷阱记录,希望大家能够以史为鉴。1.包装类型自动拆箱导致空指针异常publicintgetId(){Integerid=null;returnid;}如果调用上面的方法会发生什么?id是Integer类型,方法返回值是int类型,会自动拆箱转换。由于id为null,当转换为int类型时,会报NullPointerException。不管是《阿里Java开发手册》、《代码整洁之道》还是《Effective Java》,建议方法返回值类型写成包装类型,类似于Integer。另外,实体类中的属性、接收前端传参类、给前端的响应类都要写成封装类型,避免开箱错误。2.包装类型使用==判断相等,导致判断错误。首先看一段代码的运行结果:publicclassIntegerTest{publicstaticvoidmain(String[]args){Integera=100;整数b=100;整数c=200;整数d=200;System.out.println(a==b);//输出真System.out.println(c==d);//输出false}}很多人会很疑惑,为什么输出的两个结果会不一样呢?当给Integer类型赋值时,会调用Integer.valueOf()方法staticfinalintlow=-128;staticfinalinthigh=127;publicstaticIntegervalueOf(inti){if(i>=IntegerCache.low&&i<=IntegerCache.high)returnIntegerCache.cache[i+(-IntegerCache.low)];returnnewInteger(i);}当值在-128到127之间时,会重用缓存。当不在此范围内时,将创建该对象。而==比较的是内存地址,不同对象的内存地址是不一样的,所以出现了上面的结果。整数覆盖equals()方法:publicbooleanequals(Objectobj){if(objinstanceofInteger){returnvalue==((Integer)obj).intValue();}returnfalse;}使用equals()方法时,比较的是int值是否相等。所以,包装类判断是否相等时,一定不能用==来判断,必须用equals()方法来判断。3、传给Switch的参数为null,导致空指针异常。猜猜下面代码的运行结果:publicclassTest{publicstaticvoidmain(String[]args){Stringname=null;switch(name){case"yideng":System.out.println("一盏灯");休息;默认:System.out.println("默认");}}}是不是觉得会输出default,但是代码会抛出NullPointerException。switch比较两个对象是否相等时,会调用name.hashCode()方法和name.equals()方法,因为name为null,结果抛出NullPointerException。所以在调用switch方法之前,需要判断传入的参数为空。4.创建BigDecimal类型时精度丢失。猜猜下面代码的运行结果:publicclassTest{publicstaticvoidmain(String[]args){BigDecimalbigDecimal=newBigDecimal(0.1);System.out.println(bigDecimal);你以为它会输出0.1,结果输出结果是:0.1000000000000000055511151231257827021181583404541015625什么?这么一大串东西是什么?为什么会这样?原因是当我们使用newBigDecimal(0.1)创建对象时,会调用BigDecimal的构造方法:publicBigDecimal(doubleval){this(val,MathContext.UNLIMITED);}参数0.1被认为是一个double类型,double在计算的时候会把值转成二进制,0.1转成二进制是不可分割的,所以带了很多小数位。当我需要创建一个BigDecimal类型时应该怎么做?可以先将值转换成字符串类型,然后创建一个BigDecimal对象,像这样:BigDecimalbigDecimal=newBigDecimal(String.valueOf(0.1));还有一个问题,BigDecimal是怎么解决精度损失的问题的?答案是BigDecimal会先把值乘以10的整数倍,去掉小数位,转成long类型,再进行运算,最后将运算结果除以10的整数倍。5.分组时,主键重复,导致异常。下面代码的分组能否成功?publicclassSteamTest{staticclassUser{//用户IDprivateIntegerid;//用户名privateStringname;}publicstaticvoidmain(String[]args){Listusers=Arrays.asList(newUser(1,"Tom"),newUser(1,"Tony"),newUser(2,"Jerry"));//用户集合按id分组MapuserMap=users.stream().collect(Collectors.toMap(User::getId,user->user));System.out.println(userMap);}}结果异常,Exceptioninthread"main"java.lang.IllegalStateException:DuplicatekeySteamTest.User(id=1,name=Tom)原因是主键冲突。有两个id=1的数据。按id分组时,程序不知道如何处理。这可以做到publicclassSteamTest{staticclassUser{//用户IDprivateIntegerid;//用户名privateStringname;}publicstaticvoidmain(String[]args){Listusers=Arrays.asList(newUser(1,"Tom"),newUser(1,"Tony"),newUser(2,"Jerry"));//用户集合按id分组,当主键冲突时,取第一个用户MapuserMap=users.stream().collect(Collectors.toMap(User::getId,user->user,(用户1、用户2)->用户1));System.out.println(userMap);//输出{1:{"id":1,"name":"Tom"},2:{"id":2,"name":"Jerry"}}}}6.ArrayList的真假原因下面要添加的异常add()方法可以添加成功吗?公共类测试{publicstaticvoidmain(String[]args){Listlist=Arrays.asList(1,2);列表.添加(3);}}结果是抛出异常,Exceptioninthread"main"java.lang.UnsupportedOperationException抛出不支持该方法的异常,为什么?我们看一下Arrays.asList()方法的源码:publicstaticListasList(T...a){returnnewArrayList<>(a);}返回一个ArrayList,为什么不能添加成功吗?其实这个ArrayList并不是另一个ArrayList,它和我们常用的ArrayList同名。这个ArrayList只是Arrays对象的一个??内部类,内部并没有实现add()方法,所以添加的时候会报错。这明显不是骗局吗?实现了list接口,为什么不实现add()方法呢?其实,作者是有意设计的。除了没有实现add()方法,他也没有实现addAll()、remove()、clear()等修改方法。目的是防止用户在创建后进行修改。这样的收藏有什么用??其实在一些不可变的场景下还是很实用的,比如完成订单状态的收集:Listlist=Arrays.asList("Failure","Cancelled","Completed");这个合集一般是不会改的,在使用过程中是不允许修改的,以免出错。7、总结每一次踩坑,背后至少有一个在线问题的记录。这些总结都是用来换取教训的。不只是你,其他人肯定也遇到过。在以后的发展中如何避免类似问题的发生?从用户的角度,编写详细的单元测试,打印必要的日志,跟踪代码执行结果从创建者的角度,探究框架的架构设计和源代码实现,理解作者的意图。网上那些坑你踩过吗?