1. 程式人生 > >Java中的集合和封裝

Java中的集合和封裝

面向物件程式設計的核心租戶是封裝:不應允許呼叫者直接訪問類的欄位。這是新的語言,包括Kotlin,Swift和Ceylon,已經很好地解決了一流的屬性。

Java沒有第一類屬性的概念。相反,JavaBeans規範是作為Java執行封裝的方法引入的。編寫JavaBeans意味著您需要將類的欄位設定為私有,僅通過getter和setter方法公開它們。

如果你像我一樣,你在編寫JavaBeans時經常會感覺到你正在編寫一堆很少用於任何實際目的的理論樣板。我的大多數JavaBeans都包含私有欄位及其相應的getter和setter,它們只能獲取和設定這些私有欄位。不止一次,我一直試圖簡單地將這些領域公之於眾,並免除了getter / setter的大肆宣傳,至少在IDE發出嚴厲的警告後,我的雙腿之間的尾巴,以及JavaBeans標準。

但是,最近,我意識到封裝和JavaBean / getter / setter模式在常見場景中非常有用:集合型別欄位。怎麼會這樣?讓我們編寫一個簡單的類:

公共  MyClass {
 
    private  List < String >  myStrings ;
 
}

 

我們有一個欄位 - 一個字串列表 - 呼叫  myStrings,它被封裝在中MyClass。現在,我們需要提供訪問器方法:

公共  MyClass {
 
    private  List < String 
> myStrings ;
 
    public  void  setMyStrings(List < String >  s){
        這個。myStrings  =  s ;
    }
 
    public  List < String >  getMyStrings(){
        歸還 這個。myStrings ;
    }
 
}

 

在這裡,我們有一個適當封裝的 - 如果不是詳細的 - 類。所以我們做得很好,對嗎?堅持這個想法。

可選課程

考慮在Java 8中引入的Optional類。如果你已經使用Optionals做了很多工作,你可能聽說過你應該永遠不會null 從返回Optional的方法返回的咒語  。為什麼?考慮以下人為的例子:

公共  Foo {
 
    私人 字串 欄 ;
 
    public  可選< String >  getBar(){
        return(bar  ==  null)? null:可選。的(巴);
    }
 
}

 

現在,客戶可以這樣使用該方法:

foo。getBar()。ifPresent(log :: info);

 

冒險投擲  NullPointerException。或者,他們可以執行  null 檢查:

如果(FOO。getBar()!=  ){
    foo。getBar()。ifPresent(log :: info);
}

 

當然,這樣做會擊敗Optionals的目的。事實上,這樣違背了選配的目的,它的成為標準做法是,返回可選將任何API 從未返回一個  null 值。

回到收藏。就像Optional包含noneone一樣,Collection包含nonesome。和Optionals一樣,沒有理由返回  null 集合(除了可能在罕見的,特殊情況下,我目前無法想到的任何情況)。只需返回一個空(零大小)Collection即可指示缺少任何元素。

正是由於這個原因,確保返回Collection型別(包括陣列)的方法永遠不會返回null 值變得越來越常見  ,與返回Optional型別的方法相同。也許您或您的組織已經在編寫新程式碼時採用了此規則。如果沒有,你應該。畢竟,你(或你的客戶)會這麼做嗎?:

boolean  isUnique  =  personDao。getPersonsByName(name)。size()==  1 ;

 

或者,讓你的程式碼亂七八糟?:

列表< 人>  人 =  人道。getPersonsByName(name);
boolean  isUnique  =(persons  ==  null)? 虛假:人。size()==  1 ;

 

那麼這與封裝有什麼關係呢?

保持對我們收藏的控制

回到我們MyClass班。實際上,一個例項MyClass很容易null 從該getMyStrings()方法返回  ; 事實上,一個新的例項就是這樣做的。因此,為了遵守我們新的永不迴歸零收集指南,我們需要解決這個問題:

公共  MyClass {
 
    private  List < String >  myStrings  =  new  ArrayList <>();
 
    public  void  setMyStrings(List < String >  s){
        這個。myStrings  =  s ;
    }
 
    public  List < String >  getMyStrings(){
        歸還 這個。myStrings ;
    }
 
}

 

問題解決了?不完全是。任何客戶都可以打電話aMyClass.setMyStrings(null),在這種情況下我們會回到原點。

在這一點上,封裝聽起來像一個實際的 - 而不是單獨的理論 - 概念。讓我們擴充套件setMyStrings()方法:

