簡體   English   中英

這個設計有什么問題?覆蓋或重載java.util.HashMap

[英]What is wrong with this design? Overriding or overloading of java.util.HashMap

這個問題在MS面試時被問到了。 我想知道這段代碼中的確切設計問題。 已經提供了代碼,需要找到設計問題。

我有類MyHashMap,它擴展了Java HashMap類。 在MyHashMap課程中,我必須保留一些員工的信息。 此映射中的鍵將是firstName + lastName + Address。

public MyHashMap extends HashMap<Object, Object> {
  //some member variables 
  //
  public void put(String firstName, String lastName, String Address, Object obj) {
       String key =   firstName + lastName+ Address;
       put(key, obj);
  }

  public Object get(String firstName, String lastName, String Address) {
       String key =   firstName + lastName+ Address;
       return get(key);
  }

  public void remove(Strig key) {
        put(key, ""); 
  }

  //some more methods 
}

這個設計有什么問題? 我應該將HashMap子類化還是應該將HashMap聲明為此類的成員變量? 或者我應該實現hashCode / equals方法?

有很多問題,但最大的問題是我可以看到你使用串聯String作為鍵。 以下兩個調用是不同的,但相當於:

final MyHashMap map = new MyHashMap();

map.put("foo", "", "baz", new Object());
map.put("", "foo", "baz", new Object()); // Overwrites the previous call

還有一個問題是,您將關鍵類型聲明為Object ,但始終使用String ,因此不會利用泛型帶來的類型安全性。 例如,如果你想循環遍歷MapkeySet ,你必須將每個Set條目轉換為String ,但你不能確定有人沒有使用Integer鍵濫用你的Map ,例。

就個人而言,除非你有充分的理由不這樣做,否則我會贊成合成而非繼承 在您的情況下, MyHashMap 重載putgetremove的標准Map方法,但沒有覆蓋它們中的任何一個。 您應該從類繼承以更改其行為,但您的實現不會這樣做,因此組合是一個明確的選擇。

作為一個例子,重載而不是覆蓋意味着如果你做出以下聲明:

Map<Object, Object> map = new MyHashMap();

沒有任何聲明的方法可用。 根據其他一些答案的建議,使用由firstName,lastName和address組成的對象作為你的map鍵會好得多,但是你必須記住實現equalshashCode ,否則你的值將無法從HashMap

該設計的主要原因是聲稱是HashMap<Oject, Object> ,但實際上並非如此。 重載方法“替換”了Map方法,但仍然可以訪問它們 - 現在你應該以與Map接口不兼容的方式使用該類,並忽略(在技術上可行)兼容的方式來使用它。

執行此操作的最佳方法是使用名稱和地址字段以及基於這些的hashCode()equals()方法創建EmployeeData類(或者更好的是,唯一的ID字段)。 那么你不需要非標准的Map子類 - 你可以簡單地使用HashMap<EmployeeData, Object> 實際上,值類型也應該比Object更具體。

我會將HashMap聲明為您班級的成員變量。

我不記得是否在Clean Code或Effective Java中給出了很好的解釋,但基本上,它更簡單,如果API發生變化則需要更少的工作。

通常,擴展這樣的類意味着你想改變它的行為。

我的第一個看法是:

public MyHashMap extends HashMap<Oject, Object>

是錯的。 沒有類型安全。

如果需要的地圖,則至少使它

public MyHashMap extends HashMap<NameAddress, Employee>

並調整看跌期權並獲得相同的信息。 NameAddress使用equals和hashCode中的firstname,lastname和address。

我個人認為Set接口會更好地匹配,可能還有HashMap作為成員。 然后你就可以定義界面了:

public EmployeeSet implements Set<Employee> {
  private static class NameAddress {
    public boolean equals();
    public int hashCode();
  } 

  private HashMap employees = new HashMap<NameAddress, Employee>();   

  public add(Employee emp) {
    NameAddress nad = new NameAddress(emp);
    empoyees.add(nad, emp);
  }

  public remove(Employee emp) {
  }
}

並在實現中提取名稱和地址信息以創建密鑰。

編輯 David指出NameAddress不需要是Comparable,我刪除了該部分接口。 為正確性添加了hashCode。

地圖使用基於名字,姓氏和地址的內部密鑰。 所以remove方法應該(1)實現為remove(String firstName, String lastName, String Address)和(2)它不應該通過改變值來改變原始remove方法(它真正刪除了條目)的行為。 更好的刪除實現將是:

public void remove(String firstName, String lastName, String address) {
   String key =   firstName + lastName + address;
   return remove(key);
}

另外兩個反對良好做法的“令人發指的罪行”是remove方法:

  • 重載Map.remove(<K>)而不是覆蓋它(所以你有一個漏洞的抽象),和

  • 實現與它重寫的方法的行為不兼容的行為!

將與鍵關聯的值設置為空字符串與刪除鍵的條目不同。 (即使將其設置為null也略有不同......)

這並不違反Liskov可替代性原則,但對於試圖使用該類型的人來說,這可能會讓人感到困惑。 (根據您是通過Map類型還是MyHashMap類型調用方法,您最終可能會使用具有不同語義的不同方法....)

我認為這在很大程度上取決於背景以及他們究竟要求的內容。 例如,是高度並發的上下文,您應該使用Hashtable。 此外,您應指定類型(String,Object)而不是(Object,Object)以獲得對鍵的編譯器支持。

我認為更好的設計是實現Map接口並將HashMap / HashTable保持為對象的內部參數。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM