欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
讀《Effective java 中文版》
譯者序
前言
1引言 1
 
2創(chuàng )建和銷(xiāo)毀對象 4
1條:考慮用靜態(tài)工廠(chǎng)方法代替構造函數 4
2條:使用私有構造函數強化singleton屬性 8
3條:通過(guò)私有構造函數強化不可實(shí)例化的能力 10
4條:避免創(chuàng )建重復的對象 11
5條:消除過(guò)期的對象引用 14
6條:避免使用終結函數 17
 
3對于所有對象都通用的方法 21
7條:在改寫(xiě)equals的時(shí)候請遵守通用約定 21
8條:改寫(xiě)equals時(shí)總是要改寫(xiě)hashCode 31
9條:總是要改寫(xiě)toString 36
10條:謹慎地改寫(xiě)clone 39
11條:考慮實(shí)現Comparable接口 46
 
4類(lèi)和接口 51
12條:使類(lèi)和成員的可訪(fǎng)問(wèn)能力最小化 51
13條:支持非可變性 55
14條:復合優(yōu)先于繼承 62
15條:要么專(zhuān)門(mén)為繼承而設計,并給出文檔說(shuō)明,要么禁止繼承 67
16條:接口優(yōu)于抽象類(lèi) 72
17條:接口只是被用于定義類(lèi)型 76
18條:優(yōu)先考慮靜態(tài)成員類(lèi) 78
 
5 C語(yǔ)言結構的替代 82
19條:用類(lèi)代替結構 82
20條:用類(lèi)層次來(lái)代替聯(lián)合 84
21條:用類(lèi)來(lái)代替enum結構 88
22條:用類(lèi)和接口來(lái)代替函數指針 97
 
6方法 100
23條:檢查參數的有效性 100
24條:需要時(shí)使用保護性拷貝 103
25條:謹慎設計方法的原型 107
26條:謹慎地使用重載 109
27條:返回零長(cháng)度的數組而不是null 114
28條:為所有導出的API元素編寫(xiě)文檔注釋 116
 
7通用程序設計 120
29條:將局部變量的作用域最小化 120
30條:了解和使用庫 123
31條:如果要求精確的答案,請避免使用floatdouble 127
32條:如果其他類(lèi)型更適合,則盡量避免使用字符串 129
33條:了解字符串連接的性能 131
34條:通過(guò)接口引用對象 132
35條:接口優(yōu)先于映像機制 134
36條:謹慎地使用本地方法 137
37條:謹慎地進(jìn)行優(yōu)化 138
38條:遵守普遍接受的命名慣例 141
 
8異常 144
39條:只針對不正常的條件才使用異常 144
40條:對于可恢復的條件使用被檢查的異常,對于程序錯誤使用運行時(shí)異常 147
41條:避免不必要地使用被檢查的異常 149
42條:盡量使用標準的異常 151
43條:拋出的異常要適合于相應的抽象 153
44條:每個(gè)方法拋出的異常都要有文檔 155
45條:在細節消息中包含失?。东@信息 157
46條:努力使失敗保持原子性 159
47條:不要忽略異常 161
 
9線(xiàn)程 162
48條:對共享可變數據的同步訪(fǎng)問(wèn) 162
49條:避免過(guò)多的同步 168
50條:永遠不要在循環(huán)的外面調用wait 173
51條:不要依賴(lài)于線(xiàn)程調度器 175
52條:線(xiàn)程安全性的文檔化 178
53條:避免使用線(xiàn)程組 181
 
10序列化 182
54條:謹慎地實(shí)現Serializable 182
55條:考慮使用自定義的序列化形式 187
56條:保護性地編寫(xiě)readObject方法 193
57條:必要時(shí)提供一個(gè)readResolve方法 199
 
中英文術(shù)語(yǔ)對照 202
參考文獻 207
模式和習慣用法索引 212
索引 214
 
 
 
讀《Effective java 中文版》(2)
  第1條:考慮用靜態(tài)工廠(chǎng)方法代替構造函數
靜態(tài)工廠(chǎng)方法的一個(gè)好處是,與構造函數不同,靜態(tài)工廠(chǎng)方法具有名字。
第二個(gè)好處是,與構造函數不同,它們每次被調用的時(shí)候,不要求非得創(chuàng )建一個(gè)新的對象。
第三個(gè)好處是,與構造函數不同,它們可以返回一個(gè)原返回類(lèi)型的子類(lèi)型的對象。
  靜態(tài)工廠(chǎng)方法返回的對象所屬的類(lèi),在編寫(xiě)包含該靜態(tài)工廠(chǎng)方法的類(lèi)時(shí)可以并不存在,從而可以成為服務(wù)提供者框架(service provider framework,指這樣一個(gè)系統:提供者為框架的用戶(hù)提供了多個(gè)API實(shí)現,框架必須提供一種機制來(lái)注冊這些實(shí)現,以便用戶(hù)能夠使用它們,框架的客戶(hù)直接使用API而無(wú)需關(guān)心使用的是哪個(gè)實(shí)現)的基礎。
例子: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();
}
}
}
靜態(tài)工廠(chǎng)方法的主要缺點(diǎn)是,類(lèi)如果不含公有的或者受保護的構造函數,就不能被子類(lèi)化。
第二個(gè)缺點(diǎn)是,它們與其它的靜態(tài)方法沒(méi)有任何區別。
  對它們的命名習慣是:
<!--[if !supportLists]-->1.      <!--[endif]-->valueOf
該方法返回的實(shí)例與它的參數具有相同的值,常用于非常有效的類(lèi)型轉換操作符
<!--[if !supportLists]-->2.      <!--[endif]-->getInstance
返回的實(shí)例是由方法的參數來(lái)描述的,但不能夠說(shuō)與參數具有相同的值。常用于提供者框架中。
 
讀《Effective java 中文版》(3)
  第2條:使用私有構造函數強化Singleton屬性
  關(guān)于Singleton在《Java Design Patterns A Tutorial》一書(shū)(漢譯為JAVA設計模式)的第6章也有論述。
  Singleton(只能被實(shí)例化一次的類(lèi))的實(shí)現,要私有的構造函數與公有的靜態(tài)成員結合起來(lái),根據靜態(tài)成員的不同,分為兩種方法:
<!--[if !supportLists]-->1.      <!--[endif]-->公有靜態(tài)成員是一個(gè)final域
例如://singleton with final field
public class Elvis{
public static final Elvis INSTANCE = new Elvis()[
private Elvis(){
...
}
}
<!--[if !supportLists]-->2.      <!--[endif]-->公有靜態(tài)成員是一個(gè)工廠(chǎng)方法
例如://singleton with static factory method
public class Elvis{
private static final Elvis INSTANCE = new Elvis()[
private Elvis(){
...
}
public static Elvis getInstance(){
return INSTANCE;
}
}

  前者的好處在于成員的聲明即可表明類(lèi)的singleton特性,且效率可能高一些。
  后者的好處在于提供了靈活性。
 
 
讀《Effective java 中文版》(4)
  第3條:通過(guò)私有構造函數強化不可實(shí)例化的能力
  如果一個(gè)類(lèi)缺少顯式的構造函數,編譯器會(huì )自動(dòng)提供一個(gè)公有的、無(wú)參數的默認構造函數(default constructor)。
  我們只要讓這個(gè)類(lèi)包含單個(gè)顯式的私有構造函數,則它就不可被實(shí)例化了,而企圖通過(guò)將一個(gè)類(lèi)做成抽象類(lèi)來(lái)強制該類(lèi)不可被實(shí)例化是行不通的。
  這種做法的一個(gè)副作用,是它使得這個(gè)類(lèi)不能被子類(lèi)化,因為子類(lèi)將找不到一個(gè)可訪(fǎng)問(wèn)的超類(lèi)的構造函數。
例如://noninstantiable utility class
public class UtilityClass{
//suppress default constructor for noninstantiability
private UtilityClass(){
//this constructor will never be invoked
}
...
}
說(shuō)明:工具類(lèi)(UtilityCLass)指只包含靜態(tài)方法和靜態(tài)域的類(lèi),它不希望被實(shí)例化因為對它進(jìn)行實(shí)例化沒(méi)有任何意義。
 
 
讀《Effective java 中文版》(5)
第4條:避免創(chuàng )建重復的對象
  從性能的角度考慮,要盡可能地利用已經(jīng)創(chuàng )建的大對象(創(chuàng )建代價(jià)比較高的對象)。
  如果一個(gè)對象是非可變的(immutable),則它總是可以被重用。
  對于同時(shí)提供了靜態(tài)工廠(chǎng)方法和構造函數的非可變類(lèi),通??梢杂渺o態(tài)工廠(chǎng)方法而不是構造函數來(lái)避免創(chuàng )建重復的對象。
兩個(gè)優(yōu)化例子:
1、在一個(gè)循環(huán)中,要將
String s=new String("silly");//Don‘t do this!
改為
String s="No Longer Silly";
2、對于那些已知不會(huì )被修改的可變對象,也可以重用它們。
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;
}
}
優(yōu)化為:
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從沒(méi)被調用過(guò)時(shí),優(yōu)化的方案反而不如沒(méi)優(yōu)化的。:)
  來(lái)看一句象費話(huà)一樣的話(huà)。
  當應該重用一個(gè)已有對象的時(shí)候,請不要創(chuàng )建新的對象(本條目的核心),當應該創(chuàng )建一個(gè)新的對象的時(shí)候,請不要重用一個(gè)已有對象(第24條的核心,該條講的是保護性拷貝的問(wèn)題)。
 
 
 
讀《Effective java 中文版》(6)
  第5條:消除過(guò)期的對象引用
  下面的例子存在內容泄漏(或者說(shuō)無(wú)意識的對象保持,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);
}
}
}
  消除內容泄露,只需要改動(dòng)pop()方法。
public Object pop(){
if(size==0) throw new EmptyStackException();
Object result= elements[--size];
elements[size]=null;
return result;
}
  只要一個(gè)類(lèi)自己管理它的內存,程序員就要警惕內存泄露問(wèn)題。一旦一個(gè)元素被釋放掉,則該元素中包含的任何對象引用應該要被清空。
  內存泄露的另一個(gè)常見(jiàn)來(lái)源是緩存,因此這時(shí)要用一個(gè)線(xiàn)程定期清緩存或在加入時(shí)清最少使用的緩存對象。在1.4發(fā)行版中,可以用java.util.LinkedHashMap的revmoveEldestEntry方法來(lái)實(shí)現后一方案。
  如果一個(gè)持續運行的java應用速度越來(lái)越慢,就要考慮是否檢查內存泄露問(wèn)題。
  書(shū)中說(shuō)檢查內存泄露的軟件統稱(chēng)heap profiler,我檢索到了兩個(gè)http://sourceforge.net/projects/simpleprofiler和http://www.manageability.org/blog/stuff/open-source-profilers-for-java,以后有機會(huì )可得好好研究一番。不知讀到此文的朋友,能否再推薦幾個(gè)好的工具軟件?
 
 
 
讀《Effective java 中文版》(7)
  第6條:避免使用終結函數
  終結函數(finalizer)通常是不可預測的,常常也是很危險的,一般情況下是不必要的。使用終結函數會(huì )導致不穩定的行為、更差的性能,以及帶來(lái)移植問(wèn)題。
  JLS不僅不保證終結函數被及時(shí)地執行,而且根本不保證它們會(huì )被執行。因此,時(shí)間關(guān)鍵(time-critical)的任務(wù)不應該由終結函數完成(如文件資源的歸還),我們也不應該依賴(lài)一個(gè)終結函數來(lái)更新關(guān)鍵性的永久狀態(tài)(如共享資源的永久鎖)。另外,當終結函數的執行時(shí)拋出異常時(shí),問(wèn)題會(huì )更嚴重。
  如果確實(shí)有資源要回收則不想使用終結函數,辦法是提供一個(gè)顯式的終止方法。顯式的終止方法通常與try-finally結構配合使用,以確保及時(shí)終止。
  當然,終結函數并非一無(wú)是處:第一種用途是當一個(gè)對象的所有者忘記了調用建議的顯式終止方法時(shí),終結函數可以充當“安全網(wǎng)(safety net)”。第二種用途與對象的本地對等體(native peer)有關(guān)。本地對等體是一個(gè)本地對象,普通對象通過(guò)本地方法(native method)委托給一個(gè)本地對象。因為本地對等體不是一個(gè)普通對象,所以垃圾回收器不會(huì )知道它,當它的普通對等體被回收的時(shí)候,它不會(huì )被回收。在本地對等體不擁有關(guān)鍵資源的前提下,終結函數是執行這項任務(wù)的最合適的工具。
  使用終結函數時(shí),“終結函數鏈(finalizer chain)”并不會(huì )被自動(dòng)執行,因而子類(lèi)的終結函數必須手工調用超類(lèi)的終結函數。如:
//manual finalizer chaining
protected void finalize() throws Trowable{
try{
//Finalize subclass state
...
}finally{
super.finalize();
}
}
  可以通過(guò)使用匿名類(lèi)來(lái)實(shí)現“終結函數守衛者(finalizer guardian)”,以確保子類(lèi)和超類(lèi)的終結函數都被調用。參見(jiàn)第18條。
 
 
 
讀《Effective java 中文版》(8)
  盡管Object是一個(gè)具體類(lèi),但它主要是為擴展,它的所有非final方法(equals,hashCode,toString,clone和finalize)都是為了被改寫(xiě)的,每個(gè)方法的改寫(xiě)都有明確的通用約定。
  第7條:在改寫(xiě)equals的進(jìn)修請遵守通用約定
  equals方法實(shí)現了等價(jià)關(guān)系:
  • 自反性(reflexive)
  • 對稱(chēng)性(symmetric)
  • 傳遞性(transitive)
  • 一致性(consistent)
  • 對于任意的非空引用值x,x.equals(null)一定返回false.

一個(gè)失敗的equals改寫(xiě):
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;
}
....
}
另一個(gè)有問(wèn)題的equals改寫(xiě):
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;
}
}
  要想在擴展一個(gè)可以實(shí)例化的類(lèi)的同時(shí),既要增加新的特征,同時(shí)還要保留equals約定,沒(méi)有一個(gè)簡(jiǎn)單的辦法可以做到這一點(diǎn)。根據“復合優(yōu)先于繼承”的建議,可以如下改動(dòng):
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);
}
...
}
  實(shí)現高質(zhì)量equals方法的“處方”:
