《Effective Java》筆記(下)

《Effective Java》筆記(下)

Enums and Annotations

  • Item 30: Use enums instead of int constants
  • 類型安全
  • 可以為常量提供數(shù)據(jù)和方法的綁定
  • 可以遍歷
  • 實現(xiàn)建議
  • 如果是通用的,應(yīng)該定義為top level enum蛹疯,否則應(yīng)定義為內(nèi)部類
  • constant-specific method implementations
```java
  // Enum type with constant-specific method implementations
  public enum Operation {
      PLUS   { double apply(double x, double y){return x + y;} },
      MINUS  { double apply(double x, double y){return x - y;} },
      TIMES  { double apply(double x, double y){return x * y;} },
      DIVIDE { double apply(double x, double y){return x / y;} };
      abstract double apply(double x, double y);
  }
```
+  結(jié)合constant-specific data
```java
  // Enum type with constant-specific class bodies and data
  public enum Operation {
      PLUS("+") {
          double apply(double x, double y) { return x + y; }
      },
      MINUS("-") {
          double apply(double x, double y) { return x - y; }
      },
      TIMES("*") {
          double apply(double x, double y) { return x * y; }
      },
      DIVIDE("/") {
          double apply(double x, double y) { return x / y; }
      };

      private final String symbol;
      Operation(String symbol) { this.symbol = symbol; }

      @Override public String toString() { return symbol; }
      abstract double apply(double x, double y);
  }
```
+  If switch statements on enums are not a good choice for implementing con- stant-specific behavior on enums, what are they good for? Switches on enums are good for augmenting external enum types with constant-specific behavior.
  • A minor performance disadvantage of enums over int constants is that there is a space and time cost to load and initialize enum types.
  • 所以吗伤,在安卓設(shè)備(手機诈唬、平板)上,應(yīng)該避免使用enum伶丐,減小空間和時間的開銷
  • Item 31: Use instance fields instead of ordinals
  • 每個enum的常量都有一個ordinal()方法獲取其在該enum類型中的位置明吩,但該方法只應(yīng)該在實現(xiàn)EnumSet, EnumMap等類型的時候被使用皆串,其他情形都不應(yīng)該被使用
  • 如果需要為每一個常量綁定一個數(shù)據(jù)入偷,可以使用instance field實現(xiàn)追驴,如果需要綁定方法,則可以用constant-specific method implementations疏之,參考上一個item
  • Item 32: Use EnumSet instead of bit fields
  • bit fields的方式不優(yōu)雅殿雪、容易出錯、沒有類型安全性
  • EnumSet則沒有這些缺點锋爪,而且對于大多數(shù)enum類型來說丙曙,其性能都和bit field相當(dāng)
  • 通用建議:聲明變量時,不要用實現(xiàn)類型其骄,應(yīng)該用接口類型亏镰,例如,應(yīng)該用List<Integer>而不是ArrayList<Integer>
  • EnumSet并非immutable的拯爽,可以通過Conllections.unmodifiableSet來封裝為immutable索抓,但是代碼簡潔性與性能都將受到影響
  • Item 33: Use EnumMap instead of ordinal indexing
  • 同前文所述,應(yīng)該避免使用ordinal毯炮。當(dāng)需要用enum作為下標(biāo)從數(shù)組獲取數(shù)據(jù)時逼肯,可以換個角度思考,以enum作為key從map里面獲取數(shù)據(jù)桃煎。
  • 數(shù)組和泛型不兼容篮幢,因此使用數(shù)組也會導(dǎo)致編譯警告;而且ordinal的值本來就不是表達index含義的为迈,極易導(dǎo)致隱蔽錯誤
  • EnumMap內(nèi)部使用數(shù)組實現(xiàn)三椿,因此性能和數(shù)組相當(dāng)
  • 使用數(shù)組也會導(dǎo)致程序可擴展性下降,考慮以下兩種實現(xiàn)
  // Using ordinal() to index array of arrays - DON'T DO THIS!
  public enum Phase {
      SOLID, LIQUID, GAS;

      public enum Transition {
        MELT, FREEZE, BOIL, CONDENSE, SUBLIME, DEPOSIT;

        // Rows indexed by src-ordinal, cols by dst-ordinal
        private static final Transition[][] TRANSITIONS = {
                { null,    MELT,     SUBLIME },
                { FREEZE,  null,     BOIL    },
                { DEPOSIT, CONDENSE, null    }
        };

        // Returns the phase transition from one phase to another
        public static Transition from(Phase src, Phase dst) {
          return TRANSITIONS[src.ordinal()][dst.ordinal()];
        }
      }
  }

  // Using a nested EnumMap to associate data with enum pairs
  public enum Phase {
      SOLID, LIQUID, GAS;

      public enum Transition {
          MELT(SOLID, LIQUID), FREEZE(LIQUID, SOLID),
          BOIL(LIQUID, GAS),   CONDENSE(GAS, LIQUID),
          SUBLIME(SOLID, GAS), DEPOSIT(GAS, SOLID);

          final Phase src;
          final Phase dst;

          Transition(Phase src, Phase dst) {
              this.src = src;
              this.dst = dst;
          }

          // Initialize the phase transition map
          private static final Map<Phase, Map<Phase,Transition>> m =
              new EnumMap<Phase, Map<Phase,Transition>>(Phase.class);
          static {
              for (Phase p : Phase.values())
                  m.put(p,new EnumMap<Phase,Transition>(Phase.class));
              for (Transition trans : Transition.values())
                  m.get(trans.src).put(trans.dst, trans);
          }

          public static Transition from(Phase src, Phase dst) {
              return m.get(src).get(dst);
          }
      }
  }

