• Post Reply Bookmark Topic Watch Topic
  • New Topic

Some questions regarding the equals method implementation in Java  RSS feed

 
Abhay Bhatt
Ranch Hand
Posts: 64
2
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Refer to the equals method implementation in the below link:-

Javaranch implementation of equals method

See the code in blue consisting of 27 lines.

I think we can infer that the 'check' in Line 10 ensures that run time object type of 'obj' is 'Test'. And it is casted to 'Test' in Line 13. However, I also see that the class 'Test' doesn't extend some(any) class. So, it seems that 'Test' doesn't has a parent but can have a child class. So, the only case Line 13 becomes useful is when 'obj' has 'Test' as its run time object type and 'Test1' as its class type(where 'Test1' is the child class of 'Test'). But I am not able to figure out a scenario in which this would stand true. So far, I am only able to make the below scenario, but it doesn't make any sense and I am also not too sure of its correctness:-

Test kal = new Test1();
Test1 obj = (Test1)kal;

(where 'kal' is the upcasted object of 'Test1' class)


Given that the above query stands resolved, I have still another question:

We are casting 'obj' to 'Test' and then comparing each of its attributes with 'this' object, and if this comparison is true, we declare that both objects are equal.
This is not making too much sense to me.

We can't even access 'num' and 'data' through 'obj' as in 'obj.num' or 'obj.data'(we can do that with 'this'), without first casting it to 'Test'. So, in effect, the class type(compile time type) of 'obj' is not even 'Test', and we are considering it equal to an object(in this case 'this') whose class type is 'Test'! Doesn't this seem weird?!?
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
you should re-read that article. You might also want to read Joshua Bloch's book, Effective Java Programming, where he explains how to properly implement equals(); the article is based largely on what's in Joshua Bloch's book.

In summary, the check that you reference ensures that only Test objects can be considered equal to the current Test object. Subclasses are not considered to be eligible to be equal because of the possibility of violations of the equals() contract when inheritance comes into play, particularly with respect to symmetry. The article and Bloch's book explain this, too.

After you perform that check, it is safe to cast the reference parameter obj to a reference of type Test. With that Test reference, you are free to access all members of the other object directly. One thing I would change in that code is the name test; I would prefer to use the name other instead.
 
Junilu Lacar
Sheriff
Posts: 11494
180
Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
  • Likes 3
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
Your statements about casting to Test and about wierdness indicate that you don't fully understand how casting works.

A cast does not change an object's type. A cast is merely a reassurance to the compiler that the thing you are casting is indeed compatible to the type that you're casting it to. It's like you guaranteeing to a creditor that your friend can indeed pay his loan off. The cast basically tells the compiler, "Trust me on this, I know you only know that this other thing is an Object but I've checked it out and can assure you it's actually an instance of Test."
 
Knute Snortum
Sheriff
Posts: 4281
127
Chrome Eclipse IDE Java Postgres Database VI Editor
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
We are casting 'obj' to 'Test' and then comparing each of its attributes with 'this' object, and if this comparison is true, we declare that both objects are equal.
This is not making too much sense to me. 

We override Object#equals() when we write an equals method, so the parameter must be type Object, but that does doesn't mean that we're passing only an Object reference to the equals method.  If we did, equals would return false.

As Junilu explained, casting has nothing to do with inheritance.

Are there other things that don't make sense?
 
Campbell Ritchie
Marshal
Posts: 56570
172
  • Likes 1
  • Mark post as helpful
  • send pies
  • Quote
  • Report post to moderator
If you go through the documentation for Object#equals(), you wil find six points marked with bullets. Yes, I can count. There are six points there. They sound very complicated, having posh names. but they are really quite simple when you think about them. Most of them are standard mathematical concepts which date back to the days of Euclid.
  • 1: Reflexivity: Every object is equal to itself.
  • 2: Symmetry: If object1 is equal to object2, then object2 is equal to object1.
  • 3: Transitivity. If object1 equals object2 and object2 equals object3, then object1 equals object3.
  • 4: Consistency. You always get the same result if nothing else changes.
  • 5: Non-nullity. An object which exists is implicitly different from an object which doesn't exist. This is the only point which is not absolutely obvious, and which would have to be specified explicitly. Nos 1‑4 shou‍ld be obvious almost without having to think about them.
  • So, you will ask, where is No 6? Look at No 4. If it always returns the same result:-
  • 6: (Feasibility) It must never throw an exception.
  • In the 15 years since that JavaRanch Journal article was written, there has been a gradual change of opinion about two things, and in different directions.
    One is a gradual move away from inheritance and subclassing. Yes, inheritance is still used, but there is more attention paid to the problems of inheritance than used to be. More recent classes cannot be inherited from. Compare this new class with this older class, and look in the Java™ Tutorials, which explain the differences. When a class is marked final, it makes no difference whether its equals() method uses getClass().equals(...) or instanceof.
    The other move is that people have often considered that subclass objects can be “.equal” to the superclass objects. Maybe you have a Car class and a Taxi class which inherits from it. Maybe a Car instance will be “equal” to a Taxi object. This can only be implemented with instanceof. But what if a subclass' field is used in its equals() method? As you will see from Effective Java and from reading other articles, e.g. Odersky Spoon and Venners (the first link in this FAQ) or Langer and Kreft, there are three things you can do:-
  • 1. Maintain the Liskov Substitution Principle.
  • 2: Maintain the general contract of equals().
  • 3. Add fields in subclasses and use them in equals().
  • You can only achieve two out of three. I know people say, “Two out of three isn't bad,” but here that isn't true. You can only fulfil 1 and 2 if you never try No 3.

    Read the two articles and Bloch's book. Now hold your hand on your heart and say the following things:-
    Somebody's on their way to help already.
    The cheque's in the post.
    The equals method is easy to understand.
    For those who haven't seen it before, that is a rhetorical device called the Three Great Lies.
     
    Abhay Bhatt
    Ranch Hand
    Posts: 64
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Campbell Ritchie wrote:

    Read the two articles and Bloch's book. Now hold your hand on your heart and say the following things:-
    Somebody's on their way to help already.
    The cheque's in the post.
    The equals method is easy to understand.
    For those who haven't seen it before, that is a rhetorical device called the Three Great Lies.


    I have read the two articles. It was quite enlightening and increased my knowledge. But it doesn't solve the first question I asked about the purpose of the cast(and neither do any other comment here). Thanks anyways.
     
    Junilu Lacar
    Sheriff
    Posts: 11494
    180
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Abhay Bhatt wrote:But it doesn't solve the first question I asked about the purpose of the cast(and neither do any other comment here)

    Are you referring to the following code?

    We already explained what's happening here in our other comments. Maybe you still don't understand. The cast is merely taking a reference of type Object and telling the Java compiler that it's safe to refer to the actual object as a Test. This is safe because the preceding code has already checked that this.getClass() == obj.getClass().
     
    Campbell Ritchie
    Marshal
    Posts: 56570
    172
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Right: in the case of Object, there are two ways you can look at things:-
  • 1: There are no fields, so two Objects cannot be different from each other.
  • 2: In the absence of further information we cannot assume any two different objects are equal.
  • Now let's demonstrate that approach No 1 doesn't work. Look at this class and these three objects:-Now, if you follow approach No 1, that there is no difference between Objects, then fa and fc will be counted as equal. So that won't work. So you try approach 2, that won't work because that would have fa and fx different. So let's try approach 3: Overload the equals() method: you get this in class Foo:-Now try this:-And, even better, you get different results from the same methods. Also collections classes do not know in advance what type of element they will have, so they can only use the Object#equals(Object) method, not Foo#equals(Foo). So there is only one sensible solution. Make sure the equals method is not final and tell people to override it. That can be done such that it will give the same results from those two print statements.

    So, you have to take an Object as the parameter, and, as you were told on the other thread, everything is an Object (except the eight primitives), so everything which isn't a primitive can be passed as an argument to that method. You can pass primitives too and they are subject to boxing conversion, which didn't exist when that JavaRanch Journal article was written. So when you write your equals() method, you are going to have to compare the two name fields for equality:-So the compiler won't be happy because Object hasn't got a name field. And if it had a name field, that would be different from that declared in this class. Only Foo has a name field, so you have to reassure the compiler that you really have a Foo object there. And you do that by casting.So, that is what the cast does. As you doubtless know, and it is on the other thread, you must verify that the Object passed is actually a Foo first. That is why you have the instanceof test first. Remember that instanceof returns false if its left operand is null, so the check for nullity becomes unnecessary. Now we have:-Always use the shortcircuit operators || and && because they will stop execution without evaluating their right operands. Otherwise there is the risk of a NullPointerException.
     
    Abhay Bhatt
    Ranch Hand
    Posts: 64
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Thanks again for the generous explanation. However, I have a few questions(again  )

    Campbell Ritchie wrote:Right: in the case of Object, there are two ways you can look at things:-
  • 1: There are no fields, so two Objects cannot be different from each other.
  • [list]2: In the absence of further information we cannot assume any two different objects are equal.


    Why you are saying that there are no fields? Wouldn't 'name' be a field of objects 'fa', 'fc' and 'fx'? 


    Bdw, I am just about to gain full clarity on this thread, and know with confidence, exactly what mistake did I make!     If only you(or anyone else) can answer the below question     (I'd really appreciate):-

    What I get from your comment, is that if, passing an object of class type(compile time object type) 'Test' into the equals method, I talk about in my original query/comment - the compiler would not allow me to access fields of class 'Test' through that object, inside the equals method.
    Am I correct in my understanding?
     
    Knute Snortum
    Sheriff
    Posts: 4281
    127
    Chrome Eclipse IDE Java Postgres Database VI Editor
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Abhay Bhatt wrote:What I get from your comment, is that if, passing an object of class type(compile time object type) 'Test' into the equals method, I talk about in my original query/comment - the compiler would not allow me to access fields of class 'Test' through that object, inside the equals method.
    Am I correct in my understanding?

    If I'm understanding you correctly, this statement is false.  You pass an object that is actually type Test into the equals() method, but the declared type is Object.  In order to look at the field(s) of the passed object, you need to cast it to Test.  Once the passed object is referenced by a variable declared as Test, you can access its fields.
     
    Junilu Lacar
    Sheriff
    Posts: 11494
    180
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Stated differently: 

    Given an equals() method declared as

    @Override
    public boolean equals(Object obj)
    { ... }


    Since the parameter obj is a reference variable of type Object, you must cast it to the object's actual type in order to access the methods and fields particular to that object's actual type. So, if the actual type of the object passed in is Test, then you must do this:

    Test other = (Test) obj;

    and use the other reference variable to access methods and fields that are particular to the Test class.

    It's similar to this example:

    The soldier in the story is the same Person all throughout but before he was given the green armband of a psy ops specialist, he could only be treated as a plain old soldier. Once he was labeled as a specialist, everyone could see him for what he really was.  Casting of an Object reference to a Test reference is like handing the Soldier a green armband.

    Does that make more sense?
     
    Campbell Ritchie
    Marshal
    Posts: 56570
    172
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Abhay Bhatt wrote:. . . Why you are saying that there are no fields? . . .
    I said, in the case of Object, which doesn't have fields. Not Foo.
     
    Abhay Bhatt
    Ranch Hand
    Posts: 64
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Thanks to all, for answering my question. 

    Now, I know what mistakes or wrong assumptions I was making, due to which my question was not making much sense. I am writing this, not just for others to read, but for myself also, to help me gain an understanding, if in case, I refer to this thread. 

    1. I was ignorant or didn't paid attention to the fact that the argument in the equals method was created as a 'Object' class type. In fact, it didn't struck my mind that there was any such class(I knew however, about the 'Object' class, but failed to note the relevance of that, in here). I was thinking of 'obj' as passed as or having a class type of some specific class - probably a direct parent or child to 'Test'. 

    2. My ignorance about the fact that 'downcasting' - as a symmetrical opposite of Upcasting, was essentially impossible in Java. That you cannot have an object with object type as parent, and class type as the child class.  

    So, the below statement stands wrong or invalid in my question:-

    So, the only case Line 13 becomes useful is when 'obj' has 'Test' as its run time object type and 'Test1' as its class type(where 'Test1' is the child class of 'Test').


    I think the correct statement would be:-

    "So, the only case Line 13 becomes useful is when 'obj' has 'Test' as both, its run time object type and class type."

    I'll be more than happy, to stand corrected, if I am wrong here. That also implies or means that there is no valid question or query out of this, so the first(upper) part of my whole question was invalid. 

     
    Damon McNeill
    Ranch Hand
    Posts: 67
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Junilu Lacar wrote:Your statements about casting to Test and about wierdness indicate that you don't fully understand how casting works.

    A cast does not change an object's type. A cast is merely a reassurance to the compiler that the thing you are casting is indeed compatible to the type that you're casting it to. It's like you guaranteeing to a creditor that your friend can indeed pay his loan off. The cast basically tells the compiler, "Trust me on this, I know you only know that this other thing is an Object but I've checked it out and can assure you it's actually an instance of Test."


    Yes, but if you try to cast an object to a type which it is not a member of, then you get the dreaded ClassCastException.
     
    Abhay Bhatt
    Ranch Hand
    Posts: 64
    2
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Sorry, I need to edit my above comment, but there is no option, so I am quoting. I think instead of below:-

    Abhay Bhatt wrote:
    I think the correct statement would be:-

    "So, the only case Line 13 becomes useful is when 'obj' has 'Test' as both, its run time object type and class type." 



    the correct statement would be:-

    "So, the only case Line 13 becomes useful is when 'obj' has 'Test' as its run time object type and 'Object' as its class type." 
     
    Junilu Lacar
    Sheriff
    Posts: 11494
    180
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Likes 1
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    Abhay Bhatt wrote:
    So, the below statement stands wrong or invalid in my question:-
    So, the only case Line 13 becomes useful is when 'obj' has 'Test' as its run time object type and 'Test1' as its class type(where 'Test1' is the child class of 'Test').

    I think the correct statement would be:-
    "So, the only case Line 13 becomes useful is when 'obj' has 'Test' as both, its run time object type and class type."

    I'll be more than happy, to stand corrected, if I am wrong here.

    It seems like you have the right idea. However, your phrasing is still not quite right so I'll give you the benefit of the doubt as to what you really mean and chalk it up to English not being your first language.

    Terms that are helpful in conveying the idea correctly are as follows:

    1. Actual type or runtime type - what an object really IS, i.e., the class that was instantiated.

    2. Declared type or reference type - what an object is treated AS, i.e., the type of the reference variable you're using to access the object.

    So, in the code:

    The reference variable obj has a declared type of Object. I think we're clear on that. The object that the variable obj refers to has an actual/runtime type of Test. Now, if you use the reference variable obj, you can only treat the actual object as an Object and nothing more. You won't get to see all the things that a Test object (the actual type) has to offer.

    When you do something like this later on:

    only then and only through the reference variable realThing can you get to all the capabilities of the actual Test object.

    When you're thinking about actual objects and object references in this context, you have to keep those two concepts separate. The reference variable is only a pointer to the actual object. The actual object never changes its type. Only the way you reference or treat that object can change and that is done through the declared type of a reference variable that you point at the actual object.
     
    Junilu Lacar
    Sheriff
    Posts: 11494
    180
    Android Debian Eclipse IDE IntelliJ IDE Java Linux Mac Spring Ubuntu
    • Mark post as helpful
    • send pies
    • Quote
    • Report post to moderator
    If my example of the Soldier/PsyOps Specialist wasn't clear enough, I'll give this one that's closer to home:

    Let's take you for example. I'll assume you're a guy. Given your name, I'll also assume you're originally from India or some neighboring country. I'll further assume that you're in the US and have some kind of residency status, either you're a naturalized citizen or green card holder or whatever.

    You will always be Abhay, regardless of whether others see you as just an Indian or a US Citizen/Green Card holder, right? Let's refer to the totality of you as "Abhay".  That means when I see you as Abhay, I can see that you're a person, a CodeRanch member in good standing, an Indian, a US resident, and everything else that IS *you*.  "Abhay" is your runtime type and when I see as Abhay, I get access to all that you are and everything that you can do.

    Now, if you go out of the country, say go on vacation in India for a few weeks, when you get to the Indian immigration checkpoint, they'll probably think you're an Indian national, right? So, in the immigration officer's mind, he has labeled you as "An Indian National" so he tells you go to the queue for Indian citizens. The immigration officer has just cast you to the type "Indian National", i.e., he has mentally done this: IndianNational thisGuy = (IndianNational) passenger.  Your runtime type still remains Abhay though, regardless of how the Indian immigration officer has labeled you. Since that's his label for you, that's the only way the Indian immigration officer will treat you as. If you had dual citizenship or were still actually an Indian citizen, then you'd be fine, show the officer your travel documents, and be on your way.

    Coming back to the US, you'll run into a similar scenario with the US Immigration officials. However, if you actually had US citizenship and were traveling with a US Passport and the immigration officer treated you like a foreign national, you'd probably try to correct him, right? The US immigration officer has labeled you and treated you as a foreign national but that's not what you actually are, so you bring that to the officer's attention. That's kind of like you raising a ClassCastException. You show the officer your US passport or Green Card so the officer will treat you as a legal US resident and everything will be resolved and you can get through immigration without any further incident.

    All throughout this, you are the same Abhay that is the totality of you. However, different people will reference you as something specific to the context. When you get home to your wife or other family, they will again see you for who your runtime type, Abhay, the guy, the programmer, CodeRanch member, the everything else that Abhay is.

    Does that make more sense now?
     
    • Post Reply Bookmark Topic Watch Topic
    • New Topic
    Boost this thread!