<!--[if !supportLists]-->1.      <!--[endif]-->使用==操作符檢查“實(shí)參是否為指向對象的一個(gè)引用”
如果是,返回true;
<!--[if !supportLists]-->2.      <!--[endif]-->使用instanceof操作符檢查“實(shí)參是否為正確的類(lèi)型”,
如果不是,返回false;
<!--[if !supportLists]-->3.      <!--[endif]-->把實(shí)參轉換到正確的類(lèi)型
<!--[if !supportLists]-->4.      <!--[endif]-->對于該類(lèi)中每一個(gè)"關(guān)鍵(significant)"域,檢查實(shí)參中的域與當前對象中對應的域值是否匹配。
如果所有的測試都成功,則返回true;
<!--[if !supportLists]-->5.      <!--[endif]-->當你編寫(xiě)完成了equals方法之后,應該問(wèn)自己三個(gè)問(wèn)題:它是否是對稱(chēng)的、傳遞的、一致的?

  下面是一些告誡:
  • 當你改寫(xiě)equals的時(shí)候,總是要改寫(xiě)hasCode方法(參見(jiàn)第8條)
  • 不要企圖讓equals方法過(guò)于聰明
    如果只是簡(jiǎn)單地測試域中的值是否相等,則不難做到遵守equals約定。
  • 不要使equals方法依賴(lài)于不可靠的資源
    否則難以滿(mǎn)足一致性要求。除了少數的例外,equals方法應該針對駐留在內存中的對象執行確定性的計算
  • 不要將equals聲明中的Object對象替換為其它的類(lèi)型
    當你的equals不能正常工作時(shí),看看是不是犯了下述例子的錯誤。
    public boolean equals(MyClass o){
    ...
    }
 
 
讀《Effective java 中文版》(9)
第8條:改寫(xiě)equals時(shí)總是要改寫(xiě)hashCode
  java.lnag.Object中對hashCode的約定:
<!--[if !supportLists]-->1.      <!--[endif]-->在一個(gè)應用程序執行期間,如果一個(gè)對象的equals方法做比較所用到的信息沒(méi)有被修改的話(huà),則對該對象調用hashCode方法多次,它必須始終如一地返回同一個(gè)整數。
<!--[if !supportLists]-->2.      <!--[endif]-->如果兩個(gè)對象根據equals(Object o)方法是相等的,則調用這兩個(gè)對象中任一對象的hashCode方法必須產(chǎn)生相同的整數結果。
<!--[if !supportLists]-->3.      <!--[endif]-->如果兩個(gè)對象根據equals(Object o)方法是不相等的,則調用這兩個(gè)對象中任一個(gè)對象的hashCode方法,不要求產(chǎn)生不同的整數結果。但如果能不同,則可能提高散列表的性能。
 
看個(gè)不改寫(xiě)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))的返回值什么?
  雖然這個(gè)實(shí)例據equals是相等的,但由于沒(méi)改寫(xiě)hashCode而致兩個(gè)實(shí)例的散列碼并不同(即違反第二條要求),因則返回的結果是null而不是"Jenny".
  理想情況下,一個(gè)散列函數應該把一個(gè)集合中不相等的實(shí)例均勻地分布到所有可能的散列值上,下面是接近理想的“處方”:
<!--[if !supportLists]-->1.      <!--[endif]-->把某個(gè)非零常數值(如17)保存在一個(gè)叫result的int類(lèi)型的變量中;
<!--[if !supportLists]-->2.      <!--[endif]-->對于對象中每個(gè)關(guān)鍵字域f(指equals方法中考慮的每一個(gè)域),完成以下步驟:
<!--[if !supportLists]-->1.      <!--[endif]-->為該域計算int類(lèi)型的散列碼c:
<!--[if !supportLists]-->1.      <!--[endif]-->如果該域是bloolean類(lèi)型,則計算(f?0:1)
<!--[if !supportLists]-->2.      <!--[endif]-->如果該域是byte,char,short或int類(lèi)型,則計算(int)f
<!--[if !supportLists]-->3.      <!--[endif]-->如果該域是long類(lèi)型,則計算(int)(f^(>>>32))
<!--[if !supportLists]-->4.      <!--[endif]-->如果該域是float類(lèi)型,則計算Float.floatToIntBits(f)
<!--[if !supportLists]-->5.      <!--[endif]-->如果該域是double類(lèi)型,則計算Double.doubleToLongBits(f)得一long類(lèi)型值,然后按前述計算此long類(lèi)型的散列值
<!--[if !supportLists]-->6.      <!--[endif]-->如果該域是一個(gè)對象引用,則利用此對象的hashCode,如果域的值為null,則返回0
<!--[if !supportLists]-->7.      <!--[endif]-->如果該域是一個(gè)數組,則對每一個(gè)數組元素當作單獨的域來(lái)處理,然后安下一步的方案來(lái)進(jìn)行合成
 
<!--[if !supportLists]-->2.      <!--[endif]-->利用下面的公式將散列碼c 組合到result中。result=37*result+c;
 
<!--[if !supportLists]-->3.      <!--[endif]-->檢查“相等的實(shí)例是否具有相等的散列碼?”,如果為否,則修正錯誤。

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

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

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

  看一個(gè)“復數”的例子:
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定義是一個(gè)非工業(yè)級的復數實(shí)現,一是因為乘、除法的實(shí)現可能導致不正確舍入,二是沒(méi)有處理復數NaN或無(wú)窮大的情況。
  該例中對,對復數的操作都會(huì )返回一個(gè)新的實(shí)例,而不修原來(lái)的實(shí)例,這被稱(chēng)之為函數式作法(functional),與之對應的是修改實(shí)例狀態(tài)的過(guò)程式方法(precedural)。
  非可變對象比較簡(jiǎn)單,它本質(zhì)上是線(xiàn)程安全的,它們不要求同步,因而非可變對象可被自由地共享(一個(gè)簡(jiǎn)單地做法,是為提供公有的靜態(tài)final常量),而不需要進(jìn)行保護性拷貝,而且也應為非可變類(lèi)提供clone方法或拷貝構造函數(如String類(lèi)的拷貝構造函數,應盡量不用)。
  不僅可以共享非可變對象,而且可以共享它們的內部信息。例如,BigInteger的negate方法產(chǎn)生一個(gè)新實(shí)例,其存放數值的數組與原來(lái)的實(shí)例是同一個(gè)數組。
  非可變對象為其他對象--無(wú)論是可變的還是非可變的--提供了大量的構造(buildig block)。
  非可變類(lèi)真正唯一的缺點(diǎn)是,對于每一個(gè)不同的值都要求一個(gè)單獨的對象。常見(jiàn)的解決思路是提供一個(gè)可變的配套類(lèi),如String的配置可變類(lèi)StringBuffer.
  一個(gè)類(lèi)絕不允許其子類(lèi)改寫(xiě)的方法有三:
  • 聲明類(lèi)為final,見(jiàn)前例
  • 類(lèi)不為final,但每個(gè)方法為final,這法不值得提倡
  • 使所有構造函數為私有或者包級私有,且增加公有的靜態(tài)工廠(chǎng)來(lái)代替公有的構造函數。如:
    //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的方法可以被改寫(xiě),所以你在開(kāi)發(fā)自己的非可變類(lèi)時(shí),如果不能確保收到這兩個(gè)類(lèi)的實(shí)例的非可變性,則需要進(jìn)行保護性拷貝。如:
