java缺陷_Java编程常见缺陷汇总(一)

  • Post author:
  • Post category:java


[案例1】

1 public booleanequalNode(JudgeNode a, JudgeNode b) {2 return a.getId() ==b.getId();3 }

【点评】

应在JudgeNode类里定义equals()方法(估计刚从面向过程语言”转行”过来…)。

【案例2】

1 publicString[] getMsg() {2 List msgList = new ArrayList(2);3 msgList.add(linkStatus.get(AC)); msgList.add(getLinkMsg());4 String[] result = newString[msgList.size()];5 msgList.toArray(result);6 returnresult;7 }

【点评】

可简化为return new String[]{linkStatus.get(AC), getLinkMsg()};

【案例3】

1 private boolean bInteger(Object[][] values, inti) { //开发环境真实代码2 boolean b = false;3 try{4 b = values[0][i] instanceofInteger;5 } catch(Exception e) {6 logger.error(“values[0][i] instanceof Integer error:”+e);7 b = false;8 }9 returnb;10 }

【点评】

1. instanceof为运算符,不会抛出空指针等异常;从上下文得知,values[0][i]也不会出现越界等异常。

因此,通过instanceof判断类型时无须捕获异常。进一步,bInteger()方法毫无意义,完全可以在调用处直接用instanceof判断。

2. 使用slf4j日志组件时,logger.error(与log.warn)接受Throwable参数,以打印异常名和详细的堆栈信息(可能内部调用e.printStackTrace())。

但书写打印语句时,需要注意格式。例如:

1 logger.error(“Best print: “, e);2 logger.error(“Good print: {}”, e); //a.

3 logger.error(“Bad print: ” + e); //b. 或 + e.toString()

4 logger.error(“Bad print: ” + e.getMessage()); //c. 或: {}”, e.getMessage())

a句仍可打印异常名和堆栈信息,但多输出一对花括号”{}”;b句仅打印异常名;c句打印异常消息字符串。以空指针异常(Runtime异常)为例,b句打印”java.lang.NullPointerException”,c句打印”null”(过于简陋)。

【案例4】

1 publicDBBatch(int count) throwsSQLException {2 con =ConnnectionManager.getConnection(DBConsts.DB_SOURCE_NAME);3 preparedStatement =con.prepareStatement(DBConsts.SQL);4 result =preparedStatement.executeQuery();5 this.count =count;6 }7 public void close() { //close result\statement\connection

8 }

【点评】

应避免在构造函数里申请资源。若构造函数DBBatch(int count)里preparedStatement.executeQuery()发生异常,上面的preparedStatement、con将无法关闭。此时,外部获取不到DBBatch对象引用,也就无法调用DBBatch.close()方法,导致资源泄露。

【案例5】

1 private static String listToString(ListcolumnList) {2 StringBuilder colName = new StringBuilder(1000); //1

3 for(String s : columnList) {4 colName.append(s + “,”);5 }6 colName.deleteCharAt(colName.length() – 1); //2

7 returncolName.toString();8 }

【点评】

1. StringBuffer的初始容量为16个字节。若待处理的数据远大于16个字节,最好在创建StringBuffer时给出适当的初始容量,以减少不必要的扩容操作(容量不足时会通过Arrays.copyOf创建2倍容量的新数组来装载老数据)。但此处new StringBuilder(1000)初始化指定的长度显然过大,会造成空间浪费。

StringBuilder不同创建方式的初始容量如下:

1) StringBuilder()       // 默认分配16个字节的空间

2) StringBuilder(int size)   // 默认分配size个字节的空间

3) StringBuilder(String str)  // 默认分配16个字节 + str.length()个字节空间

4) StringBuilder(String str)  // 默认分配16个字节 + str.length()个字节空间

5) StringBuilder(CharSequence charSeq) // 默认分配16个字节 + charSeq.length()个字节空间

优选方式3,不建议方式4、5(进行字符串拼接时会面临再次扩容的问题)。

2. columnList为空集合时,例如List columnList = Collections.emptyList(),会抛出下标越界异常(StringIndexOutOfBoundsException)。

