当前位置: 首页 > 科技观察

注意-Java程序员容易犯的Top10个低级错误

时间:2023-03-17 16:33:13 科技观察

本文梳理了Java开发者在编码过程中容易忽视或经常犯错误的地方,总结出十个常见的低级错误供大家学习。1、不能用“==”来比较两个字符串的内容是否相等。2.在列表上做foreach循环时,在循环代码中不能修改列表的结构。3、日志与实际情况不符;捕获到异常后,日志中不记录异常堆栈。4.恶魔号。5.空指针异常。6、数组下标越界。7.将字符串转换为数字时不会捕获NumberFormatException。8、文件、IO、数据库等资源在执行操作后没有及时正确的释放。9.编码时没有考虑循环体的性能,循环体包含不必要的重复逻辑。10.数据类没有重载toString()方法。1不能用“==”来比较两个字符串的内容是否相等。在解释两个字符串是否相等时,如果使用“==”,当两个字符串在内存中不指向同一个地址时,即使两个字符串内容相同,使用“==”比较结果也是假的。因此,在比较两个字符串是否相等时,必须使用“equals”方法。例子下面是一个字符串比较的例子:publicclassTest{publicstaticvoidmain(String[]args){Stringa=newString("a");Stringa2="a";if(a==a2){System.out.println("a==a2returntrue.");}else{System.out.println("a==a2returnfalse.");}if(a.equals(a2)){System.out.println("a.equals(a2)returntrue.");}else{System.out.println("a.equals(a2)returnfalse.");}}}最终输出结果为:a==a2returnfalse.a.equals(a2)returntrue.2cannotforeach循环中修改list结构的解释在jdk1.5及以上的foreach循环的写法中,循环代码中不能修改循环list的结构,即进行add、remove等操作在名单上。如果执行了这些操作,必须立即退出循环,否则会抛出异常。示例publicclassTest{publicstaticvoidmain(String[]args){Listlist=newArrayList();Personp1=newPerson("张三",23);Personp2=newPerson("李四",26);Personp3=newPerson("王舞",34);Personp4=newPerson("六二",15);Personp5=newPerson("朱六",40);list.add(p1);list.add(p2);list.add(p3);list.add(p4);list.add(p5);for(Personp:list){if("王舞".equals(p.getName())){list.remove(p);//此时无法删除该对象。}elseif("Lisi".equals(p.getName())){list.remove(p);//此时无法删除对象。}}System.out.println(list.size());}}classPerson{privateStringname;privateintage;publicPerson(Stringname,intage){this.name=name;this.age=age;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}publicintgetAge(){returnage;}publicvoidsetAge(intage){this.age=age;}}解决了上面代码红色部分的问题,可以把对象通过循环,然后循环结束然后删除它。Listlist=newArrayList();Personp1=newPerson(newString("张三"),23);Personp2=newPerson(newString("李四"),26);Personp3=newPerson(newString("王五"),34);Personp4=newPerson(newString("六二"),15);Personp5=newPerson(newString("朱六"),40);list.add(p1);list.add(p2);list.add(p3);list.add(p4);list.add(p5);Personwangwu=null;Personlisi=null;for(Personp:list){if("王舞".equals(p.getName())){wangwu=p;}elseif("Lisi".equals(p.getName())){lisi=p;}}list.remove(wangwu);list.remove(lisi);3日志规范解读日志是定位问题最重要的依据。业务流程缺少必要的日志,会给定位问题带来很多麻烦,甚至可能导致问题完全无法定位。异常发生后,日志中必须记录ERROR及以上级别的异常栈,否则异常栈会丢失,无法确认异常位置。不必每次捕获异常都记录异常日志,这样可能会导致异常被重复记录,影响问题定位。但是异常发生后,它的异常栈至少要被记录一次。就像评论一样,更多的日志并不总是更好。无用的冗余日志不仅不能帮助定位问题,还会干扰问题的定位。错误的日志会误导问题,必须排除。Example下面的例子虽然打印了很多日志,但基本都是无用的日志,很难帮助定位问题。甚至还有错误的日志干扰定位问题:()!=ProductFieldEnum.ProductStatus.RELEASE){thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}log.debug("checktariff");BooleanResultresult=checkTariff(product.getTariffs());if(!result.getResult()){thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}log.debug("beforeaddproduct");ProductServiceprodSrv=(ProductService)ServiceLocator.findService(ProductService.class);try{prodSrv.addProduct(product);}catch(BMEExceptione){//not记录异常堆栈,无法定位问题根源}log.debug("afteraddproduct");log.debug("exitmethod:updateProduct()");//错误日志}和下面的示例日志都不是打印的很多,但是都是关键信息,可以很好的定位问题:publicvoidsaveProduct2(ProductServiceStruct产品){if(product.getProduct().getProductStatus()!=ProductFieldEnum.ProductStatus.RELEASE){log.error("productstatus"+product.getProduct().getProductStatus()+"error,expect"+ProductFieldEnum.ProductStatus.RELEASE);thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}BooleanResultresult=checkTariff(product.getTariffs());if(!result.getResult()){log.error("checkproducttarifferror"+result.getResultCode()+":"+result.getResultDesc());thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}ProductServiceprodSrv=(ProductService)ServiceLocator.findService(ProductService.class);try{prodSrv.addProduct(product);}catch(BMEExceptione){log.error("addproducterror",e);thrownewPMSException(PMSErrorCode.Product.ADD_ERROR,e);}}4恶魔号解读代码中使用魔鬼数字(没有具体含义的数字、字符串等)会使代码难以理解,数字应定义为名称有意义的常量,将数字定义为常量的最终目的是使代码更容易理解,所以并不是只要把一个数定义为常量,这不是一个魔鬼数字。如果常量的名字没有意义,不能帮助理解代码,那也是一个魔鬼数字。在某些特殊情况下,将数字定义为常量会使代码更难理解,此时不应强行将数字定义为常量。例子publicvoidaddProduct(ProductServiceStructproduct){//魔鬼号,无法理解3具体代表产品什么状态if(product.getProduct().getProductStatus()!=3){thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}BooleanResultresult=checkTariff(product.getTariffs());if(!result.getResult()){thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}}/***产品未激活*/privatestaticfinalintUNACTIVATED=0;/***产品已激活已激活*/privatestaticfinalintACTIVATED=1;publicvoidaddProduct2(ProductServiceStructproduct){if(product.getProduct().getProductStatus()!=ACTIVATED){thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}BooleanResultresult=checkTariff(product.get;Tariffs())!result.getResult()){thrownewPMSException(PMSErrorCode.Product.ADD_ERROR);}}5空指针异常解读空指针异常是编码过程中最常见的异常。在使用一个对象的时候,如果这个对象可能为空,而使用次要对象可能会导致空指针异常,所以在使用这个对象之前需要先判断对象是否为空。在对常量和变量进行相等判断时,建议将常量定义为Java对象封装类型(如将int类型常量定义为Integer类型),这样比较时常量可以放在左边,equals方法可以要求比较。消除不必要的判断。示例publicclassNullPointer{staticfinalIntegerRESULT_CODE_OK=0;staticfinalResultRESULT_OK=newResult();publicvoidprintResult(IntegerresultCode){Resultresult=getResult(resultCode);//result可能为null,导致空指针异常if(result.isValid()){print(result));}}publicResultgetResult(IntegerresultCode){//即使resultCode为null,也能正确执行,减少额外的空判断语句..}}6下标越界解释当访问数组、List等容器中的元素时,首先要检查下标是否越界,防止下标越界异常的发生。例子publicclassArrayOver{publicvoidcheckArray(Stringname){//得到一个数组对象String[]cIds=ContentService.queryByName(name);if(null!=cIds){//只考虑cids可能为null,但是cids完全是它可能是长度为0的数组,所以cIds[0]可能在数组下标越界Stringcid=cIds[0];cid.toCharArray();}}}7字符串转数字的解释调用Java方法来字符串转数字,如果字符串格式不合法,会抛出运行时异常NumberFormatException。示例错误示例:publicIntegergetInteger1(Stringnumber){//如果数字格式不合法,将抛出NumberFormatExceptionreturnInteger.valueOf(number);}正确的处理方法如下:publicIntegergetInteger2(Stringnumber){try{returnInteger.valueOf(number);}catch(NumberFormatExceptione){...//记录日志异常信息returnnull;}}注意:一定要在捕捉到异常后记录日志。8资源释放的解释当使用不自动释放的资源时,如文件、IO流、数据库连接等,使用后应立即关闭。关闭资源的代码应该在try...catch...finally的finally之内执行,否则资源可能无法释放。示例错误情况如下:publicvoidwriteProduct1(ProductServiceStructproduct){try{FileWriterfileWriter=newFileWriter("");fileWriter.append(product.toString());//如果append()抛出异常,close()方法不会被执行,关闭IO流的正确方式如下:(IOExceptione){...//记录log}finally{//不管之前是否发生异常,finally中的代码都会被执行if(fileWriter!=null){try{fileWriter.close();}catch(IOExceptione){...//记录日志}}}}注意:捕获异常后一定要记录日志。9循环体性能解读循环体是软件中最容易引起性能问题的地方,因此在对循环体进行编码时必须考虑性能问题。循环体中重复使用且不会发生变化的资源(如变量、文件对象、数据库连接等)应在循环体开始前构造和初始化,避免在循环体中重复构造初始化造成CPU资源浪费循环体。除非业务场景需要,否则避免在循环体中构造try...catch块,因为每次进入或退出try...catch块都会消耗一定的CPU资源,可以将try...catch块在循环体之外节省大量的执行时间。示例publicvoidaddProducts(ListprodList){for(ProductServiceStructproduct:prodList){//每次循环都会重新获取prodSrv,造成不必要的资源消耗ProductServiceprodSrv=(ProductService)ServiceLocator.findService(ProductService.class);//避免在循环体中try...catch,将其放在循环体外可以节省执行时间body添加字符串时,必须使用StringBuffer类。10数据类重载toString()方法解读如果数据类不重载toString()方法,记录日志时将无法记录数据对象的属性值,给定位问题带来困难.示例LogFactory.getDebugLog(BusinessProcess.class);publicvoiddoBusiness(MdspProductExtprodExt){try{...}catch(PMSExceptione){//MdspProductExt没有重载toString()方法,对象中属性的值无法记录到日志中,只能记录对象地址日志。error("errorwhileprocessprodExt"+prodExt);}}}这10个低级错误你犯过吗?