當(dāng)需要增加Phase時曲尸,前者需要謹(jǐn)慎地修改TRANSITIONS數(shù)組的內(nèi)容(這一步驟容易出錯)赋续,而后者則只需要增加相應(yīng)Transition即可,from函數(shù)的邏輯完全不受影響另患。

  • Item 34: Emulate extensible enums with interfaces
  • 當(dāng)enum遇到可擴展性時,總是一個糟糕的問題蛾绎;擴展類是基礎(chǔ)類的實例昆箕,但反過來不是,這一點很讓人困惑租冠;想要枚舉所有基礎(chǔ)類和擴展類的enum對象時鹏倘,并沒有一個很好地辦法;
  • 而對于可擴展性的需求顽爹,是真實存在的纤泵,例如:operation codes (opcodes)
  • 實現(xiàn)方式是通過定義一個接口,enum類型(基礎(chǔ)與擴展)均實現(xiàn)該接口镜粤,而在使用enum的地方捏题,接收這個接口作為參數(shù)
  • enum類型是不可擴展的玻褪,但是interface具備可擴展性,如果API使用接口而非實現(xiàn)去代表operation公荧,API就有了可擴展性
  • 泛型高級用法:<T extends Enum<T> & Operation> ... Class<T>带射,T類型是enum類型,且是Operation子類
  • 這一方式的不足:enum類型對接口的實現(xiàn)是不能繼承的
  • Item 35: Prefer annotations to naming patterns
  • 在1.5之前循狰,naming patterns很常見窟社,在JUnit中都是這樣,例如要求測例方法一test開頭
  • naming patterns有很多問題
  • 拼寫錯誤不能及時發(fā)現(xiàn)
  • 無法保證naming patterns只在正確的場景使用绪钥,例如可能有人以test開頭命名測例類灿里,方法卻沒有,JUnit則不會運行測例
  • 沒有值/類型信息程腹,編譯器無法提前發(fā)現(xiàn)問題
  • 使用annotations可以很好的解決這些問題匣吊,但是annotations的功能也是有限的
  • @Retention(RetentionPolicy.RUNTIME)能限定其保留時期
  • @Target(ElementType.METHOD)能限定其應(yīng)用的程序元素
  • 還有其他meta-annotations,如@IntDef
  • annotations接收的參數(shù)如果是數(shù)組跪楞,為其賦值一個單獨的元素也是合法的
  • Item 36: Consistently use the Override annotation
  • @Override會使得重寫的準(zhǔn)確性得到檢查
  • 重載和重寫的區(qū)別:一個只是函數(shù)名一樣缀去,通過參數(shù)列表決定執(zhí)行哪個版本,是編譯時多態(tài)甸祭;一個是通過虛函數(shù)機制實現(xiàn)缕碎,是運行時多態(tài);
  • Item 37: Use marker interfaces to define types
  • 定義一個空的接口池户,表明某個類型的屬性咏雌,例如Serializable
  • 另一種方式是使用annotation,表明者其具有某種屬性
  • marker interface的優(yōu)點
  • 定義了一個類型校焦,可以進行instanceof判斷赊抖,可以聲明參數(shù)類型
  • 比annotation更簡潔
  • marker annotation的優(yōu)點
  • 當(dāng)一個類型(通過interface或者annotation)被聲明后,如果想要加入更多的信息寨典,annotation更方便氛雪,即annotation對修改是開放的,因為它的屬性可以有默認(rèn)值耸成,而interface則不行报亩,定義了方法就必須實現(xiàn)
  • annotation可以被應(yīng)用到更多代碼的元素中,不僅僅是類型
  • 實現(xiàn)建議
  • 如果僅僅只應(yīng)用于類型井氢,則應(yīng)該優(yōu)先考慮annotation
  • 如果希望mark的對象被限定于某個接口的實例(即為一個接口增加另外一種語義弦追,卻不改變其API),可以考慮使用marker interface

函數(shù)

  • Item 38: Check parameters for validity
  • 一個函數(shù)(包括構(gòu)造函數(shù))首先要做的事情就是驗證參數(shù)合法性花竞,如果不合法則應(yīng)該拋出相應(yīng)異常劲件,這是對“盡早發(fā)現(xiàn)錯誤盡早拋出”原則的遵循,否則等到錯誤發(fā)生時將可能難以判斷錯誤的根源所在,甚至程序不會顯式報錯零远,而是執(zhí)行了錯誤的行為苗分,導(dǎo)致更嚴(yán)重的后果
  • 不由被調(diào)用函數(shù)使用,而是存起來留作后用的參數(shù)遍烦,更加要檢查其合法性
  • Javadoc里面應(yīng)該注明@throw項俭嘁,并說明原因
  • 非公開的API(private或package private),則不應(yīng)該通過拋異常來報錯服猪,應(yīng)該采用assert供填,assert可以通過配置虛擬機參數(shù)開啟或關(guān)閉,如果關(guān)閉則不會被執(zhí)行
  • 靈活運用罢猪,設(shè)計API時近她,就應(yīng)該盡量設(shè)計得通用一些,即可以接受更大范圍的參數(shù)膳帕,畢竟檢查參數(shù)也是有開銷的
  • 另外可以考慮拋出RuntimeException的子類粘捎,因為這樣的異常不用放到函數(shù)的異常表中,函數(shù)的使用者也不用必須try-catch或者throw危彩,但doc一定要寫明
  • Item 39: Make defensive copies when needed
  • 編碼一大原則:永遠不要信任用戶(調(diào)用方)輸入的數(shù)據(jù)攒磨,也不要信任它們不會篡改返回的數(shù)據(jù),因此defensive copy很有必要
  • 編寫一個類時汤徽,如果成員變量是mutable的娩缰,那么就需要在構(gòu)造函數(shù)(或者setter)中進行深拷貝,并且谒府,先拷貝拼坎,再驗證已拷貝數(shù)據(jù)的合法性(既不是先驗證,也不是驗證傳入的數(shù)據(jù)完疫,避免TOCTOU attack)
  • 另外深拷貝時泰鸡,傳入對象的類如果不是final的,就不能用clone方法進行拷貝壳鹤,因為不能保證clone方法返回的就正好是這個類的實例(有可能會是惡意的子類)
  • 為mutable成員提供getter方法時盛龄,返回前也要進行深拷貝,但此時可以用clone方法芳誓,因為我們確定成員就是我們想要的類的對象
  • java內(nèi)建的Map, Set等容器讯嫂,實現(xiàn)上是沒有進行深拷貝的,因為是泛型兆沙,所以put進去或者get出來的時候,編譯期都不知道具體是什么類型莉掂,是無法調(diào)用構(gòu)造函數(shù)的葛圃,如果想要測試這一問題,需要確定key和value的類型都是mutable的,如果測Map<String, Integer>库正,那結(jié)果肯定是錯誤的曲楚,但如果測Map<StringBuilder, Date>,就可以知道確實如此褥符;所以如果要把用戶傳入的數(shù)據(jù)放入Map龙誊,且key/value是mutable的,那么就需要在put之前進行深拷貝喷楣,否則可能會被用戶attack
  • 長度非零的數(shù)組都是mutable的
  • 盡量使用immutable的成員就可以省去深拷貝帶來的性能開銷
  • 如果確實信任用戶趟大,就可以把深拷貝省去,但一定要在文檔內(nèi)說明铣焊,例如:wrapper模式逊朽,如果用戶惡意,那損害的也就僅僅是其自身曲伊;或者用戶都是自己的代碼叽讳,可以確信安全。
  • Item 40: Design method signatures carefully
  • 命名要合理坟募,可理解:清除表達函數(shù)的功能岛蚤;符合常識;保持風(fēng)格一致懈糯;
  • 類/接口的成員方法數(shù)量不要太多涤妒,否則會令人難以理解,而且不利于測試昂利、維護
  • 不要隨便提供helper方法届腐,只有當(dāng)很有必要時才提供
  • 避免過長參數(shù)列表(不多于4個),尤其是參數(shù)類型相同蜂奸,否則既難記(倒還好)犁苏,又可能引起隱晦的bug(傳入?yún)?shù)順序錯了,編譯不報錯扩所,運行時行為確是錯的)
  • 可以通過把參數(shù)列表過長的方法拆分為幾個方法围详,但要避免導(dǎo)致方法過多
  • 創(chuàng)建helper類,容納作用相關(guān)聯(lián)的的參數(shù)
  • 類似于構(gòu)造對象的Builder模式祖屏,為函數(shù)的調(diào)用創(chuàng)建一個builder
  • 參數(shù)類型助赞,使用interface,而不是實現(xiàn)類
  • 對于起控制作用的參數(shù)袁勺,使用二值enum雹食,而不是boolean,便于擴展期丰;對于安卓來說群叶,可以通過@IntDef輔助定義int常量吃挑,模擬enum
  • Item 41: Use overloading judiciously
  • 慎用重載,重載(overload)與重寫(override)的區(qū)別可以見上文街立,簡言之舶衬,前者編譯時多態(tài),后者運行時多態(tài)
  • 重載是編譯時多態(tài)赎离,版本選擇在編譯期完成逛犹,根據(jù)編譯期參數(shù)的類型信息來進行決策
  • 建議不要用參數(shù)類型來設(shè)計不同的重載版本,應(yīng)該通過參數(shù)列表長度梁剔,或者沒有父子類關(guān)系的不同參數(shù)類型虽画,例如接受int和float的類型渊啰,后者也還是可能會有問題
  • Item 42: Use varargs judiciously
  • varargs的原理是調(diào)用時首先創(chuàng)建一個數(shù)組盼樟,然后把傳入的參數(shù)放入數(shù)組,數(shù)組長度為參數(shù)個數(shù)
  • 一個方法需要0或多個同類型數(shù)據(jù)這個需求很常見乐设,然而也有另一個很常見的需求:需要一個或多個同類型數(shù)據(jù)众雷,此時單純用varargs不太優(yōu)雅灸拍,可以讓方法先接受一個數(shù)據(jù),在接受一個varargs
  • varargs最初是為了printf和反射設(shè)計的
  • 可以通過把傳入?yún)?shù)從一個數(shù)組改為varargs砾省,來改良該方法(變得更靈活)鸡岗,而且對已有代碼“無影響”,Arrays.asList便是一個例子编兄,但接受varargs最初是為了打印數(shù)組內(nèi)容設(shè)計的轩性,而不是為了把多個數(shù)據(jù)變成一個List
  • Don’t retrofit every method that has a final array parameter; use varargs only when a call really operates on a variable-length sequence of values.
  • 以下兩種函數(shù)聲明都可能會產(chǎn)生問題:
```java
ReturnType1 suspect1(Object... args) { }
<T> ReturnType2 suspect2(T... args) { }
```

 如果傳入一個基本類型的數(shù)組進去(例如int[]),那么這兩個方法接受的都是一個int[][]狠鸳,即相當(dāng)于接受了一個只有一個元素的數(shù)組揣苏,而這個數(shù)組的數(shù)據(jù)類型是int[]!而如果傳入一個對象的數(shù)組件舵,則相當(dāng)于傳入了數(shù)組長度個數(shù)的varargs卸察。`Arrays.asList`方法就存在這個問題!
  • varargs也存在性能影響铅祸,因為每次調(diào)用都會創(chuàng)建坑质、初始化一個數(shù)組。如果為了不失API靈活性临梗,同時大部分調(diào)用的參數(shù)個數(shù)都是有限個涡扼,例如03個,那么可以聲明5個重載版本盟庞,分別接受03個參數(shù)吃沪,另外加一個3個參數(shù)+varargs的版本
  • Item 43: Return empty arrays or collections, not nulls
  • 可能有人認(rèn)為返回null能減小內(nèi)存開銷,然:
  • 永遠不要過度考慮性能問題什猖,只有當(dāng)profiling顯示瓶頸就是這里的時候巷波,再考慮性能優(yōu)化與代碼優(yōu)雅性的犧牲萎津,當(dāng)然,無副作用的優(yōu)化肯定盡早采納
  • 可以每次需要返回空數(shù)組/集合時抹镊,返回同一個空數(shù)組/集合,這樣就只需要一次內(nèi)存分配
  • Collection的<T> T[] toArray(T[] a)方法荤傲,可以每次調(diào)用時傳入一個空數(shù)組垮耳,因為該方法保證如果集合元素可以被放入提供的參數(shù)數(shù)組中,將不會分配新內(nèi)存遂黍,當(dāng)放不下時才會分配
  • 下面實現(xiàn)返回集合的值的方式也是值得借鑒的
  // The right way to return a copy of a collection
  public List<Cheese> getCheeseList() {
    if (cheesesInStock.isEmpty())
      return Collections.emptyList(); // Always returns same list
    else
      return new ArrayList<Cheese>(cheesesInStock);
  }
  • Item 44: Write doc comments for all exposed API elements
  • 對于API暴露的部分(類终佛、接口、方法雾家、成員等)铃彰,都應(yīng)該先寫好文檔;為了提高代碼的可維護性芯咧,未暴露的部分也應(yīng)該寫好文檔牙捉;
  • 每個方法的文檔的內(nèi)容,應(yīng)該是描述該方法與調(diào)用者之間的約定敬飒,不必是實現(xiàn)細節(jié)邪铲,細節(jié)可以看代碼,約定則是使用者關(guān)心的東西无拗;設(shè)計為被繼承的類带到,方法文檔應(yīng)該描述該方法做了什么,而不是怎么做的英染;
  • 方法的文檔中揽惹,應(yīng)該描述約定的前提條件,執(zhí)行后產(chǎn)生的影響四康,尤其是對于“系統(tǒng)”(或者說這個對象)狀態(tài)的影響搪搏;不符合前提條件的情形將拋出異常;
  • 更多細節(jié)
  • @param, @return, @throws 描述不要句號結(jié)尾
  • @throws 的描述應(yīng)該以if開頭箭养,其他都應(yīng)該是名詞描述
  • @{code}@{literal}
  • 有泛型時慕嚷,需要說明每個類型參數(shù)
  • enum類型要為每個常量注釋含義
  • annotation的定義,要為每個成員/參數(shù)注釋含義
  • 線程安全性說明毕泌,可見性說明喝检,序列化說明

編程通用

  • Item 45: Minimize the scope of local variables
  • 在變量第一次使用的時候進行聲明,聲明時盡量就進行初始化
  • 因此也更傾向于使用for-loop撼泛,而不是while-loop挠说,因為后者需要使用while-loop外定義的控制變量
  • for-loop的終結(jié)條件變量n,也應(yīng)該在循環(huán)變量i初始化時計算愿题,避免重復(fù)計算
  • 保持方法簡短损俭,一個方法只做一件事
  • Item 46: Prefer for-each loops to traditional for loops
  • 優(yōu)點之一:可以避免一些容易犯的bug
```java
  // Can you spot the bug?
  enum Suit { CLUB, DIAMOND, HEART, SPADE }
  enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
              NINE, TEN, JACK, QUEEN, KING }
  ...
  Collection<Suit> suits = Arrays.asList(Suit.values());
  Collection<Rank> ranks = Arrays.asList(Rank.values());
  List<Card> deck = new ArrayList<Card>();
  for (Iterator<Suit> i = suits.iterator(); i.hasNext(); )
      for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); )
          deck.add(new Card(i.next(), j.next()));
```

`i.next()`在內(nèi)層循環(huán)被調(diào)用了多次蛙奖。以下寫法則直觀且不易出錯:

```java
  // Preferred idiom for nested iteration on collections and arrays
  for (Suit suit : suits)
      for (Rank rank : ranks)
          deck.add(new Card(suit, rank));
```
  • 此種寫法不僅可用于集合和數(shù)組,任何實現(xiàn)Iterable接口的類都可以用于冒號后面的部分
  • 缺點
  • 有性能代價杆兵!一定會創(chuàng)建Iterator雁仲,對于安卓開發(fā),不建議如此琐脏。
  • 不能在for-each語法中進行remove攒砖,用Iterator遍歷時,能remove
  • 遍歷過程中替換原有元素
  • Parallel iteration
  • Item 47: Know and use the libraries
  • don't reinvent the wheel
  • 視野日裙!
  • Item 48: Avoid float and double if exact answers are required
  • float和double設(shè)計為用于科學(xué)計算吹艇,“精確近似”,需要確切結(jié)果的昂拂,不要使用受神,例如:貨幣相關(guān)!應(yīng)該使用BigDecimal, int, 或者long格侯。
  • BigDecimal使用有些不方便鼻听,性能也比primitive類型低
  • Item 49: Prefer primitive types to boxed primitives
  • 兩種類型的區(qū)別
  • boxed類型,除了包含數(shù)值外养交,還有不同的唯一標(biāo)示精算,即值一樣,對象可以不一樣碎连,這一點很重要灰羽!
  • boxed類型,比primitive類型多一個值鱼辙,null
  • boxed類型廉嚼,時間、空間效率均低一些
  • caveats
  • 有些操作會auto-unbox倒戏,例如:加減乘除怠噪,大小比較,但判等(==)不會杜跷!
  • Applying the == operator to boxed primitives is almost always wrong.
  • boxed類型傍念,值為null時,會unbox為什么呢葛闷?會拋出NullPointerException
  • 當(dāng)boxed和primitive出現(xiàn)在同一個運算中憋槐,boxed類型會auto-unbox(包括判等)
  • 大量重復(fù)的box/unbox會導(dǎo)致性能大幅下降
  • 使用場景與注意事項
  • 放到標(biāo)準(zhǔn)集合里面,必須是boxed類型
  • 作為類型參數(shù)(泛型)淑趾,必須是boxed類型
  • auto-box是安全的阳仔,也能省去繁瑣的代碼,但是auto-unbox則可能引起隱蔽的錯誤
  • Item 50: Avoid strings where other types are more appropriate
  • Strings are poor substitutes for other value types. 只有當(dāng)數(shù)據(jù)確實就是文本時扣泊,才適合用String近范。
  • Strings are poor substitutes for enum types.
  • Strings are poor substitutes for aggregate types. 把一系列數(shù)據(jù)轉(zhuǎn)化為一個String(序列化)嘶摊,然后再反序列化,也應(yīng)該用Json评矩,如果自定義分隔符叶堆,既不優(yōu)雅,也不安全稚照。
  • Strings are poor substitutes for capabilities. capability是一種稱呼蹂空,通常就是說不同的對象,憑借一個key去同一個地方保存果录、獲取數(shù)據(jù);如果用String咐熙,那么如果內(nèi)容相同弱恒,那key就會沖突,不安全棋恼;ThreadLocal的發(fā)展史*返弹。
  • Item 51: Beware the performance of string concatenation
  • +連接n個String,時間復(fù)雜度為O(n^2)爪飘,因為String是immutable的义起,所以每次拼接都會拷貝兩者的內(nèi)容
  • 使用StringBuilder進行拼接操作;不過對于安卓開發(fā)來說师崎,基本沒什么影響默终,因為在打包的過程中,這一優(yōu)化會自動完成犁罩;
  • Item 52: Refer to objects by their interfaces
  • 如果有接口齐蔽,那么函數(shù)參數(shù)、返回值床估、成員變量含滴、局部變量,都應(yīng)該使用接口來保持對象的引用丐巫,只有在通過構(gòu)造函數(shù)創(chuàng)建對象時才應(yīng)該引用具體的實現(xiàn)類型谈况;面向接口編程更廣義的實踐;
  • 面向接口編程使得程序更加靈活递胧,切換實現(xiàn)類非常簡單碑韵;但如果代碼功能/正確性依賴于實現(xiàn)類特有的特性,那么切換時就需要仔細考慮一下谓着;
  • 當(dāng)然泼诱,如果對應(yīng)功能的接口不存在,那直接引用該類當(dāng)然是可以的赊锚;value type; class-based framework; 或者實現(xiàn)類提供了接口不存在的功能
  • Item 53: Prefer interfaces to reflection
  • 反射可以訪問私有成員
  • 反射可以調(diào)用編譯時不存在的類的方法治筒,當(dāng)然需要運行時已經(jīng)加載
  • 但是反射也是有代價的
  • 編譯期的類型檢查完全失效屉栓,類型安全性喪失
  • 反射代碼繁瑣且易出錯,當(dāng)然這一點有一些好的框架可以避免耸袜,例如JOOR
  • 性能下降友多,反射調(diào)用性能會低很多
  • 反射常用的場景
  • class browsers, object inspectors, code analysis tools, and interpretive embedded systems, remote procedure call (RPC) systems
  • 反射功能強大,也有一些不足堤框,如果合適利用域滥,還是非常方便的
  • 例如編譯期有些類尚未獲得,但是如果有其父類/接口蜈抓,則可以聲明為父類/接口启绰,只通過反射創(chuàng)建實例,其余代碼都無需反射
  • Item 54: Use native methods judiciously
  • 設(shè)計之初的三大用途
  • 訪問平臺相關(guān)的功能沟使,例如registries and file locks
  • 訪問老的C/C++版本的庫委可,訪問老的數(shù)據(jù)
  • 追求性能
  • 近年來JVM/Java的發(fā)展,性能已有很大改善腊嗡,追求性能而使用JNI通常來說都已經(jīng)沒必要了
  • JNI的劣勢
  • 不安全着倾,內(nèi)存管理不受JVM控制了,溢出等問題都有可能發(fā)生了
  • 平臺相關(guān)
  • 難以調(diào)試
  • Java和native層的交互是有開銷的
  • native代碼比Java代碼更難懂
  • 對于安卓應(yīng)用開發(fā)來說燕少,JNI還有一點就是隱藏實現(xiàn)卡者,Java代碼反編譯非常容易,而native代碼則難一些
  • Item 55: Optimize judiciously
  • 只有當(dāng)確實需要時客们,才考慮性能優(yōu)化崇决,當(dāng)然一些常見的范式,初次編碼時就應(yīng)該遵循
  • Strive to write good programs rather than fast ones; speed will follow.
  • Strive to avoid design decisions that limit performance.
  • Consider the performance consequences of your API design decisions.
  • It is a very bad idea to warp an API to achieve good performance.
  • 當(dāng)確實需要優(yōu)化性能時:measure performance before and after each attempted optimization.
  • 找到原因后镶摘,首先考慮的是算法的優(yōu)化嗽桩,然后是上層的優(yōu)化
  • 在進行優(yōu)化前,對程序進行profiling凄敢,確定瓶頸碌冶,否則可能浪費精力反而性能下降
  • Item 56: Adhere to generally accepted naming conventions
  • 包名要體現(xiàn)出組件的層次結(jié)構(gòu),全小寫
  • 公布到外部的涝缝,包名以公司/組織的域名開頭扑庞,例如:edu.cmu, com.sun
  • ...

