简体   繁体   English

这个设计有什么问题?覆盖或重载java.util.HashMap

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

This question was asked to me in MS interview. 这个问题在MS面试时被问到了。 I wanna know the exact design issue in this piece of code. 我想知道这段代码中的确切设计问题。 Code was already given, needed to find the design issue. 已经提供了代码,需要找到设计问题。

I have class MyHashMap which extends java HashMap class. 我有类MyHashMap,它扩展了Java HashMap类。 In MyHashMap class I have to keep some information of employees. 在MyHashMap课程中,我必须保留一些员工的信息。 Key in this map will be firstName+lastName+Address . 此映射中的键将是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 
}

What is wrong with this design? 这个设计有什么问题? Should I subclass HashMap or should I declare HashMap as member variable of this class? 我应该将HashMap子类化还是应该将HashMap声明为此类的成员变量? Or should I have implemented hashCode/equals methods? 或者我应该实现hashCode / equals方法?

There are quite a few problems, but the biggest problem I can see it that you're using a concatenated String as a key. 有很多问题,但最大的问题是我可以看到你使用串联String作为键。 The following two calls are different, but equivalent: 以下两个调用是不同的,但相当于:

final MyHashMap map = new MyHashMap();

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

There's also an issue that you're declaring that the key type as an Object , but always using String and are therefore not taking advantage of the type safety that comes with generics. 还有一个问题是,您将关键类型声明为Object ,但始终使用String ,因此不会利用泛型带来的类型安全性。 For example, if you wanted to loop through the keySet of your Map , you'd have to cast each Set entry to a String , but you couldn't be sure that someone didn't abuse you Map by using an Integer key, for example. 例如,如果你想循环遍历MapkeySet ,你必须将每个Set条目转换为String ,但你不能确定有人没有使用Integer键滥用你的Map ,例。

Personally, I would favour composition over inheritance unless you have a good reason not to. 就个人而言,除非你有充分的理由不这样做,否则我会赞成合成而非继承 In your case, MyHashMap is overloading the standard Map methods of put , get and remove , but not overriding any of them. 在您的情况下, MyHashMap 重载putgetremove的标准Map方法,但没有覆盖它们中的任何一个。 You should inherit from a class in order to change its behaviour, but your implementation does not do this, so composition is a clear choice. 您应该从类继承以更改其行为,但您的实现不会这样做,因此组合是一个明确的选择。

To act as an example, overloading rather than overriding means that if you make the following declaration: 作为一个例子,重载而不是覆盖意味着如果你做出以下声明:

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

none of your declared methods will be available. 没有任何声明的方法可用。 As recommended by some of the other answers, it would be far better to use an object composed of firstName, lastName and address to act as your map key, but you must remember to implement equals and hashCode , otherwise your values will not be retrievable from the HashMap . 根据其他一些答案的建议,使用由firstName,lastName和address组成的对象作为你的map键会好得多,但是你必须记住实现equalshashCode ,否则你的值将无法从HashMap

What's wrong with that design is primarily that is claims to be a HashMap<Oject, Object> , but isn't really. 该设计的主要原因是声称是HashMap<Oject, Object> ,但实际上并非如此。 The overloaded methods "replace" the Map methods, but those are still accessible - now you're supposed to use the class in a way that is incompatible with the Map interface and ignore the (still technically possible) compatible way to use it. 重载方法“替换”了Map方法,但仍然可以访问它们 - 现在你应该以与Map接口不兼容的方式使用该类,并忽略(在技术上可行)兼容的方式来使用它。

The best way to do this would be to make an EmployeeData class with name and address fields and hashCode() and equals() methods based on those (or, better yet, a unique ID field). 执行此操作的最佳方法是使用名称和地址字段以及基于这些的hashCode()equals()方法创建EmployeeData类(或者更好的是,唯一的ID字段)。 Then you don't need a non-standard Map subclass - you can simply use a HashMap<EmployeeData, Object> . 那么你不需要非标准的Map子类 - 你可以简单地使用HashMap<EmployeeData, Object> Actually, the value type should be more specific than Object as well. 实际上,值类型也应该比Object更具体。

I would go for declaring HashMap as member variable of your class. 我会将HashMap声明为您班级的成员变量。

I don't remember if a great explanation is given in Clean Code or Effective Java, but basically, it's simplier to do, requires less work if the API changes. 我不记得是否在Clean Code或Effective Java中给出了很好的解释,但基本上,它更简单,如果API发生变化则需要更少的工作。

generally, extending such a class means you want to change its behavior. 通常,扩展这样的类意味着你想改变它的行为。

My first take would be that: 我的第一个看法是:

public MyHashMap extends HashMap<Oject, Object>

is wrong. 是错的。 No type safety. 没有类型安全。

If a Map is required then at least make it 如果需要的地图,则至少使它

public MyHashMap extends HashMap<NameAddress, Employee>

and adjust the put and get the same. 并调整看跌期权并获得相同的信息。 A NameAddress use firstname, lastname and address in the equals and hashCode. NameAddress使用equals和hashCode中的firstname,lastname和address。

I personally feel that a Set interface would match better, with possibly a HashMap as a member. 我个人认为Set接口会更好地匹配,可能还有HashMap作为成员。 Then you could define the interface like it should be: 然后你就可以定义界面了:

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) {
  }
}

and extract name and address info within the implementation to create the key. 并在实现中提取名称和地址信息以创建密钥。

EDIT David pointed out that NameAddress doesn't need to be Comparable and I removed that part of the interface. 编辑 David指出NameAddress不需要是Comparable,我删除了该部分接口。 Added hashCode for correctness. 为正确性添加了hashCode。

The map uses an internal key based on first name, last name and address. 地图使用基于名字,姓氏和地址的内部密钥。 So the remove method should (1) be implemented as remove(String firstName, String lastName, String Address) and (2) it should not change the behaviour of the original remove method (which really deleted the entry) by just changing the value. 所以remove方法应该(1)实现为remove(String firstName, String lastName, String Address)和(2)它不应该通过改变值来改变原始remove方法(它真正删除了条目)的行为。 A better implementation of remove would be: 更好的删除实现将是:

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

Two other "heinous crimes" against good practice are that the remove method: 另外两个反对良好做法的“令人发指的罪行”是remove方法:

  • overloads Map.remove(<K>) rather than overriding it (so you have a leaky abstraction), and 重载Map.remove(<K>)而不是覆盖它(所以你有一个漏洞的抽象),和

  • implements a behavior that is incompatible with the behavior of the method it overrides! 实现与它重写的方法的行为不兼容的行为!

Setting the value associated with a key to an empty string is NOT THE SAME as removing the entry for the key. 将与键关联的值设置为空字符串与删除键的条目不同。 (Even setting it to null is subtly different ...) (即使将其设置为null也略有不同......)

This is not a violation of the Liskov substitutability principle, but it could be really confusing for someone trying to use the type. 这并不违反Liskov可替代性原则,但对于试图使用该类型的人来说,这可能会让人感到困惑。 (Depending on whether you called the method via the Map type or the MyHashMap type you could end up using different methods with different semantics ....) (根据您是通过Map类型还是MyHashMap类型调用方法,您最终可能会使用具有不同语义的不同方法....)

I think it depends a lot on the context and what they exactly asked. 我认为这在很大程度上取决于背景以及他们究竟要求的内容。 For example, is a highly concurrent context, you should have used Hashtable instead. 例如,是高度并发的上下文,您应该使用Hashtable。 Also, you should specify the types (String, Object) instead of (Object,Object) to get compiler support on the keys. 此外,您应指定类型(String,Object)而不是(Object,Object)以获得对键的编译器支持。

I think a better design would have been to implement the Map interface and keep the HashMap/HashTable as an internal parameter of the object. 我认为更好的设计是实现Map接口并将HashMap / HashTable保持为对象的内部参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM