[案例1】
1 public boolean equalNode(JudgeNode a, JudgeNode b) { 2 return a.getId() == b.getId(); 3 }
【点评】
应在JudgeNode类里定义equals()方法(估计刚从面向过程语言"转行"过来...)。
【案例2】
1 public String[] getMsg() { 2 List<String> msgList = new ArrayList<String>(2); 3 msgList.add(linkStatus.get(AC)); msgList.add(getLinkMsg()); 4 String[] result = new String[msgList.size()]; 5 msgList.toArray(result); 6 return result; 7 }
【点评】
可简化为return new String[]{linkStatus.get(AC), getLinkMsg()};
【案例3】
1 private boolean bInteger(Object[][] values, int i) { //开发环境真实代码 2 boolean b = false; 3 try { 4 b = values[0][i] instanceof Integer; 5 } catch(Exception e) { 6 logger.error("values[0][i] instanceof Integer error:"+e); 7 b = false; 8 } 9 return b; 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 public DBBatch(int count) throws SQLException { 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(List<String> columnList) { 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 return colName.toString(); 8 }
【点评】
1. new StringBuilder(1000)初始化指定的长度过大,造成空间浪费。
相关资料参见《浅谈JAVA中HashMap、ArrayList、StringBuilder等的扩容机制》。
2. columnList为空集合时,例如List<String> 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<String> nameList = new ArrayList<String>(); 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<String> nameSet = new HashSet<String>() { 13 { add("LiLei"); add("HaMeimei"); add("HeDan"); 14 add("Jame"); add("Lucy"); add("Beth"); 15 }}; 16 return nameSet.contains(name); 17 } 18 19 private static final List<String> NAME_LIST = new ArrayList<String>() { 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 return NAME_LIST.contains(name); 25 } 26 27 private static final Set<String> NAME_SET = new HashSet<String>() { 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 return NAME_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<String, String> apMap = constructApMap(); 2 for(String apId: apMap.keySet()) { 3 String apType = apMap.get(apId); 4 ... ...
【点评】
建议使用apMap.entrySet遍历Map,而不是keySet+get(实际上遍历两次)。虽然Map get方法"很快",但也不能浪费!
相关资料参见《keySet 与entrySet 遍历HashMap性能差别》。
entrySet简化语法(Java 8函数式编程):
1 Map<String, Object> map = new HashMap<>(); 2 map.forEach((key, value)-> { 3 ... ... 4 });
【案例8】
1 private double[] devanningArray(List<Double> origin) { 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