将非公共字段包含在equals()中是不好的做法

时间:2014-07-31 15:00:28

标签: java

不可访问的字段(以及哪些不是也不能从任何其他公共字段派生)包含在equals()中是不是一种不好的做法>

这样做有什么不良后果?

这在某处被描述为不良做法吗? (我假设哈希/等于合同是好的)

我觉得,这样equals在哲学上是非常错误的。世界上没有观察到equals这样的{{1}}。但这会导致真正的编程错误行为,哲学不在吗?

5 个答案:

答案 0 :(得分:4)

我可以想到一个不好的后果 - 您的类的用户可能会比较两个所有可公开访问的属性相同的对象,并且他们不会理解为什么您的equals方法返回false。

我假设(并且您的编辑确认它)您指的是没有公共访问权限的私有字段(即没有getter),因为通常所有属性应该始终是私有的,并且只能由setter和getters访问。

答案 1 :(得分:4)

它非常好,有时是必要的。您不应该比较的唯一字段是标记为transient的字段。

当且仅当它们的序列化相等时,我断言两个实例是相等的。这就是为什么transient很重要以及字段封装程度不重要的原因。

答案 2 :(得分:2)

没关系。如果您自己编写类,则用户仍然可以使用您可能添加的getter和setter方法来获取和设置字段。但是,如果添加getter和setter方法,请确保字段是安全的。

如果您没有编写getter和setter方法,则用户可能很难根据所有公共字段的值来确定对象不相等的原因。说完这个之后,人们可以使用调试器来遍历私有字段的所有值。

另一种可能性是创建一个特殊方法,返回哪些特定字段不相等。虽然这并不严格符合字段应该被任何公共方法"无法访问的要求,但返回动态创建的字符串,基本类型或对象(例如字段名称数组)将保持方法安全。

答案 3 :(得分:1)

应该进入equals()

有些字段定义了对象的标识,并且有些字段定义了它的属性。手机的序列号是其身份的一部分,其颜色或电池充电百分比只是一个属性。

equals()应该只依赖于定义身份的字段,这与字段是否可以从外部访问无关,它来自字段本身的语义。

所以问题应该是:是否有任何理由将客户的身份隐藏起来?如果答案似乎是“是”,那么下一个更基本的问题是:是否有任何理由首先确定对象?毕竟,有很多物品不需要具有超出琐碎的身份。

有几个可能出现的问题,这是一个例子。

问题示例

public class Foo {
    private Bar hidden;
    private String accessible;

    @Override
    public boolean equals( Object ob ) {
       return hidden.equals(((Foo)ob).hidden);
    }

    @Override
    public int hashCode() {
       //Let's not forget that we MUST override hashCode() if we override equals() :)
    }
}

现在想象一个天真的客户希望将这些放入TreeSet。这个类不是Comparable,但没什么可担心的,我们可以为它写一个比较器:

public class FooComparator implements Comparator<Foo> {

    public int compare( Foo first, Foo second ) {
        return first.getAccessible().compareTo( second.getAccessible() );
    }
}

完成工作,很棒!

不良客户无意中违反了TreeSet的合同,因为提供的比较与equals()不一致。

如何解决这个问题?

让我们假设尽管知道这一切,你仍然希望自己的equals()取决于隐藏的字段。因为您希望将它们放在HashSet中。

然后你可以这样做:

public class Foo {
    private Bar hidden;
    private String accessible;
    private final Helper helper = new Helper();


    Helper helper() {
       return helper;
    }      

    class Helper {

        @Override
        public boolean equals( Object ob ) {
          // you can access "hidden" from here
        }

        @Override
        public int hashCode() {
          // you can access "hidden" from here
        }

        public Foo foo() {
            return Foo.this;
        }
    }
}

您将Helper个对象放在HashSet而不是Foo中。虽然Helper的可见性是包私有的,但在大多数情况下仍然可以接受,因为此级别不是公共API的一部分。

答案 4 :(得分:-2)

您应该放入使对象唯一的字段。如果你可以修改这些字段的重要性较低。