3. 可用String.join(“,”, columnList)代替。若期望”[a, b, c, d]”的输出格式,直接用columnList.toString()即可。

【案例6】

1 private static boolean checkName1(String name) { //循环10000000次,耗时350ms

2 List nameList = new ArrayList();3 nameList.add(“LiLei”); nameList.add(“HaMeimei”); nameList.add(“HeDan”);4 nameList.add(“Jame”); nameList.add(“Lucy”); nameList.add(“Beth”);5 if(nameList.contains(name)) {6 return true;7 }8 return false;9 }10

11 private static boolean checkName2(String name) { //循环10000000次,耗时1729ms

12 Set nameSet = new HashSet() {13 { add(“LiLei”); add(“HaMeimei”); add(“HeDan”);14 add(“Jame”); add(“Lucy”); add(“Beth”);15 }};16 returnnameSet.contains(name);17 }18

19 private static final List NAME_LIST = new ArrayList() {20 { add(“LiLei”); add(“HaMeimei”); add(“HeDan”);21 add(“Jame”); add(“Lucy”); add(“Beth”);22 }};23 private static boolean checkName3(String name) { //循环10000000次,耗时110ms

24 returnNAME_LIST.contains(name);25 }26

27 private static final Set NAME_SET = new HashSet() {28 { add(“LiLei”); add(“HaMeimei”); add(“HeDan”);29 add(“Jame”); add(“Lucy”); add(“Beth”);30 }};31 private static boolean checkName4(String name) { //循环10000000次,耗时52ms

32 returnNAME_SET.contains(name);33 }34

35 private static boolean checkName5(String name) { //循环10000000次,耗时46ms

36 return “LiLei”.equals(name) || “HaMeimei”.equals(name) || “HeDan”.equals(name) ||

37 “Jame”.equals(name) || “Lucy”.equals(name) || “Beth”.equals(name);38 }

【点评】

1. 不要使用”if(expression) return true; else return false;”的写法,应改为”return expression;”。

2. List的contains()方法内部调用indexOf()遍历查找整个数组,效率较低。若涉及大规模数据操作,应选用Map等容器。

3. 本案例采用《Java字符串连接的多种实现方法及效率对比》中的计时方法,统计checkName1~5()的执行效率。结果一目了然~

【案例7】

1 Map apMap =constructApMap();2 for(String apId: apMap.keySet()) {3 String apType =apMap.get(apId);4 … …

【点评】

建议使用apMap.entrySet遍历Map,而不是keySet+get(实际上遍历两次)。虽然Map get方法”很快”,但也不能浪费!

entrySet简化语法(Java 8函数式编程):

1 Map map = new HashMap<>();2 map.forEach((key, value)->{3 … …4 });

【案例8】

1 private double[] devanningArray(Listorigin) {2 double[] target = new double[origin.size()];3 for(int i = 0; i < origin.size(); ++i) {4 target[i] =origin.get(i);5 … …

【点评】

ArrayList随机访问,时间复杂度O(1);LinkedList随机访问,时间复杂度O(n)。

建议:1) 优先for-each,即for(e:list){};2) 若必须随机访问,建议从接口契约上约束,如fun(ArrayList array){}。

附Java容器类的特征概述:

ArrayList封装数组,适用于“读多写少”的场景;

LinkedList双向链表,适用于“写多”的场景,随机访问效率低;

Vector、Stack鸡肋

HashMap、HashTable关联容器,HashTable同步

TreeMap红黑树实现,有序:按逻辑大小排序

LinkedHashMap有序:按插入顺序排序

HashSet、TreeSet、LinkedHashSet封装对应的Map

ConcurrentHashMap

ConcurrentLinkedQueue、ConcurrentLinkedDueue无锁队列,慎用

ArrayBlockingQueue、LinkedBlockingQueue 阻塞队列

CopyOnWriteArrayList、CopyOnWriteArraySet 慎用

工具、封装类:

Arrays.asList、Collections.unmodifiableList\Set\Map\Collection

Collections.synchronizedList\Set\Map\Collection



版权声明:本文为weixin_27872611原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。