異常處理

  • Item 57: Use exceptions only for exceptional conditions
  • exceptions are, as their name implies, to be used only for exceptional conditions; they should never be used for ordinary control flow.
  • A well-designed API must not force its clients to use exceptions for ordinary control flow.
  • 如果一個類的某個方法,依賴于該類當(dāng)前處于某個特定狀態(tài)拒逮,則應(yīng)該提供一個單獨的狀態(tài)檢查方法罐氨,例如Iterator的next和hasNext方法
  • 另外如果不提供狀態(tài)檢查方法,也可以讓方法在異常狀態(tài)下滩援,返回一個特定的非法值
  • 如果該類被并發(fā)訪問栅隐,且訪問時未進行互斥處理,則必須使用返回非法值的方式;另外考慮到性能因素租悄,也更傾向于返回非法值谨究;其他情況下,都應(yīng)該使用狀態(tài)檢查方法泣棋,可讀性更好胶哲,更容易檢查錯誤;
  • Item 58: Use checked exceptions for recoverable conditions and runtime exceptions for programming errors
  • use checked exceptions for conditions from which the caller can reasonably be expected to recover.
  • unchecked exception: RuntimeException, Error通常都不需要潭辈、也不應(yīng)該catch
  • Use runtime exceptions to indicate programming errors. 通常用于表示程序運行的狀態(tài)違背了前提條件鸯屿,違背了API的約定
  • all of the unchecked throwables you implement should subclass RuntimeException
  • Item 59: Avoid unnecessary use of checked exceptions
  • 如果即便合理的調(diào)用了API也會遇到異常情形,并且捕獲異常之后能夠進行一些有意義的操作把敢,才應(yīng)該使用checked exception寄摆,其他情況下都應(yīng)該使用RuntimeException
  • 通常,如果一個方法會拋出checked exception修赞,都可以將其拆分為兩個方法冰肴,一個用于判斷是否會拋出異常,另一部分用于處理正常情況榔组,如果不符合約定,就拋出RuntimeException联逻,這樣使得API更易用搓扯,也更靈活;但是要考慮狀態(tài)檢查和執(zhí)行之間包归,是否可能從外部其他線程修改對象的狀態(tài)锨推;
  • Item 60: Favor the use of standard exceptions
  • IllegalArgumentException, IllegalStateException, NullPointerException, IndexOutOfBoundsException, ConcurrentModificationException, UnsupportedOperationException
  • Item 61: Throw exceptions appropriate to the abstraction
  • exception translation: higher layers should catch lower-level exceptions and, in their place, throw exceptions that can be explained in terms of the higher-level abstraction.
```java
// Exception Translation
try {
    // Use lower-level abstraction to do our bidding
    ...
} catch(LowerLevelException e) {
    throw new HigherLevelException(...);
}
```
  • While exception translation is superior to mindless propagation of excep- tions from lower layers, it should not be overused.
  • Item 62: Document all exceptions thrown by each method
  • Always declare checked exceptions individually, and document precisely the conditions under which each one is thrown using the Javadoc @throws tag. 不要通過聲明拋出多個異常的父類來實現(xiàn)拋出多種異常的效果。
  • 要為每個方法可能拋出的unchecked exception寫文檔公壤,但是不要將這些異常放到方法聲明的異常表中去换可。便于API使用者區(qū)分checked和unchecked exception。
  • 如果一個類的很多方法都拋出同一個異常厦幅,那么可以將文檔放到class doc中沾鳄,而不是method doc中。
  • Item 63: Include failure-capture information in detail messages
  • To capture the failure, the detail message of an exception should contain the values of all parameters and fields that “contributed to the exception.”
  • 良好設(shè)計的Exception類确憨,應(yīng)該把它需要的詳細信息都作為構(gòu)造函數(shù)的參數(shù)译荞,而不是統(tǒng)一接收String參數(shù);這樣將把生成有意義的detail信息的任務(wù)集中在了Exception類本身休弃,而不是其使用者吞歼。
  • checked exception可以為failure-capture information提供訪問方法,以便于使用者在程序上進行恢復(fù)處理塔猾;雖然unchecked exception通常不會在程序中進行恢復(fù)篙骡,但是提供同樣的方法也是建議的做法。
  • Item 64: Strive for failure atomicity
  • Generally speaking, a failed method invocation should leave the object in the state that it was in prior to the invocation. 滿足此屬性的方法稱為 failure atomic。
  • immutable對象是最簡單的實現(xiàn)方法
  • mutable對象要達到此效果糯俗,就需要在進行操作前尿褪,對所有的參數(shù)、field進行檢查
  • 有可能無法在函數(shù)的第一部分進行檢查叶骨,但是一定要在對對象進行修改之前進行檢查
  • 還有一種不太常見的方式:函數(shù)內(nèi)部捕獲異常茫多,異常發(fā)生之后先回退對象的狀態(tài),再把異常拋出去
  • 還可以先創(chuàng)建一個臨時的對象忽刽,在臨時對象上進行操作天揖,成功后替換原對象的值
  • 有的情況下,failure atomic是不可能的跪帝,所以也就沒必要為此做出努力了
  • 有的情況下今膊,為了failure atomic,會增加很多額外的開銷伞剑、復(fù)雜度斑唬,也就不太必要了
  • 當(dāng)方法不滿足failure atomic時,需要在文檔中進行說明
  • Item 65: Don’t ignore exceptions
  • An empty catch block defeats the purpose of exceptions
  • At the very least, the catch block should contain a comment explaining why it is appropriate to ignore the exception.
  • 忽略異常黎泣,可能導(dǎo)致程序在其他不相關(guān)的地方失敗/崩潰恕刘,這時將很難找到/解決根本問題