public void foo(BigInteger b){
if(b.getClass() != BigInteger.class) b=new BigInteger(b.toByteArray());
...
}
  其實(shí),前面所述的規則過(guò)于強,只需滿(mǎn)足:沒(méi)有一個(gè)方法能對對象狀態(tài)產(chǎn)生外部可見(jiàn)(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函數開(kāi)銷(xiāo)較大,所以在前面使用了遲緩初始化的技術(shù)。
private Foo fooVal(){...}
  除非有很好的理由要讓一個(gè)類(lèi)成為可變類(lèi),否則就應該是非可變的,缺點(diǎn)是性能問(wèn)題,如果對性能要求比較高,可以提供公有的可變配套類(lèi)。
  如果一個(gè)類(lèi)不能被做成非可變類(lèi),仍可應盡可能地限制它的可變性。構造函數應創(chuàng )建完全初始化的對象,所有約束關(guān)系應在這時(shí)候建立起來(lái),構造函數不應把“只初始化了一部分的實(shí)例”傳遞給其它的方法。
 
 
讀《Effective java 中文版》(15)
第14條:復合優(yōu)先于繼承
  繼承是實(shí)現代碼重用的有力手段,但不適當地使用繼承會(huì )導致脆弱的軟件。
  • 在一個(gè)包內使用繼承是非常安全的,因在那兒子類(lèi)和超類(lèi)在同一個(gè)程序員的控制之下。
  • 對于專(zhuān)門(mén)為了繼承而設計、且有很好文檔說(shuō)明的類(lèi),使用繼承也是安全的。
  • 對普通的具體類(lèi)(concret class)時(shí)行跨躍包邊界的繼承,則是非常危險的。
 
  與方法調用不同的是,繼承打破了封裝性,一個(gè)子類(lèi)依賴(lài)于其超類(lèi)中特定功能的實(shí)現細節。超類(lèi)的實(shí)現的變化,則子類(lèi)可能會(huì )被打破。
  • 自用性,即超類(lèi)中不同公有方法的實(shí)現時(shí)存在調用關(guān)系,當子類(lèi)改寫(xiě)這些方法時(shí),常會(huì )導致子類(lèi)的脆弱
  • 超類(lèi)在它的后續方法中增加了新的方法,如果子類(lèi)不能及時(shí)改寫(xiě)這些方法,異常的數據或操作出現。
  • 子類(lèi)繼承超類(lèi)后,增加了一個(gè)新的方法,則當超類(lèi)在新版中也增加了具有相同原型特征的方法時(shí),可能會(huì )出現問(wèn)題

  有一種辦法可以避免上述所有問(wèn)題:新類(lèi),不是擴展一個(gè)已有類(lèi),而是設置一個(gè)私有域,它引用這個(gè)已有類(lèi)的一個(gè)實(shí)例。這種設計被稱(chēng)為“復合(composition)”。新類(lèi)中的每個(gè)實(shí)例方法,都可以調用被包含的已有類(lèi)實(shí)例中對應的方法,并返回它的結果,即為“轉發(fā)方法(forwarding method)”。這樣的類(lèi)比較穩固,這不依賴(lài)于已有類(lèi)的實(shí)現細節。一個(gè)類(lèi)的實(shí)例都把另一個(gè)類(lèi)的實(shí)現包裝起來(lái)了,則前者的類(lèi)叫做包裝類(lèi)(wrapper class)。
  看一個(gè)例子:
//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類(lèi)對Set類(lèi)進(jìn)行了修飾,增加了計數特性。有時(shí),復合和轉發(fā)這兩項技術(shù)的結合被錯誤地引用為“委托(delegation)”,從技術(shù)的角度而言,這不是委托,除非包裝對象把自己傳遞給一個(gè)被包裝的對象。
  包裝類(lèi)幾乎沒(méi)有什么缺點(diǎn)。需要注意的是,包裝類(lèi)不適合用于回調框架(callback framework)中。在回調框架中,對象把自己的引用傳遞給其它的對象,以便將來(lái)調用回來(lái),當它被包裝起來(lái)以后,它并不知道外面的包裝對象的情況,所以它傳遞一個(gè)指向自己的引用(this)時(shí),會(huì )造成回調時(shí)繞開(kāi)外面的包裝對象的問(wèn)題。這被稱(chēng)為SELF問(wèn)題。
  只有當子類(lèi)真正是超類(lèi)的“子類(lèi)型(subtype)”時(shí),繼承才是合適的。即兩者之存在“is-a”的關(guān)系。java平臺中,也有違反這條規則的地方:如Stack不是向量,所以不應擴展Vector;屬性列表不是散列表,所以Properties不應擴展Hashtable。在決定使用復合還是擴展時(shí),還要看一試圖擴展的類(lèi)的API有沒(méi)有缺陷,如果你愿意傳播這些缺陷到自己的API中,則用繼承,否則可用復合來(lái)設計一個(gè)新的API。
 
讀《Effective java 中文版》(16)
第15條:要么專(zhuān)門(mén)為繼承而設計,并給出文檔說(shuō)明,要么禁止繼承
  一個(gè)專(zhuān)門(mén)為了繼承而設計并且具有良好文檔說(shuō)明的類(lèi),意味著(zhù):
  • 該類(lèi)的文檔必須精確地描述了改寫(xiě)每一個(gè)方法所帶來(lái)的影響  該類(lèi)必須有文檔說(shuō)明其可改寫(xiě)的方法的自用性:對于每一個(gè)公有的或受保護的方法或者構造函數,它的文檔必須指明它調用了哪些可改寫(xiě)的方法是以什么順序調用,每個(gè)調用的結果是如何影響后續的處理過(guò)程的?! T例是如果一個(gè)方法調用了可改寫(xiě)的方法,那么在它的文檔注釋的末尾應該包含關(guān)于這些調用的描述信息。
  • 為了使程序員能夠編寫(xiě)出更加有效的子類(lèi),而無(wú)需承受不必要的痛苦,一個(gè)類(lèi)必須通過(guò)某種形式提供適當的鉤子(hook),以便能夠進(jìn)入到它的內部工作流程中,這樣的形式可以是精心選擇的受保護方法,也可是以保護域,后者少見(jiàn)?! ‘敒榱死^承的目的而設計一個(gè)有可能會(huì )被廣泛使用的類(lèi)時(shí),須意識到,對于文檔中所說(shuō)明的自用模式(self-use pattern),以及對于其受保護方法和域所隱含的實(shí)現細節,實(shí)際上已經(jīng)做出了永久的承諾。這些承諾使得你在后續的版本中提高這個(gè)類(lèi)的性能或增加很功能非常困難,或者不可能。
  • 構造函數一定不能調用可改寫(xiě)的方法,無(wú)論是直接還是間接進(jìn)行,因為超類(lèi)的構造函數在子類(lèi)的構造函數之前運行??蠢樱?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(); } }   程序運行的結果不會(huì )打印出日期兩次,第一次打出null.
  • 在一個(gè)為了繼承而設計的類(lèi)中,實(shí)現Cloneable或Serializable接口,因為clone和readObject方法在行為上非常類(lèi)似于構造函數,所以無(wú)論clone還是readObject都不能調用一個(gè)可改寫(xiě)的方法,無(wú)論是直接還是間接的方式?! τ趓eadObject方法,子類(lèi)中改寫(xiě)版本的方法將在子類(lèi)的狀太被反序列化之前先被運行;對于clone方法,改寫(xiě)版本的方法將在子類(lèi)的clone方法有機會(huì )修正被克隆對象的狀態(tài)之前先被運行。
  • 在一個(gè)為了繼承而設計的類(lèi)中實(shí)現Serializable接口,且該類(lèi)有一個(gè)readResolve或writeReplace方法,則你必須使readResolve或者writeReplace成為受保護的方法,而不是私有的方法。
  為了繼承而設計一個(gè)類(lèi),要求對這個(gè)類(lèi)有一些實(shí)質(zhì)性的限制。對于那些并非為了安全地進(jìn)行子類(lèi)化而設計和編寫(xiě)文檔的類(lèi)(如普通的具體類(lèi)),禁止子類(lèi)化。有兩種辦法可以禁止子類(lèi)化:
