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

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

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

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

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

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

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

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

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

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

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

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

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

十三、支持非可變性

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

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

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

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

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

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

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

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

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

//不可變對象的緩存,延遲初始化函數(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)先于繼承。

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

 
// 使用組合取代繼承的包裝類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(); } 
}

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

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

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

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

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

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

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

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

下面的子類改寫了方法m,Super唯一的構(gòu)造函數(shù)就錯誤的調(diào)用了這個方法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(); 
    }

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

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

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

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

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

下面是一個靜態(tài)工廠方法,它包含了一個靜態(tài)的工廠方法,它包含一個完整的、功能全面的List實現(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);
}
};
}

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

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

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

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

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

// 常量接口模式- 請勿使用!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平臺中所有的數(shù)值包裝類,比如Integer和Float,都導(dǎo)出了常量MAX_VALUE和MIN_VALUE。
  • 如果這些常量最好被看做一個枚舉類型的成員,那么應(yīng)使用類型安全枚舉類(typesafe enum class)來導(dǎo)出這些常量。
  • 使用不可實例化的工具類(utility class)來導(dǎo)出常量。下面是PysicalConstants的工具類版本
// 常量工具類public class PhysicalConstants { 
private PhysicalConstants() { }// 防止實例化 
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)是指被定義在一個類的內(nèi)部的類。嵌套類存在的目的是為外圍的類提供服務(wù)。

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

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

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

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

}

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

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

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

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

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

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

});

匿名類的另一個用法是創(chuàng)建一個過程對象(process object),比如Thread、Runable或者TimeTask實例。第三個常見用法是在一個靜態(tài)工廠方法的內(nèi)部(見十六intArrayAsList方法)。第四個常見的用法是在復(fù)雜的類型安全枚舉類型(它要求為每個實例提供單獨的子類)中,用于公有的靜態(tài)final域的初始化器中(見二十一Operation類)。如果Operation類是Calculator的一個靜態(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; } 
 
// 通過這一常量進行運算符表示 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; } 
      }; 
   } 
 
// 返回指定的計算結(jié)果 public double calculate(double x, Operation op, double y) { 
return op.eval(x, y); 
  } 
}

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

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

廣告

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

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