1:非空判斷java
錯誤例子:正則表達式
if(user.getUserName().equals("hollis")){ }
這段代碼極有可能在實際運行的時候跑出NullPointerException
。不管是user自己爲空,仍是user.getUserName()爲空,都會拋出異常。 因此,在調用一個參數時要確保他是非空的。編程
上面的代碼能夠改成:數組
if(user!=null&&"hollis".equals(user.getUserName())){ }
2:用StringBuffer代替String緩存
在循環中構建一個String對象時從性能上講使用StringBuffer來代替String對象 例如:安全
// This is bad String s = ""; for (int i = 0; i < field.length; ++i) { s = s + field[i]; }
應該改成StringBuffer
,使用append
方法:多線程
StringBuffer buf = new StringBuffer(); for (int i = 0; i < field.length; ++i) { buf.append(field[i]); } String s = buf.toString();
3:儘可能減小對變量的重複計算併發
明確一個概念,對方法的調用,即便方法中只有一句語句,也是有消耗的,包括建立棧幀、調用方法時保護現場、調用方法完畢時恢復現場等。因此例以下面的操做:oracle
for (int i = 0; i < list.size(); i++){
...
}
建議替換爲:app
for (int i = 0, length = list.size(); i < length; i++){ ... }
這樣,在list.size()很大的時候,就減小了不少的消耗
4:儘可能採用懶加載的策略,即在須要的時候才建立
例如:
String str = "aaa"; if (i == 1) { list.add(str); }
建議替換爲:
if (i == 1) { String str = "aaa"; list.add(str); }
5:慎用異常
異常對性能不利。拋出異常首先要建立一個新的對象,Throwable接口的構造函數調用名爲 fillInStackTrace()的本地同步方法,fillInStackTrace()方法檢查堆棧,收集調用跟蹤信息。只要有異常被拋出,Java虛擬機就必須調整調用堆棧,由於在處理過程當中建立了一個新的對象。異常只能用於錯誤處理,不該該用來控制程序流程。
6:不要在循環中使用try…catch…,應該把其放在最外層
7:若是能估計到待添加的內容長度,爲底層以數組方式實現的集合、工具類指定初始長度
好比ArrayList、LinkedLlist、StringBuilder、StringBuffer、HashMap、HashSet等等,以StringBuilder爲例:
StringBuilder() // 默認分配16個字符的空間
StringBuilder(int size) // 默認分配size個字符的空間
StringBuilder(String str) // 默認分配16個字符+str.length()個字符空間
8:當複製大量數據時,使用 System.arraycopy()命令
9:乘法和除法使用移位操做
例如:
for (val = 0; val < 100000; val += 5) { a = val * 8; b = val / 2; }
用移位操做能夠極大地提升性能,由於在計算機底層,對位的操做是最方便、最快的,所以建議修改成:
for (val = 0; val < 100000; val += 5) { a = val << 3; b = val >> 1; }
移位操做雖然快,可是可能會使代碼不太好理解,所以最好加上相應的註釋。
10:循環內不要不斷建立對象引用
例如:
for (int i = 1; i <= count; i++) { Object obj = new Object(); }
這種作法會致使內存中有count份Object對象引用存在,count很大的話,就耗費內存了,建議爲改成:
Object obj = null; for (int i = 0; i <= count; i++) { obj = new Object(); }
這樣的話,內存中只有一份Object對象引用,每次new Object()的時候,Object對象引用指向不一樣的Object罷了,可是內存中只有一份,這樣就大大節省了內存空間了。
11:不要將數組聲明爲public static final
由於這毫無心義,這樣只是定義了引用爲static final,數組的內容仍是能夠隨意改變的,將數組聲明爲public更是一個安全漏洞,這意味着這個數組能夠被外部類所改變
12:儘可能在合適的場合使用單例
使用單例能夠減輕加載的負擔、縮短加載的時間、提升加載的效率,但並非全部地方都適用於單例,簡單來講,單例主要適用於如下三個方面:
控制資源的使用,經過線程同步來控制資源的併發訪問
控制實例的產生,以達到節約資源的目的
控制數據的共享,在不創建直接關聯的條件下,讓多個不相關的進程或線程之間實現通訊
13:儘可能避免隨意使用靜態變量
要知道,當某個對象被定義爲static的變量所引用,那麼gc一般是不會回收這個對象所佔有的堆內存的,如:
public class A { private static B b = new B(); }
此時靜態變量b的生命週期與A類相同,若是A類不被卸載,那麼引用B指向的B對象會常駐內存,直到程序終止
14:使用同步代碼塊替代同步方法
分析:
//下列兩個方法有什麼區別 public synchronized void method1(){} public void method2(){ synchronized (obj){} }
synchronized用於解決同步問題,當有多條線程同時訪問共享數據時,若是不進行同步,就會發生錯誤,java提供的解決方案是:只要將操做共享數據的語句在某一時段讓一個線程執行完,在執行過程當中,其餘線程不能進來執行能夠。解決這個問題。這裏在用synchronized時會有兩種方式,一種是上面的同步方法,即用synchronized來修飾方法,另外一種是提供的同步代碼塊。
這裏總感受怪怪的,這兩種方法有什麼區別呢?讓咱們看下代碼:
public class SynObj { public synchronized void methodA() { System.out.println("methodA....."); try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } public void methodB() { synchronized(this) { System.out.pritntln("methodB....."); } } public void methodC() { String str = "sss"; synchronized (str) { System.out.println("methodC....."); } } } public class TestSyn { public static void main(String[] args) { final SynObj obj = new SynObj(); Thread t1 = new Thread(new Runnable() { @Override public void run() { obj.methodA(); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run() { obj.methodB(); } }); t2.start(); Thread t3 = new Thread(new Runnable() { @Override public void run() { obj.methodC(); } }); t3.start(); } }
這段小代碼片斷打印結果以下:
methodA.....
methodC.....
//methodB會隔一段時間纔會打印出來
methodB.....
這段代碼的打印結果是,methodA…..methodC…..會很快打印出來,methodB…..會隔一段時間纔打印出來,那麼methodB爲何不能像methodC那樣很快被調用呢?
在啓動線程1調用方法A後,接着會讓線程1休眠5秒鐘,這時會調用方法C,注意到方法C這裏用synchronized進行加鎖,這裏鎖的對象是str這個字符串對象。可是方法B則不一樣,是用當前對象this進行加鎖,注意到方法A直接在方法上加synchronized,這個加鎖的對象是什麼呢?顯然,這兩個方法用的是一把鎖。
*由這樣的結果,咱們就知道這樣同步方法是用什麼加鎖的了,因爲線程1在休眠,這時鎖還沒釋放,致使線程2只有在5秒以後才能調用方法B,由此,可知兩種加鎖機制用的是同一個鎖對象,即當前對象。
另外,同步方法直接在方法上加synchronized實現加鎖,同步代碼塊則在方法內部加鎖,很明顯,同步方法鎖的範圍比較大,而同步代碼塊範圍要小點,通常同步的範圍越大,性能就越差,通常須要加鎖進行同步的時候,確定是範圍越小越好,這樣性能更好*。
同步代碼塊能夠用更細粒度的控制鎖,好比:
public class Test{ private String name = "xiaoming"; private String id = "0753"; public void setName(String name) { synchornized(name) { this.name = name; } } public void setId(String id) { synchornized(id) { this.id = id; } } }
若是你有一個Test對象 你想在多線程下同時修改Name和id, 若是你兩個set方法都聲明爲同步方法,那麼在同一時間只能修改name或者id. 可是這兩個是能夠同時修改的,因此你須要同步代碼塊,將信號量分別設置成name和id.
15:將常量聲明爲static final,並以大寫命名
這樣在編譯期間就能夠把這些內容放入常量池中,避免運行期間計算生成常量的值。另外,將常量的名字以大寫命名也能夠方便區分出常量與變量
16:不要建立一些不使用的對象,不要導入一些不使用的類
這毫無心義,若是代碼中出現」The value of the local variable i is not used」、」The import java.util is never used」,那麼請刪除這些無用的內容
17:程序運行過程當中避免使用反射
反射是Java提供給用戶一個很強大的功能,功能強大每每意味着效率不高。不建議在程序運行過程當中使用尤爲是頻繁使用反射機制,特別是Method的invoke方法,若是確實有必要,一種建議性的作法是將那些須要經過反射加載的類在項目啓動的時候經過反射實例化出一個對象並放入內存—-用戶只關心和對端交互的時候獲取最快的響應速度,並不關心對端的項目啓動花多久時間。
18:使用帶緩衝的輸入輸出流進行IO操做
帶緩衝的輸入輸出流,即BufferedReader、BufferedWriter、BufferedInputStream、BufferedOutputStream,這能夠極大地提高IO效率。
19:不要讓public方法中有太多的形參
public方法即對外提供的方法,若是給這些方法太多形參的話主要有兩點壞處:
違反了面向對象的編程思想,Java講求一切都是對象,太多的形參,和麪向對象的編程思想並不契合
參數太多勢必致使方法調用的出錯機率增長
至於這個」太多」指的是多少個,三、4個吧。好比咱們用JDBC寫一個insertStudentInfo方法,有10個學生信息字段要插如Student表中,能夠把這10個參數封裝在一個實體類中,做爲insert方法的形參。
20:公用的集合類中不使用的數據必定要及時remove掉
若是一個集合類是公用的(也就是說不是方法裏面的屬性),那麼這個集合裏面的元素是不會自動釋放的,由於始終有引用指向它們。因此,若是公用集合裏面的某些數據不使用而不去remove掉它們,那麼將會形成這個公用集合不斷增大,使得系統有內存泄露的隱患。
21:把一個基本數據類型轉爲字符串
基本數據類型.toString()是最快的方式、String.valueOf(數據)次之、數據+」"最慢
把一個基本數據類型轉爲通常有三種方式,我有一個Integer型數據i,可使用i.toString()、String.valueOf(i)、i+」"三種方式,三種方式的效率如何,看一個測試:
public static void main(String[] args) { int loopTime = 50000; Integer i = 0; long startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++) { String str = String.valueOf(i); } System.out.println("String.valueOf():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++) { String str = i.toString(); } System.out.println("Integer.toString():" + (System.currentTimeMillis() - startTime) + "ms"); startTime = System.currentTimeMillis(); for (int j = 0; j < loopTime; j++) { String str = i + ""; } System.out.println("i + "":" + (System.currentTimeMillis() - startTime) + "ms"); }
運行結果爲:
String.valueOf():11ms
Integer.toString():5ms
i + "" :25ms
因此之後遇到把一個基本數據類型轉爲String的時候,優先考慮使用toString()方法。至於爲何,很簡單:
String.valueOf()方法底層調用了Integer.toString()方法,可是會在調用前作空判斷
Integer.toString()方法就不說了,直接調用了
i + 「」底層使用了StringBuilder實現,先用append方法拼接,再用toString()方法獲取字符串
三者對比下來,明顯是2最快、1次之、3最慢。
22:使用最有效率的方式去遍歷Map
遍歷Map的方式有不少,一般場景下咱們須要的是遍歷Map中的Key和Value,那麼推薦使用的、效率最高的方式是:
public static void main(String[] args) { HashMap<String, String> hm = new HashMap<String, String>(); hm.put( "111" , "222" ); Set<Map.Entry<String, String>> entrySet = hm.entrySet(); Iterator<Map.Entry<String, String>> iter = entrySet.iterator(); while (iter.hasNext()) { Map.Entry<String, String> entry = iter.next(); System.out.println(entry.getKey() + " " + entry.getValue()); } }
若是你只是想遍歷一下這個Map的key值,那用」Set<String> keySet = hm.keySet();」會比較合適一些。
23:對資源的close()建議分開操做
意思是,好比我有這麼一段代碼:
try { XXX.close(); YYY.close(); } catch (Exception e) { ... } //建議修改成: try { XXX.close(); } catch (Exception e) { ... } try { YYY.close(); } catch (Exception e) { ... }
雖然有些麻煩,卻能避免資源泄露。咱們想,若是沒有修改過的代碼,萬一XXX.close()拋異常了,那麼就進入了catch塊中了,YYY.close()不會執行,YYY這塊資源就不會回收了,一直佔用着,這樣的代碼一多,是可能引發資源句柄泄露的。而改成下面的寫法以後,就保證了不管如何XXX和YYY都會被close掉。
24: 切記以常量定義的方式替代魔鬼數字,魔鬼數字的存在將極大地下降代碼可讀性,字符串常量是否使用常量定義能夠視狀況而定
25: long或者Long初始賦值時,使用大寫的L而不是小寫的l,由於字母l極易與數字1混淆,這個點很是細節,值得注意
26:靜態類、單例類、工廠類將它們的構造函數置爲private
這是由於靜態類、單例類、工廠類這種類原本咱們就不須要外部將它們new出來,將構造函數置爲private以後,保證了這些類不會產生實例對象。
27: 避免Random實例被多線程使用,雖然共享該實例是線程安全的,但會因競爭同一seed 致使的性能降低,JDK7以後,可使用ThreadLocalRandom來獲取隨機數
解釋一下競爭同一個seed致使性能降低的緣由,好比,看一下Random類的nextInt()方法實現:
public int nextInt() { return next( 32 ); } //調用了next(int bits)方法,這是一個受保護的方法: protected int next( int bits) { long oldseed, nextseed; AtomicLong seed = this .seed; do { oldseed = seed.get(); nextseed = (oldseed * multiplier + addend) & mask; } while (!seed.compareAndSet(oldseed, nextseed)); return ( int )(nextseed >>> ( 48 - bits)); } //而這邊的seed是一個全局變量: /** * The internal state associated with this pseudorandom number generator. * (The specs for the methods in this class describe the ongoing * computation of this value.) */ private final AtomicLong seed;
多個線程同時獲取隨機數的時候,會競爭同一個seed,致使了效率的下降。:
28:jvm對常量爲-128~127的整數進行緩存。
打印結果爲false,而下面結果爲true爲啥
解答:Integer類型當正整數小於128時是在內存棧中建立值的,並將對象指向這個值,這樣當比較兩個棧引用時由於是同一地址引用二者則相等。當大於127時將會調用new Integer(),兩個整數對象地址引用不相等了。這就是爲何當值爲128時不相等,當值爲100時相等了(java本身有個常量緩衝池 存着 -128~127的整數 )。
29:金額運算
@Test public void test4() { double num1 = 0.02d; double num2 = 0.03d; double num3 = num2 - num1; System.out.println(num3); }
console結果: 0.009999999999999998
爲何會這樣呢? 由於float和double都是浮點數, 都有取值範圍, 都有精度範圍. 浮點數與一般使用的小數不一樣, 使用中, 每每難以肯定. 常見的問題是定義了一個浮點數, 通過一系列的計算, 它原本應該等於某個肯定值, 但實際上並非! 金額必須是徹底精確的計算, 故不能使用double或者float, 而應該採用java.math.BigDecimal.
使用BigDecimal的add, substract, multiply和divide作加減乘除, 用compareTo方法比較大小
@Test public void test4() { BigDecimal num1 = new BigDecimal("0.02"); BigDecimal num2 = new BigDecimal("0.03"); //加 System.out.println(num1.add(num2)); //減 System.out.println(num2.subtract(num1)); //乘 System.out.println(num1.multiply(num2)); //除 System.out.println(num1.divide(num2, RoundingMode.HALF_UP)); BigDecimal num3 = new BigDecimal("0.03"); if(num3.compareTo(BigDecimal.ZERO) == -1) { System.out.println("num3 小於0"); }else if(num3.compareTo(BigDecimal.ZERO) == 1) { System.out.println("num3大於0"); }else if(num3.compareTo(BigDecimal.ZERO) == 1) { System.out.println("num3等於0"); } BigDecimal num4 = new BigDecimal("0.1234567"); //其中setScale的第一個參數是小數位數, 這個示例是保留2位小數, 後面是四捨五入規則. System.out.println("num4:" + num4.setScale(2, BigDecimal.ROUND_UP)); }
console結果:
0.05
0.01
0.0006
0.67
num3大於0
num4:0.13
30:oracle獲取時間的坑
執行:select to_char(sysdate, 'yyyymmddhhmmss'),to_char(sysdate, 'yyyyMMddHH24mmss'),to_char(sysdate, 'yyyyMMddHH24MISS') from dual;
輸出:
oracle中的日期格式爲:
yyyy-MM-dd HH24:mi:ss和 yyyy-MM-dd HH:mi:ss,分別表明oracle中的24小時制和12小時制
java中的的日期格式爲:
yyyy-MM-dd HH:mm:ss:表明將時間轉換爲24小時制,例: 2018-06-27 15:24:21
yyyy-MM-dd hh:mm:ss:表明將時間轉換爲12小時制,例: 2018-06-27 03:24:21
之因此 oracle和java不一樣,是由於咱們知道oracle是不區分大小寫的,因此java中根據大小寫來表明24小時和12小時的表達式在oracle中就會出問題,oracle中將24小時的小時和分鐘作了特殊處理.如上所示,在hh後面加上了24,將mm改成了mi,而一旦不注意取到的時間就會出問題!
oracle中yyyyMMddHH24mmss其中yyyyMMddHH24mmss中的mm會返回月份,不能喝java的混淆
結論:oralce取當前時間用 to_char(sysdate, 'yyyyMMddHH24MISS')
31: 獲取時間差
阿里巴巴手冊建議:
計算兩段代碼時間差,不少同窗公司的代碼是採用如下這種方式。
long startTime = System.currentTimeMillis(); // 執行代碼 long endTime = System.currentTimeMillis(); System.out.println(endTime - startTime);
這種方式並非不行。按照「能跑就行」的原則,這段代碼,確定是能用的!可是這並非最佳實踐,爲什麼? 咱們先來看一下JDK中的註釋
咱們來看另一種方式。
long startTime = System.nanoTime(); // 執行代碼 long endTime = System.nanoTime(); System.out.println(endTime - startTime);
咱們再來看看註釋:
32: 須要 Map 的主鍵和取值時,應該迭代 entrySet()
當循環中只須要 Map 的主鍵時,迭代 keySet() 是正確的。可是,當須要主鍵和取值時,迭代 entrySet() 纔是更高效的作法,比先迭代 keySet() 後再去 get 取值性能更佳。
反例:
Map<String, String> map = ...; for (String key : map.keySet()) { String value = map.get(key); ... }
正例:
Map<String, String> map = ...; for (Map.Entry<String, String> entry : map.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); ... }
33: 應該使用Collection.isEmpty()檢測空
使用 Collection.size() 來檢測空邏輯上沒有問題,可是使用 Collection.isEmpty()使得代碼更易讀,而且能夠得到更好的性能。任何 Collection.isEmpty() 實現的時間複雜度都是 O(1) ,可是某些 Collection.size() 實現的時間複雜度多是 O(n) 。
反例:
if (collection.size() == 0) { ... }
正例:
if (collection.isEmpty()) { ... }
若是須要還須要檢測 null ,可採用 CollectionUtils.isEmpty(collection) 和 CollectionUtils.isNotEmpty(collection)。
34: List 的隨機訪問
你們都知道數組和鏈表的區別:數組的隨機訪問效率更高。當調用方法獲取到 List 後,若是想隨機訪問其中的數據,並不知道該數組內部實現是鏈表仍是數組,怎麼辦呢?能夠判斷它是否實現* RandomAccess *接口。
正例:
// 調用別人的服務獲取到list List<Integer> list = otherService.getList(); if (list instanceof RandomAccess) { // 內部數組實現,能夠隨機訪問 System.out.println(list.get(list.size() - 1)); } else { // 內部多是鏈表實現,隨機訪問效率低 }
35: 頻繁調用 Collection.contains 方法請使用 Set
在 java 集合類庫中,List 的 contains 方法廣泛時間複雜度是 O(n) ,若是在代碼中須要頻繁調用 contains 方法查找數據,能夠先將 list 轉換成 HashSet 實現,將 O(n) 的時間複雜度降爲 O(1) 。
反例:
ArrayList<Integer> list = otherService.getList(); for (int i = 0; i <= Integer.MAX_VALUE; i++) { // 時間複雜度O(n) list.contains(i); }
正例:
ArrayList<Integer> list = otherService.getList(); Set<Integer> set = new HashSet(list); for (int i = 0; i <= Integer.MAX_VALUE; i++) { // 時間複雜度O(1) set.contains(i); }
36: 不要使用魔法值
當你編寫一段代碼時,使用魔法值可能看起來很明確,但在調試時它們卻不顯得那麼明確了。這就是爲何須要把魔法值定義爲可讀取常量的緣由。可是,-一、0 和 1不被視爲魔法值。
反例:
for (int i = 0; i < 100; i++){ ... } if (a == 100) { ... }
正例:
private static final int MAX_COUNT = 100; for (int i = 0; i < MAX_COUNT; i++){ ... } if (count == MAX_COUNT) { ... }
37: 不要使用集合實現來賦值靜態成員變量
對於集合類型的靜態成員變量,不要使用集合實現來賦值,應該使用靜態代碼塊賦值。
反例:
private static Map<String, Integer> map = new HashMap<String, Integer>() { { put("a", 1); put("b", 2); } }; private static List<String> list = new ArrayList<String>() { { add("a"); add("b"); } };
正例:
private static Map<String, Integer> map = new HashMap<>(); static { map.put("a", 1); map.put("b", 2); }; private static List<String> list = new ArrayList<>(); static { list.add("a"); list.add("b"); };
38: 工具類應該屏蔽構造函數
工具類是一堆靜態字段和函數的集合,不該該被實例化。可是,Java 爲每一個沒有明肯定義構造函數的類添加了一個隱式公有構造函數。因此,爲了不 java "小白"使用有誤,應該顯式定義私有構造函數來屏蔽這個隱式公有構造函數。
反例:
public class MathUtils { public static final double PI = 3.1415926D; public static int sum(int a, int b) { return a + b; } }
正例:
public class MathUtils { public static final double PI = 3.1415926D; private MathUtils() {} public static int sum(int a, int b) { return a + b; } }
39: 使用String.valueOf(value)代替""+value
當要把其它對象或類型轉化爲字符串時,使用 String.valueOf(value) 比""+value 的效率更高。
反例:
int i = 1; String s = "" + i;
正例:
int i = 1; String s = String.valueOf(i);
40: 枚舉的屬性字段必須是私有不可變
枚舉一般被當作常量使用,若是枚舉中存在公共屬性字段或設置字段方法,那麼這些枚舉常量的屬性很容易被修改。理想狀況下,枚舉中的屬性字段是私有的,並在私有構造函數中賦值,沒有對應的 Setter 方法,最好加上 final 修飾符。
反例:
public enum UserStatus { DISABLED(0, "禁用"), ENABLED(1, "啓用"); public int value; private String description; private UserStatus(int value, String description) { this.value = value; this.description = description; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; } }
正例:
public enum UserStatus { DISABLED(0, "禁用"), ENABLED(1, "啓用"); private final int value; private final String description; private UserStatus(int value, String description) { this.value = value; this.description = description; } public int getValue() { return value; } public String getDescription() { return description; } }
41: 當心String.split(String regex)
字符串 String 的 split 方法,傳入的分隔字符串是正則表達式!部分關鍵字(好比.[]()\| 等)須要轉義
反例:
"a.ab.abc".split("."); // 結果爲[] "a|ab|abc".split("|"); // 結果爲["a", "|", "a", "b", "|", "a", "b", "c"]
正例:
"a.ab.abc".split("\\."); // 結果爲["a", "ab", "abc"] "a|ab|abc".split("\\|"); // 結果爲["a", "ab", "abc"]
42: 優先使用常量或肯定值來調用 equals 方法
對象的 equals 方法容易拋空指針異常,應使用常量或肯定有值的對象來調用 equals 方法。固然,使用 java.util.Objects.equals() 方法是最佳實踐。
反例:
public void isFinished(OrderStatus status) { return status.equals(OrderStatus.FINISHED); // 可能拋空指針異常 }
正例:
public void isFinished(OrderStatus status) { return OrderStatus.FINISHED.equals(status); } public void isFinished(OrderStatus status) { return Objects.equals(status, OrderStatus.FINISHED); }