Java高效編程之三【類和接口】-創(chuàng)新互聯(lián)

本部分包含的一些指導(dǎo)原則,可以幫助哦我們更好滴利用這些語言元素,以便讓設(shè)計(jì)出來的類更加有用、健壯和靈活。Java高效編程之三【類和接口】

十二、使類和成員的訪問能力最小化

三個(gè)關(guān)鍵詞訪問修飾符:private(私有的=類級(jí)別的)、未指定(包級(jí)私有的)、protected(受保護(hù)的=繼承級(jí)別的+包級(jí)別的訪問)、pulbic(共有的)

懷遠(yuǎn)網(wǎng)站建設(shè)公司成都創(chuàng)新互聯(lián),懷遠(yuǎn)網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為懷遠(yuǎn)上千提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站建設(shè)要多少錢,請(qǐng)找那個(gè)售后服務(wù)好的懷遠(yuǎn)做網(wǎng)站的公司定做!

備注:其中未指定,使用的是默認(rèn)的訪問級(jí)別,包內(nèi)部的任何類都可以訪問這個(gè)成員。如果類或者接口是包級(jí)私有的,就應(yīng)該做成包級(jí)私有的。包級(jí)私有的是這個(gè)包實(shí)現(xiàn)的一部分,而不是這個(gè)報(bào)API的一部分,包級(jí)私有的可以更改實(shí)現(xiàn)、修改或去除,不必?fù)?dān)心傷害到客戶,如果是共有的,你就要永遠(yuǎn)支持它,并且保持兼容性。

經(jīng)驗(yàn)表明,盡可能地使每一個(gè)類或成員都不被外界訪問。即,在保證軟件功能正確的前提下,使用最低的訪問級(jí)別。

公有類不應(yīng)該包含公有域,除了后面的共有靜態(tài)final域的特殊情形——通過公有域的靜態(tài)final域來暴露類的常量。按照慣例,這樣的域的名字由大寫字母構(gòu)成,單詞之間用下劃線隔開(見三十八)。很重要的一點(diǎn)是,這個(gè)域要么包含原語類型的值,要么包含指向非可變對(duì)象的引用(見十三)。

注意:非零長(zhǎng)度的數(shù)組總是可變的,所以具有共有靜態(tài)final數(shù)據(jù)域幾乎總是錯(cuò)誤的。如果一個(gè)類包含這樣的一個(gè)域,客戶能夠修改數(shù)組中的內(nèi)容。這是安全漏洞的一個(gè)常見根源:

//潛在的安全漏洞public static final Type[] VALUES={……};

共有數(shù)組應(yīng)該被替換成私有數(shù)組,以及一個(gè)共有的非可變列表:

private static final Type[] PRIVATE_VALUES={……};

public static final List VALUES=
Collection.unmodifiableList(Arrays.asList(PRIVATE_VALUES))

十三、支持非可變性

非可變性類是一個(gè)簡(jiǎn)單的類,它的實(shí)例不能被修改。每個(gè)實(shí)例中包含的所有信息都必須在該實(shí)例被創(chuàng)建的時(shí)候就提供出來,并且在對(duì)象的整個(gè)生命周期保持不變。Java平臺(tái)庫(kù)包含許多非可變類,其中String、原語類型的包裝類、BigInteger和BigDecimal。

非可變類要遵循的五條規(guī)則:

  1. 不要提供任何會(huì)修改對(duì)象的方法。
  2. 保證沒有可被子類改寫的方法。         ->通常將類設(shè)置成final,其他方法后面討論。
  3. 使所有域都是final的。
  4. 是所有的域都成為私有的。
  5. 保證對(duì)于任何可變組件的互質(zhì)訪問。   ->在構(gòu)造方法、訪問方法、和readObject方法(見五十六)中請(qǐng)使用保護(hù)性拷貝(defensive copy)技術(shù)(見二十四)。
  •  非可變對(duì)象本質(zhì)上是安全的,他們不要求同步。     ->非可變對(duì)象可以被自由的共享,對(duì)于頻繁用到的值,為它們提供公有的靜態(tài)final常量。

如:public static final Complext ZERO=new Complex(0,0);

這種方法可以進(jìn)一步擴(kuò)展,一個(gè)非可變對(duì)象可以提供一些靜態(tài)工廠,它們吧頻繁用到的實(shí)例緩存起來,當(dāng)請(qǐng)求一個(gè)預(yù)先存在的實(shí)例的時(shí)候,可以不再創(chuàng)建新的實(shí)例。BigInteger和Boolean都有這樣的靜態(tài)工廠。使用這樣的靜態(tài)工廠可以使得客戶之間可以共享已有的實(shí)例,而不是創(chuàng)建新的實(shí)例,從而降低內(nèi)存占用和垃圾回收的代價(jià)。

  • 你不僅可以共享非可變對(duì)象,甚至可以共享它們的內(nèi)部信息。
  • 非可變對(duì)象為其他對(duì)象——無論是可變的還是非可變的——提供了大量的構(gòu)建
  • 非可變對(duì)象的唯一缺點(diǎn)是,對(duì)于每一個(gè)不同的值都需要一個(gè)單獨(dú)的對(duì)象。

注:StringBuffer是String類的可變配套類,在特定的環(huán)境下,相對(duì)BigInteger而言,BigSet是String類的可變配套類。StringBuffer是可變對(duì)象,可以對(duì)字符串進(jìn)行改寫,主要是insert和append兩個(gè)方法,用于多線程。而StringBuilder是JDK1.5之后才加入的,和StringBuffer沒有本質(zhì)區(qū)別,但是在單線程的情況下使用,速度更快。

  • 除非有更好的理由讓一個(gè)類成為可變類,否則英愛是非可變的。                 ->有g(shù)et方法,不一定就要有set方法。
  • 如果一個(gè)類不能做成非可變類,那么你要盡可能的限制其行為。構(gòu)造函數(shù)應(yīng)該創(chuàng)建完全初始化的對(duì)象,所有的約束關(guān)系都應(yīng)該在這個(gè)時(shí)候起來。

 下面是延遲初始化技術(shù)的習(xí)慣用法:

//不可變對(duì)象的緩存,延遲初始化函數(shù)private volatile Foo cacheFooVal=UNLIKE_FOO_VALUE

public Foo foo(){
          Foo result=cachedFooVal;
if(result==UNLIKE_FOO_VALUE)
                      result=cachedFooVal=fooVal();
return result;

}

//fool值的私有幫助函數(shù)private Fool fooVal(){}

 十四、組合優(yōu)先于繼承

與方法不同的是,繼承打破了封裝性。能用組合完成的就用組合,組合優(yōu)先于繼承。

用組合的方式可以避免有不合適的繼承所帶來的問題。使用組合不在擴(kuò)展一個(gè)已有的類,而是在新類中增加一個(gè)私有域,它引用了這個(gè)類的一個(gè)實(shí)例。新類中的每個(gè)實(shí)例方法都可以調(diào)用被包含已有實(shí)例中對(duì)應(yīng)的方法,并返回它的結(jié)果。這被稱為轉(zhuǎn)發(fā)(forwarding),新類中的方法被稱為轉(zhuǎn)發(fā)方法(forwarding method)。這樣的類將會(huì)非常穩(wěn)固,它不依賴已有類的實(shí)現(xiàn)細(xì)節(jié)。即使已有的類增加了新的方法,也不會(huì)影響新的類。

 
// 使用組合取代繼承的包裝類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(o); 
    } 
 
public boolean addAll(Collection c) { 
        addCount+= c.size(); 
return s.addAll(c); 
    } 
 
public int getAddCount() { 
return addCount; 
    } 
// 轉(zhuǎn)發(fā)方法  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 Iterator iterator()        { return s.iterator();   } 
public boolean remove(Object o)   { return s.remove(o);    } 
public boolean containsAll(Collection c) 
                                   {return s.containsAll(c);  } 
public boolean removeAll(Collection c) 
                                   {return s.removeAll(c);    } 
public boolean retainAll(Collection c) 
                                   {return s.retainAll(c);    } 
public Object[] toArray()           { return s.toArray();  } 
public Object[] toArray(Object[] a) { return s.toArray(a); } 
public boolean equals(Object o)     { return s.equals(o);  } 
public int hashCode()               { return s.hashCode(); } 
public String toString()            { return s.toString(); } 
}

