Map.get(Object key) 不(完全)通用的原因是什么

发布于 2024-12-02 08:15:09 字数 380 浏览 1 评论 0 原文

决定不使用完全通用的 get 方法的原因是什么 在 接口中java.util.Map

为了澄清问题,该方法的签名是

V get(Object key)

而不是

V get(K key)

我想知道为什么(对于 V get(Object key) 也是如此)代码>删除,包含键,包含值)。

What are the reasons behind the decision to not have a fully generic get method
in the interface of java.util.Map<K, V>.

To clarify the question, the signature of the method is

V get(Object key)

instead of

V get(K key)

and I'm wondering why (same thing for remove, containsKey, containsValue).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(11

忆梦 2024-12-09 08:15:10

我认为泛型教程的这一部分解释了这种情况(我的重点):

“您需要确保泛型 API 没有过度限制;它必须
继续支持API原有合约。再考虑一些例子
来自 java.util.Collection。预泛型 API 看起来像:

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

一个天真的尝试将其泛化为:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

虽然这确实是类型安全的,但它不符合 API 的原始约定。
containsAll() 方法适用于任何类型的传入集合。只会
如果传入集合确实只包含 E 的实例,则成功,但是:

  • 传入集合的静态类型
    收集可能会有所不同,也许
    因为调用者不知道
    集合的精确类型
    传入,或者也许因为它是
    集合,其中 S 是
    E 的子类型,
  • 非常完美
    调用 containsAll() 是合法的
    不同类型的集合。这
    例程应该有效,返回 false。”

I think this section of Generics Tutorial explains the situation (my emphasis):

"You need to make certain that the generic API is not unduly restrictive; it must
continue to support the original contract of the API. Consider again some examples
from java.util.Collection. The pre-generic API looks like:

interface Collection { 
  public boolean containsAll(Collection c);
  ...
}

A naive attempt to generify it is:

interface Collection<E> { 
  public boolean containsAll(Collection<E> c);
  ...
}

While this is certainly type safe, it doesn’t live up to the API’s original contract.
The containsAll() method works with any kind of incoming collection. It will only
succeed if the incoming collection really contains only instances of E, but:

  • The static type of the incoming
    collection might differ, perhaps
    because the caller doesn’t know the
    precise type of the collection being
    passed in, or perhaps because it is a
    Collection<S>,where S is a
    subtype of E.
  • It’s perfectly
    legitimate to call containsAll() with
    a collection of a different type. The
    routine should work, returning false."
梦醒灬来后我 2024-12-09 08:15:10

兼容性。

在泛型可用之前,只有 get(Object o)。

如果他们将此方法更改为 get( o),则可能会迫使 Java 用户进行大量代码维护,只是为了使工作代码再次编译。

他们可以引入一个附加方法,例如get_checked( o)并弃用旧的get()方法,以便有一个更温和的过渡路径。但由于某种原因,这并没有完成。 (我们现在的情况是,您需要安装像 findBugs 这样的工具来检查 get() 参数和映射的声明键类型 之间的类型兼容性。)

与 .equals 语义相关的参数我认为 () 是假的。 (从技术上讲,它们是正确的,但我仍然认为它们是假的。如果 o1 和 o2 没有任何共同的超类,那么任何头脑正常的设计师都不会让 o1.equals(o2) 为真。)

Compatibility.

Before generics were available, there was just get(Object o).

Had they changed this method to get(<K> o) it would have potentially forced massive code maintenance onto java users just to make working code compile again.

They could have introduced an additional method, say get_checked(<K> o) and deprecate the old get() method so there was a gentler transition path. But for some reason, this was not done. (The situation we are in now is that you need to install tools like findBugs to check for type compatibility between the get() argument and the declared key type <K> of the map.)

The arguments relating to the semantics of .equals() are bogus, I think. (Technically they're correct, but I still think they're bogus. No designer in his right mind is ever going to make o1.equals(o2) true if o1 and o2 do not have any common superclass.)

把人绕傻吧 2024-12-09 08:15:10

原因是遏制是由 equalshashCode 确定的,它们是 Object 上的方法,并且都采用 Object 参数。这是 Java 标准库的早期设计缺陷。再加上 Java 类型系统的限制,它强制任何依赖 equals 和 hashCode 的东西都采用 Object

在 Java 中拥有类型安全哈希表和相等性的唯一方法是避开 Object.equalsObject.hashCode 并使用通用替代品。 Functional Java 附带了用于此目的的类型类:哈希等于 HashMap 的包装器 提供了在其中采用 HashEqual构造函数。因此,此类的 getcontains 方法采用 K 类型的泛型参数。