<!--[if !supportLists]-->1.      <!--[endif]-->把類(lèi)聲明為final
<!--[if !supportLists]-->2.      <!--[endif]-->把所有的構造函數變成私有的或包級私有的,增加一些公有的靜態(tài)工廠(chǎng)來(lái)替代構造函數的位置
如果必須從這樣的類(lèi)來(lái)繼承,則要確保這類(lèi)永遠不會(huì )調用到它的可改寫(xiě)的方法。
 
讀《Effective java 中文版》(17)
第16條:接口優(yōu)于抽象類(lèi)
  Java語(yǔ)言中的接口與抽象類(lèi)的一些區別:
  • 抽象類(lèi)允許包含某些方法的實(shí)現,接口不允許。
  • 為實(shí)現一個(gè)由抽象類(lèi)定義的類(lèi)型,它必須成為抽象類(lèi)的一個(gè)子類(lèi)。任何一個(gè)類(lèi),只要它定義了所有要求的方法,并且遵守通用約定,則它就允許實(shí)現一個(gè)接口。
  • java只允許單繼承,抽象類(lèi)作為類(lèi)型定義受到了極大的限制。
 
  看一下接口:
 
<!--[if !supportLists]-->1.      <!--[endif]-->  已有的類(lèi)可以很容易被更新,以實(shí)現新的接口。只要:增加要求的方法,在類(lèi)的聲明上增加implements子句。
<!--[if !supportLists]-->2.      <!--[endif]-->  接口是定義mixin(混合類(lèi)型)的理想選擇。一個(gè)mixin是指這樣的類(lèi)型:一個(gè)類(lèi)除了實(shí)現它的基本類(lèi)型(primary type)之外,還可以實(shí)現這個(gè)mixin類(lèi)型,以表明它提供了某些可選擇的行為。接口之所以能定義mixin,因為它允許可選的功能可被混合到一個(gè)類(lèi)型的基本類(lèi)型中,而抽象類(lèi)不能用于定義mixin類(lèi)型,同樣的理由是因為它們不能被更新到已有的類(lèi)中:一個(gè)類(lèi)不可能有一個(gè)以上的父類(lèi),并且在類(lèi)層次結構中沒(méi)有適當的地方來(lái)放置mixin。
  接口使得我們可以構造出非層次結構的類(lèi)型結構??蠢樱?br>public interface Singer{
AudioClip sing(Song s);
}
public interface Songwriter{
Song compose(boolean hit);
}
  為解決歌唱家本人也能做曲的情況,可以很簡(jiǎn)單地做到:
public interface SingerSongwriter extends Singer, Songwriter{
AudioClip strum();
void actSensitive();
}
  如果用抽象類(lèi)來(lái)做,會(huì )是如何?
<!--[if !supportLists]-->3.      <!--[endif]-->  接口使得安全地增強一類(lèi)的功能成為可能,做法是使用第14條介紹的包裝類(lèi)模式。如果用抽象類(lèi)型來(lái)做,則程序員除了使用繼承沒(méi)有別的方法。
<!--[if !supportLists]-->4.      <!--[endif]-->  接口不允許包含方法的實(shí)現。把接口和抽象類(lèi)的優(yōu)點(diǎn)結合起來(lái),對于期望導出的每一個(gè)重要接口,都提供一個(gè)抽象的骨架實(shí)現(skeletal implementaion)類(lèi)。接口的作用仍是定義類(lèi)型,骨架實(shí)現類(lèi)負責所有與接口實(shí)現相關(guān)的工作。按照慣例,骨架實(shí)現被稱(chēng)為AbstractInterface(注:此interface是所實(shí)現的接口的名字,如AbstractList,AbstractSet)??匆粋€(gè)靜態(tài)工廠(chǎng):
//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);
}
}
}
  這個(gè)例子是一個(gè)Adapter,它使得一個(gè)int數組可以被看作一個(gè)Integer實(shí)例列表(由于存在int和Integer之間的轉換,其性能不會(huì )非常好)
  骨架實(shí)現的優(yōu)美之外在于,它們?yōu)槌橄箢?lèi)提供了實(shí)現上的幫助,但又沒(méi)有強加“抽象類(lèi)被用做類(lèi)型定義時(shí)候”所特有的嚴格限制。對一地一個(gè)接口的大多數實(shí)現來(lái)講,擴展骨架實(shí)現類(lèi)是一個(gè)很顯然的選擇,當然它也只是一個(gè)選擇而已。
  實(shí)現了這個(gè)接口的類(lèi)可以把對于接口方法的調用,轉發(fā)到一個(gè)內部私有類(lèi)的實(shí)例上,而這個(gè)內部私有類(lèi)擴展了骨架實(shí)現類(lèi)。這項技術(shù)被稱(chēng)為模擬多重繼承。
  編寫(xiě)一個(gè)骨架實(shí)現類(lèi)相對比較簡(jiǎn)單,首先要認真研究接口,并且確實(shí)哪些方法是最為基本的(primitive),其他的方法在實(shí)現的時(shí)候將以它們?yōu)榛A,這些方法將是骨架實(shí)現類(lèi)中的抽象方法;然后須為接口中的其它方法提供具體的實(shí)現。骨架實(shí)現類(lèi)不是為了繼承的目的而設計的。(怎么理解?)看例子:
//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());
}
}

  使用抽象類(lèi)來(lái)定義允許多個(gè)實(shí)現的類(lèi)型,比使用接口有一個(gè)明顯的優(yōu)勢:抽象類(lèi)的演化比接口的演化要容易的多。在后續的發(fā)行版中,如果希望在抽象類(lèi)中增加一個(gè)方法,只增加一個(gè)默認的合理的實(shí)現即可,抽象類(lèi)的所有實(shí)現都自動(dòng)提供了這個(gè)新的方法。對于接口,這是行不通的。雖然可以在骨架實(shí)現類(lèi)中增加一方法的實(shí)現來(lái)解決部分問(wèn)題,但這不能解決不從骨架實(shí)現類(lèi)繼承的接口實(shí)現的問(wèn)題。由此,設計公有的接口要非常謹慎,一旦一個(gè)接口被公開(kāi)且被廣泛實(shí)現,對它進(jìn)行修改將是不可能的。
 
讀《Effective java 中文版》(18)
第17條:接口只是被用于定義類(lèi)型
  當一個(gè)類(lèi)實(shí)現了一個(gè)接口的時(shí)候,這個(gè)接口被用做一個(gè)類(lèi)型(type),通過(guò)此類(lèi)型可以引用這個(gè)類(lèi)的實(shí)例。因此,一個(gè)類(lèi)實(shí)現了一個(gè)接口,就表明客戶(hù)可以對這個(gè)類(lèi)的實(shí)例實(shí)施某些動(dòng)作。為了任何其它目的而定義接口是不合適的。
  常量接口是對接口的不良應用,因它不滿(mǎn)足上述條件。常量接口中沒(méi)有包含任何方法,只包含靜態(tài)的final域,每個(gè)域都導出一個(gè)常量。r看下例。如果一個(gè)類(lèi)要使用這些常量,它只要實(shí)現這個(gè)接口,就可以避免用類(lèi)名來(lái)修飾常量名。但是,實(shí)現一個(gè)常量接口,會(huì )導致把這樣的實(shí)現細節(指內部使用常量)泄露到該類(lèi)的導出API中。而且在將來(lái)的發(fā)行版本中,如果類(lèi)不再需要這些常量了,但仍要實(shí)現這個(gè)接口。還有,這樣的類(lèi)的子類(lèi),也會(huì )為常量所污染。
//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平臺中有幾個(gè)常量接口,如:java.io.ObjectStreamConstants,這不值得效仿。
  如果要導出常量,有幾種方案:
<!--[if !supportLists]-->1.      <!--[endif]-->如果這常量與某個(gè)已有類(lèi)或接口緊緊聯(lián)系在一起,則應該把它們加入類(lèi)或常量中。
<!--[if !supportLists]-->2.      <!--[endif]-->如果這些常量最好被看作一個(gè)枚舉類(lèi)型的成員,那么應該用一個(gè)類(lèi)型安全枚舉類(lèi)(typesafe enum class),參見(jiàn)第21條。
<!--[if !supportLists]-->3.      <!--[endif]-->否則,應用一個(gè)不可被實(shí)例化的工具類(lèi)(utility class),見(jiàn)第3條??磳ι厦胬拥男薷模?br>//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;
}

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

  匿名類(lèi)的常見(jiàn)用法:
<!--[if !supportLists]-->1.      <!--[endif]-->
  匿名類(lèi)的一通常用法是創(chuàng )建一個(gè)函數對象(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();
}
});
<!--[if !supportLists]-->2.      <!--[endif]-->另一個(gè)常見(jiàn)用法是創(chuàng )建一個(gè)過(guò)程對象(process object),如Thread、Runnable、TimerTasker實(shí)例。
<!--[if !supportLists]-->3.      <!--[endif]-->在一個(gè)靜態(tài)工廠(chǎng)方法的內部(參見(jiàn)第16條)
<!--[if !supportLists]-->4.      <!--[endif]-->用在復雜的類(lèi)型安全枚舉類(lèi)型(它要求為每個(gè)實(shí)例提供單獨的子類(lèi))
例子:
//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);
}
}
  局部類(lèi)是四種嵌套類(lèi)中最少使用的類(lèi)。在任何“可以聲明局部變量”的地方,都可以聲明局部類(lèi)。與成員類(lèi)一樣,局部類(lèi)有名字,可以被重復使用。與匿名類(lèi)一樣,當且僅當局部類(lèi)被用于非靜態(tài)環(huán)境下的進(jìn)修,它們才有外圍實(shí)例。與匿名類(lèi)一樣,它必須非簡(jiǎn)短,以不會(huì )損害外圍方法或者初始化器的可讀性。
  總之,如果一個(gè)嵌套類(lèi)需要在單個(gè)方法之外仍是可見(jiàn)的,或者它太長(cháng)了,不適合于放在一個(gè)方法內部,則應該使用成員類(lèi)。如果成員類(lèi)的每個(gè)實(shí)例都需要一個(gè)指向其外圍的實(shí)例的引用,則把成員類(lèi)做成非靜態(tài)的;否則做成靜態(tài)的。假設一個(gè)嵌套類(lèi)屬于一方法的內部,如果只需要在一個(gè)地方創(chuàng )建它的實(shí)例,并且已經(jīng)有了一個(gè)預先存在的類(lèi)型可以說(shuō)明這個(gè)類(lèi)的特征,則把它做成匿名類(lèi);否則變成局部類(lèi)。
讀《Effective java 中文版》(20)
第19條:用類(lèi)代替結構
  Java語(yǔ)言中讓一個(gè)類(lèi)退化到只包含一些公開(kāi)的數據域,這樣的類(lèi)與C語(yǔ)言的結構類(lèi)似,如:
//degenerate classes like this should not be public
class Point{
public float x;
public float y;
}
  這樣的類(lèi),拋棄了所有數據封裝的優(yōu)點(diǎn),而你幾乎不能對這樣的API做什么改動(dòng)或限制,應該代之以私有域和公有訪(fǎng)問(wèn)方法,如:
//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平臺庫中,有幾個(gè)類(lèi)違反了"公有類(lèi)不應該直接暴露數據域“的告誡,如java.awt中的Point和Dimension。
讀《Effective java 中文版》(21)
第20條:用類(lèi)層次來(lái)代替聯(lián)合
  C語(yǔ)言中的聯(lián)合(union)是用來(lái)定義可以容納多種數據類(lèi)型的數據結構,常見(jiàn)用法是在一個(gè)結構中包含一個(gè)聯(lián)合和一個(gè)標簽(標簽通常是某一個(gè)枚舉(enum)類(lèi)型),用標簽指明聯(lián)合中存放的是哪種數據類(lèi)型的數據,這也被稱(chēng)為可區分的聯(lián)合。Java語(yǔ)言中,union結構被子類(lèi)型化這種機制代替。
  類(lèi)層次與可區分的聯(lián)合相比,有N多好處:
<!--[if !supportLists]-->1.      <!--[endif]-->類(lèi)層次提供了類(lèi)型的安全性,而C語(yǔ)言中如果標簽的指示與union存放對象不一致,則會(huì )出錯。
<!--[if !supportLists]-->2.      <!--[endif]-->代碼簡(jiǎn)潔明了。
<!--[if !supportLists]-->3.      <!--[endif]-->容易擴展,即使多方在獨立的工作。
<!--[if !supportLists]-->4.      <!--[endif]-->可以反映出這些類(lèi)型之間本質(zhì)上的層次關(guān)系,從而允許更強的靈活性,以及更好的編譯時(shí)類(lèi)型檢查。

  當要編寫(xiě)一個(gè)類(lèi),而其中包含一個(gè)顯式的標簽域的時(shí)候,就應該考慮這個(gè)標簽是否可以被取消而用一個(gè)類(lèi)層次來(lái)代替。
  C語(yǔ)言中union的另一個(gè)用途是,查看一片數據的內部表示??蠢?br>union{
float f;
int bits;
}sleaze;
sleaze.f=6.699e-41;
print("%x\n",sleeze.bits);
  在java語(yǔ)言中可以這樣做:
system.out.println(Integer.toHexString(Float.floatToIntBits(6.699e-41f)));
讀《Effective java 中文版》(22)
第21條:用類(lèi)來(lái)代替enum結構
  C語(yǔ)言中,enum結構定義一個(gè)枚舉類(lèi)型:它的合法值是由一組固定的常量組成的。但這種類(lèi)型定義有問(wèn)題:
  • 它只是定義了一組被命名的整數常量,在類(lèi)型安全和使用方便性方面沒(méi)有提供任何幫助。如:
    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結構并沒(méi)有為它產(chǎn)生的常量建立起一個(gè)名字空間,如下面則會(huì )出現沖突:
    typedef enum{BLOOD, SWEAT, TEARS} fluid-t;
  • 用enum結構定義的類(lèi)型是非常脆弱的。在enum類(lèi)型增加新的常量而不重新編譯客戶(hù)程序,會(huì )引起不可預知的行為。多個(gè)開(kāi)發(fā)方不能獨立地為這樣的類(lèi)型增加常量,因為可能沖突。
  • enum結構中沒(méi)提供便利的方法來(lái)將枚舉常量轉換成可打印字符串,或者枚舉出一個(gè)類(lèi)型中的所有的常量。

  不幸的是,java中最常用的針對枚舉類(lèi)型的模式,也有這些缺點(diǎn),如例:
