讀《Effect Java中文版》


讀《Effect Java中文版》


譯者序
前言
第1章引言 1
 
第2章創建和銷毀對象 4
第1條:考慮用靜態工廠方法代替構造函數 4
第2條:使用私有構造函數強化singleton屬性 8
第3條:通過私有構造函數強化不可實例化的能力 10
第4條:避免創建重復的對象 11
第5條:消除過期的對象引用 14
第6條:避免使用終結函數 17
 
第3章對於所有對象都通用的方法 21
第7條:在改寫equals的時候請遵守通用約定 21
第8條:改寫equals時總是要改寫hashCode 31
第9條:總是要改寫toString 36
第10條:謹慎地改寫clone 39
第11條:考慮實現Comparable接口 46
 
第4章類和接口 51
第12條:使類和成員的可訪問能力最小化 51
第13條:支持非可變性 55
第14條:復合優先於繼承 62
第15條:要么專門為繼承而設計,並給出文檔說明,要么禁止繼承 67
第16條:接口優於抽象類 72
第17條:接口只是被用於定義類型 76
第18條:優先考慮靜態成員類 78
 
第5章 C語言結構的替代 82
第19條:用類代替結構 82
第20條:用類層次來代替聯合 84
第21條:用類來代替enum結構 88
第22條:用類和接口來代替函數指針 97
 
第6章方法 100
第23條:檢查參數的有效性 100
第24條:需要時使用保護性拷貝 103
第25條:謹慎設計方法的原型 107
第26條:謹慎地使用重載 109
第27條:返回零長度的數組而不是null 114
第28條:為所有導出的API元素編寫文檔注釋 116
 
第7章通用程序設計 120
第29條:將局部變量的作用域最小化 120
第30條:了解和使用庫 123
第31條:如果要求精確的答案,請避免使用float和double 127
第32條:如果其他類型更適合,則盡量避免使用字符串 129
第33條:了解字符串連接的性能 131
第34條:通過接口引用對象 132
第35條:接口優先於映像機制 134
第36條:謹慎地使用本地方法 137
第37條:謹慎地進行優化 138
第38條:遵守普遍接受的命名慣例 141
 
第8章異常 144
第39條:只針對不正常的條件才使用異常 144
第40條:對於可恢復的條件使用被檢查的異常,對於程序錯誤使用運行時異常 147
第41條:避免不必要地使用被檢查的異常 149
第42條:盡量使用標准的異常 151
第43條:拋出的異常要適合於相應的抽象 153
第44條:每個方法拋出的異常都要有文檔 155
第45條:在細節消息中包含失敗-捕獲信息 157
第46條:努力使失敗保持原子性 159
第47條:不要忽略異常 161
 
第9章線程 162
第48條:對共享可變數據的同步訪問 162
第49條:避免過多的同步 168
第50條:永遠不要在循環的外面調用wait 173
第51條:不要依賴於線程調度器 175
第52條:線程安全性的文檔化 178
第53條:避免使用線程組 181
 
第10章序列化 182
第54條:謹慎地實現Serializable 182
第55條:考慮使用自定義的序列化形式 187
第56條:保護性地編寫readObject方法 193
第57條:必要時提供一個readResolve方法 199
 
中英文術語對照 202
參考文獻 207
模式和習慣用法索引 212
索引 214
 
 
 
讀《Effective java 中文版》(2)
  第1條:考慮用靜態工廠方法代替構造函數
靜態工廠方法的一個好處是,與構造函數不同,靜態工廠方法具有名字。
第二個好處是,與構造函數不同,它們每次被調用的時候,不要求非得創建一個新的對象。
第三個好處是,與構造函數不同,它們可以返回一個原返回類型的子類型的對象。
  靜態工廠方法返回的對象所屬的類,在編寫包含該靜態工廠方法的類時可以並不存在,從而可以成為服務提供者框架(service provider framework,指這樣一個系統:提供者為框架的用戶提供了多個API實現,框架必須提供一種機制來注冊這些實現,以便用戶能夠使用它們,框架的客戶直接使用API而無需關心使用的是哪個實現)的基礎。
例子:JCE
//Provider framework sketch
public abstract class Foo{
//map string key to corresponding class object
private static Map implementations=null;

//initializes implementations map the the first time it's called
private static syncronized void initMapIfNecessary(){
if (implementations==null){
implementations=new HashMap();
//load implementation class name and keys from properties file,
//translate names into class objects using Class.forName and store mappings.
....
}
}
public static Foo getInstance(String key){
initMapIfNecessary();
Class c=(CLass)implementations.get(key);
if(c==null){
return new DefaultFoo();
}
try{
return (Foo)c.newInstance();
}catch(Exception e){
return new DefaultFoo();
}
}
}
靜態工廠方法的主要缺點是,類如果不含公有的或者受保護的構造函數,就不能被子類化。
第二個缺點是,它們與其它的靜態方法沒有任何區別。
  對它們的命名習慣是:
1.       valueOf
該方法返回的實例與它的參數具有相同的值,常用於非常有效的類型轉換操作符
2.       getInstance
返回的實例是由方法的參數來描述的,但不能夠說與參數具有相同的值。常用於提供者框架中。
 
讀《Effective java 中文版》(3)
  第2條:使用私有構造函數強化Singleton屬性
  關於Singleton在《Java Design Patterns A Tutorial》一書(漢譯為JAVA設計模式)的第6章也有論述。
  Singleton(只能被實例化一次的類)的實現,要私有的構造函數與公有的靜態成員結合起來,根據靜態成員的不同,分為兩種方法:
1.      公有靜態成員是一個final域
例如://singleton with final field
public class Elvis{
public static final Elvis INSTANCE = new Elvis()[
private Elvis(){
...
}
}
2.      公有靜態成員是一個工廠方法
例如://singleton with static factory method
public class Elvis{
private static final Elvis INSTANCE = new Elvis()[
private Elvis(){
...
}
public static Elvis getInstance(){
return INSTANCE;
}
}

  前者的好處在於成員的聲明即可表明類的singleton特性,且效率可能高一些。
  后者的好處在於提供了靈活性。
 
 
讀《Effective java 中文版》(4)
  第3條:通過私有構造函數強化不可實例化的能力
  如果一個類缺少顯式的構造函數,編譯器會自動提供一個公有的、無參數的默認構造函數(default constructor)。
  我們只要讓這個類包含單個顯式的私有構造函數,則它就不可被實例化了,而企圖通過將一個類做成抽象類來強制該類不可被實例化是行不通的。
  這種做法的一個副作用,是它使得這個類不能被子類化,因為子類將找不到一個可訪問的超類的構造函數。
例如://noninstantiable utility class
public class UtilityClass{
//suppress default constructor for noninstantiability
private UtilityClass(){
//this constructor will never be invoked
}
...
}
說明:工具類(UtilityCLass)指只包含靜態方法和靜態域的類,它不希望被實例化因為對它進行實例化沒有任何意義。
 
 
讀《Effective java 中文版》(5)
第4條:避免創建重復的對象
  從性能的角度考慮,要盡可能地利用已經創建的大對象(創建代價比較高的對象)。
  如果一個對象是非可變的(immutable),則它總是可以被重用。
  對於同時提供了靜態工廠方法和構造函數的非可變類,通常可以用靜態工廠方法而不是構造函數來避免創建重復的對象。
兩個優化例子:
1、在一個循環中,要將
String s=new String("silly");//Don't do this!
改為
String s="No Longer Silly";
2、對於那些已知不會被修改的可變對象,也可以重用它們。
public class Person{
private final Date birthDate;
public Person(Date birthDate){
this.birthDate=birthDate;
}
//Don't Do this!
public boolean isBabyBoomer(){
Calendar gmtCal=Calendar.getinstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
Date boomStart=gmtCal.getTime();
gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
Date boomEnd=gmtCal.getTime();
return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd)<0;
}
}
優化為:
public class Person{
private final Date birthDate;
public Person(Date birthDate){
this.birthDate=birthDate;
}
/**
*The starting and ending dates of the baby boom
*/
private static final Date BOOM_START;
private static final Date BOOM_END;

static{
Calendar gmtCal=Calendar.getinstance(TimeZone.getTimeZone("GMT"));
gmtCal.set(1946,Calendar.JANUARY,1,0,0,0);
Date boomStart=gmtCal.getTime();
gmtCal.set(1965,Calendar.JANUARY,1,0,0,0);
Date boomEnd=gmtCal.getTime();
return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd)<0;
}
public boolean isBabyBoomer(){
return birthDate.compareTo(boomStart)>=0 && birthDate.compareTo(boomEnd)<0;
}
}
當isBabyBoomer從沒被調用過時,優化的方案反而不如沒優化的。:)
  來看一句象費話一樣的話。
  當應該重用一個已有對象的時候,請不要創建新的對象(本條目的核心),當應該創建一個新的對象的時候,請不要重用一個已有對象(第24條的核心,該條講的是保護性拷貝的問題)。
 
 
 
讀《Effective java 中文版》(6)
  第5條:消除過期的對象引用
  下面的例子存在內容泄漏(或者說無意識的對象保持,unintentional object retention)。
//Can u spot the "memory leak"?
public class Stack{
private Ojbect[] elements;
private int size=0;
public Stack(int initialCapacity){
this.elements=new Object[initialCapacity];
}
public void push(Object e){
ensureCapacity();
elements[size++]=e;
}
public Object pop(){
if(size==0) throw new EmptyStackException();
return elements[--size];
}
/**
*Ensure space for at least one more element, roughly doubling
*the capacity each time the array needs to grow.
*/
private void ensureCapacity(){
if(elements.length==size){
Object[] oldElements=elements;
elements=new Object[2*elements.length+1];
System.arrayCopy(oldElements,0,elements,0,size);
}
}
}
  消除內容泄露,只需要改動pop()方法。
public Object pop(){
if(size==0) throw new EmptyStackException();
Object result= elements[--size];
elements[size]=null;
return result;
}
  只要一個類自己管理它的內存,程序員就要警惕內存泄露問題。一旦一個元素被釋放掉,則該元素中包含的任何對象引用應該要被清空。
  內存泄露的另一個常見來源是緩存,因此這時要用一個線程定期清緩存或在加入時清最少使用的緩存對象。在1.4發行版中,可以用java.util.LinkedHashMap的revmoveEldestEntry方法來實現后一方案。
  如果一個持續運行的java應用速度越來越慢,就要考慮是否檢查內存泄露問題。
  書中說檢查內存泄露的軟件統稱heap profiler,我檢索到了兩個http://sourceforge.net/projects/simpleprofiler和http://www.manageability.org/blog/stuff/open-source-profilers-for-java,以后有機會可得好好研究一番。不知讀到此文的朋友,能否再推薦幾個好的工具軟件?
 
 
 
讀《Effective java 中文版》(7)
  第6條:避免使用終結函數
  終結函數(finalizer)通常是不可預測的,常常也是很危險的,一般情況下是不必要的。使用終結函數會導致不穩定的行為、更差的性能,以及帶來移植問題。
  JLS不僅不保證終結函數被及時地執行,而且根本不保證它們會被執行。因此,時間關鍵(time-critical)的任務不應該由終結函數完成(如文件資源的歸還),我們也不應該依賴一個終結函數來更新關鍵性的永久狀態(如共享資源的永久鎖)。另外,當終結函數的執行時拋出異常時,問題會更嚴重。
  如果確實有資源要回收則不想使用終結函數,辦法是提供一個顯式的終止方法。顯式的終止方法通常與try-finally結構配合使用,以確保及時終止。
  當然,終結函數並非一無是處:第一種用途是當一個對象的所有者忘記了調用建議的顯式終止方法時,終結函數可以充當“安全網(safety net)”。第二種用途與對象的本地對等體(native peer)有關。本地對等體是一個本地對象,普通對象通過本地方法(native method)委托給一個本地對象。因為本地對等體不是一個普通對象,所以垃圾回收器不會知道它,當它的普通對等體被回收的時候,它不會被回收。在本地對等體不擁有關鍵資源的前提下,終結函數是執行這項任務的最合適的工具。
  使用終結函數時,“終結函數鏈(finalizer chain)”並不會被自動執行,因而子類的終結函數必須手工調用超類的終結函數。如:
//manual finalizer chaining
protected void finalize() throws Trowable{
try{
//Finalize subclass state
...
}finally{
super.finalize();
}
}
  可以通過使用匿名類來實現“終結函數守衛者(finalizer guardian)”,以確保子類和超類的終結函數都被調用。參見第18條。
 
 
 
讀《Effective java 中文版》(8)
  盡管Object是一個具體類,但它主要是為擴展,它的所有非final方法(equals,hashCode,toString,clone和finalize)都是為了被改寫的,每個方法的改寫都有明確的通用約定。
  第7條:在改寫equals的進修請遵守通用約定
  equals方法實現了等價關系:
  • 自反性(reflexive)
  • 對稱性(symmetric)
  • 傳遞性(transitive)
  • 一致性(consistent)
  • 對於任意的非空引用值x,x.equals(null)一定返回false.

一個失敗的equals改寫:
public final class CaseInsensitiveString{
private String s;
public CaseInsensitiveString(String s){
if(s==null) throw new NullPointerException();
this.s=s;
}
//Broken-violates symmetry!
public boolean equals(Object o){
if (o instanceof CaseInsensitiveString) return s.equalsIgnoreCase((CaseInsensitiveString)o).s);
if (o instanceof String) return s.equalsIgnoreCase((String)o);
return false;
}
....
}
另一個有問題的equals改寫:
public class Point{
private final int x;
private final int y;
public Point(int x,int y){
this.x=x;this.y=y;
}
public boolean equals(Object o){
if(!(o instanceof Point)) return false;
Point p=(Point)o;
return p.x==x && p.y==y;
}
...
}
public class ColorPoint extends Point{
private Color color;
public ColorPoint(int x,int y,Color color){
super(x,y);
this.color=color;
}
//Broken - violates transitivity!
public boolean equals(Object o){
if(!(o instanceof Point))return false;
//if O is a normal Point ,do a color-blind comparison
if(!(o instanceof ColorPoint))return o.equals(this);
//o is a ColorPoint,do a full comparison
ColorPoint cp=(ColorPoint)o;
return super.equals(o)) && cp.color==color;
}
}
  要想在擴展一個可以實例化的類的同時,既要增加新的特征,同時還要保留equals約定,沒有一個簡單的辦法可以做到這一點。根據“復合優先於繼承”的建議,可以如下改動:
public class ColorPoint{
private Point point;
private Color color;
public ColorPoint(int x,int y,Color color){
point=new Point(x,y);
this.color=color;
}
public Point asPoint(){
return point;
}
public boolean equals(Object o){
if(!(o instanceof ColorPoint)) return false;
ColorPoint cp=(ColorPoint)o;
return cp.point.equals(point) && cp.color.equals(color);
}
...
}
  實現高質量equals方法的“處方”:
1.       使用==操作符檢查“實參是否為指向對象的一個引用”
如果是,返回true;
2.       使用instanceof操作符檢查“實參是否為正確的類型”,
如果不是,返回false;
3.       把實參轉換到正確的類型
4.       對於該類中每一個"關鍵(significant)"域,檢查實參中的域與當前對象中對應的域值是否匹配。
如果所有的測試都成功,則返回true;
5.       當你編寫完成了equals方法之后,應該問自己三個問題:它是否是對稱的、傳遞的、一致的?

  下面是一些告誡:
  • 當你改寫equals的時候,總是要改寫hasCode方法(參見第8條)
  • 不要企圖讓equals方法過於聰明
    如果只是簡單地測試域中的值是否相等,則不難做到遵守equals約定。
  • 不要使equals方法依賴於不可靠的資源
    否則難以滿足一致性要求。除了少數的例外,equals方法應該針對駐留在內存中的對象執行確定性的計算
  • 不要將equals聲明中的Object對象替換為其它的類型
    當你的equals不能正常工作時,看看是不是犯了下述例子的錯誤。
    public boolean equals(MyClass o){
    ...
    }
 
 
讀《Effective java 中文版》(9)
第8條:改寫equals時總是要改寫hashCode
  java.lnag.Object中對hashCode的約定:
1.       在一個應用程序執行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話,則對該對象調用hashCode方法多次,它必須始終如一地返回同一個整數。
2.       如果兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。
3.       如果兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不同的整數結果。但如果能不同,則可能提高散列表的性能。
 
看個不改寫hashCode導致使用hashMap不能出現預期結果的例子:
public final class PhoneNumber{
private final short areaCode;
private final short exchange;
private final short extension;

public PhoneNumber(int areaCode,int exchage,int extension){
rangeCheck(areaCode,999,"area code");
rangeCheck(exchange,999,"exchange");
rangeCheck(extension,9999,"extension");
this.areaCode=(short) areaCode;
this.exchange=(short) exchange;
this.extension=(short)extension;
}
private static void rangeCheck(int arg,int max, String name){
if(arg<0 || arg>max) throw new IllegalArgumentException(name+":"+arg);
}
public boolean equals(Object o){
if (o == this) reutrn true;
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber pn=(PhoneNumber)o;
return pn.extension==extension && pn.exchange=exchange && pn.areaCode=areaCode;
}
//No hashCode method
...
}
現在有以下幾行程序:
Map m=new HashMap();
m.put(new PhoneNumber(1,2,3),"Jenny");
則m.get(new PhoneNumber(1,2,3))的返回值什么?
  雖然這個實例據equals是相等的,但由於沒改寫hashCode而致兩個實例的散列碼並不同(即違反第二條要求),因則返回的結果是null而不是"Jenny".
  理想情況下,一個散列函數應該把一個集合中不相等的實例均勻地分布到所有可能的散列值上,下面是接近理想的“處方”:
1.       把某個非零常數值(如17)保存在一個叫result的int類型的變量中;
2.       對於對象中每個關鍵字域f(指equals方法中考慮的每一個域),完成以下步驟:
<!--[if 1.       為該域計算int類型的散列碼c:
<!--[if 1.       如果該域是bloolean類型,則計算(f?0:1)
<!--[if 2.       如果該域是byte,char,short或int類型,則計算(int)f
<!--[if 3.       如果該域是long類型,則計算(int)(f^(>>>32))
<!--[if 4.       如果該域是float類型,則計算Float.floatToIntBits(f)
<!--[if 5.       如果該域是double類型,則計算Double.doubleToLongBits(f)得一long類型值,然后按前述計算此long類型的散列值
<!--[if 6.       如果該域是一個對象引用,則利用此對象的hashCode,如果域的值為null,則返回0
<!--[if 7.       如果該域是一個數組,則對每一個數組元素當作單獨的域來處理,然后安下一步的方案來進行合成
 
<!--[if 2.       利用下面的公式將散列碼c 組合到result中。result=37*result+c;
 
3.       檢查“相等的實例是否具有相等的散列碼?”,如果為否,則修正錯誤。

  依照這個處方,得PhoneNumber的hashCode方法:
public int hashCode(){
int result=17;
result=37*result+areaCode;
result=37*result+exchange;
result=37*result+extension;
return result;
}
  如果計算散列碼的代價比較高,可以考慮用內部保存這個碼,在創建是生成或遲緩初始化生成它。不要試圖從散列碼計算中排除掉一個對象的關鍵部分以提高性能。
 
 
讀《Effective java 中文版》(9)
第9條:總是要改寫toString
  對於toString的通用約定是:
  • 返回的字符串應是一個簡潔、信息豐富且易於閱讀的表達形式
  • 建議所有的子類都改寫這個方法(超類的實現是"類名@散列串")
 
  當一個對象被傳遞給println、字符串連接操作符(+)、assert(java1.4版)時,toString會被自動調用。
  在實際應用中,toString方法應該返回對象中包含的所有令人感興趣的信息或摘要信息。不管你是否決定指定返回值的格式,都應該在文檔中明確地表明你的意圖。另外,為toString返回值中包含的所有信息都提供一種編程訪問途徑是一個好的做法,這樣可以讓程序直接得到特定的數據,則無需要費力來解析這個字符串來獲得。
 
 
讀《Effective java 中文版》(11)
第10條:謹慎地改寫clone
  Cloneable接口的目的是作為對象的一個mixin接口,表明這樣的對象允許克隆。Cloneable沒有包含任何方法,只是決定了Object中受保護的clone方法實現的行為:如果一個類實現了Cloneable,則Object的clone方法返回該對象的逐域拷貝,否則拋出一個CloneNotSupportedException異常。
  至於clone本身,是一種很有風險的、語言之外的對象創建機制:無須調用構造函數就可以創建一個函數。
  clone方法的約定是:創建和返回對象一個拷貝,且
  • x.clone!=x為true,
  • x.clone().getClass()==x.getClass()為true,
  • x.clone.equals(x)為true,
當然,這三個也不是絕對的要求。
  如果你改寫了一個非final類的clone方法,則應該返回一個通過調用super.clone而得到的對象。如果一個類的所有超類都遵守這條規則,那么一直調用super.clone最終會調用到object的clone方法,從而創建出正確的類的實例。這種機制大致上類似於自動的構造函數鏈,只不過它不是強制要求的。
  實際上,clone方法是另一個構造函數,你必須確保它不會傷害到原始的對象,並且正確地建立起被克隆對象中的約束關系。
  Clone結構與指向可變對象的final域的正常用法是不兼容的。非除非在原始對象和克隆對象之間可以安全地共享此可變對象。為了使一個類成為可克隆的,可能有必要從某些域中去掉final修飾符。
  實現對象拷貝的好辦法,是提供一個拷貝構造函數(其唯一的參數的類型是包含該構造函數的類)。
  Cloneable有如此多問題,其它的接口不應該擴展該接口,為了繼承而設計的類(參見第15條)也就該實現這個接口。
 
 
讀《Effective java 中文版》(12)
第11條:考慮實現Comparable接口
  compareTo方法在Object中並沒有被聲明,它是java.lang.Compareable接口中唯一的方法。一個類實現了Compareable接口,就表明它的實例具有內在的排序關系(natural ordering)。如果一個數組中的對象實現了Compareable接口,則對這個數組進行排序非常簡單:Arrays.sort();對於存儲在集合中的Comareable對象,搜索、計算極值以及自動維護工作都非常簡單。一旦你的類實現了Compareable接口,它就可以跟許多泛型算法(generic algorithm)以及依賴於該接口的集合的操作實現進行協作,以小的努力得到強大的功能。
  Java平台庫中的所有值類(value classes)都實現了Compareable接口。
  compareTo的約定是:
  將當前這個對象與指定的對象進行順序比較,當該對象小於、等於或大於指定對象時,分別返回一個負整數、0或正整數,如果無法進行比較,則拋出ClassCastException異常。
  • 實現者必須保證對於所有的x和y,滿足sgn(x.compareTo(y))==-sgn(y.compareTo(x)),且如果前者拋異常后者也拋。
  • 實現者必須保證該比較關系可傳遞:x.compareTo(y)>0且y.comareTo(z)>0暗示x.compareTo(z)>0
  • 實現者必須保證經x.compareTo(y)==0暗示着:對所有的z,sgn(x.compareTo(z))==sgn(y.compareTo(z))
  • 強烈建議(x.compareTo(y)==0)==(x.equals(y)),對於實現了Compareable接口的類,如果違反這個條件,應該予以明確說明。

  與equals不同的是,在跨越不同類的時候,comapreTo可以不做比較。
  依賴於比較關系的類包括有序集合類TreeSet和TreeMap,以及工具類Collections和Arrays,它們內部都含有搜索和排序算法。
  與equals相似,compareTo也遵循:自反性、對稱性、傳遞性、非空性。並且,沒有一種簡單地方法可以做到,在擴展一個新的可實例化的類的時候,既增加了新的特征,同時以保持了compareTo約定。
 
讀《Effective java 中文版》(13)
第12條:使類和成員的可訪問能力最小化
  一個設計良好的模塊會隱藏所有的實現細節,把它的API與實現清晰地隔離開來。模塊之間只通過它們的API進行通信,一個模塊不需要知道其它模塊的內部工作情況,此即為信息隱藏(information hiding)或封裝(encapsulation)。這樣做的理由,是為有效地解除一個系統中各模塊之間的耦合關系,其代價是性能的犧牲。
  java中一個實體(類、接口、成員等)的可訪問性,由該實體聲明所在位置,以及該實體聲明中所出現的訪問修飾符(private protected 和 public)共同決定的。
  經驗表明,應盡可能地使一個類或成員不被外界訪問,即應使用最低可能地、且與該軟件正確功能相一致的訪問級別。
  “使一個不必要的公有類成為包級私有的類”。對於頂層的(非嵌套的)類和接口,只有兩種可能的訪問級別:包級私有和公有。把一個類或接口做成包級私有,則它成了包的實現的一部分,而不是包導出的API的一部分,從而可在以后自由地修改它;如果做成公有,則有義務永遠支持它。
  如果一個包級私有的頂層類或接口只在某一個類的內部使用,則應考慮將其做為后者的一個私有嵌套類(或接口)。
  對於成員(域、方法、嵌套類和嵌套接口)有四種可能的訪問級別,可訪問性遞增順序為:
  • 私有的(private)-只在類內部可訪問
  • 包級私有的(package-private)-默認訪問級別(不顯式指定修飾符時),包內部的任何類都可以訪問
  • 受保護的(protected)-子類或包內的類的可以訪問
  • 公有的(public)-任何地方可以訪問

  受保護的成員是一類的導出的API的一部分,須永遠支持,受保護的成員應盡量少用。
  如果一個方法改寫了超類中的一個方法,那么子類中該方法的訪問級別低於超類中的級別是不允許的。
  公有類應盡可能地減少公有的域。包含公有可變域(公有的非final域或公有指向可變對象的final域)的類不是線程安全的。一個例外是,通過公有的靜態final域來暴露類的常量是允許的。
  具有公有的靜態final數組域幾乎總是錯誤的。如:
//protential security hole!
public static fina Type[] VALUES={...};
  應被替換為一個私有數組,以及一個公有的非可變列表,如:
private static Type[] PRIVATE_VALUES={...};
public static final List VALUES= Collections.unmodifiableList(Arrays.asList(PRIVATE_VALUES));
  或者被替換為一個私有數組,以及返回數組拷貝的公有方法,如:
private static Type[] PRIVATE_VALUES={...};
public static final Types[] values(){
return (Type[] PRIVATE_VALUES.clone());
};
 
讀《Effective java 中文版》(14)
第13條:支持非可變性
  一個非可變類是一個簡單的類,它的實例不能被修改。每個實例中包含的所有信息都必須在該實例被創建的時候就提供出來,並在對象的整個生存期內固定不變。如java的String類、原語類型的包裝類、BigInteger和BigDecimal等。
  非可變類的5條規則:
  • 不要提供任何會修改對象的方法(也稱為mutator)
  • 保證沒有可被子類改寫的方法,一般做法是加個final
  • 使所有的域都是final的
  • 使所有的域都成為私有的
  • 保證對於任何可變組件的互斥訪問。如果類具有指向可變對象的域,則必須確保該類的客戶無法獲得指向這些對象的引用,且,永遠不要用客戶提供的對象引用來初始化這樣的域,也不要提供能返回這樣的引用的方法。在構造函數、訪問方法和readObject方法中使用保護性拷貝技術(參見第24條).

  看一個“復數”的例子:
public final class Complex{
private final float re;
private final fload im;

public Complex(float re,float im){
this.re=re;
this.im=im;
}
public float realPart(){return re;};
public float imaginaryPart(){return im;};

public Complex add(Complex c){
return new Complex(re+c.re,im+c.im);
}
public Complex subtract(Complex c){
return new Complex(re-c.re,im-c.im);
}
public Complex multiply(Complex c){
return new Complex(re*c.re-im*c.im,re*c.im+im*c.re);
}
public Complex divide(Complex c){
float tmp=c.re*c.re+c.im*c.im;
return new Complex((re*c.re+im*c.im)/tmp,(im*c.re-re*c.im)/tmp);
}
public boolean euqals(Object o){
if (o==this) return true;
if(!(o instanceof Complex))return false;
Complex c=(Complex)o;
return (Float.floatToIntBits(re)==Float.floatToIntBits(c.re))&&
Float.floatToIntBits(im)==Float.floatToIntBits(c.im));
}
public int hashCode(){
int result=17+Float.floatToIntBits(re);
result=37*result+Float.floatToIntBits(im);
return result;
}
public String toString(){
return "("+re+"+"+im+"i)";
}
}//此Complex定義是一個非工業級的復數實現,一是因為乘、除法的實現可能導致不正確舍入,二是沒有處理復數NaN或無窮大的情況。
  該例中對,對復數的操作都會返回一個新的實例,而不修原來的實例,這被稱之為函數式作法(functional),與之對應的是修改實例狀態的過程式方法(precedural)。
  非可變對象比較簡單,它本質上是線程安全的,它們不要求同步,因而非可變對象可被自由地共享(一個簡單地做法,是為提供公有的靜態final常量),而不需要進行保護性拷貝,而且也應為非可變類提供clone方法或拷貝構造函數(如String類的拷貝構造函數,應盡量不用)。
  不僅可以共享非可變對象,而且可以共享它們的內部信息。例如,BigInteger的negate方法產生一個新實例,其存放數值的數組與原來的實例是同一個數組。
  非可變對象為其他對象--無論是可變的還是非可變的--提供了大量的構造(buildig block)。
  非可變類真正唯一的缺點是,對於每一個不同的值都要求一個單獨的對象。常見的解決思路是提供一個可變的配套類,如String的配置可變類StringBuffer.
  一個類絕不允許其子類改寫的方法有三:
  • 聲明類為final,見前例
  • 類不為final,但每個方法為final,這法不值得提倡
  • 使所有構造函數為私有或者包級私有,且增加公有的靜態工廠來代替公有的構造函數。如:
    //Immutable class with static factories instead of construtors
    public class Complex{
    private final float re;
    private final float im;
    private Complex(float re,float im){
    this.re=re;
    this.im=im;
    }
    public static Complex valueOf(float re,float im){
    return new Complex(re,im);
    }
    ...
    }這種方法靈活性強,值得提倡。

  由於歷史的原因,BigInteger或BigDecimal的方法可以被改寫,所以你在開發自己的非可變類時,如果不能確保收到這兩個類的實例的非可變性,則需要進行保護性拷貝。如:
public void foo(BigInteger b){
if(b.getClass() != BigInteger.class) b=new BigInteger(b.toByteArray());
...
}
  其實,前面所述的規則過於強,只需滿足:沒有一個方法能對對象狀態產生外部可見(externally visible)的改變。例如:
//Cached, lazily initialized function of an immutable object
private volatile Foo cachedFooVal = UNLIKELY_FOO_VALUE;
public Foo foo(){
Foo result=cachedFooVal;
if(result==UNLIKELY_FOO_VALUE) result=cachedFooVal=fooVal();
return result;
}
//fooVal函數開銷較大,所以在前面使用了遲緩初始化的技術。
private Foo fooVal(){...}
  除非有很好的理由要讓一個類成為可變類,否則就應該是非可變的,缺點是性能問題,如果對性能要求比較高,可以提供公有的可變配套類。
  如果一個類不能被做成非可變類,仍可應盡可能地限制它的可變性。構造函數應創建完全初始化的對象,所有約束關系應在這時候建立起來,構造函數不應把“只初始化了一部分的實例”傳遞給其它的方法。
 
 
讀《Effective java 中文版》(15)
第14條:復合優先於繼承
  繼承是實現代碼重用的有力手段,但不適當地使用繼承會導致脆弱的軟件。
  • 在一個包內使用繼承是非常安全的,因在那兒子類和超類在同一個程序員的控制之下。
  • 對於專門為了繼承而設計、且有很好文檔說明的類,使用繼承也是安全的。
  • 對普通的具體類(concret class)時行跨躍包邊界的繼承,則是非常危險的。
 
  與方法調用不同的是,繼承打破了封裝性,一個子類依賴於其超類中特定功能的實現細節。超類的實現的變化,則子類可能會被打破。
  • 自用性,即超類中不同公有方法的實現時存在調用關系,當子類改寫這些方法時,常會導致子類的脆弱
  • 超類在它的后續方法中增加了新的方法,如果子類不能及時改寫這些方法,異常的數據或操作出現。
  • 子類繼承超類后,增加了一個新的方法,則當超類在新版中也增加了具有相同原型特征的方法時,可能會出現問題

  有一種辦法可以避免上述所有問題:新類,不是擴展一個已有類,而是設置一個私有域,它引用這個已有類的一個實例。這種設計被稱為“復合(composition)”。新類中的每個實例方法,都可以調用被包含的已有類實例中對應的方法,並返回它的結果,即為“轉發方法(forwarding method)”。這樣的類比較穩固,這不依賴於已有類的實現細節。一個類的實例都把另一個類的實現包裝起來了,則前者的類叫做包裝類(wrapper class)。
  看一個例子:
//wrapper class - uses composition in place of inheritance
public class InstrumentedSet implements Set{
private final Set s;
private int addCount=0;

public InstrumentedSet(Set s){
this.s=s;
}
public boolean add(Object o){
addCount++;
return s.add();
}
public boolean addAll(Collection c){
addCount+=c.size();
return s.addAll(c);
}
public int getAddCount(){
return addCount;
}
//forwarding methods
public void clear() { s.clear(); }
public boolean contains(Object o){return s.contains(o); }
public boolean isEmpty() {return s.isEmpty(); }
public int size() {return s.size(); }
....
public String toString() {return s.toString(); }
}
  上例中,InstrumentedSet類對Set類進行了修飾,增加了計數特性。有時,復合和轉發這兩項技術的結合被錯誤地引用為“委托(delegation)”,從技術的角度而言,這不是委托,除非包裝對象把自己傳遞給一個被包裝的對象。
  包裝類幾乎沒有什么缺點。需要注意的是,包裝類不適合用於回調框架(callback framework)中。在回調框架中,對象把自己的引用傳遞給其它的對象,以便將來調用回來,當它被包裝起來以后,它並不知道外面的包裝對象的情況,所以它傳遞一個指向自己的引用(this)時,會造成回調時繞開外面的包裝對象的問題。這被稱為SELF問題。
  只有當子類真正是超類的“子類型(subtype)”時,繼承才是合適的。即兩者之存在“is-a”的關系。java平台中,也有違反這條規則的地方:如Stack不是向量,所以不應擴展Vector;屬性列表不是散列表,所以Properties不應擴展Hashtable。在決定使用復合還是擴展時,還要看一試圖擴展的類的API有沒有缺陷,如果你願意傳播這些缺陷到自己的API中,則用繼承,否則可用復合來設計一個新的API。
 
讀《Effective java 中文版》(16)
第15條:要么專門為繼承而設計,並給出文檔說明,要么禁止繼承
  一個專門為了繼承而設計並且具有良好文檔說明的類,意味着:
  • 該類的文檔必須精確地描述了改寫每一個方法所帶來的影響  該類必須有文檔說明其可改寫的方法的自用性:對於每一個公有的或受保護的方法或者構造函數,它的文檔必須指明它調用了哪些可改寫的方法是以什么順序調用,每個調用的結果是如何影響后續的處理過程的。  慣例是如果一個方法調用了可改寫的方法,那么在它的文檔注釋的末尾應該包含關於這些調用的描述信息。
  • 為了使程序員能夠編寫出更加有效的子類,而無需承受不必要的痛苦,一個類必須通過某種形式提供適當的鈎子(hook),以便能夠進入到它的內部工作流程中,這樣的形式可以是精心選擇的受保護方法,也可是以保護域,后者少見。  當為了繼承的目的而設計一個有可能會被廣泛使用的類時,須意識到,對於文檔中所說明的自用模式(self-use pattern),以及對於其受保護方法和域所隱含的實現細節,實際上已經做出了永久的承諾。這些承諾使得你在后續的版本中提高這個類的性能或增加很功能非常困難,或者不可能。
  • 構造函數一定不能調用可改寫的方法,無論是直接還是間接進行,因為超類的構造函數在子類的構造函數之前運行。看例子: public class Super{ //broken-constructor invokes overridable method public Super(){ m(); } public void m(){}; } final class Sub extends Super{ private final Date date; Sub(){ date=new Date(); } public void m(){ System.out.println(date); } public static void main(String[] args){ Sub s=new Sub(); s.m(); } }   程序運行的結果不會打印出日期兩次,第一次打出null.
  • 在一個為了繼承而設計的類中,實現Cloneable或Serializable接口,因為clone和readObject方法在行為上非常類似於構造函數,所以無論clone還是readObject都不能調用一個可改寫的方法,無論是直接還是間接的方式。  對於readObject方法,子類中改寫版本的方法將在子類的狀太被反序列化之前先被運行;對於clone方法,改寫版本的方法將在子類的clone方法有機會修正被克隆對象的狀態之前先被運行。
  • 在一個為了繼承而設計的類中實現Serializable接口,且該類有一個readResolve或writeReplace方法,則你必須使readResolve或者writeReplace成為受保護的方法,而不是私有的方法。
  為了繼承而設計一個類,要求對這個類有一些實質性的限制。對於那些並非為了安全地進行子類化而設計和編寫文檔的類(如普通的具體類),禁止子類化。有兩種辦法可以禁止子類化:
1.       把類聲明為final
2.       把所有的構造函數變成私有的或包級私有的,增加一些公有的靜態工廠來替代構造函數的位置
如果必須從這樣的類來繼承,則要確保這類永遠不會調用到它的可改寫的方法。
 
讀《Effective java 中文版》(17)
第16條:接口優於抽象類
  Java語言中的接口與抽象類的一些區別:
  • 抽象類允許包含某些方法的實現,接口不允許。
  • 為實現一個由抽象類定義的類型,它必須成為抽象類的一個子類。任何一個類,只要它定義了所有要求的方法,並且遵守通用約定,則它就允許實現一個接口。
  • java只允許單繼承,抽象類作為類型定義受到了極大的限制。
 
  看一下接口:
 
1.         已有的類可以很容易被更新,以實現新的接口。只要:增加要求的方法,在類的聲明上增加implements子句。
2.         接口是定義mixin(混合類型)的理想選擇。一個mixin是指這樣的類型:一個類除了實現它的基本類型(primary type)之外,還可以實現這個mixin類型,以表明它提供了某些可選擇的行為。接口之所以能定義mixin,因為它允許可選的功能可被混合到一個類型的基本類型中,而抽象類不能用於定義mixin類型,同樣的理由是因為它們不能被更新到已有的類中:一個類不可能有一個以上的父類,並且在類層次結構中沒有適當的地方來放置mixin。
  接口使得我們可以構造出非層次結構的類型結構。看例子:
public interface Singer{
AudioClip sing(Song s);
}
public interface Songwriter{
Song compose(boolean hit);
}
  為解決歌唱家本人也能做曲的情況,可以很簡單地做到:
public interface SingerSongwriter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}
  如果用抽象類來做,會是如何?
3.         接口使得安全地增強一類的功能成為可能,做法是使用第14條介紹的包裝類模式。如果用抽象類型來做,則程序員除了使用繼承沒有別的方法。
4.         接口不允許包含方法的實現。把接口和抽象類的優點結合起來,對於期望導出的每一個重要接口,都提供一個抽象的骨架實現(skeletal implementaion)類。接口的作用仍是定義類型,骨架實現類負責所有與接口實現相關的工作。按照慣例,骨架實現被稱為AbstractInterface(注:此interface是所實現的接口的名字,如AbstractList,AbstractSet)。看一個靜態工廠:
//List adapter for int array
static List intArrayAsList(final int[] a){
if (a==null) throw new NullPointerException();
return new AbstractList(){
public Object get(int i){
return new Integer(a[i]);
}
public int size(){
return a.length;
}
public Object set(int i,Object o){
int oldVal=a[i];
a[i]=((Integer)o).intValue();
return new Integer(oldVal);
}
}
}
  這個例子是一個Adapter,它使得一個int數組可以被看作一個Integer實例列表(由於存在int和Integer之間的轉換,其性能不會非常好)
  骨架實現的優美之外在於,它們為抽象類提供了實現上的幫助,但又沒有強加“抽象類被用做類型定義時候”所特有的嚴格限制。對一地一個接口的大多數實現來講,擴展骨架實現類是一個很顯然的選擇,當然它也只是一個選擇而已。
  實現了這個接口的類可以把對於接口方法的調用,轉發到一個內部私有類的實例上,而這個內部私有類擴展了骨架實現類。這項技術被稱為模擬多重繼承。
  編寫一個骨架實現類相對比較簡單,首先要認真研究接口,並且確實哪些方法是最為基本的(primitive),其他的方法在實現的時候將以它們為基礎,這些方法將是骨架實現類中的抽象方法;然后須為接口中的其它方法提供具體的實現。骨架實現類不是為了繼承的目的而設計的。(怎么理解?)看例子:
//skeletal implementation
public abstract class AbstractMapEntry implements Map.Entry{
//primitives
public abstract Object getKey();
public abstract Object getValue();

//Entries in modifiable maps must override this method
public Object setValue(Object value){
throw new UnsupportedOperationException();
}
//Implements the general contract of Map.Entry.equals
public boolean equals(Object o){
if (o==this) return true;
if (!(o instanceof Map.Entry)) return false;
Map.Entry arg=(Map.Entry)o;
return eq(getKey(),arg.getKey())&&eq(getValue(),arg.getValue());
}
private static boolean eq(Object o1,Object o2){
return (o1==null?02==null:o1.equals(o2));
}
//implements the general contract of Map.Entry.hashCode
public int hashCode(){
return (getKey()==null?0:getKey.hashCode())^(getValue()==null?0:getValue().hashCode());
}
}

  使用抽象類來定義允許多個實現的類型,比使用接口有一個明顯的優勢:抽象類的演化比接口的演化要容易的多。在后續的發行版中,如果希望在抽象類中增加一個方法,只增加一個默認的合理的實現即可,抽象類的所有實現都自動提供了這個新的方法。對於接口,這是行不通的。雖然可以在骨架實現類中增加一方法的實現來解決部分問題,但這不能解決不從骨架實現類繼承的接口實現的問題。由此,設計公有的接口要非常謹慎,一旦一個接口被公開且被廣泛實現,對它進行修改將是不可能的。
 
讀《Effective java 中文版》(18)
第17條:接口只是被用於定義類型
  當一個類實現了一個接口的時候,這個接口被用做一個類型(type),通過此類型可以引用這個類的實例。因此,一個類實現了一個接口,就表明客戶可以對這個類的實例實施某些動作。為了任何其它目的而定義接口是不合適的。
  常量接口是對接口的不良應用,因它不滿足上述條件。常量接口中沒有包含任何方法,只包含靜態的final域,每個域都導出一個常量。r看下例。如果一個類要使用這些常量,它只要實現這個接口,就可以避免用類名來修飾常量名。但是,實現一個常量接口,會導致把這樣的實現細節(指內部使用常量)泄露到該類的導出API中。而且在將來的發行版本中,如果類不再需要這些常量了,但仍要實現這個接口。還有,這樣的類的子類,也會為常量所污染。
//Constant interface pattern - do not use!
public interface PhysicalConstants{
static final double AVOGADROS_NUMBER =6.02214199e23;
static final double BOLTZMANN_CONSTANT =1.3806503e-23;
static final double ELECTRON_MASS 9.10938188e-31;
}
  Java平台中有幾個常量接口,如:java.io.ObjectStreamConstants,這不值得效仿。
  如果要導出常量,有幾種方案:
1.       如果這常量與某個已有類或接口緊緊聯系在一起,則應該把它們加入類或常量中。
2.       如果這些常量最好被看作一個枚舉類型的成員,那么應該用一個類型安全枚舉類(typesafe enum class),參見第21條。
3.       否則,應用一個不可被實例化的工具類(utility class),見第3條。看對上面例子的修改:
//constant utility class
public class PhysicalConstants{
private PhysicalConstants(){};//prevent instantiation
public static final double AVOGADROS_NUMBER =6.02214199e23;
public static final double BOLTZMANN_CONSTANT =1.3806503e-23;
public static final double ELECTRON_MASS 9.10938188e-31;
}

  可以通過將常量放在局部變量中,來減少鍵盤的錄入工作量。如:
private static final double PI=Math.PI;
 
讀《Effective java 中文版》(19)
第18條:優先考慮靜態成員類
  嵌套類(nested class)是指被定義在另一類的內部的類,它只為它的外圍類服務。如果一個嵌套類可能會用於其它的某個環境,那就應為一個頂層類(top-level class)。嵌套類有四種:靜態成員類(static member class)、非靜態成員類(nonstatic member class)、匿名類(anonymous class)和局部類(local class),其中后三種稱為內部類(inner class)。
  靜態成員類是一種最簡單的嵌套類,最后把它看作一個普通的類,碰巧被聲明在另一個類的內部而已,它可以訪問外圍類的所有成員,包括那些聲明為私有的成員。靜態成員類是外圍類的一個靜態成員,與其他的靜態成員一樣,遵守同樣的可訪問性規則。如果它被聲明為私有的,則只能在外圍類的內部才可以被訪問。靜態成員類的一種通常用法是作為公有的輔助類,僅當它與它的外部類一起使用時才有意義。
  從語法上,非靜態成員類與靜態成員類的差別在於后者有一個static。非靜態成員類的每一個實例,都隱含着與外圍類的一個外圍實例緊密聯系在一起。在非靜態成員類的實例方法內部,調用外圍實例上的方法是可能的,或者使用一個經過修飾的this也可以得到個指向外圍實例的引用。在沒有外圍實例的情況下,要想創建非靜態成員類的實例是不可能的。非靜態成員類的一種通常用法是定義一個Adapter,它允許外部類的一個實例被看作另一個不相關的類的實例。如Map接口的實現往往使用非靜態成員類來實現它們的“集合”視圖,這些“集合”視圖是由Map的keySet、entrySet、Value方法返回。再看Set集合接口實現迭代器的例子:
//Typical use of a nonstatic member class
public class MySet extends AbstractSet{
// bulk of the class omitted
public Iterator interator(){
return new MyIterator();
}
private class MyIterator implements Iterator{
...
}
}
  如果聲明的成員類不要求訪問外圍實例,那么就把static修飾符放到成員類的聲明中。這會減少維護成員類實例對外圍實例對象的引用的開銷。
  私有靜態成員類的一種通常用法是用來代表外圍類對象的組件。例如:Map實例把一些Key和Value關聯起來,其內部通常有一個Entry對象對應於每個鍵值對。每個Entry對象都與一個Map對象關聯,但Entry的方法(getKey、getValue、setValue)都不需要外圍的Map。此時,用私有的靜態成員類是最佳選擇。
  匿名類沒有名字,它不是外圍類的一個成員。它並不與其它的成員一起被聲明,而在被使用的點上同時聲明和被實例化。匿名類可以出現在代碼中任何允許表達式出現的地方。匿名類的行為與成員類非常類似,具體取決於它所在環境:如果匿名類出現在一個非靜態的環境中,則它有一個外圍實例。
  匿名類的適用性有一些限制:
  • 匿名類只能被用在代碼中它將被實例化的那個點上。
  • 它沒有名字,實例化后不能 再 對它進行引用
  • 匿名通常只實現了其接口或者超類中方法,不會聲明新的方法,因為沒有訪問新方法的途徑。
  • 因為匿名類出現於表達中,故它們應該非常短(20行或更少),以增強程序的可讀性

  匿名類的常見用法:
1.      
  匿名類的一通常用法是創建一個函數對象(function object),如:
//typical use of an anonymous class
//Arrays.sort(args,new comparator(){
public int compare(Object o1,Object o2){
return ((String)o1).length()-((String)o2).length();
}
});
2.       另一個常見用法是創建一個過程對象(process object),如Thread、Runnable、TimerTasker實例。
3.       在一個靜態工廠方法的內部(參見第16條)
4.       用在復雜的類型安全枚舉類型(它要求為每個實例提供單獨的子類)
例子:
//typical use of a public static member class
public class Calculator{
public static abstract class Operation{
private final String name;
Operation(String name){ this.name=name;}
public String toString(){ return this.name};
//perform arithmetic op represented by this constant
abstract double eval(double x,double y);

//doubly nested anonymous classes
public static final Operation PLUS = new Operation("+"){
double eval(double x,double y){return x+y;}
}
public static final Operation MINUS= new Operation("-"){
double eval(double x,double y){return x-y;}
}
public static final Operation TIMES= new Operation("*"){
double eval(double x,double y){return x*y;}
}
public static final Operation DIVIDE=new Operation("/"){
double eval(double x,double y){return x/y;}
}
}
//Return hte results of the specified calculation
public double calculate(double x,Operation op,double y){
return op.eval(x,y);
}
}
  局部類是四種嵌套類中最少使用的類。在任何“可以聲明局部變量”的地方,都可以聲明局部類。與成員類一樣,局部類有名字,可以被重復使用。與匿名類一樣,當且僅當局部類被用於非靜態環境下的進修,它們才有外圍實例。與匿名類一樣,它必須非簡短,以不會損害外圍方法或者初始化器的可讀性。
  總之,如果一個嵌套類需要在單個方法之外仍是可見的,或者它太長了,不適合於放在一個方法內部,則應該使用成員類。如果成員類的每個實例都需要一個指向其外圍的實例的引用,則把成員類做成非靜態的;否則做成靜態的。假設一個嵌套類屬於一方法的內部,如果只需要在一個地方創建它的實例,並且已經有了一個預先存在的類型可以說明這個類的特征,則把它做成匿名類;否則變成局部類。
讀《Effective java 中文版》(20)
第19條:用類代替結構
  Java語言中讓一個類退化到只包含一些公開的數據域,這樣的類與C語言的結構類似,如:
//degenerate classes like this should not be public
class Point{
public float x;
public float y;
}
  這樣的類,拋棄了所有數據封裝的優點,而你幾乎不能對這樣的API做什么改動或限制,應該代之以私有域和公有訪問方法,如:
//Encapsulated structure class
class Point{
private float x;
private float y;

public Point (float x,float y){
this.x=x;
this.y=y;
}

public float getx(){return this.x;}
public float gety(){return this.y;}
public void setx(float x){this.x=x;}
public void sety(float y){this.y=y;}
}
  JAVA平台庫中,有幾個類違反了"公有類不應該直接暴露數據域“的告誡,如java.awt中的Point和Dimension。
讀《Effective java 中文版》(21)
第20條:用類層次來代替聯合
  C語言中的聯合(union)是用來定義可以容納多種數據類型的數據結構,常見用法是在一個結構中包含一個聯合和一個標簽(標簽通常是某一個枚舉(enum)類型),用標簽指明聯合中存放的是哪種數據類型的數據,這也被稱為可區分的聯合。Java語言中,union結構被子類型化這種機制代替。
  類層次與可區分的聯合相比,有N多好處:
1.       類層次提供了類型的安全性,而C語言中如果標簽的指示與union存放對象不一致,則會出錯。
2.       代碼簡潔明了。
3.       容易擴展,即使多方在獨立的工作。
4.       可以反映出這些類型之間本質上的層次關系,從而允許更強的靈活性,以及更好的編譯時類型檢查。

  當要編寫一個類,而其中包含一個顯式的標簽域的時候,就應該考慮這個標簽是否可以被取消而用一個類層次來代替。
  C語言中union的另一個用途是,查看一片數據的內部表示。看例:
union{
float f;
int bits;
}sleaze;
sleaze.f=6.699e-41;
print("%x\n",sleeze.bits);
  在java語言中可以這樣做:
system.out.println(Integer.toHexString(Float.floatToIntBits(6.699e-41f)));
讀《Effective java 中文版》(22)
第21條:用類來代替enum結構
  C語言中,enum結構定義一個枚舉類型:它的合法值是由一組固定的常量組成的。但這種類型定義有問題:
  • 它只是定義了一組被命名的整數常量,在類型安全和使用方便性方面沒有提供任何幫助。如:
    typedef enum{FUJI, PIPPIN, GRANNY_SMITH}apple_t;
    typedef enum{NAVEL,TEMPLE,BLOOD} orange_t;
    下一行是合法的:
    orange_t myFavorite=PIPPIN;/*mixing apples and oranges*/
    下一行是可怕的:
    orange_t x=(FUJI-PIPPIN)/TEMPLE;/* Applesauce! */
  • enum結構並沒有為它產生的常量建立起一個名字空間,如下面則會出現沖突:
    typedef enum{BLOOD, SWEAT, TEARS} fluid-t;
  • 用enum結構定義的類型是非常脆弱的。在enum類型增加新的常量而不重新編譯客戶程序,會引起不可預知的行為。多個開發方不能獨立地為這樣的類型增加常量,因為可能沖突。
  • enum結構中沒提供便利的方法來將枚舉常量轉換成可打印字符串,或者枚舉出一個類型中的所有的常量。

  不幸的是,java中最常用的針對枚舉類型的模式,也有這些缺點,如例:
//the int enum pattern - problematic!!
public class PlayingCard{
public static final int SUIT_CLUBS =0;
public static final int SUIT_DIAMONDS =1;
public static final int SUIT_HEARTS =2;
public static final int SUIT_SPADES =3;
....
}
  幸運的是,java程序還有被稱為類型安全枚舉的模式:定義一類來代表枚舉類型的單個元素,並且不提供任何公有的構造函數,相反提供公有的靜態final域,使枚舉類型的每一個常量都對應一個域。如:
//the typesafe enum pattern
public class Suit{
private final String name;
private Suit(String name){this.name=name;}
public String toString(){return name;}
public static final Suit CLUBS =new Suit("Clubs");
public static final Suit DIAMANDS=new Suit("Diamands");
public static final Suit HEARTS =new Suit("Hearts");
public static final Suit SPADES =new Suit("Spades");
}
  即使這個類沒有聲明為final,客戶也沒法創建這個類的對象,也無法擴展這個類,因而除了通過這些公有的靜態final域導出的Suit對象之外,永遠不會有其它的對象存在。
  好處:
  • 提供了編譯的類型安全性
  • 多個“類型安全枚舉”可以包含相同名字的枚舉常量,因為每個類都有自己的命名空間。
  • 新的常量加入到一個類型安全枚舉類中,無需重新編譯客戶代碼,因為常量並沒有被編譯到客戶代碼中。
  • 可以通過改寫toString來允許其值轉化為可打印字符串。
  • 因為任何方法都可以被加到類型安全枚舉中類中,所以它們可以實現任何接口。如Comparable:
    //ordinal-based typesafe enum
    public class Suit implements Comparable{
    private final String name;
    private static int nextOrdinal=0;
    private final int ordinal = nextOrdinal++;
    private Suit(String name){this.name=name;}
    public String toString(){return name;}
    public int compareTo(Object o){
    return ordinal-((Suit)o).ordinal;
    }
    public static final Suit CLUBS =new Suit("Clubs");
    public static final Suit DIAMANDS=new Suit("Diamands");
    public static final Suit HEARTS =new Suit("Hearts");
    public static final Suit SPADES =new Suit("Spades");
    }
  • 因為類型安全枚舉類型的常量是對象,所以你可以把這些常量放到集合中。如:
    private static final Suit[] PRIVATE_VALUES={CLUBS, DIAMAONDS, HEARTS, SPADES};
    public static final List VALUES=Collection.unmodifiableList(Array.asList(PRIVATE_VALUES));
  • 基於序數形式的類型安全枚舉模式,在聲明中增加implements Serializable,然后提供一一個readResolve方法,即可支持序列化。
    private Object readResolve() throws ObjectStreamException{
    return PRIVATE_VALUES[ordinal];
    }
  • 類型安全枚舉類在性能可與int枚舉常量相比美,因為可以使用“引用的同一性比較”來檢查邏輯上的相關等關系。

  使得一個類型安全枚舉類可以擴展,只需要提供一個受保護的構造函數即可。
  • 對客戶沒有用的方法,應聲明為prtotected,對客戶隱藏,允許子類修改。且如果沒有合理的默認實現,應聲明為abstract.
  • 改寫equals和hashCode,使他們成為final,以保證該枚舉類型的所有相等的對象也一定是相同的

  看一個可擴展的、可序列化的類型安全枚舉類:
//Serializable,extensible typesafe enum
public abstract class Operation implements Serializable{
private final transient String name;
protected Operation(String name){this.name=name;}

public static Operation PLUS=new Operation("+"){
protected double eval(double x,double y){return x+y;}
};
public static Operation MINUS=new Operation("-"){
protected double eval(double x,double y){return x-y;}
};
public static Operation TIMES=new Operation("*"){
protected double eval(double x,double y){return x*y;}
};
public static Operation DIVIDE=new Operation("/"){
protected double eval(double x,double y){return x/y;}
};

protected abstract double eval(double x,double y);

public String toString(){return this.name;}
public final boolean equals(Object that){
return super.equals(that);
}
public final int hashCode(){
return super.hashCode();
}

//the 4 following lines are necessary fro serialization
private static int nextOrdinal =0;
private final int ordinal=nextOrdinal++;
private static final Operation[] VALUES={PLUS, MINUS, TIMES, DIVIDE};
Object readResolve() throws ObjectStreamException{
return VALUES[ordinal];
}
}
//subclass of extensible , serializable typesafe enum
abstract class ExtendedOperation extends Operation{
ExtendedOperation(String name){super(name);}

public static Operation LOG=new Operation("log"){
protected double eval(double x,double y){return Math.log(x)/Math.log(y);}
};
public static Operation EXP=new Operation("exp"){
protected double eval(double x,double y){return Math.power(x,y);}
};
//the 4 following lines are necessary fro serialization
private static int nextOrdinal =0;
private final int ordinal=nextOrdinal++;
private static final Operation[] VALUES={LOG,EXP};
Object readResolve() throws ObjectStreamException{
return VALUES[ordinal];//canonicalize
}
}
  與int模式相比,類型安全枚舉模式的缺點:
  • 把類型安全枚舉常量組合到一起比較困難
  • 不能用在switch語句中
  • 類型安全枚舉需要一定的時空開銷
讀《Effective java 中文版》(23)
第22條:用類和接口來代替函數指針
  C語言支持函數指針,其主要用途是實現Strategy(策略)模式,典型的應用如比較器函數,就是策略模式的一個例子。Java省略了函數指針,因為對象引用可以被用於提供同樣的功能。如例:
class StringLengthComparator{
public int compare(String s1,String s2){
return s1.lenght()-s2.length();
}
}
  java中實現策略模式,聲明一個接口來表示該策略,且為每個具體策略聲明一個實現了該接口的類。如果一個具體策略只被使用一次,則常用匿名類實現聲明和實例化。如果一個具體策略要被導出以重復使用,則常為一個私有的靜態成員類,且通過一個公有靜態final域被導出,其類型為該策略接口。
  作為一個典型的具體策略類,StringLengthComparator類是無狀態的,沒有域,它作為一個singleton非常合適,如下:
class StringLengthComparator{
private StringLengthComparator(){}
public static final StringLengthComparator INSTANCE=new StringLengthComparator();
public int compare(String s1,String s2){
return s1.lenght()-s2.length();
}
}
  在設計具體策略類的時候,還需要定義一個策略接口,如:
//Stragetegy interface
public interface Comparator{
public int compare(Object o1,Object o2);
}
  具體的策略類往往使用匿名類聲明,如:
Arrays.sort(stringArray,new Comparator(){
public int compare(Object o1,Object o2){
String s1=(String)o1;
String s2=(String)o2;
return s1.length()-s2.length();
}
});
  由於策略接口被用做所有具體策略實例的類型,故不必將具體策略類做成公有的。如下:
//exporting a concrete stategy
class Host{
...
private static class StrlenCmp implements Comparator , Serializable{
public int compare(Object o1,Object o2){
String s1=(String)o1;
String s2=(String)o2;
return s1.length()-s2.length();
}
}
//returned comparator is serializable
public static final Comparator STRING_LENGTH_COMPARATOR=new StrLenCmp();
}
讀《Effective java 中文版》(24)
第23條:檢查參數的有效性
  當編寫一個方法或者構造函數的時候,應該考慮對於它的參數有哪些限制,且應把這些限制寫到文檔中,然后在這個方法體的起始處,通過的檢查來實施這些限制。
  對於公有的方法,使用javadoc的@throws標簽在文檔中寫下“一旦針對參數值的限制被違反之后將會拋出的異常”,如IllegalArgumentException、IndexOutOfBoundsException或NullPointerException。
  非公有的方法,通常應該使用assertions斷言來檢查它們的參數。
  一些情況下可以不進行參數限制,如:
  • 有效性檢查工作非常昂貴
  • 檢查根本不切實際
  • 計算過程中隱含着有有效性檢查的工作.此時,如果由於無效參數而致的計算拋出的異常,與聲明中的異常並不匹配,則需要使用異常轉譯技術。
讀《Effective java 中文版》(25)
第24條:需要時使用保護性拷貝
  從安全的角度考慮,應該設計面對客戶的不良行為時仍能保持健壯性的類,無論系統的其它部分發生什么事情,這些類的約束都可以保持為真。
  下面是一個有問題的例子:
//Broken "immutable" time period class
public final class Period{
private final Date start;
private final Date end;

/**
*@param start the begining of the period
*@param end the end of the period
*@throws IllegalArgumentException if start is after end
*@throws NullPointerException if start or end is null
*/
public Period(Date start,Date end){
if (start.compareTo(end)>0) throw new IllegalArgumentException(start+" after "+end);
this.start=start;
this.end =end;
}
public Date start(){
return start;
}
public Date end(){
return end;
}
....//remainder omitted
}
  由於Date本身可變,所以約束條件很容易被打破,如下:
//attack the internals of a Period instance
Date start=new Date();
Date end=new Date();
Period p=new Period(start,end);
end.setYear(78);//modify the internals of P!!
  為保護Period實例的內部信息免受攻擊,對構造函數的每個參數進行保護性拷貝是必要的。而且,保護性拷貝要在檢查參數的有效性之前進行,並且有效性檢查是針對拷貝后的對象而不是原始的對象,以防止其它的線程會修改原始值。如下:
//repared constructor- make defensive copy of parameters.
public Period(Date start,Date end){
this.start=new Date(start.getTime());
this.end=new Date(end.getTime());
if(this.start.compareTo(this.end)>0) throw new IllegalArgumentException(start+" after "+end);
}
  注意,如果一個類可以被子類化,則不要用clone方法進行參數的保護性拷貝。
  到目前為止,雖然對Period類進行了保護,但對它的攻擊還是有可能的。如下:
//second attack on the internals of a Period instance.
Date start=new Date();
Date end=new Date();
Period p=new Period(start,end);
p.end().setYear(78);//modifis the internal of p!!
  此時要對相關的方法進行修改,要求它們返回的是內部域的保護性拷貝即可,如下:
//repaired accessors-make defensive copies of internal fields
public Date start(){
return (Date)start.clone();
}
public Date end(){
return (Date)end.clone();
}
  至此,“一個周期的起始時間不能落后於結束時間”的約束,才真正做的到。
  只要有可能,都使用非可變的對象做為對象內部的組件,這樣就不必關心保護性拷貝的問題。
讀《Effective java 中文版》(26)
第25條:謹慎地設計方法的原型
1.       謹慎選擇方法的名字
2.       不要過於追求提供便利的方法。
只有當一個方法被用的非常頻繁的時候,才考慮為它提供一個快捷方法。如果不能確定,還是不考慮為好。
3.       避免長長的參數列表。
三個參數應為實踐中的最大值。有兩項技術可以縮短方法的參數列表:
<!--[if o        把一個方法分解成多個方法,每個方法只要求這些參數一個子集。
<!--[if o        創建輔助類,來保存參數的聚集
4.       對於參數類型,優先使用接口而不是類
例如,應該使用Map而不是Hashtable作為參數類型。
5.       謹慎地使用函數對象
讀《Effective java 中文版》(27)
第26條:謹慎地使用重載
  下面的例子希望能識別出實例的類型分別為Set, List, 和unkown:
//broken - incorrect use of overloading.
public class CollectionClassifier{
public static String classify(Set s){
return "Set";
}
public static String classify(List l){
return "List";
}
public static String classify(Collection c){
return "Unkown collection";
}
public static void main(String[] args){
Collection[] tests=new Collection[]{
new HashSet(),
new ArrayList();
new HashMap().values()};
for(int i=0;i<TESTS.LENGTH;I++){
System.out.println(classify(tests[i]));
}
}
}
  顯示結果卻是unkown,unkown,unkown,:(,雖然常識是“重載方法的選擇是靜態的,改寫方法的選擇是動態的”。
  下面是重寫的一個例子:
class A{
String name(){return "A";}
}
class B extends A{
String name(){return "B";}
}
class C extends B{
String name(){return "C";}
}
public class Overriding{
public static void main(String[] args){
A[] tests=new A[]{new A(), new B(), new C()};
for(int i=0;i<TESTS.LENGTH;I++){SYSTEM.OUT.PRINTLN(TESTS[I].NAME());}
}
}
  打印結果當然是"ABC"了。
  因為改寫機制是規范,而重載機制是例外,所以改寫機制滿足了人們對於方法調用的行為的期望,而重載機制很容易混淆這些期望。以避免重載機制的混淆用法的方法有
  • 一個安全而保守的策略是,永遠不要導出兩個具有相同參數數目的重載方法。
  • 對於每一對重載方法,只有一個對應的形參具有“根本不同”的類型,指把一個參數的類型轉換為另一個類型是不可能的。
  • 對於構造函數或實現某個接口時,可能會較多地出現重載的機會,前者可考慮用靜態工廠,后者可以考慮兩個重載方法合並。
讀《Effective java 中文版》(28)
第27條:返回零長度的數組而不是null
  看例子:
private List cheeseInStock=...;
/**
* @return an array containing all of the cheese in the shop,
* or null if no cheeses are available for purchase.
*/
public Cheese[] getCheeses(){
if( cheeseInStock.size==0) return null;
...
}
  調用這個方法時,需要:
Cheese[] cheeses=shop.getCheeses();
if(cheeses!=null && Arrays.asList(shop.getCheeses()).contains(Cheese.STILTON))
System.out.println("Jolly good, just the thing");
  如果改用返回長度為0的數組,則是這樣:
private List cheesesInStock=...;
private final static Cheese[] NULL_CHEESE_ARRAY=new Cheese[0];
/**
* @return an array containing all of the cheeses in the shop.
*/
public Cheese[] getCheese(){
return (Cheese[])cheeseInStock.toArray(NULL_CHEESE_ARRAY);
}
  調用代碼改為:
if(Arrays.asList(shop.getCheeses()).contains(Cheese.STILTON))
System.out.println("Jolly good, just the thing");
讀《Effective java 中文版》(29)
第28條:為所有導出的API元素編寫文檔注釋
  為了正確地編寫API文檔,必須在每一個被導出的類、接口、構造函數、方法和域聲明之前增加一個文檔注釋。
  • 每個文檔注釋應簡潔描述出它和客戶之間的約定,以說明這個方法做了什么而不說明它是如何做的,專門為繼承而設計的類例外。
  • 通過@throws和@param來說明調用這個方法的前提條件。
  • 還要描述它的副作用(指系統狀態中一個可觀察到的變化,它不是為了獲得后置條件而要求的變化。
  • 應該描述一個類的線程安全性
 
  具體編寫時的一些注意事項:
  • @param和@return后跟一個名詞短語,@throws后跟一個if語句再加一個名詞短語。
  • 可以HTML元字符和標簽,尤其是>、<、&符號不要忘了轉義。
  • 單詞this指被調用的方法所在的實例
  • 元素文檔的第一句話是概要描述,注意不要在第一句話內部使用句號(.),如果非要出現可以使用.來代替。
    對於方法和構造函數,概要描述是一個動詞短語;
    對於類、接口或域,是一個名詞短語。

  從1.2.2開始,javadoc已經有“自動重用”或“繼承”方法注釋的能力了。
讀《Effective java 中文版》(30)
第29條:將局部變量的作用域最小化
  應該打破C語言設計的一個習慣:局部變量須被聲明在代碼塊的開始處。java語言允許在任何可以出現語句的地方聲明變量。
  使一個局部變量的作用域最小化,最有力的技術是在第一次使用它的地方聲明。幾乎每一個局部變量的聲明都應包含一個初始化表達式,如果沒有足夠的信息進行初始化,則應該推遲這個聲明,try-catch是個例外。
  最小化局部變量作用域的另一項技術是方法小而功能單一。
  最常見的局部變量作用最小化的例子是for循環,相對於while循環,除了前者可以少一行代碼外,前者可以避免“復制-粘貼”類錯誤。如下例:
Iterator i=c.iterator();
while(i.hasNext()){
doSomething(i.next());
}
Iterator i2=c2.iterator();
while(i.hasNext()){ //BUG!!!
doSomething(i2.next());
}
-------------------------------------
for (Iterator i=c.iterator();i.hasNext();){
doSomething(i.next());
}
for (Iterator i2=c2.iterator();i.hasNext();){//Compile-time error.
doSomething(i2.next());
}
讀《Effective java 中文版》(31)
第30條:了解和使用庫
  不要從頭發明輪子。
  使用的標准庫的益處:
1.       通過使用標准庫,可以充分利用這些編寫標准庫的專家的知識,以及其它人的使用經驗。
2.       不必浪費時間為那些與工作關系不大的問題提供特別的解決方案。精力應該在應用上,而不是在底層的細節上。
3.       它的性能會隨着版本更新不斷提高,而你不必須為它做任何工作。
4.       使自己的代碼融入主流。
 
  每一個程序員都應該熟悉java.lang和java.util,以及java.io中的內容。如集合框架(Collections Framework),它以6個集合接口Colection, Set, List, Map, SortedSet, SortedMap為基礎, 以前的Vector, HashTable被更新以后也加入到這里。另外,java.util.regex(正則式工具), java.util.prefs(配置信息管理工具), java.nio(高性能的io工具), java.util.LinkedHashSet/LinkedHashMap/IdentityHashMap(新的集合實現)也都值得特別關注。
讀《Effective java 中文版》(32)
第31條:如果要求精確的答案,請避免使用float和double.
  float和double類型的主要設計目標是為了科學計算和工程計算,采用二進制浮點運算,不適於要求精確結果的場合,float與dobule尤其不合適於貨幣計算。
  對於貨幣計算的解決方案是:
1.       采用int類型,自己處理小數位,數值不超過9位
2.       采用long類型,自己處理小數位,數值不超過18位
3.       采用BigDecimal類型,不是很方便且運算慢,但可以從8種舍入方式中進行選擇以靈活控制
讀《Effective java 中文版》(33)
第32條:如果其它類型晚適合,則盡量避免使用字符串
  字符器不適合代替其它的值類型:
  • 字符串不適合代替枚舉類型,應該采用類型安全枚舉類型(參見第21條)或int值。
  • 字符串不適合代替聚集類型,即將實體的多個屬性放在一個字符串中,屬性間以某一分隔符相間。
    最好的辦法,是編寫一個類,通常會用靜態成員類(見18條).
  • 字符器也不適合代替權限對象。如例:
    //Broken - inappropriate use of string as capability!
    public class ThreadLocal{
    private ThreadLocal(){}//noninstantiable
    public static void set(String key,Object value);
    public static Object get(String key);
    }
      key是一種權限描述。這個設計有安全問題。下面是用不可偽造的鍵來代替字符串。
    public class ThreadLoacl{
    private ThreadLocal()//noninstantiable
    public static class Key{
    Key(){}
    }
    public static key getKey()
    return new Key();
    }
    public static void set(Key key,Object value);
    public static Object get(Key key);
    }
      根據具體情況,這個類還可進一步優化。
讀《Effective java 中文版》(34)
第33條:了解字符串連接的性能
  為連接n個字符串而重復地使用字符串連接操作符,將需要n的平方級的時間。這是由於字符器是非可變的(見第13條),當兩個字符串被連接的時候,它們的內容都要被拷貝。為了獲得可接受的性能,使用StringBuffer替代String.
  下面的例子被優化后,性能是優化前的數十倍。
public String statement(){
String s="";
for (int i=0;i<NUMITEMS();I++){
s += lineForItem(i);
return s;
}
  優化后如下:
public String statement(){
StringBuffer s=new StringBuffer(numItems()*LINE_WIDTH);
for (int i=0;i<NUMITEMS();I++)
s.append(lineForItem(i));
return s.toString();
}
讀《Effective java 中文版》(35)
第34條:通過接口引用對象
  第25條中建議過:應使用接口而不是類作為參數的類型。更進一步,應該優先使用接口而不是類來引用對象。只有當創建某個對象的時候,才需要引用這個對象的類。
  這一條好象有點偏激,hehe.
  例子:
//Good -uses interface as type
List subscribers=new Vector();
//Bad - use class as type!
Vector subscribers=new Vector();
  這么做的最大好處,就是使程序更加靈活。接着上面的例子,如果你的程序中使用的Vector的方法,ArrayList方法都有,且你現在由於某種原因更喜歡使用ArrayList,則只需要改動一行就可以了。即:
Vecotr subscribers=new ArrayList();
  當然,也有一些情況,適合於用類來引用對象:
  • 沒有合適的接口存在,如值類就很少有多個實現且多是final的。
  • 對象屬於一個框架,而框架的基本類型是類,則應該用基類來引用對象。
  • 一個類實現了某個接口,但它提供了接口中不存在的額外方法,而程序依賴於這額外的方法,則只能用類來引用這個對象。
  總之,如果有接口,則用接口來引用對象,如果沒有則使用類層次結構中提供了所需功能的最高層的類。
讀《Effective java 中文版》(36)
第35條:接口優先於映像機制
  映像設施(reflection facility)java.lang.reflect,提供了通過程序來訪問善於已裝載的類的信息。映像機制允許一個類使用另一個類,即使當前者被編譯時后者還根本不存在。映像設施最初是為了基於組件的應用創建工具而設計的。這樣的工具通常要根據需要裝載類,並且用映偈功能找出它們支持哪些方法和構造函數。映像功能是在設計時刻被使用的:通常,普通應用在運行時刻不應該以映像方式訪問對象,因為使用映像是有代價的:
  • 損失了編譯時類型檢查和異常檢查的好處
  • 要求執行映像訪問的代碼非常笨拙和冗長
  • 性能損失
 
  一些復雜的應用程序需要使用映像機制,如瀏覽器、對象監視器、代碼分析工具、內嵌的解釋器、RPC系統等。
  如果某個類在程序編譯時不可用,但其接口或超類可用,則可以用映像方式創建實例,然后通過它們的接口或超類,以正常的方式訪問這些實例。如果存在構造函數沒有參數,則只需要Class.newInstance()而無需reflection來創建實例。看例子:
//Reflective instantiation with interface access
public static void main(String[] args){
Class cl=null;
try{
c1=Class.forName(args[0]);
}catch(ClassNotFoundException e){
System.err.println("CLass not found");
System.exit(1);
}
Set s=null;
try{
s=(Set)c1.newInstance();
}catch(IllegalAccessException e){
System.err.println("Class not Accessible");
System.exit(1);
}catch(InstantiationException e){
System.err.println("Class not instantiable");
System.exit(1);
}
s.addAll(Arrays.asList(args)).subList(1,args.length-1));
System.out.println(s);
}
  如果第一個參數為java.util.HashSet則輸出是隨機順序,如果是java.util.TreeSet則按字母排序輸出。例子演示的技術,可以用來實現服務提供者框架(service provider framewrok)。在這個例子中,三個異常都是在不使用映像時的編譯時錯誤,而且代碼比較冗長。
讀《Effective java 中文版》(37)
第36條:謹慎地使用本地方法
  Java Native Interface允許java應用可以調用本地方法(用C/C++等本地程序語言來編寫的特殊方法),用途有三:
  • 訪問與平台相關的設施
  • 訪問老式代碼庫或數據庫
  • 實現應用的關鍵部分以提高性能

  使用本地方法也有一些缺點:
  • 本地語言不安全
  • 難以移植
  • 進入、退出本地代碼需要開銷
.
  隨着java的發展和優化,其三種用途大都有相應的替代方案。所以謹慎使用本地方法。
讀《Effective java 中文版》(38)
第37條:謹慎地進行優化
  不要費力地去編寫快速的程序--應該努力編寫好的程序,速度自然會隨之而來。在設計系統的時候,特別是在設計API、線路層協議和永久數據格式的時候,要考慮性能的因素。在每次試圖做優化之前和之后,要借助性能分析工具對性能進行分析。
  考慮API設計決定的性能后果。如使一個公有的類型成為可變的,則可能會導致大量的保護性拷貝(參見第24條);該用復合模式時使用了類繼承,人為地將子類和超類綁在了一起(參見第14條);在API中使用實現類型則不是接口,會把應用束縛在一個具體的實現上(參見第34條)等。一般而言,好的API設計也伴隨着好的性能。
讀《Effective java 中文版》(39)
第38條:遵守普遍接受的命名慣例
  java的命名慣例分為兩大類:字面的和語法的。
  字面命名慣例涉及包、類、接口、方法和域。
  • 包的名字是層次結構的,用句號分隔第一部分。每一部分的長度不要超過8,由小寫字母和數字組成(數字少見用),鼓勵使用有意義的縮寫。除了java和javax外,一般以域名做開頭,順序是頂級域名放在最前面。
  • 類和接口的名字應至少1至多個單詞,每個單詞的首字母大寫,盡量避免縮寫。
  • 方法和域的名字與類和接口的名字遵守相同的字面慣例,只是第一個首字母要小寫。常量域要全部字母都大寫,詞之間通過下划線區分。

  語法命名慣例比字面慣例更靈活。
  • 對於包而言,沒有語法命名慣例。
  • 類通常用一個名詞或名詞短語,接口或者與類相同,或者以"-able"或"-ible"結尾的形容詞。
  • 執行某個動作的方法,常用一個動詞或動詞短語,
    • 對於返回boolean類型的方法,名字常以“is"開頭后加一個名詞或形容詞或短語,
    • 如果返回的不是boolean,則常用一個名詞/短語,或以"get"開頭的動詞短語。
    • 如果一方法所在的類是一個Bean,則強制要求以get開頭。
    • 如果類包含對屬性操作,常用setAttribute或getAttribute格式命名。
    • 轉換對象類型的方法,
      • 如果返回不同類型的獨立的對象,則稱為toType
      • 如果返回一個視圖,則用asType,
      • 如果返回與被調用對象同值的原語類型,稱為typeValue
    • 靜態工廠的方法,常用valueOf或getInstance.
 
  • 域的命名沒有很好地建立。
讀《Effective java 中文版》(40)
第39條:只針對不正常的條件才使用異常
  異常只應該被用於不正常的條件,它們永遠不應被用於正常的控制流。
  下面是一個用異常作遍歷結束條件的濫用異常的例子:
//horrible abuse of exceptions. Don't ever do this!
try{
int i=0;
while(true)a[i++].f();
}catch(ArrayIndexOutOfBoundsException e){
}
  其錯有三:
1.       創建、拋出和捕獲異常的開銷是很昂貴的。因為它的初衷是用於不正常的情形,少有jvm會它進行性能優化。
2.       把代碼放在try-catch中會阻止jvm實現本來可能要執行的某些特定的優化。
3.       有些現代的jvm對循環進行優化,不會出現冗余的檢查。

  這條原則也適用於API設計。一個設計良好的API不應該強迫它的客戶為了正常的控制流而使用異常。如果類中有一個”狀態相關”的方法,即只有特定的條件下可被調用的方法,則這個類也應有一個單獨的“狀態測試”方法,以為調用這個狀態相關方法前的檢查。如Collection類的next方法和hasNext方法。
Posted by Hilton at March 7, 2004 05:58 PM | TrackBack
Comments

我倒是想和大家討論一下,如何處理函數返回值的問題。
如果一個函數執行錯誤了,一種方法可以通過int返回各種錯誤。 二可以通過異常,將錯誤丟出。
想知道大家都是怎么處理的,書中顯然是不推崇第二種做法,但我卻覺得這么做可以簡化程序。
讀《Effective java 中文版》(41)
第40條:三種可拋出結構的使用
  對於可恢復的條件,使用被檢查的異常;對於程序錯誤,使用運行時異常;錯誤往往被JVM保留用於指示資源不足、約束失敗、或其它使程序無法繼續執行的條件。
  對於一個方法聲明要拋出的每一個被檢查的異常,它是對API用戶的一種潛在指示:與異常相關聯的條件是調用這次個方法的一種可能結果。
  兩種未被檢查的可拋出結構:運行時異常和錯誤,在行為上相同的,它們都不需要、也不應該被捕獲的拋出物。你所實現的所有未被檢查的拋出結構都應是RuntimeException的子類。定義一個非Exception、RuntimeException或Error子類的拋出物是可行的,但從行為意義上它等同於被普通的被檢查異常(即Exception子類而非RuntimeException子類).
  異常是個完全意義上的對象,在其上可以定義任意的方法。因被檢查的異常往往指示了可恢復的條件,所以可通過定義方法,使調用者可獲得一些有助於恢復的信息。
讀《Effective java 中文版》(42)
第41條:避免不必要地使用被檢查的異常
  與返回代碼不同,被檢查的異常強迫程序處理例外的情況,從而大大地提高了程序的可靠性。而過分地使用被檢查的異常,則增加了不可忽視的負擔。如果正確地使用API並不能阻止這種異常條件的產生,並且一旦產生了異常,使用API的程序可以采取有用的動作,那么這種負擔被認為是正當的。
  }catch(TheCheckedException e){
e.printStackTree();
System.exit(1);
  }
  如果使用API的程序員無法做得比這更好,那么未被檢查的異常可能更為合適。在實踐中,catch幾乎總有斷言失敗的特征。
  “把被檢查的異常變成未被檢查的異常”的一種技術是,把這個要拋出異常的方法分成兩個方法,第一個方法返回一個boolean以指明是否要拋出異常,另一個執行真正的功能,如果條件不滿足就拋異常。如下:
//Invocation with checked exception
try{
obj.action(args);
}catch(TheCheckedException e){
//Handle exception condition
}
  轉換為:
//Invocation with state-testing method and unchecked exception
if(obj.actionPermitted(args)){
obj.action(args));
}else{
//handle exception condition
}
  當然這種轉換並不總是合適的,例如一對象將在缺少外部同步的情況下被並發訪問,或者可被外界改變狀態,那么這種轉換將是不合適的。
讀《Effective java 中文版》(43)
第42條:盡量使用標准的異常
  java平台庫中訖今為止最常被重用的異常如下:
異常
使用場合
IllegalArgumentException
參數值不合適
IllegalStateException
對於這個方法調用而言,對象的狀態不合適(如初始化不恰當)
NullPointerException
在null被禁止的情況下參數值為null
IndexOutOfBoundsException
下標越界
ConcurrentModificationException
在禁止並發修改的情況下,對象檢測到並發修改
UnsupportedOperationException
對象不支持客戶請求的方法
  
  其它的異常也可以使用,只要確保拋出異常的條件與文檔要求一致即可。
讀《Effective java 中文版》(44)
第43條:拋出的異常要適合於相應的抽象
  高層的實現,應該捕獲低層的異常,同時拋出一個可以按照高層抽象進行解釋的異常,這種做法叫做異常轉譯(exception translation)。即如:
//exception translation!
try{
//use lowlevel abstraction to do our bidding
...
}catch(LowerLevelException e){
throw new HigherLevelException(...);
}
  低層的異常被高層的異常保存起來,且高層的異常提供一個公有的訪問方法來獲得低層的異常,這種做叫做異常鏈接(exception chaining)。
//Exception chaining.
try{
//use lower-level abstraction to do our bindding
...
}catch(LowerLevelException e){
throw new HigherLevelException(e);
}
  異常鏈的實現非常簡單,在1.4及以后版本中,可以通過Throwable來獲得支持。
//Exception chaining in release 1.4 or later
HigherLevelException(Throwable t){
super(t);
}
  如果是在早期java版本中,則需要先將其保存:
//Exception chaining prior to release 1.4
private Throwable cause;
HigherLevelException(Throwable t){
cause=t;
}
public Throwable getCause(){
return cause;
}
  處理來自低層的異常,
1.       最好的做法是,在調用低層方法之前通過一些檢查等手段來確保它們會成功執行;
2.       其次的做法是,讓高層處理這些異常,從而將高層方法的調用者與低層的問題隔離開;
3.       一般的做法是使用異常轉譯;
4.       如果低層方法的異常對高層也是合適的,則將其從低層傳到高層。
讀《Effective java 中文版》(48)
第47條:不要忽略異常
  異常的目的是強迫你處理不正常的條件,空的catch塊會使異常達不到應有的目的,至少catch塊中也應包含一條說明,用來解釋為什么忽略掉這個異常。這對被檢查的異常和未被檢查的異常都適用。
  簡單地將一個未被檢查的異常傳播到外界至少會使程序迅速地失敗,從而保留了有助於調試該失敗條件信息,比異常被忽略后的一個不可預測的時刻程序失敗這種情況要強。
讀《Effective java 中文版》(49)
第48條:對共享可變數據的同步訪問
  同步,不僅可以阻止一個線程看到對象處於不一致的狀態中,它還可以保證通過一系列看似順序執行的狀態轉變序列,對象從一種一致的狀態變遷到另一種一致的狀態。
  synchronized關鍵字可以保證在同一時刻,只有一個線程在執行一條語句,或者一段代碼塊。java語言保證讀或寫一個變量是原子的,除非這個變量的類型是long或double.
  java的內存模型決定,為了在線程之間可靠地通信,以及為了互斥訪問,對原子數據的讀寫進行同步是需要的。看一個可怕的例子:
//Broken - require synchronization!
private static int nextSerialNumber=0;
public static int generateSerialNumber(){
return nextSerialNumber++;
}
  對其改進,只需要在generateSerialNumber的聲明中增加synchronized修飾符即可。
  為了終止一個線程,一種推薦的做法是讓線程輪詢某個域,該域的值如果發生變化,就表明此線程就應該終止自己。下面的例子就是這個思路,但在同步出了問題。
//Broken - requires synchronization
public class StoppableThread extends Thread{
private boolean stopRequested=false;
public void run(){
boolean done=false;
while(!stopRequested && !done){
...//do what needs to be done in the thread
}
}
public void requestStop(){
stopRequested=true;
}
}
  對其改進如下:
//Properly synchronized cooperative thread temination
public class StoppableThread extends Thread{
private boolean stopRequested=false;
public void run(){
boolean done=false;
while(!stopRequested() && !done){
...//do what needs to be done in the thread
}
}
public synchronized void requestStop(){
stopRequested=true;
}
private synchronized boolean stopRequested(){
return stopRequested;
}
}
  另一種改進是,將stopRequested聲明為volatile,則同步可以省略。
  再來看遲緩初始化(lazy initialization)問題,雙重訪問模式並不一定都能正常工作,除非被共享的變量包含一個原語值。看例子:
//The double-check idion fro lazy initialization - broken!
private static Foo foo=null;
public static Foo getFoo(){
if (foo==null){
synchronized(Foo.class){
if(foo==null)foo=new Foo();
}
}
return foo;
}
  最容易的修改是省去遲緩初始化:
//normal static initialization (not lazy)
private static finall Foo foo=new Foo();
public static Foo getFoo(){
return foo;
}
  或者使用正確的同步方法,但可能增加少許的同步開銷:
//properly synchronized lazy initialization
private static Foo foo=null;
public static synchronized Foo getFoo(){
if(foo==null)foo=new Foo();
return foo;
}
  按需初始化容器模式也不錯,但是它只能用於靜態域,不能用於實例域。
//The initialize-on-demand holder class idiom
private static class FooHolder(){
static final Foo foo=new Foo();
}
public static Foo getFoo(){ return FooHolder.foo;}
  簡而言之,無論何時當多個線程共享可變數據的時候,每個讀或寫數據的線程必須獲得一把鎖。如果沒有同步,則一個線程所做的修改就無法保證被另一個線程所觀察到。
讀《Effective java 中文版》(52
51 條:不要依賴於線程調度器
  不能讓應用程序的正確性依賴於線程調度器。否則,結果得到的應用程序既不健壯也不具有可移植性。作為一個推論,不要依賴 Thread.yield 或者線程優先級。這些設施都只是影響到調度器,它們可以被用來提高一個已經能夠正常工作的系統的服務質量,但永遠不應用來 修正 一個原本並不能工作的程序。
  編寫健壯的、響應良好的、可移植的多線程應用程序的最好辦法是,盡可能確保在任何給定時刻只有少量的可運行線程。這種辦法采用的主要技術是,讓每個線程做少量的工作,然后使用 Object.Wait 等待某個條件發生,或者使用 Thread.sleep 睡眠一段時間。
讀《Effective java 中文版》(53
52 條:線程安全性的文檔化
  每個類都應該清楚地在文檔中說明它的線程安全屬性。在一個方法的聲明中出現 synchronized 修飾符,這是一個實現細節,並不是導出的 API 文檔的一部分。
  一個類為了可被多個線程安全地使用,必須在文檔中清楚地說明它所支持的線程安全性級別。
  • 非可變性(immutable)-這個類的實例對於其它客戶而言是不變的,不需要外部的同步。參見13條。
  • 線程程安全的(thread-safe)-這個類的實例是可變的,但是所有的地方都包含足夠的同步手段,這些實例可以被並發使用無需外部同步。
  • 有條件的線程安全(conditionally thread-safe)-這個類(或關聯的類)包含有某些方法,它們必須被順序調用,而不能受到其它線程的干擾,除此之外,這種線程安全級別與上一種情形相同。為了消除被其他線程干擾的可能性,客戶在執行此方法序列期間,必須獲得一把適當的鎖。如HashTableVector,它們的迭代器要求外部同步。如:
    Hashtable h=...;
    synchronized(h){
    for(Enumeration e=h.keys();e.hasMoreElements();)
    f(e.nextElement());
    }
  • 線程兼容的(thread-compatible)-在每個方法調用的外圍使用外部同步,此時這個類的實例可以被安全的並發使用。如ArrayListHashMap
  • 線程對立的(thread-hostile)這個類不能安全地被多個線程並發使用,即使所有的方法調用都被外部同步包圍。通常情況下,線程對立的根源在於,這個類的方法要修改靜態數據,而這些靜態數據可能會影響到其它的線程。

  對於有條件的線程安全類,在文檔中指明 為了允許方法調用序列以原子方式執行,哪一個對象應被鎖住 ”.
讀《Effective java 中文版》(54
53 條:避免使用線程組
  除了線程、鎖和監視器之外,線程系統還提供了一個基本的抽象,即線程組 (thread-group) 。然而線程組並沒有提供太多有用的功能。
  一個例外是,當線程組中的一個線程拋出一個未被捕獲的異常時, ThreadGroup.uncaughtException 方法會被自動調用。 執行環境 使用這個方法,以便用適當的方式來響應未被捕獲的異常。
讀《Effective java 中文版》(57
56 條:保護性地編寫 readObject 方法
  編寫一個類的 readObject 方法,相當於編寫一個公有的構造函數,無論給它傳遞一個什么樣的字節流,它都必須產生一個有效的實例。下面是縮寫健壯的 readObject 方法的指導原則:
 
  • 對於對象引用域必須保持為私有的類,對將被保存到這些域中的對象進行保護性拷貝。非可變類的可變組件就屬於這一類別。
  • 對於具有約束條件的類,一定要檢查約束條件是否滿足,如果不滿足的話,則拋出一個InvalidObjectException異常。這些檢查應跟在所有的保護性拷貝之后。
  • 如果在對象圖被反序列化之后,整個對象圖必須都是有效的,則應該使用ObjectInputValidation接口。
  • 無論是直接方式還是間接方式,都不要調用類中可被改寫的方法。

   readResolve 方法有可能取被用來替代保護性的 readObject 方法。
不嚴格地說, readObject 是一個 用字節流作為唯一參數 的構造函數。當面對一個人工偽造的字節流的時候, readObject 產生的對象會違反它所屬的類的約束條件。初步的方法,是在 readObject 方法進行約束性檢查,如下例:
private void readObject(OjbectInputStream s) throws IOException, ClassNotFoundException{
s.defaultReadObject();
//Check that our invariants are satisfied
if(start.compareTo(end)>0) throw new InvalidObjectException(start+" after "+ end);
}
  對上述的防范仍可進行攻擊:偽造一個字節流,這個字節流以一個有效的 Period 實例所產生的字節流作為開始,然后附加上兩個額外的引用,指向 Period 實例中的兩個內部私有 Date 域,攻擊者通過引用攻擊內部域。所以,當一個對象被反序列化的時候,對於客戶不應該擁有的對象引用,如果哪個域包含了這樣的對象引用,則必須要做保護性拷貝,這是非常重要的。如下例:
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException{
s.defaultReadObject();
start=new Date(start.getTime());
end=new Date(end.getTime());
if(start.compareTo(end)>0) throw new InvlaidObjectException(start+" after "+end);
}
讀《Effective java 中文版》(58
57 條:必要時提供一個 readResolve 方法
  無論是 singleton ,或是其他實例受控 (instance-controlled) 的類,必須使用 readResolve 方法來保護 實例-控制的約束 。從本質上來講, readResovle 方法把一個 readObject 方法從一個事實上的公有構造函數變成一個事實上的公有靜態工廠。對於那些禁止包外繼承的類而言, readResolve 方法作為保護性的 readObject 方法的一種替代,也是非常有用的。
  如下 sigleton 類:
public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
...
}
...//remainder omitted
}
  如果 Elvis 實例序列化接口,則下面的 readResolve 方法足以保證它的 sigleton 屬性。
private Object readResolve() throws ObjectStreamException{
//return the one true elvis and let the GC take care of the Elvis impersonator
return INSTANCE;
}
  不僅僅對於 singleton 對象是必要的, readResolve 方法對於所有其它的實例受控類 ( 如類型安全枚舉類型 ) 也是必需的。
   readResolve 方法的第二個用法是,就像在第 56 條建議的那樣,作為保護性的 readObject 方法的一種保守的替代選擇。此時,第 56 條中的 readObject 方法可以下例的例子替代:
//the defensive readResolve idiom
private Object readResolve() throws ObjectStreamException(){
return new Period(start,end);
}
  對於那些允許繼承的類, readResolve 方法可能無法替代保護性的 readObject 方法。如果超類的 readResolve 方法是 final 的,則使得子類實例無法被正常地反序列化。如果超類的 readResolve 方法是可改寫的,則惡意的子類可能會用一個方法改寫它,該方法返回一個受損的實例。
原文地址:https://sheng.iteye.com/blog/520637


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM