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

时间:2009-05-14 02:38:03

标签: java generics collections

我理解在一个Set中只允许根据.equals()的任何对象的一个​​实例,并且如果你已经有一个等效的对象,你不应该“需要”从Set中获取一个对象,但我会仍然希望有一个.get()方法,该方法在给定等效对象作为参数的情况下返回Set中的对象的实际实例(或null)。

关于它为何如此设计的任何想法/理论?

我通常不得不通过使用Map并使键和值相同或类似的东西来解决这个问题。

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

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

24 个答案:

答案 0 :(得分:30)

虽然pure参数确实使方法get(Object)怀疑,但潜在的意图并非没有实际意义。

有各种类和接口系列稍微重新定义equals(Object)。只需要集合界面就可以了。例如,ArrayList和LinkedList可以相等;它们各自的内容只需要相同和相同的顺序。

因此,有很好的理由在集合中找到匹配元素。也许更明确的指示意图的方法是使用像

这样的方法
public interface Collection<E> extends ... {
  ...
  public E findMatch(Object o) throws UnsupportedOperationException;
  ...
}

请注意,此API的值大于Set中的值。

至于问题本身,我没有任何关于为什么省略这种操作的理论。我会说最小生成集参数不成立,因为集合API中定义的许多操作都是出于方便和高效的原因。

答案 1 :(得分:12)

问题是:Set不是用于“获取”对象,而是用于添加和测试存在。 我理解你在寻找什么,我有类似的情况,并在关键和价值中使用相同对象的地图结束。

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

答案 2 :(得分:7)

几年前我在java论坛上遇到了同样的问题。他们告诉我定义了Set接口。它无法更改,因为它将破坏Set接口的当前实现。然后,他们开始声称废话,就像你在这里看到的那样:&#34; Set不需要get方法&#34;并开始钻我,Map必须始终用于从集合中获取元素。

如果你只使用set进行数学运算,比如intersection或union,那么可能是contains()就足够了。但是,Set在集合中定义以存储数据。我在Set中使用关系数据模型解释了需要get()。

在下文中,SQL表就像一个类。列定义属性(在Java中称为字段),记录表示类的实例。这样一个对象就是一个字段向量。一些字段是主键。它们定义了对象的唯一性。这就是你为Java中的contains()做的事情:

class Element {

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

我不知道DB内部。但是,在定义表时,只指定一次键字段。您只需使用@primary注释关键字段。在向表中添加记录时,不会第二次指定键。您不像在映射中那样将键与数据分开。 SQL表是集合。它们不是地图。然而,除了保持唯一性和contains()检查之外,它们还提供了get()

在计算机编程艺术&#34;中,引入搜索,D。Knuth说同样的话:

  

本章的大部分内容都致力于研究一个非常简单的搜索   问题:如何查找已存储给定的数据   鉴定。

您看,数据存储时带有标识。不是指向数据的标识,而是带标识的数据。他继续说道:

  

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

看起来他开始谈论制图。但是,

  

一般来说,我们假设已经存储了一组N条记录,   问题是找到合适的一个。我们通常要求   N个键是不同的,因此每个键唯一地标识它   记录。所有记录的集合称为文件,   其中的单词&#34; table&#34;通常用于表示一个小文件,和   &#34;文件&#34;通常用于表示大表。一个大文件或   文件组通常称为数据库

     

用于搜索的算法以所谓的参数K表示,   问题是找到哪个记录以K为关键。虽然   搜索的目的是找到存储在记录中的信息   与K相关联,本章中的算法通常会忽略   除钥匙本身以外的一切。在实践中我们可以找到   我们找到K后的相关数据;例如,如果K出现在   location TABLE + i,关联数据(或指向它的指针)可能是   在位置TABLE + i + 1

也就是说,搜索找到记录的密钥字段,它不应该&#34; map&#34;数据的关键。两者都位于相同的记录中,作为java对象的文件。也就是说,搜索算法检查记录的关键字段,就像在集合中一样,而不是像在地图中那样检查一些远程密钥。

  

我们有N个要分类的项目;我们称之为记录,和   整个N条记录集将被称为文件。每   记录Rj有一个密钥Kj,它管理排序过程。额外   除了密钥之外,数据通常也存在;这个额外的卫星   信息&#34;除了必须携带之外,对排序没有影响   作为每条记录的一部分。

我也认为没有必要在一个额外的&#34;密钥集中复制密钥&#34;在他对排序的讨论中。

... [&#34;计算机编程艺术&#34;,第6章,简介]

  

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

     

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

... [&#34;数据库系统概念&#34;,第6版]

基本上,class描述了其所有实例共有的属性。关系数据库中的表也是如此。 "The easiest mapping you will ever have is a property mapping of a single attribute to a single column."我正在谈论这种情况。

我在证明对象和数据库记录之间的类比(同构)时非常冗长,因为有些愚蠢的人不接受它(为了证明他们的Set不能有 get 方法)

你在回放中看到那些不理解这一点的人怎么说Set with get会多余?这是因为他们用来代替集合的滥用地图引入了冗余。他们对put(obj.getKey(),obj)的调用存储了两个键:原始键作为对象的一部分,以及它在地图键集中的副本。重复是冗余。它还涉及代码中的更多膨胀并浪费运行时消耗的内存。我不知道数据库内部,但良好设计和数据库规范化的原则说这种重复是个坏主意 - there must be only one source of truth。冗余意味着可能发生不一致:键映射到具有不同键的对象。不一致是冗余的表现。 Edgar F. Codd proposed DB normalization只是为了摆脱冗余及其推断的不一致性。教师明确规范化:Normalization will never generate two tables with a one-to-one relationship between them. There is no theoretical reason to separate a single entity like this with some fields in a single record of one table and others in a single record of another table

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

  1. 当我们有一组独特的对象时,地图是不必要的
  2. map在运行时存储中引入冗余
  3. map在DB(在集合中)中引入代码膨胀
  4. 使用map与数据存储规范化相矛盾
  5. 即使你不了解记录集的想法和数据规范化,玩集合,你也可以自己发现这个数据结构和算法,就像我们org.eclipse.KeyedHashSet和C ++ STL设计者那样。

    我被Sun论坛禁止指出这些想法。偏见是反对这个理由的唯一论据,这个世界由偏执者主宰。他们不希望看到概念以及事物如何不同/改进。他们只看到现实世界,无法想象Java Collections的设计可能存在缺陷并且可以改进。向这些人提醒理性事物是危险的。如果你不服从,他们会教导你他们的失明和惩罚。

    2013年12月添加:SICP also says that DB is a set with keyed records rather than a map:

      

    典型的数据管理系统花费大量时间   访问或修改记录中的数据,因此需要   一种访问记录的有效方法。这是通过识别来完成的   每条记录的一部分作为识别密钥。现在我们代表   数据库作为一组记录。

答案 3 :(得分:1)

好吧,如果你已经从套装中“拿到”了东西,你不需要得到()它,是吗? ; - )

我认为你使用地图的方法是正确的。听起来你正试图通过equals()方法“规范化”对象,我总是按照你的建议使用Map完成。

答案 4 :(得分:1)

我不确定你是否正在寻找一个解释为什么集合这样做的原因,或者是为了解决它所带来的问题的简单解决方案。其他答案涉及前者,因此这是对后者的建议。

您可以迭代Set的元素,并使用equals()方法测试它们中的每一个是否相等。它易于实现且几乎不容易出错。显然,如果您不确定元素是否在集合中,请事先使用contains()方法进行检查。

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

答案 5 :(得分:1)

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

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

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

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

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

如果你有Joshua Bloch的Effective Java,请看一下名为“当超越平等时遵守一般合同”和“尽量减少可变性”的章节。

答案 6 :(得分:1)

只需使用Map解决方案...... TreeSet和HashSet也可以这样做,因为它们由TreeMap和HashMap备份,所以这样做没有任何惩罚(实际上它应该是一个最小的增益)。

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

[]]

答案 7 :(得分:1)

我认为,给定一些Set实现,你唯一的解决方案是遍历其元素以找到一个等于() - 然后你在Set中匹配的实际对象。

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

答案 8 :(得分:1)

如果您将其视为数学集合,则可以找到找到对象的方法 将集合与仅包含要查找的对象的对象集合相交。如果交集不为空,则集合中剩下的唯一项目就是您要查找的项目。

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

它不是最有效的内存使用,但它在功能上和数学上都是正确的。

答案 9 :(得分:0)

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

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中创建get方法没有任何意义。

**注****为了解释我使用了int类型,这同样也适用于Object类型。

答案 10 :(得分:0)

这只是一个意见。我相信我们需要了解我们有几个没有字段/属性的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不是数据结构,因为它的目的不同。

答案 11 :(得分:0)

简单的接口/ API在实现过程中提供了更多的自由。例如,如果将a5接口简化为单goto方法,我们会得到一个典型的函数式编程定义 - 它只是一个谓词,实际上没有存储任何对象。 java.util.EnumSet也是如此 - 它只包含每个可能值的位图。

答案 12 :(得分:0)

我知道在一个Set中只允许根据.equals()的任何对象的一个​​实例,并且你不应该&#34;需要&#34;如果你已经拥有一个等效的对象,从Set中获取一个对象,但是我仍然想要一个.get()方法,该方法在给定一个等价对象作为参数的情况下返回Set中的对象的实际实例(或null)。

答案 13 :(得分:0)

没有得到的原因很简单:

如果你需要从集合中获取对象X是因为你需要X中的东西而你没有对象。

如果您没有该对象,则需要一些方法(键)来定位它。 ..名字,数字,永远。那是什么地图是正确的。

map.get(“key”) - &gt; X!

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

那么,为什么不添加一个方便的get(X) - &gt; X

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

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

假设我创建了对象Y,它与X的等号匹配,因此set.get(Y) - &gt; X. Volia,然后我可以访问我没有的X数据。比如说X有一个名为get flag()的方法,我希望得到结果。

现在看看这段代码。

ý

X = map.get(Y);

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

,但..

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

所以,你看,如果set允许你得到像这样的对象肯定会打破equals的基本语义。之后你会生活在X的所有小克隆中,所以当它们不存在时它们是相同的。

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

答案 14 :(得分:0)

我同意我希望Set实现提供get()方法。

作为一个选项,在您的Objects实现(或可以实现)java.lang.Comparable的情况下,您可以使用TreeSet。然后可以通过调用ceiling()或floor()来获取get()类型函数,然后检查结果是非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)) {
:
:
}