public  void  setMyStrings(List < String >  s){
    if(s  ==  null){
        這個。myStrings。clear();
    } else {
        這個。myStrings  =  s ;
    }
}

 

現在,即使  null 傳遞給setter,  myStrings 也會保留一個有效的引用(在這裡的例子中,我們  null 認為應該清除元素,這是一個合理的假設)。當然,呼叫aMyClass.getMyStrings() = nullMyClass“基礎myStrings 變數”  沒有影響  。我們都這樣做了嗎?

呃,好吧,有點兒。我們可以在這裡停下來 但實際上,我們應該做的更多。

考慮到我們正在取代我們的私人  ArrayList 與  List 呼叫者傳遞給我們。這有兩個問題:首先,我們不再知道所List 使用的確切  實現  myStrings。從理論上講,這應該不是問題,對嗎?好吧,考慮一下:

myClass。setMyStrings(收藏。unmodifiableList(“嘿,疑難雜症!” ));

 

因此,如果我們更新MyClass它試圖修改內容   myStrings,那麼壞事可能會在執行時開始發生。

第二個問題是呼叫者保留對我們的底層的引用  List。所以現在,呼叫者現在可以直接操縱我們的  List

我們應該做的是將傳遞給我們的元素儲存在 初始化的  ArrayList to中  myStrings。雖然我們正在努力,但我們真正擁抱封裝。我們應該從外部呼叫者隱藏我們班級的內部。實際情況是我們類的呼叫者不應該關心是否存在底層List,Set,陣列或一些執行時動態程式碼生成voodoo,它們儲存我們傳遞給它的字串。他們應該知道的是,字串以某種方式儲存。所以讓我們setMyStrings()這樣更新方法:

public  void  setMyStrings(Collection < String >  s){
    這個。myStrings。clear();
    if(s  !=  null){
        這個。myStrings。addAll(s);
    } 
}

 

這樣可以確保  myStrings 以輸入引數中包含的相同元素結束(如果傳遞null,則為  ),同時確保呼叫者沒有引用   myStrings

現在   myStrings'參考不能改變,讓我們把它變成一個常數:

公共  MyClass {
    private  final  List < String >  myStrings  =  new  ArrayList <>();
    ...
}

 

雖然我們在這裡,但我們不應該通過我們的getter返回我們的基礎List。這也會使呼叫者直接引用myStrings。為了解決這個問題,請回想一下有效Java擊敗我們頭腦的“防禦性副本”口號(或至少應該有):

public  List < String >  getMyStrings(){
    //取決於我們想要返回的確切內容
    返回  的ArrayList <> (。myStrings);  
}

 

此時,我們有一個封裝良好的類,null無論何時呼叫其getter ,都不需要  -checking。但是,我們已經從客戶那裡獲得了一些控制權。由於他們不再可以直接訪問我們的基礎列表,因此他們不能再新增或刪除單個字串。 

沒問題。如果我們可以簡單地新增像

public  void  addStringString  s){
    這個。myStrings。新增(小號);
}

 

public  void  removeStringString  s){
    這個。myStrings。除去(小號);
}

 

我們的呼叫者是否需要一次向MyClass例項新增多個字串?那也沒關係:

public  void  addStrings(Collection < String >  c){
    if(c  !=  null){
        這個。myStrings。addAll(c);
    }
}

 

等等...

public  void  clearStrings(){
    這個。myStrings。clear();
}
 
public  void  replaceStrings(Collection < String >  c){
    clearStrings();
    addStrings(c);
}

 

收集我們的想法

以下是我們班級最終的樣子:

公共  MyClass {
 
    private  final  List < String >  myStrings  =  new  ArrayList <>();
 
    public  void  setMyStrings(Collection < String >  s){
        這個。myStrings。clear();
        if(s  !=  null){
            這個。myStrings。addAll(s);
        } 
    }
 
    public  List < String >  getMyStrings(){
        返回  的ArrayList <> (。myStrings);
    }
 
    public  void  addString(String  s){
        這個。myStrings。新增(小號);
    }
 
    public  void  removeString(String  s){
        這個。myStrings。除去(小號);
    }
 
    //也許還有一些更有幫助的方法......
 
}

 

有了這個,我們實現了一個類:

  • 仍然基本上是一個符合JavaBean規範的POJO
  • 完全封裝其私人成員

並且,最重要的是,它確保其返回Collection的方法始終只執行 - 返回Collection並且永遠不會返回null