//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程序還有被稱(chēng)為類(lèi)型安全枚舉的模式:定義一類(lèi)來(lái)代表枚舉類(lèi)型的單個(gè)元素,并且不提供任何公有的構造函數,相反提供公有的靜態(tài)final域,使枚舉類(lèi)型的每一個(gè)常量都對應一個(gè)域。如:
//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");
}
  即使這個(gè)類(lèi)沒(méi)有聲明為final,客戶(hù)也沒(méi)法創(chuàng )建這個(gè)類(lèi)的對象,也無(wú)法擴展這個(gè)類(lèi),因而除了通過(guò)這些公有的靜態(tài)final域導出的Suit對象之外,永遠不會(huì )有其它的對象存在。
  好處:
  • 提供了編譯的類(lèi)型安全性
  • 多個(gè)“類(lèi)型安全枚舉”可以包含相同名字的枚舉常量,因為每個(gè)類(lèi)都有自己的命名空間。
  • 新的常量加入到一個(gè)類(lèi)型安全枚舉類(lèi)中,無(wú)需重新編譯客戶(hù)代碼,因為常量并沒(méi)有被編譯到客戶(hù)代碼中。
  • 可以通過(guò)改寫(xiě)toString來(lái)允許其值轉化為可打印字符串。
  • 因為任何方法都可以被加到類(lèi)型安全枚舉中類(lèi)中,所以它們可以實(shí)現任何接口。如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");
    }
  • 因為類(lèi)型安全枚舉類(lèi)型的常量是對象,所以你可以把這些常量放到集合中。如:
    private static final Suit[] PRIVATE_VALUES={CLUBS, DIAMAONDS, HEARTS, SPADES};
    public static final List VALUES=Collection.unmodifiableList(Array.asList(PRIVATE_VALUES));
  • 基于序數形式的類(lèi)型安全枚舉模式,在聲明中增加implements Serializable,然后提供一一個(gè)readResolve方法,即可支持序列化。
    private Object readResolve() throws ObjectStreamException{
    return PRIVATE_VALUES[ordinal];
    }
  • 類(lèi)型安全枚舉類(lèi)在性能可與int枚舉常量相比美,因為可以使用“引用的同一性比較”來(lái)檢查邏輯上的相關(guān)等關(guān)系。

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

  看一個(gè)可擴展的、可序列化的類(lèi)型安全枚舉類(lèi):
//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模式相比,類(lèi)型安全枚舉模式的缺點(diǎn):
  • 把類(lèi)型安全枚舉常量組合到一起比較困難
  • 不能用在switch語(yǔ)句中
  • 類(lèi)型安全枚舉需要一定的時(shí)空開(kāi)銷(xiāo)
讀《Effective java 中文版》(23)
第22條:用類(lèi)和接口來(lái)代替函數指針
  C語(yǔ)言支持函數指針,其主要用途是實(shí)現Strategy(策略)模式,典型的應用如比較器函數,就是策略模式的一個(gè)例子。Java省略了函數指針,因為對象引用可以被用于提供同樣的功能。如例:
class StringLengthComparator{
public int compare(String s1,String s2){
return s1.lenght()-s2.length();
}
}
  java中實(shí)現策略模式,聲明一個(gè)接口來(lái)表示該策略,且為每個(gè)具體策略聲明一個(gè)實(shí)現了該接口的類(lèi)。如果一個(gè)具體策略只被使用一次,則常用匿名類(lèi)實(shí)現聲明和實(shí)例化。如果一個(gè)具體策略要被導出以重復使用,則常為一個(gè)私有的靜態(tài)成員類(lèi),且通過(guò)一個(gè)公有靜態(tài)final域被導出,其類(lèi)型為該策略接口。
  作為一個(gè)典型的具體策略類(lèi),StringLengthComparator類(lèi)是無(wú)狀態(tài)的,沒(méi)有域,它作為一個(gè)singleton非常合適,如下:
class StringLengthComparator{
private StringLengthComparator(){}
public static final StringLengthComparator INSTANCE=new StringLengthComparator();
public int compare(String s1,String s2){
return s1.lenght()-s2.length();
}
}
  在設計具體策略類(lèi)的時(shí)候,還需要定義一個(gè)策略接口,如:
//Stragetegy interface
public interface Comparator{
public int compare(Object o1,Object o2);
}
  具體的策略類(lèi)往往使用匿名類(lèi)聲明,如:
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();
}
});
  由于策略接口被用做所有具體策略實(shí)例的類(lèi)型,故不必將具體策略類(lèi)做成公有的。如下:
//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條:檢查參數的有效性
  當編寫(xiě)一個(gè)方法或者構造函數的時(shí)候,應該考慮對于它的參數有哪些限制,且應把這些限制寫(xiě)到文檔中,然后在這個(gè)方法體的起始處,通過(guò)的檢查來(lái)實(shí)施這些限制。
  對于公有的方法,使用javadoc的@throws標簽在文檔中寫(xiě)下“一旦針對參數值的限制被違反之后將會(huì )拋出的異常”,如IllegalArgumentException、IndexOutOfBoundsException或NullPointerException。
  非公有的方法,通常應該使用assertions斷言來(lái)檢查它們的參數。
  一些情況下可以不進(jìn)行參數限制,如:
  • 有效性檢查工作非常昂貴
  • 檢查根本不切實(shí)際
  • 計算過(guò)程中隱含著(zhù)有有效性檢查的工作.此時(shí),如果由于無(wú)效參數而致的計算拋出的異常,與聲明中的異常并不匹配,則需要使用異常轉譯技術(shù)。
讀《Effective java 中文版》(25)
第24條:需要時(shí)使用保護性拷貝
  從安全的角度考慮,應該設計面對客戶(hù)的不良行為時(shí)仍能保持健壯性的類(lèi),無(wú)論系統的其它部分發(fā)生什么事情,這些類(lèi)的約束都可以保持為真。
  下面是一個(gè)有問(wèn)題的例子:
//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實(shí)例的內部信息免受攻擊,對構造函數的每個(gè)參數進(jìn)行保護性拷貝是必要的。而且,保護性拷貝要在檢查參數的有效性之前進(jìn)行,并且有效性檢查是針對拷貝后的對象而不是原始的對象,以防止其它的線(xiàn)程會(huì )修改原始值。如下:
//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);
}
  注意,如果一個(gè)類(lèi)可以被子類(lèi)化,則不要用clone方法進(jìn)行參數的保護性拷貝。
  到目前為止,雖然對Period類(lèi)進(jìn)行了保護,但對它的攻擊還是有可能的。如下:
//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!!
  此時(shí)要對相關(guān)的方法進(jìn)行修改,要求它們返回的是內部域的保護性拷貝即可,如下:
//repaired accessors-make defensive copies of internal fields
public Date start(){
return (Date)start.clone();
}
public Date end(){
return (Date)end.clone();
}
  至此,“一個(gè)周期的起始時(shí)間不能落后于結束時(shí)間”的約束,才真正做的到。
  只要有可能,都使用非可變的對象做為對象內部的組件,這樣就不必關(guān)心保護性拷貝的問(wèn)題。
