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

Java编程中最容易被忽视的10个常见问题

时间:2023-03-18 12:56:05 科技观察

在Java编码中,我们很容易犯一些错误,忽视一些问题。因此,笔者总结了一些日常编码中遇到的经典情况,并写在了一起。讨论。1、同名纠结现象。许多类具有相同的名称(例如:常见于异常、常量、日志等),这导致在导入时有时会出现模棱两可的错误。有时这种错误非常隐蔽。因为同名的类往往功能相似,所以IDE不会提示warn。写完代码,扫一扫导入部分,看看有没有不熟悉的地方。替换为正确的import后,注意注释是否也进行了相应的修改。启示录命名要尽量避免重名,尤其要避免和JDK中的类重名,否则容易导入错误,而且同名类较多,查找时更费时间区分.2.想当然的API现象有时候在调用API的时候,会想当然的直接通过名字信心满满的调用,导致一些让人吃惊的错误:例1:标志是否为真?布尔标志=Boolean.getBoolean("true");可能永远是假的。例2:这是去年的今天吗(今年是2012年,不考虑闰年)?结果还是2012年:Calendarcalendar=GregorianCalendar.getInstance();calendar.roll(Calendar.DAY_OF_YEAR,-365);以下是去年的:calendar.add(Calendar.DAY_OF_YEAR,-365);解决办法就是问自己几个问题,这个方法我熟悉吗?有类似的API吗?有什么不同?就示例1而言,区别如下:Boolean.valueOf(b)VSBoolean.parseBoolean(b)VSBoolean.getBoolean(b);名称应该更详细,注释应该更清楚。为了测试,使用一些API是理所当然的。如果时间有限,请使用您最熟悉的API。3.有时溢出并不困难。有时溢出并不难,尽管它不常被重现:示例1:longx=Integer.MAX_VALUE+1;System.out.println(x);什么是x?原来是-2147483648,加1后还在long范围内。时间计算中经常出现类似的东西:数1×数2×数3...例2:在检查是否为正数的参数校验中,为了避免重载,选择了参数数,所以结果下面代码中的小于0,也是因为Overflow导致:Numberi=Long.MAX_VALUE;System.out.println(i.intValue()>0);解决让最后一个操作数为long类型,比如加L或l(不推荐小写字母l,因为它和数字1太像了);当你不确定时,使用重载,即使你使用doubleValue(),当参数是一个BigDecimal参数时,也不能解决问题。启示对数字的使用敏感:当涉及到数字计算时,需要考虑溢出;涉及除法时,需要考虑被除数为0;容不下的话可以考虑BigDecimal之类的。#p#4。日志在哪里?现象有时候感觉日志已经打完了,为什么找不到呢?示例1:没有堆栈跟踪!}catch(Exceptionex){log.error(ex);}示例2:找不到日志!}catch(ConfigurationExceptione){e.printStackTrace();}解决方法换成log.error(ex.getMessage(),ex);将其更改为普通的log4j而不是System.out。EnlightenmentAPI定义应该防止人们犯错误。如果再加上重载的log.error(Exception),产品代码自然不会出现错误。使用的一些方法应该考虑是否有效。使用e.printStackTrace()考虑终端(控制台在哪里)。5.忘记Volatile现象在DCL模式下,总是忘记加一个Volatile。私有静态CacheImpl实例;//失去volatilepublicstaticCacheImplgetInstance(){if(instance==null){synchronized(CacheImpl.class){if(instance==null){instance=newCacheImpl();}}}returninstance;}解决方案没有疑问,我们加一个,synchronized锁住一段代码(整个方法或者某个代码块),保证了这“块”代码的可见性和原子性,但是instance==null***在第一次判断的时候已经不在范围内了。因此可以读取过期的空值。启示我们总觉得有些小概率事件是很难发生的,比如某个时刻有可能并发,有可能抛出某个异常,所以我们不去控制,但是如果有可能,就遵循以前的“**实践”写代码。至少不需要过多解释为什么换一种方式。6.互不影响。当释放多个IO资源时,会抛出IOException,为了方便可以这样写:publicstaticvoidinputToOutput(InputStreamis,OutputStreamos,booleanisClose)throwsIOException{BufferedInputStreambis=newBufferedInputStream(is,1024);BufferedOutputStreambos=newBufferedOutputStream(os,1024);……如果(isClose){bos.close();二.关闭();}}假设bos关闭失败,bis还能关闭吗?当然不是!解决办法是,虽然抛出的是同一个异常,但是最好分别捕获每一个。否则前者失败,后者就没有释放资源的机会。揭示出代码/模块之间可能存在依赖关系,必须充分识别相互依赖关系。#p#7。用断言代替参数校验现象如题中所述,是防御性编程常用的一种方式:断言,写在产品代码中进行参数校验等。例如:privatevoidsend(ListeventList){asserteventList!=null;}解决办法是换成普通的统一参数校验方式。因为断言默认是关闭的,所以能不能用完全取决于配置。如果使用默认配置,eventList!=null的结果还没有生效,也是徒劳的。启示有时,代码是否有效不仅取决于用例,还取决于配置,例如是否启用断言、日志级别等,有用的编码应结合真实环境。8、用户的认知负荷有时很重。我们先来对比一下三组样例,看看哪个看起来更流畅?示例1:publicvoidcaller(inta,Stringb,floatc,Stringd){methodOne(d,z,b);methodTwo(b,c,d);}publicvoidmethodOne(Stringd,floatz,Stringb)publicvoidmethodTwo(Stringb,floatc,Stringd)示例2:publicbooleanremove(Stringkey,longtimeout){Futurefuture=memcachedClient.delete(key);publicbooleandelete(Stringkey,longtimeout){Futurefuture=memcachedClient.delete(key);例3:publicstaticStringgetDigest(StringfilePath,DigestAlgorithmalgorithm)publicstaticStringgetDigest(StringfilePath,DigestAlgorithmdigestAlgorithm)解决保持参数传递顺序的问题;remove变成了delete,看起来有点突兀,统一表达比较好;保留表达式,更少的缩写会使它看起来更流畅。启示在编码过程中,参数的顺序和命名都应尽可能统一,这样对用户的认知负担最小,并且用户不应该容易出错或混淆。例如,使用枚举而不是字符串,这样用户就不会混淆要传递什么字符串,等等。9.忽略记录日志的时机和级别的例子有两个:例子一:是否应该记录日志?catch(SocketExceptione){LOG.error("服务器错误",e);thrownewConnectionException(e.getMessage(),e);}例2:记录什么级别的日志?用户登录系统中,每次登录失败:LOG.warn("Failedtologinby"+username+");解决清除日志记录的问题:当遇到需要重新抛出的异常时,如果大家先登录的话使用post-throw的方式处理,那么一个error会记录太多的log,所以不建议这样做;但是如果重新抛出的exception没有完整的trace(即cause),那么最好记录下来,如果恶意登录,系统中WARN过多,会让管理员误认为是代码错误,可以反馈给用户错误,但不要记录用户的错误行为,除非你想达到控制的目的。revelationlog可以改还是不改?记录到什么级别?怎么记录?这些都是问题,要根据具体情况考虑:是是用户行为错误还是代码错误?记录的日志能不能给别人看?造成太多干扰?提供有用信息,快速定位问题。cache=newLRULinkedHashMap(maxCapacity);解决初始容量的影响有多大?以LinkedHashMap为例,初始容量默认不设置为16,如果超过16×LOAD_FACTOR,会resize(2*table.length)并扩容两次:使用Entry[]newTable=newEntry[newCapacity];transfer(newTable),也就是复制整个数组,所以对于一个大容量的缓存来说,可想而知需要将数组从16复制多少次到很大。初始容量设置大了,resize自然会变小,但是你可能会担心初始容量设置大了,没有Cache内容还是会占用太多空间。其实大家可以参考下表进行简单的计算。一开始并没有缓存内容,每个对象只是一个4字节的引用。参考字段的内存(每个4个字节);原始字段的内存Java类型所需字节数boolean1bytechar2shortint4floatlong8double启说明不仅是map,stringBuffer也有扩容的过程。如果数据量很大,初始容量也不容忽视。可以考虑设置一下,不然在Java编程中不仅频繁resize而且容易浪费容量,除了上面枚举中容易忽略的一些问题,在日常实践中仍然存在。相信通过不断的总结和努力,我们的程序一定可以完美的呈现给读者。

猜你喜欢