(五) 集合處理
1.【強制】關於hashCode和equals的處理,遵循如下規則:
1) 只要重寫equals,就必須重寫hashCode。
2) 因爲Set存儲的是不重複的對象,依據hashCode和equals進行判斷,所以Set存儲的對象必須重寫這兩個方法。
3) 如果自定義對象作爲Map的鍵,那麼必須重寫hashCode和equals。
說明:String重寫了hashCode和equals方法,所以我們可以非常愉快地使用String對象作爲key來使用。
2. 【強制】ArrayList的subList結果不可強轉成ArrayList,否則會拋出ClassCastException異常:java.util.RandomAccessSubList cannot be cast to java.util.ArrayList ;
說明:subList 返回的是 ArrayList 的內部類 SubList,並不是 ArrayList ,而是 ArrayList 的一個視圖,對於SubList子列表的所有操作最終會反映到原列表上。
返回原來list的從[fromIndex, toIndex)之間這一部分的視圖,之所以說是視圖,是因爲實際上,返回的list是靠原來的list支持的。如:List<Character>集合裏有a,b,c,d,e這幾個字符,subList(1,3)就會返回一個包含b,c字符subList
所以,你對原來的list和返回的list做的“非結構性修改”(non-structural changes),都會影響到彼此對方。
所謂的“非結構性修改”,是指不涉及到list的大小改變的修改。相反,結構性修改,指改變了list大小的修改。
3. 【強制】在subList場景中,高度注意對原集合元素個數的修改,會導致子列表的遍歷、增加、刪除均產生ConcurrentModificationException 異常。
說明:使用toArray帶參方法,入參分配的數組空間不夠大時,toArray方法內部將重新分配內存空間,並返回新數組地址;如果數組元素大於實際所需,下標爲[ list.size() ]的數組元素將被置爲null,其它數組元素保持原值,因此最好將方法入參數組大小定義與集合元素個數一致。
正例:
List<String> list = new ArrayList<String>(2);
list.add("guan"); list.add("bao");
String[] array = new String[list.size()];
array = list.toArray(array);
反例:直接使用toArray無參方法存在問題,此方法返回值只能是Object[]類,若強轉其它類型數組將出現ClassCastException錯誤。
5. 【強制】使用工具類Arrays.asList()把數組轉換成集合時,不能使用其修改集合相關的方法,它的add/remove/clear方法會拋出UnsupportedOperationException異常。
說明:asList的返回對象是一個Arrays內部類,並沒有實現集合的修改方法。Arrays.asList體現的是適配器模式,只是轉換接口,後臺的數據仍是數組。
String[] str = new String[] { "you", "wu" };
List list = Arrays.asList(str);
第一種情況:list.add(“c”); 運行時異常。
第二種情況:str[0]= “gujin”; 那麼list.get(0)也會隨之修改。// 變成 “gujin”
6. 【強制】泛型通配符<? extends T>來接收返回的數據,此寫法的泛型集合不能使用add方法,而<? super T>不能使用get方法,作爲接口調用賦值時易出錯。
說明:擴展說一下PECS(Producer Extends Consumer Super)原則:第一、頻繁往外讀取內容的,適合用<? extends T>。第二、經常往裏插入的,適合用<? super T>。
<? extends T>:是指 “上界通配符(Upper Bounds Wildcards)”
<? extends Fruit>會使往盤子裏放東西的set( )方法失效。但取東西get( )方法還有效
<? super T>:是指 “下界通配符(Lower Bounds Wildcards)”
使用下界<? super Fruit>會使從盤子裏取東西的get( )方法部分失效,只能存放到Object對象裏。
7. 【強制】不要在foreach循環裏進行元素的remove/add操作。remove元素請使用Iterator方式,如果併發操作,需要對Iterator對象加鎖。
正例:
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (刪除元素的條件) {
iterator.remove();
}
}
反例:
List<String> list = new ArrayList<String>();
list.add("1");
list.add("2"); //換成1 list裏有1個1
for (String item : list) {
if ("1".equals(item)) { //換成2報錯
list.remove(item);
}
}
Iterator用於遍歷集合中的元素,適用於不知道集合內部結構的情況。用戶不再與集合類交互,而是與Iterator交互,其清楚知道集合類的內部狀態,通過控制iterator達到遍歷集合的目的。
hasnext()是判斷語句,表示集合中元素是否遍歷完
next()相當於指針,返回Object對象,需要強轉爲需要的類型
remove()刪除集合中元素,不常用。
8. 【強制】在JDK7版本以上,Comparator要滿足自反性,傳遞性,對稱性,不然Arrays.sort,Collections.sort會報IllegalArgumentException異常。
1) 自反性:x,y的比較結果和y,x的比較結果相反。
2) 傳遞性:x>y,y>z,則x>z。
3) 對稱性:x=y,則x,z比較結果和y,z比較結果相同。
反例:下例中沒有處理相等的情況,實際使用中可能會出現異常:
new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getId() > o2.getId() ? 1 : -1; //常用return o1.getId() - o2.getId();
}
};
Comparator可以認爲是是一個外比較器,個人認爲有兩種情況可以使用實現Comparator接口的方式:
1、想對兩個類進行比較,那麼可以實現Comparator接口,自定義一個比較器,寫比較算法
2、實現Comparator的類是在外部進行比較的,不需要對實現類有任何修改,就是一種典型的策略模式(意圖:定義一系列的算法,把它們一個個封裝起來, 並且使它們可相互替換)。
Comparator接口裏面有一個compare方法,方法有兩個參數T o1和T o2,是泛型的表示方式,分別表示待比較的兩個對象,方法返回值和Comparable接口一樣是int,有三種情況:
1、o1大於o2,返回正整數
2、o1等於o2,返回0
3、o1小於o3,返回負整數
9. 【推薦】集合初始化時,儘量指定集合初始值大小。
說明:HashMap使用HashMap(int initialCapacity) 初始化,
正例:initialCapacity =(需要存儲的元素個數 / 負載因子) + 1。注意負載因子(即loader factor)默認爲0.75,如果暫時無法確定初始值大小,請設置爲16(即默認值)。
反例:HashMap需要放置1024個元素,由於沒有設置容量初始大小,隨着元素不斷增加,容量7次被迫擴大,resize需要重建hash表,嚴重影響性能。
缺省的構造器設定最初的容量爲11,負載係數是0.75,所以臨界值是8。當第九條記錄被添加到表中時,就重新調整哈希表,使其有23個列表,新的臨界值將是17(23*0.75的整數部分)。
10.【推薦】使用entrySet遍歷Map類集合KV,而不是keySet方式進行遍歷。
說明:keySet其實是遍歷了2次,一次是轉爲Iterator對象,另一次是從hashMap中取出key所對應的value。而entrySet只是遍歷了一次就把key和value都放到了entry中,效率更高。如果是JDK8,使用Map.foreach方法。
正例:values()返回的是V值集合,是一個list集合對象;keySet()返回的是K值集合,是一個Set集合對象;entrySet()返回的是K-V值組合集合。
1.entrySet實現了Set接口,裏面存放的是鍵值對。一個K對應一個V。
Set<Map.Entry<String, String>> entryseSet=map.entrySet();
for (Map.Entry<String, String> entry:entryseSet) {
System.out.println(entry.getKey()+","+entry.getValue());
}
通過getKey()得到K,getValue得到V。
2.Set<String> set = map.keySet();
for (String s:set) {
System.out.println(s+","+map.get(s));
}
裏面存的是Map的K。
11.【推薦】高度注意Map類集合K/V能不能存儲null值的情況,如下表格:
集合類 | Key | Value | Super | 說明 |
Hashtable | 不允許爲null | 不允許爲null | Dictionary | 線程安全 |
ConcurrentHashMap | 不允許爲null | 不允許爲null | AbstractMap | 鎖分段技術(JDK8:CAS) |
TreeMap | 不允許爲null | 允許爲null | AbstractMap | 線程不安全 |
HashMap | 允許爲null | 允許爲null | AbstractMap | 線程不安全 |
反例:由於HashMap的干擾,很多人認爲ConcurrentHashMap是可以置入null值,而事實上,存儲null值時會拋出NPE異常。
ConcurrentHashMap通常只被看做併發效率更高的Map,用來替換其他線程安全的Map容器,比如Hashtable、
12.【參考】合理利用好集合的有序性(sort)和穩定性(order),避免集合的無序性(unsort)和不穩定性(unorder)帶來的負面影響。
說明:穩定性指集合每次遍歷的元素次序是一定的。有序性是指遍歷的結果是按某種比較規則依次排列的。如:ArrayList是order/unsort;HashMap是unorder/unsort;TreeSet是order/sort。
13.【參考】利用Set元素唯一的特性,可以快速對另一個集合進行去重操作,避免使用List的contains方法進行遍歷去重操作。
(六) 併發處理
1. 【強制】獲取單例對象需要保證線程安全,其中的方法也要保證線程安全。
說明:資源驅動類、工具類、單例工廠類都需要注意。
2. 【強制】創建線程或線程池時請指定有意義的線程名稱,方便出錯時回溯。
正例:
public class TimerTaskThread extends Thread {
public TimerTaskThread() {
super.setName("TimerTaskThread");
...
}
3. 【強制】線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。
說明:使用線程池的好處是減少在創建和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
4. 【強制】線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。
說明:Executors返回的線程池對象的弊端如下:
1)FixedThreadPool和SingleThreadPool:允許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。
2)CachedThreadPool和ScheduledThreadPool:允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。
1.減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務。
2.可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最後死機)。
Java裏面線程池的頂級接口是Executor,但是嚴格意義上講Executor並不是一個線程池,而只是一個執行線程的工具。真正的線程池接口是ExecutorService。比較重要的幾個類:
ExecutorService | 真正的線程池接口。 |
ScheduledExecutorService | 能和Timer/TimerTask類似,解決那些需要任務重複執行的問題。 |
ThreadPoolExecutor | ExecutorService的默認實現。 |
ScheduledThreadPoolExecutor | 繼承ThreadPoolExecutor的ScheduledExecutorService接口實現,週期性任務調度的類實現。 |
1. newSingleThreadExecutor
創建一個單線程的線程池。這個線程池只有一個線程在工作,也就是相當於單線程串行執行所有任務。如果這個唯一的線程因爲異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。2.newFixedThreadPool
創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。線程池的大小一旦達到最大值就會保持不變,如果某個線程因爲執行異常而結束,那麼線程池會補充一個新線程。
3. newCachedThreadPool
創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閒(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。
4.newScheduledThreadPool
創建一個大小無限的線程池。此線程池支持定時以及週期性執行任務的需求。
5.【強制】SimpleDateFormat 是線程不安全的類,一般不要定義爲static變量,如果定義爲static,必須加鎖,或者使用DateUtils工具類。
正例:注意線程安全,使用DateUtils。亦推薦如下處理:
private static final ThreadLocal<DateFormat> df = new ThreadLocal<DateFormat>() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
說明:如果是JDK8的應用,可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方給出的解釋:simple beautiful strong immutable thread-safe。
6. 【強制】高併發時,同步調用應該去考量鎖的性能損耗。能用無鎖數據結構,就不要用鎖;能鎖區塊,就不要鎖整個方法體;能用對象鎖,就不要用類鎖。
說明:儘可能使加鎖的代碼塊工作量儘可能的小,避免在鎖代碼塊中調用RPC方法。
7. 【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。
說明:線程一需要對錶A、B、C依次全部加鎖後纔可以進行更新操作,那麼線程二的加鎖順序也必須是A、B、C,否則可能出現死鎖。
8. 【強制】併發修改同一記錄時,避免更新丟失,需要加鎖。要麼在應用層加鎖,要麼在緩存加鎖,要麼在數據庫層使用樂觀鎖,使用version作爲更新依據。
說明:如果每次訪問衝突概率小於20%,推薦使用樂觀鎖,否則使用悲觀鎖。樂觀鎖的重試次數不得小於3次。
9. 【強制】多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
10. 【推薦】使用CountDownLatch進行異步轉同步操作,每個線程退出前必須調用countDown方法,線程執行代碼注意catch異常,確保countDown方法被執行到,避免主線程無法執行至await方法,直到超時才返回結果。
說明:注意,子線程拋出異常堆棧,不能在主線程try-catch到。
11. 【推薦】避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 導致的性能下降。
說明:Random實例包括java.util.Random 的實例或者 Math.random()的方式。
正例:在JDK7之後,可以直接使用API ThreadLocalRandom,而在 JDK7之前,需要編碼保證每個線程持有一個實例。
12. 【推薦】在併發場景下,通過雙重檢查鎖(double-checked locking)實現延遲初始化的優化問題隱患(可參考 The "Double-Checked Locking is Broken" Declaration),推薦解決方案中較爲簡單一種(適用於JDK5及以上版本),將目標屬性聲明爲 volatile型。
反例:
class Singleton {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) synchronized(this) {
if (helper == null)
helper = new Helper();
}
return helper;
}
// other methods and fields...
}
13. 【參考】volatile解決多線程內存不可見問題。對於一寫多讀,是可以解決變量同步問題,但是如果多寫,同樣無法解決線程安全問題。如果是count++操作,使用如下類實現:AtomicInteger count = new AtomicInteger(); count.addAndGet(1); 如果是JDK8,推薦使用LongAdder對象,比AtomicLong性能更好(減少樂觀鎖的重試次數)。
14. 【參考】 HashMap在容量不夠進行resize時由於高併發可能出現死鏈,導致CPU飆升,在開發過程中可以使用其它數據結構或加鎖來規避此風險。
15. 【參考】ThreadLocal無法解決共享對象的更新問題,ThreadLocal對象建議使用static修飾。這個變量是針對一個線程內所有操作共享的,所以設置爲靜態變量,所有此類實例共享此靜態變量 ,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只要是這個線程內定義的)都可以操控這個變量。
(七) 控制語句
【強制】在一個switch塊內,每個case要麼通過break/return等來終止,要麼註釋說明程序將繼續執行到哪一個case爲止;在一個switch塊內,都必須包含一個default語句並且放在最後,即使空代碼。
2. 【強制】在if/else/for/while/do語句中必須使用大括號。即使只有一行代碼,避免採用單行的編碼方式:if (condition) statements;
3. 【強制】在高併發場景中,避免使用”等於”判斷作爲中斷或退出的條件。
說明:如果併發控制沒有處理好,容易產生等值判斷被“擊穿”的情況,使用大於或小於的區間判斷條件來代替。
反例:判斷剩餘獎品數量等於0時,終止發放獎品,但因爲併發處理錯誤導致獎品數量瞬間變成了負數,這樣的話,活動無法終止。
4. 【推薦】表達異常的分支時,少用if-else方式,這種方式可以改寫成:
if (condition) {
...
return obj;
}
// 接着寫else的業務邏輯代碼; //多if語句
說明:如果非得使用if()...else if()...else...方式表達邏輯,【強制】避免後續代碼維護困難,請勿超過3層。
正例:超過3層的 if-else 的邏輯判斷代碼可以使用衛語句、策略模式、狀態模式等來實現,其中衛語句示例如下:
public void today() {
if (isBusy()) {
System.out.println(“change time.”);
return;
}
if (isFree()) {
System.out.println(“go to travel.”);
return;
}
System.out.println(“stay at home to learn Alibaba Java Coding Guidelines.”);
return;
}
衛語句 把複雜的條件表達式拆分成多個條件表達式,比如一個很複雜的表達式,嵌套了好幾層的if - then-else語句,轉換爲多個if語句,實現它的邏輯,這多條的if語句就是衛語句.
5. 【推薦】除常用方法(如getXxx/isXxx)等外,不要在條件判斷中執行其它複雜的語句,將複雜邏輯判斷的結果賦值給一個有意義的布爾變量名,以提高可讀性。
說明:很多if語句內的邏輯相當複雜,閱讀者需要分析條件表達式的最終結果,才能明確什麼樣的條件執行什麼樣的語句,那麼,如果閱讀者分析邏輯表達式錯誤呢?
正例:
// 僞代碼如下
final boolean existed = (file.open(fileName, "w") != null) && (...) || (...);
if (existed) {
...
}
反例:
if ((file.open(fileName, "w") != null) && (...) || (...)) {
...
}
6. 【推薦】循環體中的語句要考量性能,以下操作儘量移至循環體外處理,如定義對象、變量、獲取數據庫連接,進行不必要的try-catch操作(這個try-catch是否可以移至循環體外)。
7. 【推薦】避免採用取反邏輯運算符。
說明:取反邏輯不利於快速理解,並且取反邏輯寫法必然存在對應的正向邏輯寫法。
正例:使用if (x < 628)來表達x 小於628。
反例:使用if (!(x >= 628))來表達x 小於628。
8.【推薦】接口入參保護,這種場景常見的是用作批量操作的接口。 //參數校驗
接口入參保護,“保護”的是服務端應用,即接口提供方,最常見的做法就是入參的範圍或者邊界;
比如,接口定義時明確校驗入參的請求日期是N天內的記錄,或者最大數量限制是1W條;
批量操作的批量數應該有上限,而不是無限的。假如客戶端請求一次批量操作10W筆轉賬訂單,服務器應該果斷拒絕,很不是很SB的忠實執行,會對服務端造成嚴重應該的批量請求,服務器端應做好保護性編程,必要時應直接失敗,並在Result中返回明確的errorCode和errorMsg;
入參保護,一般都是通過衛語句實現:if(請求記錄>10000條) return;直接返回。
9. 【參考】下列情形,需要進行參數校驗:
1) 調用頻次低的方法。
2) 執行時間開銷很大的方法。此情形中,參數校驗時間幾乎可以忽略不計,但如果因爲參數錯誤導致中間執行回退,或者錯誤,那得不償失。
3) 需要極高穩定性和可用性的方法。
4) 對外提供的開放接口,不管是RPC/API/HTTP接口。
5) 敏感權限入口。
10. 【參考】下列情形,不需要進行參數校驗:
1) 極有可能被循環調用的方法。但在方法說明裏必須註明外部參數檢查要求。
2) 底層調用頻度比較高的方法。畢竟是像純淨水過濾的最後一道,參數錯誤不太可能到底層纔會暴露問題。一般DAO層與Service層都在同一個應用中,部署在同一臺服務器中,所以DAO的參數校驗,可以省略。
3) 被聲明成private只會被自己代碼所調用的方法,如果能夠確定調用方法的代碼傳入參數已經做過檢查或者肯定不會有問題,此時可以不校驗參數。
(八) 註釋規約
1. 【強制】類、類屬性、類方法的註釋必須使用javadoc規範,使用/**內容*/格式
,不得使用//xxx方式。
說明:在IDE編輯窗口中,javadoc方式會提示相關注釋,生成javadoc可以正確輸出相應註釋;在IDE中,工程調用方法時,不進入方法即可懸浮提示方法、參數、返回值的意義,提高閱讀效率。
2. 【強制】所有的抽象方法(包括接口中的方法)必須要用javadoc註釋、除了返回值、參數、異常說明外,還必須指出該方法做什麼事情,實現什麼功能。
說明:如有實現和調用注意事項,請一併說明。
說明:對子類的實現要求,或者調用注意事項,請一併說明。
3. 【強制】所有的類都必須添加創建者和創建日期。
4. 【強制】方法內部單行註釋,在被註釋語句上方另起一行,使用//註釋。方法內部多行註釋使用/* */註釋,注意與代碼對齊。
5. 【強制】所有的枚舉類型字段必須要有註釋,說明每個數據項的用途。
6. 【推薦】與其“半吊子”英文來註釋,不如用中文註釋把問題說清楚。專有名詞、關鍵字,保持英文原文即可。
反例:“TCP連接超時”解釋成“傳輸控制協議連接超時”,理解反而費腦筋。
7. 【推薦】代碼修改的同時,註釋也要進行相應的修改,尤其是參數、返回值、異常、核心邏輯等的修改。
說明:代碼與註釋更新不同步,就像路網與導航軟件更新不同步一樣,如果導航軟件嚴重滯後,就失去了導航的意義。
8. 【參考】註釋掉的代碼儘量要配合說明,而不是簡單的註釋掉。
說明:代碼被註釋掉有兩種可能性:1)後續會恢復此段代碼邏輯。2)永久不用。前者如果沒有備註信息,難以知曉註釋動機。後者建議直接刪掉(代碼倉庫保存了歷史代碼)。
9. 【參考】對於註釋的要求:第一、能夠準確反應設計思想和代碼邏輯;第二、能夠描述業務含義,使別的程序員能夠迅速瞭解到代碼背後的信息。完全沒有註釋的大段代碼對於閱讀者形同天書,註釋是給自己看的,即使隔很長時間,也能清晰理解當時的思路;註釋也是給繼任者看的,使其能夠快速接替自己的工作。
10. 【參考】好的命名、代碼結構是自解釋的,註釋力求精簡準確、表達到位。避免出現註釋的一個極端:過多過濫的註釋,代碼的邏輯一旦修改,修改註釋是相當大的負擔。
反例:
// put elephant into fridge
put(elephant, fridge);
方法名put,加上兩個有意義的變量名elephant和fridge,已經說明了這是在幹什麼,語義清晰的代碼不需要額外的註釋。
11.【參考】特殊註釋標記,請註明標記人與標記時間。注意及時處理這些標記,通過標記掃描,經常清理此類標記。線上故障有時候就是來源於這些標記處的代碼。
1) 待辦事宜(TODO):( 標記人,標記時間,[預計處理時間]) 表示需要實現,但目前還未實現的功能。這實際上是一個javadoc的標籤,目前的javadoc還沒有實現,但已經被廣泛使用。只能應用於類,接口和方法(因爲它是一個javadoc標籤)。
2) 錯誤,不能工作(FIXME):(標記人,標記時間,[預計處理時間]) 在註釋中用FIXME標記某代碼是錯誤的,而且不能工作,需要及時糾正的情況。
(九) 其它
1. 【強制】在使用正則表達式時,利用好其預編譯功能,可以有效加快正則匹配速度。
說明:不要在方法體內定義:Pattern pattern = Pattern.compile(規則);
表達式一定要求是預先編譯的, 這個編譯過程是非常具有消耗性能的。如果一些是非頻繁的使用的表達預先編譯可能在代碼的處理上會有點麻煩,ORO庫提供給我們一個比較好的util實現:Perl5Util, 他能在cache住一些經常被使用的表達式(20個)
private final static Pattern pattern = new Perl5Compiler().compile("^\\d+$", '''Perl5Compiler.READ_ONLY_MASK''');
//請注意,由於Pattern本身不是線程安全的,只有加了READ_ONLY_MASK的編譯參數才能用於共享使用,否則會出現併發訪問的問題,導致錯誤結果
public void mach(){
PatternMatcher matcher = new Perl5Matcher();
if (matcher.matches(str, pattern)) { …… }
}
2. 【強制】避免用Apache Beanutils進行屬性的copy。
說明:Apache BeanUtils性能較差,可以使用其他方案比如Spring BeanUtils, Cglib BeanCopier。
import org.springframework.beans.BeanUtils;
BeanUtils.copyProperties(entity,this);
3. 【強制】velocity調用POJO類的屬性時,建議直接使用屬性名取值即可,模板引擎會自動按規範調用POJO的getXxx(),如果是boolean基本數據類型變量(注意,boolean命名不需要加is前綴),會自動調用isXxx()方法。
說明:注意如果是Boolean包裝類對象,優先調用getXxx()的方法。
4. 【強制】後臺輸送給頁面的變量必須加!var——中間的感嘆號。
說明:如果var=null或者不存在,那麼{var}會直接顯示在頁面上。
Freemark,當var=null或者不存在報異常
5. 【強制】注意 Math.random() 這個方法返回是double類型,注意取值範圍 0≤x<1(能夠取到零值,注意除零異常),如果想獲取整數類型的隨機數,不要將x放大10的若干倍然後取整,直接使用Random對象的nextInt或者nextLong方法。
6. 【強制】獲取當前毫秒數:System.currentTimeMillis(); 而不是new Date().getTime();
說明:如果想獲取更加精確的納秒級時間值,用System.nanoTime。在JDK8中,針對統計時間等場景,推薦使用Instant類。
Instant instant =Instant.now();
System.out.println(instant); //2018-02-20T03:38:47.413Z
Date legacyDate = Date.from(instant);
System.out.println(legacyDate); //Tue Feb 20 11:38:47 CST 2018
7. 【推薦】儘量不要在vm中加入變量聲明、邏輯運算符,更不要在vm模板中加入任何複雜的邏輯。
velocity(VM)與freemaker、jstl並稱爲java web開發三大標籤技術
Velocity 是一個基於 Java 的模板引擎框架,提供的模板語言可以使用在 Java 中定義的對象和變量上。
8. 【推薦】任何數據結構的使用都應限制大小。
說明:這點很難完全做到,但很多次的故障都是因爲數據結構自增長,結果造成內存被喫光。
9. 【推薦】對於“明確停止使用的代碼和配置”,如方法、變量、類、配置文件、動態配置屬性等要堅決從程序中清理出去,避免造成過多垃圾。清理這類垃圾代碼是技術氣場,不要有這樣的觀念:“不做不錯,多做多錯”。