這里的包裝類可以用來包裝任何一個(gè)Set實(shí)現(xiàn),并且可以與任何以前已有的構(gòu)造函數(shù)一起工作。如

Set s1=new InstrumentedSet(new TreeSet(list));
Set s2=new InstrumentedSet(new HashSet(capacity, loadFactor));

因?yàn)橐粋€(gè)Instrument實(shí)例都把另一個(gè) Set實(shí)例包裝了起來,所以我們稱其為包裝類。這也正是裝飾模式,因?yàn)镮nstrument對(duì)一個(gè)集合進(jìn)行了修飾,為它增加了計(jì)數(shù)器的特性。有時(shí)候,修改和轉(zhuǎn)發(fā)這兩項(xiàng)技術(shù)的結(jié)合被錯(cuò)誤的引用為“委托(delegation)”,從技術(shù)角度講,這不是委托,除非包裝類把自己傳遞給一個(gè)被包裝的對(duì)象。

十五、要么專門為繼承而設(shè)計(jì),給出文檔說明,要么禁止繼承

對(duì)于專門為繼承而設(shè)計(jì)的類而言,需要滿足:

  • 該類的文檔必須清晰的描述改寫每一個(gè)方法所帶來的影響。改寫的方法是指非final的,公有的或受保護(hù)的。
  • 一個(gè)類必須通過某種形式提供適當(dāng)?shù)你^子(hook),以便能進(jìn)入它的內(nèi)部工作流程中,這樣的形式可以是精心選擇的受保護(hù)(protected)方法。
  • 構(gòu)造函數(shù)一定不能調(diào)用可被改寫的方法,無論是直接進(jìn)行還是間接進(jìn)行。   ->注:

注:如果違反了第三條規(guī)則,很有可能會(huì)導(dǎo)致程序失敗。超類的構(gòu)造函數(shù)在子類的構(gòu)造函數(shù)之前運(yùn)行,所以子類中改寫版本的方法將會(huì)在子類的構(gòu)造函數(shù)運(yùn)行之前先被調(diào)用。如果改寫版本的方法依賴于子類構(gòu)造函數(shù)所執(zhí)行的初始化工作,那么該方法就不會(huì)如期執(zhí)行。

public class Super { 
// 違反了規(guī)則 -構(gòu)造函數(shù)調(diào)用了重寫的方法  public Super() { 
        m(); 
    } 
 
public void m() { 
    } 
}

下面的子類改寫了方法m,Super唯一的構(gòu)造函數(shù)就錯(cuò)誤的調(diào)用了這個(gè)方法m:

 
final class Sub extends Super { 
private final Date date; // 空的終結(jié)字段,由構(gòu)造函數(shù)設(shè)置 
    Sub() { 
        date= new Date(); 
    } 
 
// 重寫了 Super.m, 被Super的構(gòu)造函數(shù)調(diào)用  public void m() { 
        System.out.println(date); 
    } 
 
public static void main(String[] args) { 
        Sub s= new Sub(); 
        s.m(); 
    }

本來期望打印出兩個(gè)日期,但是第一次打印出Null,因?yàn)榉椒ū粯?gòu)造函數(shù)Super()調(diào)用的時(shí)候,造函數(shù)Sub還沒有機(jī)會(huì)初始化data域。
這個(gè)的執(zhí)行順序是父類的構(gòu)造函數(shù)->重寫的方法(回到子類)->子類的構(gòu)造函數(shù)。

  • Cloneable的clone()和Serializable的readObject方法,在行為上非常相似于構(gòu)造函數(shù),所以一個(gè)類的限制規(guī)則也是適用的。無論是clone或者是readObject方法都不能調(diào)用一個(gè)可被改寫的方法,不管是直接的方式還是間接地方式。
  • 如果你決定在一個(gè)為了繼承而設(shè)計(jì)的類中實(shí)線Serializable,并且該類有一個(gè)readResolve或者writeReplace方法,那么你必須使readReslove或者writeReplace方法稱為受保護(hù)的方法,而不是私有的方法。

