Java中是否可以控制对象的身份,如果可以,如何实现?

7

这是一个简单的编程问题。我不是Java专家。假设我使用自定义类Company和Employee的对象,类似于许多关系型数据库示例:

class Employee
{
    Company company;
}

class Company
{
    String name;
}

我需要确保不同的Company对象具有唯一的名称,即不能有两个这样的对象具有相同的名称,因为在我的观点中这没有意义,并且会浪费内存 - 如果两个员工在IBM工作,则有一个带有该nameCompany对象,结束。

目前我的想法是将Company构造函数设为私有 - 这样分配具有任意名称的Company对象的工作将委托给一个受信任的方法 - 该方法将拒绝任何后续尝试创建已经存在名称的对象或返回现有的或新的对象(如果必要则创建此对象)。

问题是,我不确定如何优雅地完成这项工作。一个好的解决方案是不必每次请求具有名称的Company对象时都进行O(n)查找 - 因此,也许可以使用哈希映射或二叉树来方便地实现?我还想覆盖Company对象的标识方式 - 这引导我到这里:我将重写Object.equals和/或Object.hashCode方法吗?


你确定你应该担心额外的内存使用吗?你必须以某种方式付出代价来跟踪所有公司,这将花费你(可能是CPU)。确保你的优化不是过早的。 - nojo
这并不是为了优化,而是为了避免两个名称相同的“公司”对象的约束条件的重要性。这样,如果有N个员工引用的公司更改了名称,我只需要实际上对一个“公司”对象进行更改。就像这样的事情。 - Armen Michaeli
5个回答

8

请看享元模式。

我会做类似于以下的事情:

// incomplete, but you get the idea hopefully
CompanyFactory
{
    private Map<String, Company> companies;

    public getCompany(final String name)
    {
        Company company;

        company = compaines.get(name);

        if(company == null)
        { 
            company = new Company(name);
            companies.put(name, company);
        }

        return (company);
    }
}

+1 - 这正是享元模式被设计来解决的问题。 :) - aperkins
是的,那就是我开始做的事情。但是,我担心对公司对象的引用 - 看起来这个映射表,即使在这里很有用,也将保留对所有曾经创建的公司对象的引用,即使可能没有更多“好”的引用留给这些对象了。我正在考虑使用WeakHashMap,但弱引用指的是键而不是值 :'-( - Armen Michaeli
我相信当从WeakHashMap中移除键时,值也会被移除,因此可以进行垃圾回收。 - TofuBeer
是的,你说得对,但是我的键是字符串 - 什么时候会被删除?因此,我不确定WeakHashMap<String, Company>会如何行为,考虑到它将使用字符串作为键... - Armen Michaeli
如果您允许使用WeakHashMap来收集公司对象,那么您无法保证满足以下要求:“我需要确保不同的公司对象具有唯一的名称”。除非您知道公司已被删除,并且可以在删除时通知Factory对象。我认为这里不需要使用WeakHashMap。 - nojo
我无法理解你的逻辑,你说我不能保证需求是什么意思?我看不出我使用基于弱密钥的哈希映射如何影响名称的唯一性?此外,通知工厂对象已删除的公司实际上是通过所谓的引用队列完成的,这又是一种处理弱引用的形式。或者我可能误解了你的观点... - Armen Michaeli

4
你可以重写equalshashCode,并将它们存储在HashMapHashSet中。

@Fabian:除了这些用法不需要覆盖任何内容,但它们不能帮助保持值的唯一性。 - Michael Borgwardt
1
@Michael:除非正确重写 equalshashCode,否则 hashMaphashSet 将无法帮你消除重复值。什么都不做是没有同样效果的。 - Lie Ryan
@Lie:除非像amn打算做的那样在创建时防止重复值,否则它确实会有影响。仔细阅读问题真的很有帮助... - Michael Borgwardt
2
@Lie: 是你仍然误解了。在域类中使用hashCode和equals只对识别已存在的重复项有用,但你想要避免创建重复项。唯一的方法是拥有一个以名称为键的实例映射表 - 这使得域类中的hashCode和equals变得不必要。 - Michael Borgwardt
@Michael True,我明白你的观点 - 我想答案更多地是针对问题的标题而不是实际问题本身 :-) - Fabian Steeg
显示剩余5条评论

1
有一件很好的事情是不必每次请求一个名称为Company对象时都进行O(n)查找,因此可能会有哈希映射或二叉树方便我的使用?
听起来是对的,没错。
我还想覆盖掉Company对象的标识方式,这让我想到了:我需要重写Object.equals和/或Object.hashCode方法吗?
如果您确保没有两个具有相同键值的实例,那么您实际上不需要这样做。

1

当我想要创建一个可以通过唯一名称查找对象的集合时,我会存储一个Map,将字符串(名称)映射到对象。然后我就可以查找与名称相关联的对象。

严格来说,你不需要触及equals()hashCode()来实现这个功能,因为你没有将对象作为键存储。正确实现equals()和hashCode()可能很难做到,而在像HashMap这样的Map实现中,键对这些方法非常敏感,这会影响到它们的高效查询。使用现有的(并且关键是不可变的)String类作为键可以帮助你正确地实现这种查找功能。


更新:如你所提到的,如果将构造函数设置为私有,则可以防止创建新的Company实例。提供其他创建Company实例的方法(工厂模式)可确保仅当相同名称的实例尚未存在时,'new' Company实例才是真正的新实例;否则,会返回给定名称的现有实例(单例模式的一个示例)。

实际上,将字符串存储为键并不足以满足数学要求。在数学上,两个不同的字符串可能会哈希到相同的值。 - John Engelman
5
这句话的意思是,“它们被放在同一个组里,然后使用String equals()方法确保唯一性。” - brabster
@John 实际上这已经足够了。 - user207421

0
如果一个对象的名称随时间变化,考虑为每个对象包括一个生成的唯一ID。您的映射将从名称到唯一ID,然后从唯一ID到实际对象。
顺便说一下,如果所有对象在编译时都是已知的,您可以使用枚举。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接