讀《Effective java 中文版》(26)
第25條:謹慎地設計方法的原型
<!--[if !supportLists]-->1.      <!--[endif]-->謹慎選擇方法的名字
<!--[if !supportLists]-->2.      <!--[endif]-->不要過(guò)于追求提供便利的方法。
只有當一個(gè)方法被用的非常頻繁的時(shí)候,才考慮為它提供一個(gè)快捷方法。如果不能確定,還是不考慮為好。
<!--[if !supportLists]-->3.      <!--[endif]-->避免長(cháng)長(cháng)的參數列表。
三個(gè)參數應為實(shí)踐中的最大值。有兩項技術(shù)可以縮短方法的參數列表:
<!--[if !supportLists]-->o        <!--[endif]-->把一個(gè)方法分解成多個(gè)方法,每個(gè)方法只要求這些參數一個(gè)子集。
<!--[if !supportLists]-->o        <!--[endif]-->創(chuàng )建輔助類(lèi),來(lái)保存參數的聚集
<!--[if !supportLists]-->4.      <!--[endif]-->對于參數類(lèi)型,優(yōu)先使用接口而不是類(lèi)
例如,應該使用Map而不是Hashtable作為參數類(lèi)型。
<!--[if !supportLists]-->5.      <!--[endif]-->謹慎地使用函數對象
讀《Effective java 中文版》(27)
第26條:謹慎地使用重載
  下面的例子希望能識別出實(shí)例的類(lèi)型分別為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,:(,雖然常識是“重載方法的選擇是靜態(tài)的,改寫(xiě)方法的選擇是動(dòng)態(tài)的”。
  下面是重寫(xiě)的一個(gè)例子:
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"了。
  因為改寫(xiě)機制是規范,而重載機制是例外,所以改寫(xiě)機制滿(mǎn)足了人們對于方法調用的行為的期望,而重載機制很容易混淆這些期望。以避免重載機制的混淆用法的方法有
  • 一個(gè)安全而保守的策略是,永遠不要導出兩個(gè)具有相同參數數目的重載方法。
  • 對于每一對重載方法,只有一個(gè)對應的形參具有“根本不同”的類(lèi)型,指把一個(gè)參數的類(lèi)型轉換為另一個(gè)類(lèi)型是不可能的。
  • 對于構造函數或實(shí)現某個(gè)接口時(shí),可能會(huì )較多地出現重載的機會(huì ),前者可考慮用靜態(tài)工廠(chǎng),后者可以考慮兩個(gè)重載方法合并。
讀《Effective java 中文版》(28)
第27條:返回零長(cháng)度的數組而不是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;
...
}
  調用這個(gè)方法時(shí),需要:
Cheese[] cheeses=shop.getCheeses();
if(cheeses!=null && Arrays.asList(shop.getCheeses()).contains(Cheese.STILTON))
System.out.println("Jolly good, just the thing");
  如果改用返回長(cháng)度為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元素編寫(xiě)文檔注釋
  為了正確地編寫(xiě)API文檔,必須在每一個(gè)被導出的類(lèi)、接口、構造函數、方法和域聲明之前增加一個(gè)文檔注釋。
  • 每個(gè)文檔注釋?xiě)?jiǎn)潔描述出它和客戶(hù)之間的約定,以說(shuō)明這個(gè)方法做了什么而不說(shuō)明它是如何做的,專(zhuān)門(mén)為繼承而設計的類(lèi)例外。
  • 通過(guò)@throws和@param來(lái)說(shuō)明調用這個(gè)方法的前提條件。
  • 還要描述它的副作用(指系統狀態(tài)中一個(gè)可觀(guān)察到的變化,它不是為了獲得后置條件而要求的變化。
  • 應該描述一個(gè)類(lèi)的線(xiàn)程安全性
 
  具體編寫(xiě)時(shí)的一些注意事項:
  • @param和@return后跟一個(gè)名詞短語(yǔ),@throws后跟一個(gè)if語(yǔ)句再加一個(gè)名詞短語(yǔ)。
  • 可以HTML元字符和標簽,尤其是>、<、&符號不要忘了轉義。
  • 單詞this指被調用的方法所在的實(shí)例
  • 元素文檔的第一句話(huà)是概要描述,注意不要在第一句話(huà)內部使用句號(.),如果非要出現可以使用.來(lái)代替。
    對于方法和構造函數,概要描述是一個(gè)動(dòng)詞短語(yǔ);
    對于類(lèi)、接口或域,是一個(gè)名詞短語(yǔ)。

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

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

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

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

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

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

  readResolve方法有可能取被用來(lái)替代保護性的readObject方法。
不嚴格地說(shuō),readObject是一個(gè)用字節流作為唯一參數的構造函數。當面對一個(gè)人工偽造的字節流的時(shí)候,readObject產(chǎn)生的對象會(huì )違反它所屬的類(lèi)的約束條件。初步的方法,是在readObject方法進(jìn)行約束性檢查,如下例:
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);
}
  對上述的防范仍可進(jìn)行攻擊:偽造一個(gè)字節流,這個(gè)字節流以一個(gè)有效的Period實(shí)例所產(chǎn)生的字節流作為開(kāi)始,然后附加上兩個(gè)額外的引用,指向Period實(shí)例中的兩個(gè)內部私有Date域,攻擊者通過(guò)引用攻擊內部域。所以,當一個(gè)對象被反序列化的時(shí)候,對于客戶(hù)不應該擁有的對象引用,如果哪個(gè)域包含了這樣的對象引用,則必須要做保護性拷貝,這是非常重要的。如下例:
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條:必要時(shí)提供一個(gè)readResolve方法
  無(wú)論是singleton,或是其他實(shí)例受控(instance-controlled)的類(lèi),必須使用readResolve方法來(lái)保護實(shí)例-控制的約束。從本質(zhì)上來(lái)講,readResovle方法把一個(gè)readObject方法從一個(gè)事實(shí)上的公有構造函數變成一個(gè)事實(shí)上的公有靜態(tài)工廠(chǎng)。對于那些禁止包外繼承的類(lèi)而言,readResolve方法作為保護性的readObject方法的一種替代,也是非常有用的。
  如下sigleton類(lèi):
public class Elvis{
public static final Elvis INSTANCE = new Elvis();
private Elvis(){
...
}
...//remainder omitted
}
  如果Elvis實(shí)例序列化接口,則下面的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方法對于所有其它的實(shí)例受控類(lèi)(如類(lèi)型安全枚舉類(lèi)型)也是必需的。
  readResolve方法的第二個(gè)用法是,就像在第56條建議的那樣,作為保護性的readObject方法的一種保守的替代選擇。此時(shí),第56條中的readObject方法可以下例的例子替代:
//the defensive readResolve idiom
private Object readResolve() throws ObjectStreamException(){
return new Period(start,end);
}
  對于那些允許繼承的類(lèi),readResolve方法可能無(wú)法替代保護性的readObject方法。如果超類(lèi)的readResolve方法是final的,則使得子類(lèi)實(shí)例無(wú)法被正常地反序列化。如果超類(lèi)的readResolve方法是可改寫(xiě)的,則惡意的子類(lèi)可能會(huì )用一個(gè)方法改寫(xiě)它,該方法返回一個(gè)受損的實(shí)例。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
關(guān)于靜態(tài)函數的一些思考
百合花如何保鮮 百合花保鮮方法
Java線(xiàn)程安全精解 - 潛魚(yú)在淵 - BlogJava
JAVA調用動(dòng)態(tài)庫方法說(shuō)明
lambda表達式
Java 8新特性終極指南
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久