話不多說直接上乾貨,你我共勉。
1. 構造器參數太多怎麼辦
解決辦法 :引入Builder模式
場景:當構造器有5個或者以上的構造參數時或者目前參數不多但是以後會不斷增多的時候。
demo 如下:
public class Computer {
protected String mBoard;
protected String mDisplay;
protected String mOs;
private Computer(Builder builder) {
this.mOs = builder.mOs;
this.mBoard = builder.mBoard;
this.mDisplay = builder.mDisplay;
}
@Override
public String toString() {
return "Computer{" +
"mBoard='" + mBoard + '\'' +
", mDisplay='" + mDisplay + '\'' +
", mOs='" + mOs + '\'' +
'}';
}
static class Builder {
protected String mBoard;
protected String mDisplay;
protected String mOs;
public Builder setmOs(String mOs) {
this.mOs = mOs;
return this;
}
public Builder setmBoard(String mBoard) {
this.mBoard = mBoard;
return this;
}
public Builder setmDisplay(String mDisplay) {
this.mDisplay = mDisplay;
return this;
}
public Computer build() {
return new Computer(this);
}
}
}
調用的時候如下:
public static void main(String[] args) {
Computer macbook = new Computer.Builder()
.setmBoard("board")
.setmDisplay("sowhat")
.setmOs("Mac")
.build();
System.out.println(macbook);
}
2. 不需要實例化的類構造器要私有化
經常用到的Utils類,比如Java自帶的java.util.Arrays 這樣的類,工具類都儘量不要實例化。
public class Arrays {
private static final int MIN_ARRAY_SORT_GRAN = 8192;
private static final int INSERTIONSORT_THRESHOLD = 7;
private Arrays() {
}
}
3. 不要創建不必要對象
- 能用基本類型到時候儘量要用基本類型。比如我們做數字運算,如果定義稱了個Long 類型,會涉及到自動裝箱。大大耗時。
- 對於一些程序中共用的參數儘量設置爲static類型變量。
- 對於一些耗時性較大的對象比如數據庫連接,儘量創建數據庫連接池。
- 對於一些佔據內存較大的對象也儘量少創建。因爲在eden區來回倒騰,它累啊!。
public class Sum {
public static void main(String[] args) {
long start = System.currentTimeMillis();
Long sum = 0L; //對象
for(long i=0;i<Integer.MAX_VALUE;i++) {
sum = sum+i;
//new 20多億的Long的實例
}
System.out.println("spend time:"+(System.currentTimeMillis()-start)+"ms");
}
}
耗時:spend time:7042ms
public class Sum {
public static void main(String[] args) {
long start = System.currentTimeMillis();
long sum = 0L; //基本類型
for(long i=0;i<Integer.MAX_VALUE;i++) {
sum = sum+i;
}
System.out.println("spend time:"+(System.currentTimeMillis()-start)+"ms");
}
}
耗時:spend time:820ms
4. 避免使用終結方法
- finalze方法:
- finalize()是Object的protected方法,子類可以覆蓋該方法以實現資源清理工作,GC在回收對象之前調用該方法。
- finalize()與C++中的析構函數不是對應的,但Java中的finalize的調用具有不確定性。
- finalize方法在垃圾回收器準備垃圾回收前被調用,但是不一定會被調用
4.finalize()其實是用來釋放不是通過java的new關鍵字分配的內存,比如說通過本地方法調用了c程序,該c程序malloc分配了內存,那麼垃圾回收器就不能通過java語言來釋放內存,只能在finalize方法內通過本地方法調用c程序進行釋放內存。
一個對象要被回收要經過一次標記可達法已經兩次check纔算死亡。
總結來說:finalize()並不是必須要執行的,它只能執行一次或者0次。如果在finalize中建立對象關聯,則當前對象可以復活一次
- System.gc
用System.gc()
的時候,其實並不會馬上進行垃圾回收,甚至不一定會執行垃圾回收。查看System.gc()
的源碼可以看到只有當justRanFinalization=true
的時候系統纔會真正GC。如果真要回收查看源碼知道System.gc()
要跟System.runFinalization()
一起搭配使用纔好。
5. 類跟成員的可訪問性最小化
這個說白了就是設計模式中的迪米特法則。
定義:要求一個對象應該對其他對象有最少的瞭解,所以迪米特法則又叫做最少知識原則(Least Knowledge Principle, LKP)。
意義:迪米特法則的意義在於降低類之間的耦合。由於每個對象儘量減少對其他對象的瞭解,因此,很容易使得系統的功能模塊功能獨立,相互之間不存在(或很少有)依賴關係,日常最常見的比如成員變量私有化。
6. 使可變性最小化
儘可能的使用final 來修飾一些變量,這樣的化線程操作就不需要考慮同步問題,同時也對於一個變量不同setX方法也 OK。
7. 優先使用複合勝過繼承
繼承是實現代碼重用的有力手段,但是使用不當會導致軟件變得脆弱。在包的內部使用繼承是非常安全的,子類和超類的實現都處在同一個程序員的控制之下。對於專門爲了繼承而設計、並且具有很好的文檔說明的類來說,使用繼承也是非常安全的。然而們對於進行跨越包邊界的繼承,則要非常小心。“繼承”在這裏特指一個類擴展另一個類。需要我們對父類十分對了解纔可繼承,只有當子類和超類之間確實存在父子關係時,纔可以考慮使用繼承。否則都應該用複合,包裝類不僅比子類更加健壯,而且功能也更加強大。Effective Java
8.接口優於抽象類
簡而言之,Java只允許單繼承但是允許實現多個接口。通過接口擴充方法很簡單,這樣也複合設計模式中的開閉原則。接口可以簡單的理解比抽象類還要抽象的一層,是我們對外提供的接口。
同時這裏提一下骨架抽象類。
骨架類存在的意義是實現一個接口中設計者認爲比較公用的方法,然後在具體類實現的時候,具體類繼承自骨架類同時實現接口類的若干方法。
HashSet源碼
public class HashSet<E>
extends AbstractSet<E>
implements Set<E>, Cloneable, java.io.Serializable
看Set源碼
public interface Set<E> extends Collection<E> {
}
AbstractSet源碼
public abstract class AbstractSet<E> extends
AbstractCollection<E> implements Set<E> //
AbstractSet抽象類有若干函數已經實現了。
HashSet
實現了Set<E>
的接口,同時它還繼承自AbstractSet<E>
這個骨架類(類中有一些公用方法)
9. 可變參數謹慎使用
JDK5增加了可變參數方法(variable arity method),可變參數方法接受0個或多個指定類型的參數。
可變參數機制:先創建一個數組,數組大小爲調用位置所傳遞的參數數量,然後將參數值傳遞到數組中,最後將數組傳遞給方法。弊端如下:
1.如果所傳參數爲null,方法裏有對參數的引用(比如 args[0])時,那麼就會在運行時失敗(編譯時卻檢測不出錯誤);
2.在對性能有要求時,我們要慎重考慮是否使用可變參數。因爲,可變參數方法的調用都會引起array的內存分配和初始化,這會給性能帶來損耗。
3. 當可變參數的使用發生變動時(比如:以前用可變參數方法,現在用普通方法),所有引用參數列表的類的.class都要重新生成,因爲可變參數的array的分配和初始化是在編譯期間完成的。
Effective Java給我們的建議是:假設調用可變參數的方法中,有95%只是調用參數個數小於4,那麼就可以將個數小於4的方法,用普通方法定義;剩餘的5%,調用可變參數方法。
public void foo() {}
public void foo(int a1) {}
public void foo(int a1, int a2) {}
public void foo(int a1, int a2, int a3) {}
public void foo(int a1, int a2, int a3, int... rest) {}
10. 儘量不要返回NULL,儘量返回零數組或集合
函數中如果返回NULL,那麼代碼還要去重新的判斷返回值。JDK都主動給我們提供好了Collections.EMPTY_LIST
。當然如果是對象就只能是NULL了。
阿里巴巴開發手冊也是這樣建議的哦。
1.返回值爲null並不會有什麼問題,但是在“解引用”(dereference)時,調用者沒有對null進行判斷就會出現NullPointerException。
2. 在返回值爲數組或者集合時,儘量返回長度爲零的數組或者集合,而不是null,這樣在調用時就能簡化代碼,減少不必要的麻煩,並且不必擔心NullPointer異常(除非這對性能會造成很大的影響)。
11.優先使用標準異常
總結來說好處就是:追求代碼的重用考慮,在裝載類的性能上面考慮。具體細節如下:
- 它使你對官方API更加易於學習和使用,因爲他與程序員已經熟悉的習慣用法是一致的。
- 對於用到這些API的程序而言,他們的可讀性會更好,因爲他們不會出現很多程序員不熟悉的異常。
- 異常類越少,意味着內存印跡就越小,裝載這些類的時間開銷也越少。
12.儘量使用枚舉替換int
枚舉的本質就是一個類的具體實例。枚舉比普通業務類型int類型的區別跟好處,以及策略枚舉的使用,這些以前寫給直接看==>花樣玩枚舉
13. 局部變量作用域最小化
- 從系統GC的角度考慮(一個變量的週期越短整個gc過程越快)。
2.從棧楨中的局部變量表的可重用性來看,作用域越小系統的棧楨空間利用了越大。- 可以增強代碼的可讀性和可維護性,並降低出錯的可能性。
應該:
- 在第一次使用某個局部變量的地方進行聲明。
- 如果你還沒有足夠的前置信息來對一個變量進行有意義的初始化,就應該推遲這個聲明,直到可以初始化爲止。
- 儘量將方法小而集中。方法的功能儘量單一。
14. 對於精度技術不用float或double
Java在允許float或者double類之間計算的時候會有誤差。
double a = 0.2;
double b = 0.1;
double c = a+ b;
System.out.println(c);
-----
0.30000000000000004
要用:BigDecimal 或者FloatDecimal。具體類用法自行百度。
15.字符串操作少用String
這點操作是大家都知道的,String定義爲private final byte[] value;
是不可變的。StringBuilder 和 StringBuffer都繼承於:AbstractStringBuilder
他們的底層使用的是沒有用final
修飾的字符數組:char[]
1.如果要操作少量的數據用 String;
2. 多線程操作字符串緩衝區下操作大量數據 StringBuffer;
3. 單線程操作字符串緩衝區下操作大量數據 StringBuilder。
16.對資源的close建議分開操作
比如說我們有這樣的一個close方法,
try{
a.close()
b.close()
}catch(Exception e){
...
}
上面這樣寫一旦a關閉的時候出錯了,b的關閉也會出錯。儘量分開來關閉。
try{
a.close()
}catch(Exception e){
...
}
try{
b.close()
}catch(Exception e){
...
}
17. 數據類型轉換
基本數據類型轉換爲String的時候注意性能優化,比如Integer類型數據轉換爲String一般有三種方法
- Integer.toString方法,首先推薦。速度最好
- String.valueOf() ,該方法底層調用的是Integer.toString方法,速度中等。
- i + “” 這種方法,底層是用StringBuilder實現,先用append方法拼接,再用toString方法獲取字符串。速度最慢。
18. 不用的對象記得置NULL
我們不用一個空間對象後而沒有將其置NULL,JDK底層代碼對用不到的對象都會立馬置空,如果不這樣容易造成內存泄露,比如我自己實現了一個棧
public class Stack {
public Object[] elements;
private int size = 0; //指示器,用來指示當前棧頂的位置
private static final int Cap = 16;
public Stack() {
elements = new Object[Cap];
}
//入棧
public void push(Object e){
elements[size] = e;
size++;
}
//出棧
public Object pop(){
size = size-1;
Object object = elements[size];
elements[size] = null; // 這裏很重要 JDK底層都是這樣實現的,不用了,即使置NULL
return object;
}
}
19. if判斷常量在前
if(i==1)
跟if(1==i)
看起來沒有差別,但是從容錯性上面來考慮,如果手誤寫成了if(i=1)
就是賦值語句了,而if(1=i)
則不會出現這樣的錯誤。因此if判斷建議常量在前,變量在後。
20. 字符串變量比較的時候
str.equal("sowhat")
跟“sowhat”.equal(str)
功能上看是一樣的,但是從代碼的健壯性來看推薦後者,因爲你無法確實str
一定是非空,可以避免空指針異常。
21. 同步方法跟同步方法塊
儘量使用同步方法塊而不是同步方法,這點在多線程模塊中的synchronized鎖 方法塊文章中已經講得很清楚了,除非我們能確定一整個方法都是需要進行同步的,否則儘量使用同步代碼塊,避免對那些不需要進行同步的代碼也進行了同步,影響了代碼執行效率。
22.方法要儘可能小
一個方法要儘量實現單一指責,方法編譯後字節碼越小越可能會引發JIT的方法內聯,
public void SetAge(int age){
this.age = age
}
-------JIT 優化後直接就是 如下,避免類方法調用。
this.age = age
23.一定記得寫註釋
代碼寫的再好也要記得寫類跟方法的大概註釋,不然接收你工作的人絕對分分鐘化身祖安玩家!