十六、 接口優(yōu)于抽象類

 接口和抽象類都是允許多個(gè)實(shí)現(xiàn)的類型。兩者的區(qū)別是抽象類允許包含某些方法的實(shí)現(xiàn),但是接口不允許。實(shí)現(xiàn)一個(gè)抽象類的類型,它必須成為抽象類的子類。因?yàn)镴ava只允許單繼承,所以抽象類作為類型定義收到了極大的限制。

  • 已有的類可以被更新,以實(shí)現(xiàn)新的接口。
  • 接口是定義mixin(混合類型)的理想選擇。
  • 接口可以使我們構(gòu)造出非層次結(jié)構(gòu)的類型框架。
  • 接口使得安全的增加一個(gè)類的功能成為可能。
  • 雖然接口不允許包含方法的實(shí)現(xiàn),但是,我們可以接口和抽象類的優(yōu)點(diǎn)結(jié)合起來,對(duì)于你期望導(dǎo)出的每一個(gè)重要接口,都提供一個(gè)抽象的骨架(skeletal implements)實(shí)現(xiàn)類。

下面是一個(gè)靜態(tài)工廠方法,它包含了一個(gè)靜態(tài)的工廠方法,它包含一個(gè)完整的、功能全面的List實(shí)現(xiàn):

//整形數(shù)組的List適配器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è)適配器模式,它使得int數(shù)組可以被看做一個(gè)Integer實(shí)例列表。這個(gè)例子只提供了一個(gè)靜態(tài)工廠,并且這個(gè)類是一個(gè)可被訪問的匿名類,它被隱藏在靜態(tài)工廠的內(nèi)部。
下面是Map.Entry接口的骨架實(shí)現(xiàn)類:

public abstract class AbstractMapEntry implements Map.Entry { 
// 基本的  public abstract Object getKey(); 
public abstract Object getValue(); 
 
// 要改變maps的實(shí)體必須要重寫的方法  public Object setValue(Object value) { 
throw new UnsupportedOperationException(); 
    } 
 
// 實(shí)現(xiàn)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 ? o2 == null : o1.equals(o2)); 
    } 
 
// 實(shí)現(xiàn)Map.Entry.hashCode的通用約定  public int hashCode() { 
return 
            (getKey()== null ? 0 :   getKey().hashCode()) ^ 
            (getValue()== null ? 0 : getValue().hashCode()); 
    } 
}
  • 抽象類的演化比接口的演化要容易的多。

十七、接口只是被用于定義類型

當(dāng)一個(gè)類實(shí)現(xiàn)了一個(gè)接口的時(shí)候,這個(gè)接口被用做一個(gè)類型。通過這個(gè)類型可以引用這個(gè)類的實(shí)例。因此,一個(gè)類實(shí)現(xiàn)了某個(gè)接口,就表明客戶可以對(duì)這個(gè)類的實(shí)例實(shí)施某些動(dòng)作。為了其他的目的而定義的接口是不合適的。

常量接口是對(duì)接口的不良使用,下面是常量接口的例子:

// 常量接口模式- 請(qǐng)勿使用!public interface PhysicalConstants { 
// Avogadro's number (1/mol)  static final double AVOGADROS_NUMBER   = 6.02214199e23; 
 
// Boltzmann constant (J/K)  static final double BOLTZMANN_CONSTANT = 1.3806503e-23; 
 
// Mass of the electron (kg)  static final double ELECTRON_MASS      = 9.10938188e-31; 
}

導(dǎo)出常量的幾種可行方案:

  • 常量與類或接口緊密的聯(lián)系在一起,那么將常量添加到類或接口中。如Java平臺(tái)中所有的數(shù)值包裝類,比如Integer和Float,都導(dǎo)出了常量MAX_VALUE和MIN_VALUE。
  • 如果這些常量最好被看做一個(gè)枚舉類型的成員,那么應(yīng)使用類型安全枚舉類(typesafe enum class)來導(dǎo)出這些常量。
  • 使用不可實(shí)例化的工具類(utility class)來導(dǎo)出常量。下面是PysicalConstants的工具類版本
// 常量工具類public class PhysicalConstants { 
private PhysicalConstants() { }// 防止實(shí)例化 
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; 
}

總之,接口是被用來定義類型的,它們不應(yīng)該被導(dǎo)出常量。

十八、優(yōu)先考慮靜態(tài)成員類

嵌套類(nested class)是指被定義在一個(gè)類的內(nèi)部的類。嵌套類存在的目的是為外圍的類提供服務(wù)。

嵌套類有四種:靜態(tài)成員類(static member class)、非靜態(tài)成員類(nostatic member class)、匿名類(anonymous class)和局部類(local class)。

除了第一種之外,其他三種都被成為內(nèi)部類(inner class)。靜態(tài)成員類是一種簡(jiǎn)單的嵌套類,最好把它看做一個(gè)普通類,只是碰巧被聲明在類的內(nèi)部而已。

非靜態(tài)成員的另一個(gè)用法是定義一個(gè)Adapter,它允許外部類的一個(gè)實(shí)例被看做另一個(gè)不相關(guān)的實(shí)例。如,Map接口的實(shí)現(xiàn)往往使用非靜態(tài)成員類來實(shí)現(xiàn)它們的集合視圖(collection view),這些集合視圖是有Map的keySet、entrySet和Value方法返回的。類似地,諸如Set和List這樣的集合接口的實(shí)現(xiàn)往往也使用非靜態(tài)成員類來實(shí)現(xiàn)它們的迭代器。

//非靜態(tài)成員的典型用法public class MySet extends AbstractSet{
       ……//省去不相關(guān)的public Iterator iterator(){
return MyIterator();
 }
  priavateclass MyIterator implements Iterator{
            ……
}

}

如果你聲明的成員類不要求訪問外圍實(shí)例,那么請(qǐng)記住把static修飾符放到成員類的聲明中。

非靜態(tài)成員類需要訪問外圍實(shí)例,如果省略了static修飾符,則每個(gè)實(shí)例都將包含一個(gè)額外的指向外圍實(shí)例的引用,維護(hù)這份引用需要耗費(fèi)時(shí)間和空間,但又沒有相應(yīng)的好處。

匿名類僅僅在使用的時(shí)候被聲明和實(shí)例化,行為與靜態(tài)成員類或非靜態(tài)成員類非常相似,取決于它所處的環(huán)境:如果匿名類出現(xiàn)在一個(gè)非靜態(tài)的環(huán)境中,則它有一個(gè)外圍實(shí)例。

匿名類通常出現(xiàn)在表達(dá)式的中間,可能20行或者更短,太長(zhǎng)影響到程序的可讀性。

匿名類的一個(gè)通用方法是創(chuàng)建一個(gè)函數(shù)對(duì)象(function object),比如Comparator實(shí)例。例如,下面的方法調(diào)用對(duì)一組字符串按照其長(zhǎng)度進(jìn)行排序:

//匿名類的典型使用Arrays.sort(args, new Comparator(){
public int compare(Object o1, Object o2){
return ((String)o1).length()-((String)o2).length();
}

});

匿名類的另一個(gè)用法是創(chuàng)建一個(gè)過程對(duì)象(process object),比如Thread、Runable或者TimeTask實(shí)例。第三個(gè)常見用法是在一個(gè)靜態(tài)工廠方法的內(nèi)部(見十六intArrayAsList方法)。第四個(gè)常見的用法是在復(fù)雜的類型安全枚舉類型(它要求為每個(gè)實(shí)例提供單獨(dú)的子類)中,用于公有的靜態(tài)final域的初始化器中(見二十一Operation類)。如果Operation類是Calculator的一個(gè)靜態(tài)成員類,那么Operation類是雙重嵌套類。

// 公有靜態(tài)成員類的典型使用public class Calculator { 
public static abstract class Operation { 
private final String name; 
 
      Operation(String name)   {this.name = name; } 
 
public String toString() { return this.name; } 
 
// 通過這一常量進(jìn)行運(yùn)算符表示 abstract double eval(double x, double y); 
 
// 雙重嵌套匿名類 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; } 
      }; 
   } 
 
// 返回指定的計(jì)算結(jié)果 public double calculate(double x, Operation op, double y) { 
return op.eval(x, y); 
  } 
}

網(wǎng)頁(yè)標(biāo)題:Java高效編程之三【類和接口】-創(chuàng)新互聯(lián)
分享地址:http://bm7419.com/article34/dsedpe.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供云服務(wù)器面包屑導(dǎo)航、手機(jī)網(wǎng)站建設(shè)、網(wǎng)站收錄、微信公眾號(hào)網(wǎng)站導(dǎo)航

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

成都網(wǎng)站建設(shè)公司