并發(fā)

  • Item 66: Synchronize access to shared mutable data
  • synchronized不僅是為了保證每個線程訪問/執(zhí)行時,看到的都是“正常狀態(tài)”的對象(所謂正常就是沒有發(fā)生多線程同時未加同步的寫同一個對象抒倚,導(dǎo)致其狀態(tài)不一致)褐着;還能保證每個線程看到的都是最新的對象;
  • Java語言保證了基本類型中除了long和double的訪問都是原子性的托呕,并發(fā)寫這些類型的數(shù)據(jù)而不進行同步控制含蓉,也不會有問題
  • 有人建議訪問具有原子性操作屬性的對象無需進行同步控制,還能提升性能项郊,純屬一派胡言
  • Java語言不會保證并發(fā)訪問時馅扣,其他線程寫的值能立即被讀的線程感知,所以同步操作不僅僅是為了互斥訪問着降,也是為了保證多線程之間看到的始終是最新的值
  • 上述問題的根本原因就是Java memory model
  • 一個簡單差油、常見、易錯的例子
  • 如何停止后臺線程鹊碍?首先不能調(diào)用Thread.stop方法厌殉,這個方法會導(dǎo)致data corruption
  • 常用的方法就是用一個boolean變量,后臺線程根據(jù)其值決定是否停止侈咕,而主線程想要停止后臺線程時公罕,修改這個變量的值即可
  • boolean的讀寫操作是原子性的,并發(fā)訪問不加同步耀销,不會導(dǎo)致data corruption楼眷,但是卻無法保證主線程對變量的修改能及時被后臺線程感知铲汪,甚至無法保證能被感知
  • 指令重排,如果done就是個普通聲明的boolean罐柳,以下變換在Java memory model下是允許的
    while (!done)
      i++;
    
    //==>
    if (!done)
      while (true)
        i++;
    
  • 可想而知掌腰,如果未進行同步操作,后臺線程將永遠不會停止
  • 解決方法有兩種
  • done的讀寫訪問都加上synchronized张吉,注意齿梁,讀寫都需要,否則沒有數(shù)據(jù)同步(communication)的效果肮蛹;由于boolean的讀寫訪問是原子性的勺择,所以這里的synchronized僅僅起數(shù)據(jù)同步的作用;
  • 聲明done的時候加上volatile關(guān)鍵字伦忠,volatile沒有互斥的作用省核,僅僅是起數(shù)據(jù)同步的作用,在這里正好滿足需求昆码;這種方式性能比上一種要好一些气忠;
  • 使用volatile需要格外謹(jǐn)慎,因為它并沒有互斥作用赋咽,如果聲明一個volatile int旧噪,然后對其進行++操作,那將會導(dǎo)致data corruption脓匿,因為++不是原子性的
  • 對于這種需求舌菜,可以聲明為synchronized int;更好的方式是使用java.util.concurrent.atomic包下的類亦镶,安全,高效袱瓮;
  • 更根本的解決方式就是不要多線程共享mutable對象缤骨,而是共享immutable對象;甚至不要多線程共享數(shù)據(jù)尺借;
  • 引入框架/庫時绊起,需要考慮一下它們是否會引入多線程問題
  • effectively immutable:對象不是真的immutable,但是對象分享出去之后燎斩,就不會再改變了虱歪;當(dāng)然這個還是很危險的,因為并沒有強制的機制保證不會被修改栅表;
  • 小結(jié):多線程訪問共享變量時笋鄙,讀和寫都需要進行同步操作
  • Item 67: Avoid excessive synchronization
  • 在同步代碼塊中,不要調(diào)用可能被重寫的方法怪瓶,更不要調(diào)用使用者傳入對象的方法萧落,因為這些代碼是不可控的,可能導(dǎo)致異常、死鎖找岖、data corruption
  • 對于Observer模式中的observer list陨倡,Java 1.5之后有一個單獨優(yōu)化的高效并發(fā)容器:CopyOnWriteArrayList,每次寫(添加许布、刪除)操作都會從內(nèi)部的數(shù)組創(chuàng)建一份新的拷貝兴革,讀(遍歷)操作時完全不用加鎖,對于讀多寫少的場景性能很好
  • 一個總的原則是蜜唾,在同步代碼塊中杂曲,執(zhí)行盡可能少的操作;如果有耗時操作灵妨,應(yīng)該在保證安全的前提下解阅,嘗試各種手段,將其移出同步塊泌霍;
  • 過度同步的性能影響
  • 喪失了多核CPU的并行性货抄,獲得鎖的開銷倒是其次
  • 任何時刻都需要保證每個CPU核心之間的數(shù)據(jù)同步,這有不小的開銷
  • 限制了JVM的代碼優(yōu)化空間
  • 共享數(shù)據(jù)的并發(fā)訪問朱转,一定要保證線程安全蟹地;如果可以在類內(nèi)部,通過少量/高效的同步塊保證藤为,就不要把整個類的任何操作都加鎖怪与;如果做不到,那就不要進行任何同步缅疟,把這個責(zé)任交給使用者分别,給他們優(yōu)化的空間,但一定要在文檔中說明存淫;
  • 如果static成員可以被某些方法修改耘斩,那一定要為它們加鎖,因為這種情況下使用者無法保證線程安全性
  • Item 68: Prefer executors and tasks to threads
  • Executor Framework
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(runnable);
executor.shutdown();
  • Executors提供了多個工廠方法桅咆,創(chuàng)建ExecutorService括授,還可以直接使用ThreadPoolExecutor,對線程池做更精細的控制
  • 如果程序負(fù)載輕岩饼,可以使用Executors.newCachedThreadPool荚虚,任務(wù)提交時如果沒有空閑線程,將創(chuàng)建新的線程籍茧;如果負(fù)載重版述,用Executors.newFixedThreadPool更合適;
  • 不僅不應(yīng)該自己實現(xiàn)任務(wù)隊列寞冯,甚至都應(yīng)該避免直接使用線程院水,而是使用Executor Framework腊徙;
  • 任務(wù)和機制被分別抽象了,前者為RunnableCallable檬某,后者則是executor service撬腾;
  • java.util.Timer也盡量不要用了,可以使用ScheduledThreadPoolExecutor恢恼;
  • Item 69: Prefer concurrency utilities to wait and notify
  • 正確使用waitnotify有難度民傻,而Java又提供了更高層的抽象,何樂而不用呢场斑?
  • java.util.concurrent包主要包含三塊:
  • Executor Framework
  • concurrent collections
  • synchronizers
  • concurrent collections提供了標(biāo)準(zhǔn)容器的多線程高性能版本漓踢,它們內(nèi)部進行了同步互斥操作,保證正確性漏隐;外部使用的時候喧半,無需加鎖,否則只會導(dǎo)致性能下降青责;
  • concurrent collections中的每一種實現(xiàn)挺据,可能都有性能優(yōu)化的側(cè)重點,可能有的是多讀少寫高效脖隶,例如CopyOnWriteArrayList扁耐,所以使用時需要了解清楚其試用場景;
  • 除非有明確的理由产阱,否則婉称,優(yōu)先使用ConcurrentHashMap,而不是Collections.synchronizedMap或者Hashtable构蹬;也盡量避免在使用者那端進行同步操作王暗;
  • 有的concurrent collections提供了block操作接口,例如BlockingQueue庄敛,從中取數(shù)據(jù)的時候瘫筐,如果隊列為空,線程將等待铐姚,新的數(shù)據(jù)加入后,將自動喚醒等待的線程肛捍;大部分的ExecutorService都是采用這種方式實現(xiàn)的隐绵;
  • Synchronizers: CountDownLatch, Semaphore, CyclicBarrier, Exchanger
  • CountDownLatch: 多個線程等待另外一個或多個線程完成某種工作
  • 注意thread starvation deadlock問題
  • Thread.currentThread().interrupt() idiom:異常可能從其他線程拋出拙毫?用此方法回到原來的線程依许?
  • 計時的話,用System.nanoTime()而不是System.currentTimeMillis()缀蹄,前者更準(zhǔn)確峭跳,更明確
  • 如果非要用waitnotify膘婶,注意以下幾點:
  • Always use the wait loop idiom to invoke the wait method; never invoke it outside of a loop.
  • wait前的條件檢查可以保證不會死鎖,wait后的檢查可以保證安全
  • 通常情況下都應(yīng)該使用notifyAll
  • Item 70: Document thread safety
  • 一個方法的聲明中加了synchronized并不能保證它是線程安全的蛀醉,并且Javadoc也不會把這個關(guān)鍵字輸出到文檔中
  • 線程安全也分好幾個層次悬襟,文檔中應(yīng)該說明類/方法做到了何種程度上的線程安全
  • 線程安全的分類
  • immutable,對象創(chuàng)建后不可修改拯刁,無需進行外部的同步操作(互斥訪問控制或許更恰當(dāng))脊岳;例如:String, Long, BigInteger
  • unconditionally thread-safe垛玻,對象可變割捅,但是其內(nèi)部進行了正確的同步操作,無需外部進行同步帚桩;例如:ConcurrentHashMap亿驾;
  • conditionally thread-safe,和絕對線程安全類似账嚎,但是有些方法需要進行外部的同步操作莫瞬;例如:Collections.synchronized返回的容器,它們的iterator使用時需要進行同步醉锄;
  • not thread-safe乏悄,類自身沒有任何同步操作,需要使用者自己保證線程安全恳不;例如:ArrayList檩小;
  • thread-hostile,由于類的實現(xiàn)原因烟勋,使用者無論如何也無法保證線程安全规求,例如未加同步的修改static成員;例如:System.runFinalizersOnExit卵惦;
  • jsr-305引入了幾個注解:Immutable, ThreadSafe, NotThreadSafe阻肿,對應(yīng)上述前四種情形,絕對線程安全與條件線程安全都屬ThreadSafe沮尿,對于條件線程安全還應(yīng)在文檔中說明何種情況下是需要外部進行同步的丛塌;
  • 如果一個類,將它用于synchronized的對象暴露出去了畜疾,那是很危險的赴邻,通常的做法是,內(nèi)部創(chuàng)建一個Object實例啡捶,將其用于synchronized姥敛,但這種方式通常只適用于unconditionally thread-safe的實現(xiàn)。
```java
  // Private lock object idiom - thwarts denial-of-service attack
  private final Object lock = new Object();
  public void foo() {
    synchronized(lock) {
      ...
    }
  }
```
  • Item 71: Use lazy initialization judiciously
  • don’t do it unless you need to
  • 如果使用lazy initialization瞎暑,那這個成員的訪問方法要用synchronized修飾
  • 靜態(tài)成員實現(xiàn)lazy initialization且希望高性能彤敛,使用lazy initialization holder class idiom与帆,例如:
```java
  // Lazy initialization holder class idiom for static fields
  private static class FieldHolder {
    static final FieldType field = computeFieldValue();
  }
  
  static FieldType getField() { return FieldHolder.field; }
```
  • 實例成員要實現(xiàn)lazy initialization且希望高性能,使用double-check idiom墨榄,但是注意玄糟,double-check并非嚴(yán)格意義的線程安全,例如:
```java
  // Double-check idiom for lazy initialization of instance fields 
  private volatile FieldType field;
  FieldType 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;
  }
```

`result`這個局部變量的作用是渠概,通常情況下茶凳,`field`已經(jīng)初始化過了,這時將只會對其產(chǎn)生一次讀操作播揪,性能會有所提升
  • double-check idiom還有兩個變體贮喧,各有其使用場景:single-check idiom,racy single-check idiom猪狈;前者忍受多次賦值箱沦,后者忍受多次賦值且field的操作具有原子性(primitive類型且不是long和double);
  • Item 72: Don’t depend on the thread scheduler
  • 依賴線程調(diào)度器的正確性雇庙、性能的程序谓形,很可能是無法移植的
  • 好的多線程程序绞幌,同時運行的線程數(shù)不應(yīng)該多于CPU內(nèi)核數(shù)
  • 線程無法進行有意義的工作時弄企,就不應(yīng)繼續(xù)運行,忙等是不好的實現(xiàn)方式
  • 另外一個線程(task)的工作也不能太少彬坏,否則線程切換的開銷都會大于線程執(zhí)行的時間竹椒,此時性能可想而知很低
  • Thread.yield has no testable semantics. 所以不要用Thread.yield童太,當(dāng)程序的有些線程因為線程過多而無法獲得CPU時間時,應(yīng)該減少線程數(shù)胸完。
  • 線程優(yōu)先級是Java平臺中移植性最差的部分书释,所以也不要用
  • Item 73: Avoid thread groups
  • 如果設(shè)計的類需要處理一些邏輯上有關(guān)聯(lián)的線程,應(yīng)該考慮 thread pool executors

Serialization

  • Item 74: Implement Serializable judiciously
  • 實現(xiàn)Serializable接口之后赊窥,一旦類發(fā)布出去爆惧,就不能隨意更改實現(xiàn)方式了,否則序列化-反序列化時可能失敗锨能,這降低了靈活性
  • 序列化-反序列化的格式也是暴露的API之一扯再,而默認(rèn)的格式是和內(nèi)部具體實現(xiàn)細節(jié)綁定的,所以默認(rèn)格式把內(nèi)部實現(xiàn)細節(jié)也暴露出去了
  • 自定義序列化-反序列化格式(ObjectOutputStream.putFields, ObjectInputStream.readFields)址遇,可以緩解上述問題熄阻,但是這又帶來了新的實現(xiàn)復(fù)雜度
  • serialVersionUID問題
  • 會增加bug、安全漏洞的可能性傲隶,因為反序列化得到的對象,其狀態(tài)是無法保證的
  • 會增加發(fā)布新版時的測試工作
  • 被設(shè)計于用來被繼承的類窃页,謹(jǐn)慎實現(xiàn)Serializable接口跺株,同樣复濒,設(shè)計的接口也謹(jǐn)慎繼承Serializable接口
  • 內(nèi)部類不應(yīng)該實現(xiàn)Serializable接口
  • Item 75: Consider using a custom serialized form
  • Do not accept the default serialized form without first considering whether it is appropriate.
  • The default serialized form is likely to be appropriate if an object’s physical representation is identical to its logical content.
  • Even if you decide that the default serialized form is appropriate, you often must provide a readObject method to ensure invariants and security.
  • Regardless of what serialized form you choose, declare an explicit serial version UID in every serializable class you write.
  • Item 76: Write readObject methods defensively
  • readObject方法的功效和public的構(gòu)造函數(shù)一樣
  • 反序列化的時候,readObject如果不進行深拷貝乒省、以及數(shù)據(jù)合法性驗證巧颈,就會導(dǎo)致生成的對象數(shù)據(jù)非法,同時袖扛,也有可能獲得反序列化后對象內(nèi)部成員的引用(rogue object reference attacks)
  • 不要使用writeUnsharedreadUnshared方法砸泛,它們并不安全
  • 前文應(yīng)該提到過,非final類蛆封,構(gòu)造函數(shù)以及readObject方法中唇礁,不能調(diào)用可重載的方法
  • Item 77: For instance control, prefer enum types to readResolve
  • if you depend on readResolve for instance control, all instance fields with object reference types must be declared transient. 否則可能會無法達到實例控制的目的。
  • The accessibility of readResolve is significant.
  • final類惨篱,應(yīng)該置為private
  • Item 78: Consider serialization proxies instead of serialized instances
  • 為需要實現(xiàn)Serializable的類添加一個內(nèi)部類盏筐,它的構(gòu)造函數(shù)接收外部類的實例,并將其field拷貝到自身的field砸讳,并且實現(xiàn)readResolve方法琢融,創(chuàng)建外部類實例,創(chuàng)建方法可以是構(gòu)造函數(shù)簿寂、static factory函數(shù)漾抬,在其中就可以進行實例控制了
```java
  // Serialization proxy for Period class
  private static class SerializationProxy implements Serializable { 
    private final Date start;
    private final Date end;
    
    SerializationProxy(Period p) {
      this.start = p.start;
      this.end = p.end;
    }
    
    // readResolve method for Period.SerializationProxy 
    private Object readResolve() {
      return new Period(start, end);  // Uses public constructor
    }
    
    private static final long serialVersionUID = 234098243823485285L; // Any number will do (Item 75)
  }
```
  • 外部類實現(xiàn)一個writeReplace方法
```java
  // writeReplace method for the serialization proxy pattern
  private Object writeReplace() {
    return new SerializationProxy(this);
  }
```
  • 外部類實現(xiàn)readObject方法,并在其中拋出異常
```java
  // readObject method for the serialization proxy pattern
  private void readObject(ObjectInputStream stream) throws InvalidObjectException {
    throw new InvalidObjectException("Proxy required");
  }
```

摘錄來源:https://notes.piasy.com/Android-Java/EffectiveJava.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末常遂,一起剝皮案震驚了整個濱河市纳令,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌烈钞,老刑警劉巖泊碑,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毯欣,居然都是意外死亡馒过,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門酗钞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來腹忽,“玉大人,你說我怎么就攤上這事砚作【阶啵” “怎么了?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵葫录,是天一觀的道長着裹。 經(jīng)常有香客問我,道長米同,這世上最難降的妖魔是什么骇扇? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任摔竿,我火速辦了婚禮,結(jié)果婚禮上少孝,老公的妹妹穿的比我還像新娘继低。我一直安慰自己,他們只是感情好稍走,可當(dāng)我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布袁翁。 她就那樣靜靜地躺著,像睡著了一般婿脸。 火紅的嫁衣襯著肌膚如雪粱胜。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天盖淡,我揣著相機與錄音年柠,去河邊找鬼。 笑死褪迟,一個胖子當(dāng)著我的面吹牛冗恨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播味赃,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼掀抹,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了心俗?” 一聲冷哼從身側(cè)響起傲武,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎城榛,沒想到半個月后揪利,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡狠持,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年疟位,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片喘垂。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡甜刻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出正勒,到底是詐尸還是另有隱情得院,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布章贞,位于F島的核電站祥绞,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜕径,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一怪蔑、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丧荐,春花似錦、人聲如沸喧枷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽隧甚。三九已至车荔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間戚扳,已是汗流浹背忧便。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留帽借,地道東北人珠增。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像砍艾,于是被迫代替她去往敵國和親蒂教。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法脆荷,而不是構(gòu)造函數(shù)創(chuàng)建對象:僅僅是創(chuàng)建對象的方法凝垛,并非Fa...
    孫小磊閱讀 1,996評論 0 3
  • 文章作者:Tyan博客:noahsnail.com | CSDN | 簡書 CHAPTER3 Method...
    SnailTyan閱讀 736評論 1 4
  • 《Effective Java》筆記(上) 對象的創(chuàng)建與銷毀 Item 1: 使用static工廠方法,而不是構(gòu)造...
    OCNYang閱讀 2,599評論 2 17
  • 鋪子里 古鐘攜著青銹氣敲響了四月 清名謠 穿破了窩藏在角落的蜘蛛網(wǎng) 紅墻外 透過葉脈灑進的湛露陽光 鼓樓下 彝族鄰...
    人間觀光錄閱讀 274評論 4 10
  • 第二十更 《我 我進門了蜓谋! 是的梦皮!我就這么昂首挺胸 抱著不怕死的精神進門了! 結(jié)果 超級超級平靜 為什么呢桃焕? ...
    天涯海角都隨你去閱讀 186評論 0 0