public static final int[] VALUES={...} //錯誤
//正確1:增長一個公有的不可變列表
private static final int[] VALUES=...
public static final List< intergeR > VALUES=
{Collections.unmodifiableList(Arrays.adList(PRIVATE_VALUES));
//正確2:返回私有數組的拷貝
private static final int[] VALUES=...
public static final int[] values() {
return VALUES.clone();
}
複製代碼
public class Point{ //錯誤
public int x;
public int y;
}
public class Point{ //正確
private int x;
private int y;
public int getX() { return x;}
}
複製代碼
結合上面說到的,HashSet是implement Set類的,在HashSet裏重寫了Set接口定義的add,addAll等方法。所以新的子類繼承Hashset重寫add、addAll就不可避免會將HashSet裏的實現繼承下來。html
使用裝飾者模式:ForwardingSet implements Set,該類有成員private final Set s s,構造器裏就是傳入一個Set ,該類不具體實現Set的任何方法,例如:java
public boolean add(E e) {
return s.add(e);
}
複製代碼
InstrumentedSet extends ForwardingSet,構造器super父類便可,在這個類裏添加一些功能,例如:android
@Override
public boolean add(E e){
count++;
return super.add(e);
}
複製代碼
這種模式下,InstrumentedSet 只是一個包裝類,只是對其成員Set進行修飾,爲它增長計數特性。包裝類並不實現具體功能,構造器裏傳入的就是實現具體功能的Set,能夠是HaseSet或者本身實現的Set。程序員
另可參考閱讀: Android源碼學習之裝飾模式應用編程
public class Son extends Father {
public Son() {
// super(); //沒加默認調用父類無參構造方法
super("from son");
Log.e("zyz", "son-constructor");
}
public Son(String str) {
// super(); //沒加默認調用父類無參構造方法
Log.e("zyz", str + " son-constructor-with-params");
}
@Override
public void print() {
Log.e("zyz", "son-print");
}
}
public class Son extends Father {
public Son() {
// super(); //沒加默認調用父類無參構造方法
super("from son");
Log.e("zyz", "son-constructor");
}
public Son(String str) {
// super(); //沒加默認調用父類無參構造方法
Log.e("zyz", str + " son-constructor-with-params");
}
@Override
public void print() {
Log.e("zyz", "son-print");
}
}
複製代碼
抽象類能夠寫實例方法,經過派生繼承,實現代碼複用(子類可直接調用父類方法),但因爲重用方法增長了耦合度,接口的方法必定須要重寫,最大程度實現瞭解耦。數組
標籤類: 例如使用枚舉或常量定義了圓和矩形,成員裏有半徑、長、寬。在公共方法 計算面積裏,使用switch來判斷是那種形狀,再分別計算。相似的把多個實現亂七八糟地擠在單個類中,破壞可讀性,又增長了內存佔用,由於實例承擔着屬於其餘類型的域。緩存
應該使用類層次來優化: 定義一個抽象類,包含抽象方法:將共有的方法(計算面積),若是有公有的成員還能夠將其放在抽象類中。以後不一樣的類圓和矩形繼承公共抽象類,另外添加本身的參數,並重寫本身的計算面積的方法。安全
若是成員類不要求訪問外圍實例,就要定義成靜態內部類。非靜態內部類始終要保持外圍對象的引用,不只消耗內存,還將致使外圍實例沒法被垃圾回收。 例如Map實現的內部都有Entry對象,每一個Entry都與Map關聯,可是entry的方法(getKey/getValue)等並不須要訪問Map,所以私有的靜態成員類是最佳的選擇。bash
兩者的不一樣點:數據結構
若是B是A的子類,那麼B[]就是A[]的子類型。
//編譯時不報錯,運行時報錯ArrayStoreException
Object[] test = new Long[1];
test[0] = "test";
複製代碼
而兩個不一樣的類型A、B,List既不是List的子類也不是超類。
List<Object> test2 = new ArrayList<Long>(); //編譯時報錯
test2.add("123");
複製代碼
數組在運行時才知道並檢查他們的元素類型約束。泛型則是經過擦除(erasure)來實現的。泛型只在編譯時強化類型信息,在運行時擦除元素類型信息。擦除就是使泛型能夠與沒有使用泛型的代碼隨意互用。
PECE producer-extends,consumer-siper 若是參數化類型表示生產者T,就使用<? extends T>,若是表示消費者T,就使用<? super T>
//src產生E實例供使用,是生產者
public void pushAll(Iterable<? extands E> src) {
for (E e : src) push(e);
}
//dst消費E實例,是消費者
public void popAll(Collection<E> dst) {
while(!isEmpty()) {
dst.add(pop());
}
}
複製代碼
不要用通配符類型做爲返回參數
(android不推薦使用enum)
public enum Test {
APPLE("test1", 2),
pen("test2", 1);
private final String name;
private final int num;
Test(String name, int num) {
this.name = name;
this.num = num;
}
public void print() {
Log.e("zyz", APPLE.name + APPLE.num);
}
}
//遍歷枚舉
Test[] values = Test.values();
複製代碼
@Retention(RetentionPolicy.RUNTIME) //運行時保留
@Target(ElementType.METHOD) //只在方法聲明中才是合適的
public @interface MyTest {
}
複製代碼
覆蓋equals時的參數是Object類型的,不然則變成了重載。但若是使用@Override註解後寫錯了編譯器就會報錯。
若是類的成員是可變的,爲了保護內部信息變化,對於構造器的每一個可變can'shu參數進行保護性拷貝是必要的,使用被封對象做爲實例的組件,而不使用原始的對象。但注意,保護性拷貝是在檢查參數的有效性以前進行的,而且有效性檢查是針對拷貝以後的對象而不是原始對象。
慎用clone。若是對於非final的成員,不能保證clone方法必定返回一樣的類的對象,它有可能返回專門出於惡意目的而設計的不可信子類的實例,例如這樣的子類能夠在每一個實例被建立時把指向該實例的引用記錄到一個私有的靜態列表中,而且容許攻擊者訪問這個列表,這將使得攻擊者能夠自由地控制全部的實例。爲了阻止這種攻擊,對於參數類型能夠被不可信任方子類話的參數,請不要使用clone方法進行保護性拷貝。
另外須要修改訪問方法,返回可變內部域的保護性拷貝:
public Data end() {
return new Data(end.getTime());
}
複製代碼
只要可能,都應該使用不可變的對象做爲對象內部的組件,這樣就沒必要再爲保護型拷貝操心。
類型仍是父類,雖然調用父類方法指向子類引用。
安全而保守的策略是:永遠不要導出兩個具備相同參數數目的重載方法。若是方法使用可變參數,保守的策略是根本不要重載它。
若是客戶端調用這個方法時並無傳遞參數進去,它就會在運行時而不是編譯時失敗。
//帶兩個參數,避免沒有傳參致使的問題
static init min(int firstArg, int... remainingArgs) {
int min = firstArg;
for(int arg : remainingArgs) {
...
}
}
複製代碼
在重視性能的狀況下,使用可變參數要特別小型,可變參數方法的每次調用都會致使進行一次數組分配和初始化。可使用多個重載方法,每一個重載方法帶有0至3個普通參數,當參數數目超過3個時,就使用可變參數方法。
僞隨機數生成器
//錯誤
Math.abs(new Random().nextInt());
//正確
Random.nextInt(int)
複製代碼
瞭解和使用標準類庫提供的便利工具,而不用浪費時間爲那些與工做不太相關的問題提供特別的解決方案。標準類庫太龐大了,以致於不可能去學習全部文檔,可是每一個程序員都應該熟悉java.lang,java.util,某種程度上還有java.io種的內容。有兩種工具值得特別一提。
總而言之,不要從新發明輪子,若是你要作的事情看起來是十分常見的,有可能類庫中已經有某個類完成了這樣的工做。
float和double類型尤爲不適合用於貨幣計算,由於要讓一個float或double精確地表示0.1(或者10的ren'he'qi'ta任何其餘負數次方值)是不可能的。
使用BigDecimal代替double:
BigDecimal bigDecimal = new BigDecimal(0.1);
複製代碼
BigDecimal容許你徹底控制舍入,每當一個操做設計舍入的時候,它容許你從8種舍入模式中選擇其一。可是缺點是與基本運算類型比,不只不方便,並且很慢。若是性能很是關鍵,而且又不介意本身記錄是金子小數點,並且涉及的數值又不太大,就可使用int或long(例如0.1改變單位計做10)。若是數值範圍沒超過9位十進制數字,就可使用int。若是不超過18位數值,就可使用long。若是數值超過18位數字,就必須使用BigDecimal。
當程序裝箱了基本類型值時,會致使高開銷和沒必要要的對象建立。
鏈接操做符不適合運用在大規模的場景中,爲鏈接n個字符串而重複地使用字符串鏈接操做符,須要n的平方級的時間。這是因爲字符串不可變,當兩個字符串被鏈接在一塊兒時,它們的內容都要被拷貝。
使用StringBuilder:
StringBuilder test = new StringBuilder("test");
test.append("test2")
複製代碼
若是有合適的接口類型存在,那麼對於參數、返回值、變量和域來講,就都應該使用接口類型進行聲明。只有當你利用構造器建立某個對象的時候,才真正須要引用這個對象的類。
List<String> list = new ArrayList<>();
複製代碼
這樣會使程序更靈活,當你決定更換實現時,只須要改變構造器中類的名稱:
List<String> list = new Vector<>();
複製代碼
全部的代碼均可以繼續工做,代碼並不知道原來的實現類型,因此對於這種變化並不在乎。
反射機制容許一個類使用另外一個類,即便當前者被編譯的時候後者還根本不存在,然而這種能力也是要付出代價的:
//Dont't do this try { int i = 0; while (true) { range[i++].climb(); } } catch (ArrayIndexOutOfBoundsException e) { } 複製代碼
不要優先使用基於異常的模式:
通常而言,失敗的方法調用應該使對象保持在被調用以前的狀態。具備這種屬性的方法被稱爲具備失敗原子性(failure atomic)。有幾種途徑能夠實現這種效果:
忽略一個異常很是容易,只需將方法調用經過try語句包圍起來,幷包含一個空的catch塊。空的catch塊會使異常達不到應有的目的,至少,catch塊也應該包含一條說明,解釋爲何能夠忽略這個異常。
正確地使用同步能夠保證沒有任何方法會看到對象處於不一致的狀態中。它還能夠保證剛進入同步方法或者同步代碼塊的每一個線程,都看到由同一個鎖保護的以前全部的修改效果。換句話說,讀取一個非long或double類型的變量,能夠保證返回的值是某個線程保存在該變量中的,即便多個線程在沒有同步的狀況下併發地修改這個變量也是如此。
不要使用 Thread.stop方法。要阻止一個線程妨礙另外一個線程,建議作法是讓第一個線程輪訓一個boolean域,這個域一開始爲false,可是能夠經過第二個線程設置爲true,以表示第一個線程將終止本身。因爲boolean域的讀寫操做都是原子的,程序員在訪問這個域的時候再也不使用同步。
實際上,若是讀和寫操做沒有都被同步,同步就不會起做用。
若是變量修飾符是volatile,則讀取變量時不須要鎖,雖然volatile修飾符不執行互斥訪問,但它能夠保證任何一個線程在讀取該域的時候都將看到最近剛剛被寫入的值。
使用volatile的時候務必要當心。
//錯誤
private static volatile int number = 0;
//須要使用synchronization
public static int getNumber() {
return number++;
}
複製代碼
雖然number是原子的,可是增量操做符不是原子的,它首先讀取值,而後寫回一個新值。若是第二個線程在第一個線程讀取舊值和返回新值期間讀取這個域就會出錯。
在一個被同步的區域內部,不要調用設計成要被覆蓋的方法,或者是由客戶端以函數對象的形式提供的方法。這樣的方法是外來的,這個類不知道方法會作什麼事情,也沒法控制它,從同步區域中調用它極可能會致使異常、死鎖或者數據損壞。
一般,你應該在同步區域內作儘量少的工做。若是你必需要執行某個很耗時的動做,應該設法把這個動做移到同步區域的外面。
Java1.5增長了java.util.concurrent,這個包中包含了一個Executor Framework:
ExecutorService executorService = Executors.newSingleThreadExecutor();
//執行提交一個runnable方法
executorService.execute(runnable);
//告訴executor如何優雅地終止
executor.shutdonw();
複製代碼
你能夠利用executor service完成更多的事情。例如,能夠等待一個任務集合中的任何任務或全部任務完成(invokeAny或invokeAll),你能夠等待executor service優雅地完成終止(awaitTermination),能夠在任務完成時逐個地獲取這些任務的結果(ExecutorCompletionService)等。
自從java1.5髮型版本開始,java就提供了更高級的併發工具,他們能夠完成之前必須在wait和notify上手寫代碼來完成的各項工做。其分紅三類:
同步器(Synchronizer)是一些使線程可以等待另外一個線程的對象,容許他們協調動做。最經常使用的同步器是CountDownLatch和Semaphore。
倒計數鎖存器(CountDown Latch)是一次性的障礙,容許一個或者多個線程等待一個或者多個其餘線程來作某些事情。CountDownLatch是惟一構造器帶有一個int類型的參數,這個int參數是指容許全部在等待的線程被處理以前,必須在鎖存器上調用countDown方法的次數。
例如:一個方法帶有一個執行該動做的executor,一個併發級別(表示要併發執行該動做的次數),以及表示該動做的runnable。全部的工做線程自身都準備好,要在time線程啓動時鐘以前運行該動做(爲了實現準確的定時)。當最後一個工做線程準備好運行該動做時,timer線程就「發起頭炮」,同事容許工做線程執行該動做,一旦最後一個工做線程執行完該動做,timer線程就當即中止計時。直接在wait和notify上實現這個邏輯至少來講會很混亂,而在CountDownLatch之上實現則至關簡單:
public long getTime(Executor executor, int councurrency, final Runnable action) throws InterruptedException {
final CountDownLatch ready = new CountDownLatch(councurrency);
final CountDownLatch start = new CountDownLatch(1);
final CountDownLatch done = new CountDownLatch(councurrency);
for (int i = 0; i < councurrency; i++) {
executor.execute(new Runnable() {
@Override
public void run() {
ready.countDown();
try {
start.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
done.countDown();
}
}
});
}
ready.await();
long startNano = System.nanoTime();
start.countDown();
done.await();
return System.nanoTime() - startNano;
}
複製代碼
用ready來告訴timer線程他們已經準備好了。而後工做線程會在start上等待。當最後一個工做線程調用ready.countDown時,timer線程記錄下起始時間,並調用start.countDown,容許全部的工做線程繼續進行。而後timer線程在done上等待,直到最後一個工做線程運行完該動做,並調用donw.countDown。一旦調用這個,timer線程就會甦醒過來,並記錄下結束時間。
wait方法的標準模式:
synchronized(obj) {
while() {
obj.wait(); //release lock, and reacquires on wakeup
}
}
複製代碼
始終應該使用wait循環模式來調用wait方法;永遠不要在循環以外調用wait方法。循環會在等待以前和以後測試條件。
線程安全性的幾種級別。(這份列表並無涵蓋全部的可能,而只是些常見的情形:
//私有鎖對象
private final Object lock = new Object();
public void foo() {
synchronized(lock) {
...
}
}
複製代碼
私有鎖對象模式只能用在無條件的線程安全類上。有條件的線程安全類不能使用這種模式,由於它們必須在文檔中說明:在執行某些方法調用序列時,它們的客戶端程序必須得到哪把鎖。
私有鎖對象模式特別適用於那些專門爲繼承而設計的類。若是這種類使用它的實例做爲鎖對象,之類可能很容易在無心中妨礙基類的操做,反之亦然,出於不一樣的目的而使用相同的鎖,子類和基類極可能會「互相絆住對方的腳」。
有條件的線程安全類必須在文檔中指明哪些方法調用序列須要外部同步,以及在執行這些序列的時候須要得到哪把鎖。若是你編寫的是無條件的線程安全類,就應該考慮使用私有鎖對象來代替同步的方法以防止客戶端程序和子類的不一樣步干擾。
若是處於性能的考慮須要對靜態域使用延遲初始化:
private static class FieldHolder {
static final FieldType field = computeFieldValue();
}
static FieldHolder getField() {
複製代碼
若是處於性能的考慮須要對實例域使用延遲初始化:
private volatile FieldType field;
FieldTpye getField() {
FieldType result = field;
if(result == null) { //First check(no locking)
synchronized (this) {
result = field;
if(result == null) //Second check(with locking)
field = result = computeFieldValue();
}
}
return result;
}
複製代碼
若是須要延遲初始化一個能夠接受重複初始化的實例域:
private volatile FieldType field;
private FieldType getField() {
FieldType result = field;
if(result == null) {
field = result = computeFiedlValue();
}
return result;
}
複製代碼
線程不該該一直處於忙-等狀態,即反覆地檢查一個共享對象,以等待某些事情的發生。
不要讓應用程序的正確性依賴於線程調度器,不然結果獲得的應用程序將既不健壯,也不具備可移植性。不要依賴Thread.yield或者線程優先級。線程優先級能夠用來提升一個已經可以正常工做的程序的服務質量,但永遠不該該用來「修正」一個本來能不能工做的程序。
實現Serializable接口而付出的巨大代價是,一旦一個類被髮布,就大大下降了「改變這個類的實現」的靈活性。 若是一個類實現了Serializable接口,它的字節流編碼(序列化形式)就變成了它的導出的API的一部分,一旦這個類被普遍使用,每每必須永遠支持這種序列化形式。
第二個代價是,它增長了出現bug和安全漏洞的可能性。你可能會忘記確保:反序列化過程必須也要保證全部「由真正的構造器創建起來的約束關係」,而且不容許攻擊者訪問正在構造過程當中的對象的內部信息。
第三個代價是,隨着類發行新的版本,相關的測試負擔也增長了。可序列化的類被修訂後,你必須既要確保「序列化-反序列化」過程成功,也要確保結果產生的對象真正是原始對象的複製品。
內部類不該該實現Serializable。
若是一個類爲了繼承而設計,要更加當心。對於這樣的類而言,在「容許子類實現Serializable接口」或者「禁止子類實現serialzable」二者間的一個折衷方案是:提供一個可訪問的無參構造器,這種方案容許(但不要求)子類實現Serializable接口。