答案 15 :(得分:0)

这显然是Set API的缺点。

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

我必须循环通过我的(哈希)套装才能找到我的对象......叹息......

答案 16 :(得分:0)

如果你已经在Java bug parade中列出了这个请求,我们就可以投票了。我认为至少只需要一个集合和一个对象的便捷类java.util.Collections 并实现类似

searchSet(Set ss, Object searchFor){

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

答案 17 :(得分:0)

我遇到了同样的问题。我通过将我的设置转换为地图,然后从地图中获取它来修复它。我用这个方法:

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);

您可以覆盖类中的等号,让它只检查ID或名称等特定属性。

答案 18 :(得分:0)

说,我有一个带有ID和名称的用户POJO。 ID保持equals和hashcode之间的契约。 name不是对象相等的一部分。 我想根据某个地方的输入更新用户名,比如说UI。

由于java set不提供get方法,我需要迭代代码中的set并在找到相等对象时更新名称(即ID匹配时)。

如果你有方法,可以缩短这段代码。

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

答案 19 :(得分:0)

Object fromSet = set.tailSet(obj).first();

if (! obj.equals(fromSet)) fromSet = null;

做你想要的。我不知道为什么java会隐藏它。

答案 20 :(得分:0)

Functional Java有一个持久集的实现(由红/黑树支持),偶然包含split方法,似乎可以做你想要的。它返回一个三元组:

  1. 在找到的对象之前出现的所有元素的集合。
  2. 类型为Option的对象,该对象为空或包含找到的对象(如果它存在于集合中)。
  3. 找到的对象之后出现的所有元素的集合。
  4. 你会做这样的事情:

    MyElementType found = hayStack.split(needle)._2().orSome(hay);
    

答案 21 :(得分:0)

我认为期望是equals真正代表一些平等,而不仅仅是两个对象具有相同的主键,例如。如果equals表示两个非常相等的对象,那么get将是多余的。你想要的用例建议一个Map,也许是一个不同的键值,代表一个主键,而不是整个对象,然后相应地正确实现equals和hashcode。

答案 22 :(得分:0)

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

这没有意义。说你做:

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

你现在这样做:

s.contains(newFoo)

如果你希望只有当集合中的对象是== newFoo时才这样,那么实现Foo的equals和带有对象标识的hashCode。或者,如果您尝试将多个相等的对象映射到规范原件,那么Map可能是正确的选择。

答案 23 :(得分:-6)

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

Set提供Set#contains(Object o),它提供所需Set#get(Object o)的等效身份测试,并返回一个布尔值,如预期的那样。