例子:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error

The reason is that containment is determined by equals and hashCode which are methods on Object and both take an Object parameter. This was an early design flaw in Java's standard libraries. Coupled with limitations in Java's type system, it forces anything that relies on equals and hashCode to take Object.

The only way to have type-safe hash tables and equality in Java is to eschew Object.equals and Object.hashCode and use a generic substitute. Functional Java comes with type classes for just this purpose: Hash<A> and Equal<A>. A wrapper for HashMap<K, V> is provided that takes Hash<K> and Equal<K> in its constructor. This class's get and contains methods therefore take a generic argument of type K.

Example:

HashMap<String, Integer> h =
  new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);

h.add("one", 1);

h.get("one"); // All good

h.get(Integer.valueOf(1)); // Compiler error
别挽留 2024-12-09 08:15:10

还有一个更重要的原因,它在技术上无法完成,因为它破坏了地图。

Java 具有多态泛型结构,如 。标记的此类引用可以指向用 签名的类型。但多态泛型使该引用成为只读。编译器允许您仅使用泛型类型作为方法的返回类型(如简单的 getter),但会阻止使用泛型类型为参数的方法(如普通的 setter)。
这意味着如果你写 Map,编译器不允许你调用方法get(),这样映射就没有用了。唯一的解决方案是使该方法不再通用:get(Object)

There is one more weighty reason, it can not be done technically, because it brokes Map.

Java has polymorphic generic construction like <? extends SomeClass>. Marked such reference can point to type signed with <AnySubclassOfSomeClass>. But polymorphic generic makes that reference readonly. The compiler allows you to use generic types only as returning type of method (like simple getters), but blocks using of methods where generic type is argument (like ordinary setters).
It means if you write Map<? extends KeyType, ValueType>, the compiler does not allow you to call method get(<? extends KeyType>), and the map will be useless. The only solution is to make this method not generic: get(Object).

梦魇绽荼蘼 2024-12-09 08:15:10

我猜是向后兼容。 Map(或HashMap)仍然需要支持get(Object)

Backwards compatibility, I guess. Map (or HashMap) still needs to support get(Object).

↙温凉少女 2024-12-09 08:15:10

我看着这个并思考他们为什么这样做。我认为现有的任何答案都不能解释为什么他们不能让新的通用接口仅接受正确的密钥类型。真正的原因是,即使他们引入了泛型,他们也没有创建新的接口。 Map 接口与旧的非通用 Map 相同,它只是充当通用和非通用版本。这样,如果您有一个接受非通用 Map 的方法,您可以向它传递一个 Map 并且它仍然可以工作。同时 get 的契约接受 Object,因此新接口也应该支持这个契约。

在我看来,他们应该添加一个新接口并在现有集合上实现这两个接口,但他们决定支持兼容接口,即使这意味着 get 方法的设计更糟糕。请注意,集合本身与现有方法兼容,只有接口不兼容。

I was looking at this and thinking why they did it this way. I don't think any of the existing answers explains why they couldn't just make the new generic interface accept only the proper type for the key. The actual reason is that even though they introduced generics they did NOT create a new interface. The Map interface is the same old non-generic Map it just serves as both generic and non-generic version. This way if you have a method that accepts non-generic Map you can pass it a Map<String, Customer> and it would still work. At the same time the contract for get accepts Object so the new interface should support this contract too.

In my opinion they should have added a new interface and implemented both on existing collection but they decided in favor of compatible interfaces even if it means worse design for the get method. Note that the collections themselves would be compatible with existing methods only the interfaces wouldn't.

空心空情空意 2024-12-09 08:15:10

我们刚刚正在进行大规模重构,我们错过了这个强类型的 get() 来检查我们是否错过了一些旧类型的 get() 。

但我发现了编译时间检查的解决方法/丑陋技巧:使用强类型 get、containsKey、remove... 创建 Map 接口,并将其放入项目的 java.util 包中。

你会得到编译错误只是为了调用 get(),...使用错误的类型,其他一切对于编译器来说似乎都可以(至少在 eclipse kepler 内)。

检查构建后不要忘记删除此接口,因为这不是您在运行时想要的。

We are doing big refactoring just now and we were missing this strongly typed get() to check that we did not missed some get() with old type.

But I found workaround/ugly trick for compilation time check: create Map interface with strongly typed get, containsKey, remove... and put it to java.util package of your project.

You will get compilation errors just for calling get(), ... with wrong types, everything others seems ok for compiler (at least inside eclipse kepler).

Do not forget to delete this interface after check of your build as this is not what you want in runtime.

趁年轻赶紧闹 2024-12-09 08:15:09

正如其他人所提到的, get() 等之所以不通用,是因为您要检索的条目的键不必与您传递给 < 的对象具有相同的类型。代码>get();该方法的规范仅要求它们相等。这是从 equals() 方法如何接受一个对象作为参数得出的,而不仅仅是与对象相同的类型。

虽然许多类都定义了 equals() ,因此它的对象只能等于它自己类的对象,但在 Java 中很多地方情况并非如此。例如,List.equals() 的规范规定,如果两个 List 对象都是列表并且具有相同的内容,则它们相等,即使它们是 List 的不同实现>。因此,回到这个问题中的示例,根据方法的规范,可以有一个 Map 并让我调用 get()以 LinkedList 作为参数,它应该检索具有相同内容的列表的键。如果 get() 是通用的并且限制其参数类型,则这是不可能的。

As mentioned by others, the reason why get(), etc. is not generic because the key of the entry you are retrieving does not have to be the same type as the object that you pass in to get(); the specification of the method only requires that they be equal. This follows from how the equals() method takes in an Object as parameter, not just the same type as the object.

Although it may be commonly true that many classes have equals() defined so that its objects can only be equal to objects of its own class, there are many places in Java where this is not the case. For example, the specification for List.equals() says that two List objects are equal if they are both Lists and have the same contents, even if they are different implementations of List. So coming back to the example in this question, according to the specification of the method is possible to have a Map<ArrayList, Something> and for me to call get() with a LinkedList as argument, and it should retrieve the key which is a list with the same contents. This would not be possible if get() were generic and restricted its argument type.

‘画卷フ 2024-12-09 08:15:09

Google 的一位出色的 Java 程序员 Kevin Bourrillion 在 博客文章 不久前(诚然,在 Set 而不是 Map 的上下文中)。最相关的一句话:

统一地,Java 的方法
集合框架(以及 Google
馆藏图书馆也)从来没有
限制其参数的类型
除非有必要阻止
收藏品免遭损坏。

我不完全确定我是否同意这一原则 - 例如,.NET 似乎可以要求正确的密钥类型 - 但值得遵循博客文章中的推理。 (提到 .NET,值得解释的是,它在 .NET 中不是问题的部分原因是 .NET 中存在更大的问题,且方差更有限......)

An awesome Java coder at Google, Kevin Bourrillion, wrote about exactly this issue in a blog post a while ago (admittedly in the context of Set instead of Map). The most relevant sentence:

Uniformly, methods of the Java
Collections Framework (and the Google
Collections Library too) never
restrict the types of their parameters
except when it's necessary to prevent
the collection from getting broken.

I'm not entirely sure I agree with it as a principle - .NET seems to be fine requiring the right key type, for example - but it's worth following the reasoning in the blog post. (Having mentioned .NET, it's worth explaining that part of the reason why it's not a problem in .NET is that there's the bigger problem in .NET of more limited variance...)

枕头说它不想醒 2024-12-09 08:15:09

合同表述如下:

更正式地说,如果这张地图包含
从键 k 到值 v 的映射
(key==null ? k==null :
key.equals(k)),那么这个方法
返回 v;否则返回 null。
(最多可以有一个这样的
映射。)

(我的重点)

因此,成功的键查找取决于输入键对相等方法的实现。这不一定取决于 k 的类别。

The contract is expressed thus:

More formally, if this map contains a
mapping from a key k to a value v such
that (key==null ? k==null :
key.equals(k)), then this method
returns v; otherwise it returns null.
(There can be at most one such
mapping.)

(my emphasis)

and as such, a successful key lookup depends on the input key's implementation of the equality method. That is not necessarily dependent on the class of k.

千寻… 2024-12-09 08:15:09

这是Postel定律的应用,“在你所做的事情上要保守” ,对你从别人那里接受的东西要宽容。”

无论类型如何,都可以执行相等性检查; equals 方法在 Object 类上定义,并接受任何 Object 作为参数。因此,键等价以及基于键等价的操作接受任何 Object 类型都是有意义的。

当映射返回键值时,它通过使用类型参数来保留尽可能多的类型信息。

It's an application of Postel's Law, "be conservative in what you do, be liberal in what you accept from others."

Equality checks can be performed regardless of type; the equals method is defined on the Object class and accepts any Object as a parameter. So, it makes sense for key equivalence, and operations based on key equivalence, to accept any Object type.

When a map returns key values, it conserves as much type information as it can, by using the type parameter.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文