5

Is it a bad practice to include fields inaccessible by any public methods (and which themselves are not and can not be derived from any other public fields) into equals() ?

What are possible bad consequences of doing so?

Is this described as a bad practice somewhere? (I assume hash/equals contract is ok)

I feel, such equals would be philosophically very wrong. Such equals is not observed in the world. But can it lead to real programming misbehavior, philosophy aside?

Alexander Kulyakhtin
  • 47,782
  • 38
  • 107
  • 158
  • 4
    It is bad practice to have public fields :) – rhobincu Jul 31 '14 at 15:02
  • 1
    Why would it be bad? I don't remember when I last time saw public fields ([having them would be bad practice](http://stackoverflow.com/questions/1568091/why-use-getters-and-setters)). – Pshemo Jul 31 '14 at 15:02
  • Come on this can not be opinion-based. I'm lookng for any example proving it can lead to some errors – Alexander Kulyakhtin Jul 31 '14 at 15:03
  • @musefan It's a good question. That's what. – peter.petrov Jul 31 '14 at 15:03
  • Sorry, edited the question – Alexander Kulyakhtin Jul 31 '14 at 15:04
  • @peter.petrov: in what respect... because you are interested in a discussion about it? It's not good in terms of SO questions. It's lacking any attempt to include examples, or to help make the question more "on topic" – musefan Jul 31 '14 at 15:04
  • 1
    @Alex: So in that case it's too broad, but I think it is personally opinion as to weather the effects of doing so are negative, it would also very much depend on the situation too – musefan Jul 31 '14 at 15:06
  • I think it's bad practice. For something like `equals()` I feel like the user of your object should be able to verify themselves using publicly available methods. Otherwise it seems weird that you are checking equality with a hidden field that the user cannot be aware of. – telkins Jul 31 '14 at 15:06
  • It can't be too broad. The answer would be an example demonstrating possible negative consequences (if such an example can exist) – Alexander Kulyakhtin Jul 31 '14 at 15:08
  • I think the question should not be "can non-public attributes be included in the equals method" but "should all attributes that contribute to equals be publicly accessible (at least readable)". And for this latter question, I would clearly say: Yes. – tobias_k Jul 31 '14 at 15:09
  • 1
    @Alex: What if there are 100 examples of how it can have negative effects? And again, who gets to decide if the effect is negative or not? – musefan Jul 31 '14 at 15:09
  • 1 example would be enough, care to give me such? I feel it's a bad practice and I can not prove it for myself that's why I'm asking. Because, maybe it's ok? – Alexander Kulyakhtin Jul 31 '14 at 15:10
  • 1
    @Alex: If you can't prove it then perhaps you have a scenario where it is fine to do so. Let's say for example you have a private field which actually store some kind of "meta information" (it could be a cache of some expensive calculation), doesn't need to be public, but important for checking equality... again... so many possibilities. Maybe you should find a chat room for such a **discussion** – musefan Jul 31 '14 at 15:15
  • 6 answers (1 deleted) all with different *views*... still think this isn't an opinion based question? – musefan Jul 31 '14 at 15:28
  • @musefan There's absolutely nothing opinion-based in the question. And most of the answers say roughly the same too. If the question was re-phrased as "What are the dangers of including hidden fields into `equals()`?", you wouldn't even have noticed it. – biziclop Jul 31 '14 at 16:02
  • @biziclop: If you rename it to that it's still opinion based (people won't all agree things are dangerous) and there could be lots of so called dangers (too broad) – musefan Jul 31 '14 at 16:04
  • @musefan So because there could potentially be lots of perfectly well defined and objective dangers (Only in your opinion, ironically. In reality there are about a handful of scenarios where it matters), the question is opinion-based? – biziclop Jul 31 '14 at 16:09
  • @biziclop: OK, lets say there is only one example possible to keep it simple. Who gets to decide if it's actually a danger or not? That's where the opinion part comes in. The reality is you could probably provide a scenario where it would cause problems, and I could provide an example of a scenario where it's perfectly fine. There we have conflicting opinions of "is it bad practice"... it really does depend on the situation, there is no one hard rule that fits all scenarios – musefan Jul 31 '14 at 16:14
  • @musefan Objective reality. Who has decided it is dangerous to jump from the 13th floor, the same entity here – Alexander Kulyakhtin Jul 31 '14 at 16:25
  • @musefan Now you're just arguing for arguing's sake. If something violates the contract of a library class, it violates that contract, end of story. You may well craft an example where the outcome is the same, it doesn't change the simple fact that what you're doing is wrong. The absurdity of your argument becomes obvious when you apply it to naive multi-threading. Just because you can write a hello world example without proper synchronisation where they don't occur, are the dangers of deadlocks and race conditions relative? No, they obviously aren't, you're just nitpicking. – biziclop Jul 31 '14 at 16:25

5 Answers5

4

I can think of one bad consequence - the users of your class might compare two objects for which all the publicly accessible properties are equal, and they won't understand why your equals method returns false.

I'm assuming (and your edit confirms it) you mean private fields with no public access (i.e. without getters), since in general all properties should always be private, and only accessed by setters and getters.

Eran
  • 387,369
  • 54
  • 702
  • 768
  • 3
    This is a bit misleading. If users of a class need to check the equality of two instances of the class, the _should_ use `equals`, not some homemade property comparison method. Because, OOP is about state and behavior, and equality of two objects should be determined by state, and state include values of all members, public or private. – sampathsris Jul 31 '14 at 15:09
  • 1
    @Krumia Lets assume I write the code : `... String str = "foo"; String str2 = "foo"; if (str.equals(str2)) {...} else {...} ...` I would be very surprised if the `else` clause is executed. – Eran Jul 31 '14 at 15:13
  • @Eran: You would be surprised because string is not designed that way. OP is referring to custom class, which could have many different data attributes that determine equality, and not all may need to be public. The point is, the rules of equality are determined by the specification... which can be anything – musefan Jul 31 '14 at 15:17
  • @Eran: I would be too. If you have this problem described by OP, one of following must be true: 1. You haven't exposed a member that should have been exposed using a getter. Expose the instance member via getter. 2. You have promoted what should have been a local variable in a method to be an instance variable. Demote that instance member to a local variable of the relevant method. 3. You have to use instance members because you have split a method unnecessarily. So in order to pass state, you need instance members. This is quite similar to #2. – sampathsris Jul 31 '14 at 15:19
4

It's absolutely fine and sometimes necessary. The only fields that you shouldn't compare are ones marked as transient.

I'd assert that two instances are equal if and only if their serialisations are equal. That's why transient matters and the degree of field encapsulation doesn't.

Bathsheba
  • 231,907
  • 34
  • 361
  • 483
2

It's fine. If you are writing the class yourself, the users can still get and set the fields using getter and setter methods you might add. Make sure the fields are secure if you add getter and setter methods, though.

If you are not writing getter and setter methods, it can be difficult for Users to identify why objects are not equal based on the values of all the public fields. Having said this, one might be able to use a debugger to step through all the values of the private fields.

Another possibility is to create a special method that will return which specific fields are not equal. Although this does not strictly conform to the requirement that the fields should be "inaccessible by any public methods", returning Strings, primitive types or objects that are created on the fly (such as an array of the field names) will keep the method secure.

La-comadreja
  • 5,627
  • 11
  • 36
  • 64
1

What should go into equals()?

There are fields that define the identity of an object and there are fields that define its properties. The serial number of your phone is part of its identity, its colour or battery charge percentage is just a property.

equals() should only depend on fields that define identity, and that is independent of whether the fields are accessible from the outside or not, it comes from the semantics of the fields themselves.

So the question should really be: is there any reason to hide the identity of an object from its clients? And if the answer seems to be "yes", the next, more fundamental question is: is there any reason to identify the objects in the first place? After all, there are lots of objects out there that don't require to have an identity beyond the trivial.

There are several possible problems that arise from it, here's one example.

Example of problem

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() :)
    }
}

Now image a naive client wants to put these into a TreeSet. The class isn't Comparable but nothing to worry, we can write a comparator for it:

public class FooComparator implements Comparator<Foo> {

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

Job done, great!

Except poor client unwittingly violated the contract of TreeSet as the comparator provided isn't consistent with equals().

How to work around this?

Let's suppose that despite knowing all this, you still want your own equals() that depends on hidden fields. Because you want to put them in a HashSet for example.

You can then do this:

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

And you put your Helper objects in the HashSet instead of Foo. Although Helpers visibility is package-private, that's still acceptable in most cases, as this level is not part of the public API.

biziclop
  • 48,926
  • 12
  • 77
  • 104
-2

You should put in fields that makes the object unique. If you can modify the fields are of lesser importence.

AppX
  • 528
  • 2
  • 12