为什么java.util.Set <V>接口不提供get(Object o)方法?

我知道只有一个根据.equals()的对象的实例被允许在一个Set中,如果你已经有了一个等价的对象,你不应该“需要”从Set中获取一个对象,但是我仍然想有一个.get()方法,返回Set(或null)中对象的实际实例,给定一个等效对象作为参数。

任何想法/理论为什么它是这样devise的?

我通常不得不通过使用Map来创build关键和值,或者类似的东西来解决这个问题。

编辑:我不认为人们理解我的问题到目前为止。 我希望确切的对象实例已经在集合中,而不是一个可能不同的对象实例,其中.equals()返回true。

至于为什么我想要这种行为,通常.equals()不考虑对象的所有属性。 我想提供一些虚拟的查找对象,并返回Set中的实际对象实例。

虽然纯度参数确实使方法get(Object)可疑,但其基本意图不是没有争议的。

有很多类和接口家族,稍微重新定义equals(Object) 。 一个需要看看没有进一步比collections界面。 例如,一个ArrayList和一个LinkedList可以相等; 它们各自的内容只需要是相同的,并且是相同的顺序。

因此,find一个集合中的匹配元素有很好的理由。 也许更明确的表示意图的方法是有一个像

 public interface Collection<E> extends ... { ... public E findMatch(Object o) throws UnsupportedOperationException; ... } 

请注意,此API具有在Set中更广泛的价值。

至于这个问题本身,我没有任何的理论说明为何省略这个行动。 我会说, 最小的spanning set参数并不成立,因为集合API中定义的许多操作都是出于方便和效率的原因。

问题是:设置不是为了“获取”对象,是为了添加和testing存在。 我明白你在找什么,我有类似的情况,并结束使用键和值相同的对象的地图。

编辑:只是为了澄清: http : //en.wikipedia.org/wiki/Set_(abstract_data_type)

我在几年前的java论坛有同样的问题。 他们告诉我,Set接口是定义的。 它不能改变,因为它会打破Set接口的当前实现。 然后,他们开始声称废话,就像你在这里看到的:“Set不需要get方法”,并开始钻我,Map必须总是用来从一个集合中获取元素。

如果仅将该集合用于math运算,如交集或联合,则可能是contains()就足够了。 但是,Set在集合中定义来存储数据。 我使用关系数据模型解释了需要get()的Set。

在下面的内容中,SQL表就像一个类。 列定义属性(在Java中称为字段),logging代表类的实例。 所以一个对象是一个字段的vector。 一些字段是主键。 他们定义了对象的唯一性。 这是你在Java中为contains()做的事情:

 class Element { public int hashCode() {return sumOfKeyFields()} public boolean equals(Object e) {keyField1.equals(e) && keyField2.equals(e) && ..} 

我不知道DB内部。 但是,在定义表格时,只能指定一次关键字段。 您只需使用@primary注释关键字段。 向表中添加logging时,不要再次指定密钥。 您不像在映射中那样将密钥与数据分开。 SQL表是集合。 他们不是地图。 然而,除了维护唯一性和包含()检查外,他们还提供了get()

在“计算机编程艺术”中,介绍search,D. Knuth也这样说:

本章的大部分内容都致力于研究一个非常简单的search问题:如何查找已经存储的具有给定标识的数据。

你看,数据是存储与识别。 不是识别指向数据,而是识别数据 。 他继续:

例如,在一个数字应用程序中,我们可能想要findf(x),给定x和一个f值的表; 在一个非数字化的应用中,我们可能想find一个给定的俄语单词的英文翻译。

看起来他开始谈论绘图。 然而,

一般来说,我们假设一组N个logging已被存储,问题是find合适的logging。 我们通常要求N个密钥是不同的,以便每个密钥唯一地标识它的logging。 所有logging的集合被称为文件 ,其中通常使用单词“表”来表示小文件,“文件”通常用于表示大表。 大文件或一组文件经常被称为数据库

用于search的algorithm用所谓的参数K来表示,问题是要找出哪个logging​​具有K作为其关键字。 尽pipesearch的目的是find存储在与K相关的logging中的信息,但本章中的algorithm通常忽略除了密钥本身之外的所有信息。 实际上,一旦我们findK,我们就可以find相关的数据; 例如,如果K出现在位置TABLE + i中,则关联的数据(或指向它的指针)可能在位置TABLE + i + 1

也就是说,search定位logging的关键字段,而不应该将关键字“映射”到数据上。 两者都位于相同的logging,作为Java对象的fileds。 也就是说,searchalgorithm检查logging的关键字段,就像它在集合中所做的那样,而不是像在地图中那样检查某个远程关键字。

我们给N个项目进行sorting; 我们将它们称为logging ,并且将整个Nlogging集合称为文件 。 每个loggingRj都有一个密钥Kj,它控制着sorting过程。 除钥匙以外的其他数据通常也存在; 这个额外的“卫星信息”对sorting没有影响,只是它必须作为每个logging的一部分进行。

在他的分类讨论中,我也没有看到需要在一个额外的“密钥集”中复制密钥。

… [“计算机编程的艺术”,第6章,介绍]

实体集是集合或设置特定实体types的所有实体[http://wiki.answers.com/Q/What_is_entity_and_entity_set_in_dbms]单个类的对象共享它们的类属性。 同样,在DB中做logging。 他们共享列属性。

集合的一个特例是类的范围,它是属于类的所有对象的集合。 类范围允许将类视为关系

… [“数据库系统概念”,第6版]

基本上,类描述了所有实例共有的属性。 关系数据库中的表格也是一样的。 “最简单的映射是将单个属性映射到单个列。” 我正在谈论的就是这种情况。

我在certificate对象和数据库logging之间的类比(同构)方面如此冗长,因为有些愚蠢的人不接受它(certificate他们的Set不能有get方法)

你会看到在重播中,不明白这一点的人如何说Set with get会是多余的? 这是因为他们滥用的地图,他们强加给地方使用,引入冗余。 他们对put(obj.getKey(),obj)的调用存储两个键:作为对象的一部分的原始键和它在地图的键集中的副本。 重复是冗余。 它还涉及更多的代码,并浪费运行时消耗的内存。 我不知道DB内部的东西,但是好的devise原则和数据库正常化说这样的重复是坏主意 – 只能有一个事实根源 。 冗余意味着可能会出现不一致:密钥映射到具有不同密钥的对象。 不一致是冗余的performance。 Edgar F. Codd提出DB正常化只是为了摆脱冗余和他们推断的不一致。 教师对规范化是明确的: 规范化永远不会生成两个表之间的一对一关系。 没有理由将这样的单个实体与一个表中的单个logging中的某些字段分开,而将另一个中的某些字段分隔到另一个表的单个logging中

所以,我们有4个参数,为什么使用map来实现get set是不好的:

  1. 当我们有一组独特的对象时,地图是不必要的
  2. 映射在运行时存储中引入冗余
  3. 地图在DB中引入代码膨胀(在集合中)
  4. 使用映射与数据存储规范化相矛盾

即使你没有意识到logging集的想法和数据规范化,玩集合,你可能会发现这个数据结构和algorithm,就像我们org.eclipse.KeyedHashSet和C ++ STLdevise者那样。

我被禁止从Sun论坛指出这些想法。 偏执是唯一的反对理由的论点,这个世界被偏执狂所控制。 他们不想看到概念,也不想看到事物会有所不同/改进。 他们只看到现实世界,无法想象Java Collections的devise可能有不足之处,有待改进。 向这样的人提醒理由是危险的。 如果你不服从,他们会教你盲目性和惩罚性。

新增2013年12月: SICP还表示, 数据库是一个具有键logging而不是地图的集合

一个典型的数据pipe理系统花费大量的时间访问或修改logging中的数据,因此需要一个有效的方法来访问logging。 这是通过识别每个logging的一部分来作为识别关键字来完成的。 现在我们将数据库表示为一组logging。

我不确定你是在寻找一个解释为什么集合performance这种方式,或为了一个简单的解决scheme,它构成的问题。 其他答案与前者有关,所以这是对后者的build议。

您可以迭代Set的元素,并使用equals()方法testing每个元素的相等性。 这很容易实现,几乎不容易出错。 显然,如果不确定元素是否在集合中,请事先检查contains()方法。

与HashSet的contains()方法相比,这种方法效率不高,它可以“查找”存储的元素,但不会返回它。 如果你的集合可能包含很多元素,它甚至可能是使用像你提到的地图实现那样的“更重”的解决方法的原因。 但是,如果这对你来说很重要(而且我确实看到了拥有这种能力的好处),那么这可能是值得的。

所以我明白你可能有两个相同的对象,但它们不是同一个实例。

 Integer a = new Integer(3); Integer b = new Integer(3); 

在这种情况下,a.equals(b)是因为它们指的是相同的内在值,但是a = b,因为它们是两个不同的对象。

还有其他Set的实现,比如IdentitySet ,它们可以在不同的项目之间进行比较。

不过,我认为你正试图将不同的哲学应用于Java。 如果你的对象是相等的(a.equals(b)),尽pipea和b有不同的状态或含义,这里有一些错误。 您可能希望将该类分成两个或多个实现通用接口的语义类 – 或者重新考虑.equals和.hashCode。

如果你有Joshua Bloch的Effective Java ,可以看看“在重写equals时服从一般合约”和“Minimize mutability”的章节。

只要使用Map解决scheme…一个TreeSet和一个HashSet也可以做到这一点,因为它们由一个TreeMap和一个HashMap来支持,所以这样做不会有任何惩罚(事实上它应该是一个最小的增益)。

你也可以扩展你最喜欢的Set来添加get()方法。

[]]

我认为你唯一的解决scheme,给定一些Set实现,是迭代它的元素来find一个是equals() – 那么你有匹配的实际对象。

 K target = ...; Set<K> set = ...; for (K element : set) { if (target.equals(element)) { return element; } } 

如果你把它看作一个math集,你可以导出一个方法来find这个对象。
将该集合与仅包含要查找的对象的对象集合进行交集。 如果交叉点不是空的,那么剩下的唯一项目就是你正在寻找的项目。

 public <T> T findInSet(T findMe, Set<T> inHere){ inHere.retainAll(Arrays.asList(findMe)); if(!inHere.isEmpty){ return inHere.iterator().next(); } return null; } 

它不是最有效的内存使用,但在function和math上是正确的。

那么,如果你已经从场上“拿到”了,那么你不需要(),是吗? 😉

我想,你使用Map的方法是正确的。 这听起来像是在尝试通过equals()方法来“规范化”对象,我一直使用Map来完成这个任务。

“我希望确切的对象实例已经在集合中,而不是一个可能不同的对象实例,其中.equals()返回true。

这没有意义。 说你这样做:

 Set<Foo> s = new Set<Foo>(); s.Add(new Foo(...)); ... Foo newFoo = ...; 

你现在做:

 s.contains(newFoo) 

如果你希望只有当set中的对象是== newFoo时才是true,那么使用对象标识实现Foo的equals和hashCode。 或者,如果您尝试将多个相同的对象映射到规范的原件,那么Map可能是正确的select。

我认为,期望是等于真正代表一些平等,而不是简单地说这两个对象具有相同的主键。 如果平等代表了两个真正相等的对象,那么一个get就是多余的。 你想要的用例表示一个Map,可能是一个不同的键值,代表一个主键,而不是整个对象,然后正确地实现equals和hashcode。

Functional Java有一个持久化Set(由红/黑树支持)的实现,它附带地包含了一个拆分方法,这似乎是你想要的。 它返回一个三元组:

  1. 在find的对象之前出现的所有元素的集合。
  2. Optiontypes的对象,如果它存在于集合中,则为空或包含find的对象。
  3. find的对象之后出现的所有元素的集合。

你会做这样的事情:

 MyElementType found = hayStack.split(needle)._2().orSome(hay); 
 Object fromSet = set.tailSet(obj).first(); if (! obj.equals(fromSet)) fromSet = null; 

做你正在寻找的东西。 我不知道为什么Java隐藏它。

说,我有一个用户名和ID的POJO。 ID保持equals和hashcode之间的契约。 名称不是对象平等的一部分。 我想根据来自某处的input(比如UI)更新用户的名称。

由于java集不提供get方法,我需要迭代我的代码中的集合,并更新名称,当我find平等的对象(即当ID匹配时)。

如果你有办法,这个代码可能已经缩短。

Java现在带有各种愚蠢的东西,如javadb和增强for循环,我不明白为什么在这种特殊情况下,他们是纯粹的。

我有同样的问题。 我通过将我的集合转换为Map来解决这个问题,然后从地图中获取它们。 我用这个方法:

 public Map<MyObject, MyObject> convertSetToMap(Set<MyObject> set) { Map<MyObject, MyObject> myObjectMap = new HashMap<MyObject, MyObject>(); for(MyObject myObject: set){ myObjectMap.put(myObject, myObject); } return myObjectMap } 

现在你可以通过调用这个方法来获得你的设置中的项目:

 convertSetToMap(myset).get(myobject); 

你可以重写你的类中的equals,让它只检查某个属性,如Id或Name。

如果你已经在Java bug列表中提出了这个请求,我们可以把它列出来。 我想至less有一个方便的java.util.Collections类,它只需要一个set和一个对象,并被实现类似的东西

 searchSet(Set ss, Object searchFor){ Iterator it = ss.iterator(); while(it.hasNext()){ Object s = it.next(); if(s != null && s.equals(searchFor)){ return s; } } 

这显然是Set API的一个缺点。

简单地说,我想在我的Set中查找一个对象并更新它的属性。

我必须通过我的(哈希)集循环到我的对象…叹息…

我同意我希望看到Set实现提供了一个get()方法。

作为一种select,在你的Objects实现(或者可以实现)java.lang.Comparable的情况下,你可以使用TreeSet。 然后可以通过调用ceiling()或floor()来获得get()types的函数,然后检查结果是否为null,并等于比较对象,如:

 TreeSet myTreeSet<MyObject> = new TreeSet(); : : // Equivalent of a get() and a null-check, except for the incorrect value sitting in // returnedMyObject in the not-equal case. MyObject returnedMyObject = myTreeSet.ceiling(comparisonMyObject); if ((null != returnedMyObject) && returnedMyObject.equals(comparisonMyObject)) { : : } 

没有得到的原因很简单:

如果你需要从集合中获取对象X,因为你需要从X中获得一些东西,而你没有这个对象。

如果你没有对象,那么你需要一些手段(键)来find它。 他们的名字,一个数字。 那是什么地图是正确的。

map.get(“key”) – > X!

集没有键,你需要哟遍历它们来获取对象。

那么,为什么不添加一个方便的get(X) – > X

这是没有道理的,因为你已经有了X,纯粹主义者会说。

但现在把它看作非纯粹主义者,看看你是否真的想要这样:

说我做对象Y,匹配X的等号,这样set.get(Y) – > X。 Volia,那么我可以访问我没有的X的数据。 举个例子,X有一个叫get flag()的方法,我想得到这个结果。

现在看看这个代码。

ÿ

X = map.get(Y);

所以Y.equals(x)是真的!

但..

Y.flag()== X.flag()= false。 (他们不等于?)

所以,你看,如果设置允许你得到这样的对象它肯定是要打破平等的基本语义。 后来,你将要生活在一起的小克隆人,他们都不相信他们是一样的。

你需要一个地图,存储的东西,并使用一个密钥来检索它。

我明白,只有一个根据.equals()的对象的实例是允许在一个集合,并且你不应该“需要”从集合中获得一个对象,如果你已经有一个等效的对象,但我仍然想有一个.get()方法,返回Set(或null)中对象的实际实例,给定一个等效对象作为参数。

简单的接口/ API在实现过程中提供了更多的自由。 例如,如果Set接口简化为单个contains()方法,我们就得到了一个典型的函数式编程的集合定义 – 这只是一个谓词,实际上并不存储对象。 对于java.util.EnumSet也是如此 – 它仅包含每个可能值的位图。

这只是一个意见。 我相信我们需要明白,我们有几个没有字段/属性的java类,即只有方法。 在这种情况下,equals不能通过比较函数来衡量,例如requestHandlers。 请参阅下面的JAX-RS应用程序示例。 在这种情况下,SET比任何数据结构更有意义。

 @ApplicationPath("/") public class GlobalEventCollectorApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<Class<?>>(); classes.add(EventReceiverService.class); classes.add(VirtualNetworkEventSerializer.class); return classes; } } 

要回答你的问题,如果你有一个浅层雇员对象(即只有EMPID,在equals方法中用来确定唯一性),并且如果你想通过在set中查找来获得一个深层对象,SET不是数据结构,因为其目的不同。

列表是有序的数据结构 。 所以它遵循插入顺序。 因此,您input的数据将在插入时的确切位置可用。

 List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); list.add(3); list.get(0); // will return value 1 

记住这个简单的数组。

设置是无序的数据结构 。 所以它没有顺序。 您在某个位置插入的数据可以在任何位置使用。

 Set<Integer> set = new HashSet<>(); set.add(1); set.add(2); set.add(3); //assume it has get method set.get(0); // what are you expecting this to return. 1?.. 

但它会返回别的东西。 因此,在Set中创buildget方法没有任何意义。

**注意****为了说明我用inttypes,这也适用于对象types。

我想你已经回答了你自己的问题:这是多余的。

Set提供了Set#contains(Object o),它提供了你想要的Set#get(Object o)的等价的身份testing,并且返回一个布尔值,如预期的那样。