Overriding Equals in Java - confused about implementation - java

I've been doing the MOOC Java programming course and they typically override equals like this:
If addresses of the 2 objects are the same = return true.
If compared object is not an instance of the same class as the first object = return false.
Convert the object to the same type as the one being compared to
Compare the variables of each object - returns true or false
I'm just a bit confused about step 2 and 3.
Step 2 checks if the 2nd object is an instance of the same type as the one its compared to...
If it isn't, the method ignores the rest of the code and returns false
If it is, we convert it to that Class. So at this point i'm confused about why we need to convert an object that is already an instance of the same class - to that class? If Java recognises it is of the same type, why do you need to convert it?
*Example below:
public boolean equals(Object comparedObject) {
// if the variables are located in the same place, they're the same
if (this == comparedObject) {
return true;
}
// if comparedObject is not of type Book, the objects aren't the same **
if (!(comparedObject instanceof Book)) {
return false;
}
// convert the object to a Book object
Book comparedBook = (Book) comparedObject;**
// if the instance variables of the objects are the same, so are the objects
if (this.name.equals(comparedBook.name) &&
this.published == comparedBook.published &&
this.content.equals(comparedBook.content)) {
return true;
}
// otherwise, the objects aren't the same
return false;
}

Because you’ve only compared the dynamic type; the static type of comparedObject is unchanged: it’s Object, and you cannot access its Book members (think about it: at this point you know that the dynamic type of comparedObject is Book; but the Java compiler still sees it with its declared type). To make the compiler understand that the object type is Book, you first need to create an object whose static type is Book, and to do that you need a cast.
The static type information is what the Java compiler sees, and which is what the compiler uses to check whether your code is statically correct.

Java is statically typed, so if the variable is declared as Object it will not let you access any fields or call any methods of Book. In this case, it is easily proven that the object can only be a Book, but the compiler does not make any attempt to prove it. (And, in a more complicated example, it couldn't.)
In the newest versions of Java, there is a shortcut you can take with the syntax:
if (comparedObject instanceof Book book) {
// do whatever with book
return this.title.equals(book.title) && this.author.equals(book.author);
}
But in earlier versions, you simply have to downcast the instance:
if (comparedObject instanceof Book) {
Book book = (Book) comparedObject;
return this.title.equals(book.title) && this.author.equals(book.author);
}

Related

In this example, why is it still necessary to typecast an object to be sure of its type, even after getClass() has been called?

I'm following this MOOC on OOP in Java and it has presented an example I don't fully understand. In the example, a Book class has been created and they are constructing an 'equals' override method for the Book class. The new equals() method takes a parameter of any object. It returns true if that parameter is a Book object with the same name and publishingYear as this Book object. Because the parameter object could be any object of the Object class, before calling the getPublishingYear() and getName() methods, which would throw an error if called on an object not in the Book class, the equals() method checks to make sure it is indeed dealing with a Book object through this code:
if (getClass() != object.getClass()) {
return false;
}
This much (I think) I understand. What I don't understand is why, after the code above, they then cast the parameter object into a Book and then call getPublishingYear() and getName() on the newly casted object instead of the original:
Book compared = (Book) object;
if (this.publishingYear != compared.getPublishingYear()) {
return false;
}
if (this.name == null || !this.name.equals(compared.getName())) {
return false;
}
return true;
I don't get why this step is necessary when the method should already have returned false if the object was not of type Book due to the getClass() check above. I tried compiling without this extra casting step and found that the step is indeed necessary - the compiler gives a 'cannot find symbol' error on getPublishingYear() and getName() if you do not include this step. So what am I missing?
The equals() method in full:
#Override
public boolean equals(Object object) {
if (object == null) {
return false;
}
System.out.println(getClass());
if (getClass() != object.getClass()) {
return false;
}
Book compared = (Book) object;
if (this.publishingYear != compared.getPublishingYear()) {
return false;
}
if (this.name == null || !this.name.equals(compared.getName())) {
return false;
}
return true;
}
Because the compiler cannot (is forbidden to) infer that your previous type check was done for the purpose of eliminating improper object types before casting.E.g., sometimes less is more: what if in the next statements you only need to cast your object to one of the base classes or implemented interfaces?
interface Authored {
public getAuthorName();
}
interface Publication {
public String getISBN();
};
public class Book
implements Authored, Publication {
// ...
public boolean equals(Object other) {
if (other == null) {
return false;
}
if (getClass() != other.getClass()) {
return false;
}
// on purpose, we use only the Publication interface
// because we want to avoid potential misspelling or missing
// title, author name, etc. impacting on equality check
// We'll consider ISBN as a key unique enough to
// determine if two books (e.g. registered in different
// libraries) are the same
Publication p=(Publication)other;
return this.getISBN().equals(p.getISBN());
}
}
Casting a variable to a data type basically is telling the compiler "Trust me, I know what I'm doing." In the case of casting an "Object" to "Book", you are assuring the compiler that the object is, in fact, a Book and to proceed accordingly. It also as the effect of forcing you to believe you know what you are doing.
Edit: I'm not sure if you are asking why you needed the actual cast (adding "(Book)") or why you needed to make the assignment to a Book variable. I answered the former. If the question is the latter, the answer is you need the Book variable so the Book methods are available.
Since you have the tools to make the determination, you would think that the cast is not necessary since the compiler is capable of generating the same code you use to make the determination, requiring you to just make the assignment:
Book book = object; // Wrong
instead of
Book book = (Book) object; // Right
If you expect the compiler to "just know" that the Object is a Book, then the compiler will have to test the Object each time you use a Book method. By explicitly telling it in the assignment, the compiler can create code specific to the Book class without any further examination of the Object.
You have to cast your Object-type object to Book, because you want to use getPublishingYear() and getName() functions which are specific to the Book class. The Object class does not have such methods.
after
if (getClass() != object.getClass()) {
return false;
}
you can be sure your object is a book, but java still does not know it
so you can savely tell it to try the casting with
Book compared = (Book) object;

Java getClass and super classes

public boolean equals(Object o) {
if (this == o)
return true;
if ((o == null) || (this.getClass() != o.getClass()))
return false;
else {
AlunoTE umAluno = (AlunoTE) o;
return(this.nomeEmpresa.equals(umAluno.getNomeEmpresa()) && super.equals(umAluno);
}
}
Could anyone explain me how the fourth line ((this.getClass() != o.getClass())) works when the argument is a super class? Because the classes have different names. this.getClass will return a different name than o.getClass, right?
Check the following code snippet which answers your question. Object O can hold any object. o.getClass() will return the run time class of the object
public class Main {
void method(Object o) {
System.out.println(this.getClass() == o.getClass());
}
public static void main(String[] args) {
new Main().method(new Object()); // false
new Main().method(new Main()); // true
new Main().method(new String()); // false
new Main().method(new MainOne()); // false
}
}
class MainOne extends Main
{
}
So lets say there are two classes. Class A and Class B. Class A is a super class of class B. So this method would be in class B. "this.getClass()" refers to an object of class B while o.getClass()(The super class) will refer to class A. So Class B will not equal Class A. Meaning it will go into the if statement.
Suppose your super class is SHAPE and you have a class RECT that is a subclass of SHAPE.
If the this variable is for a RECT and the Object o is also a RECT,
then line 4 will return true because they are the same class (RECT).
The two objects will be equal as long as their types are the same at runtime.
However, if Object o is of type SQUARE, which also subclasses SHAPE,
(and could even subclass RECT).
then it will not be equal to the this pointer (RECT),
because their classes are different at runtime.
Now for why this kind of type checking is bad in the equals method (specifically for the use case of Hibernate entity classes).
If you use Hibernate and you are checking a newly created object whose class type is RECT against an object whose class type was RECT at the time it was cached in Hibernate, the class of the object in the cache will actually be a sub-class of type RECT, because Hibernate does byte-code manipulation and wraps the objects in a synthetic sub-class (RECT_$$javassist).
This means that your Hibernate cached objects that you expect to be equal will never be equal.
If the object is in a child collection, Hibernate will assume you wanted to delete the old object from the collection and create the new object in the collection instead of doing a (potential) update on an existing object in the collection.
We have legacy code that did this and could never figure out why (until now) it kept doing deletes and re-inserts on our collection.
For Hibernate entity objects you should use the instanceof operator to determine if two objects could be equal - and then cast Object o and continue the comparison operation with class SHAPE specific fields.
If your subclasses should not be considered equal, then you will have to implement equals() in each subclass to check for instanceof.
For other use cases, you will have to determine if there is a chance that someone (or some other library) could sub-class your Class (even through byte code manipulation) and whether any sub-classes should still be considered equal or not.
For instance, if you do any kind of mocking in your unit tests, a bad equals method may cause otherwise equal objects to be non-equal due to their classes not being equal.
Back to the OP's code. A better way to code the equals method would be:
public boolean equals(Object o) {
if (this == o)
return true;
if (!(o instanceof AlunoTE))
return false;
AlunoTE umAluno = (AlunoTE) o;
return(this.nomeEmpresa.equals(umAluno.getNomeEmpresa()) && super.equals(umAluno);
}
Because the instanceof operator always returns false for null, there is no need for a null check too.

Inheritance and 'Instanceof' testing result

I'm studying 'instanceof' java, but I couldn't understand 'instanceof' clearly, I thought below answer would be true and false, but result is both true. Could you explain why this result happen? As I know, when A is child of B (Parent), and a instanceof B is 'false' but result is different with what I thought.
class Car{
String color;
int door;
}
class FireEngine extends Car{
void water(){
System.out.println("water");
}
}
public class Operator {
public static void main(String[] args) {
Car car = new FireEngine();
FireEngine fireCar = new FireEngine();
System.out.println(car instanceof FireEngine);
System.out.println(fireCar instanceof Car);
}
}
Declaration != Value
You declare car as an Car, but the Value is an FireEngine.
instanceof works based on values, not on the declarations of their variables!!!
Shortening may help to understand:
System.out.println(new FireEngine() instanceof FireEngine); // true
System.out.println(new FireEngine() instanceof Car); // true
The output of instanceof depends on the runtime type of the variable whose type you are testing. The compile time type of the variable doesn't matter (as long as it is possible that x instanceof Y will return true for some value of x, otherwise the expression won't pass compilation).
Both car and fireCar hold instances of FireEngine in your code. And since FireEngine is a kind of a Car, both car and fireCar are instanceof both Car and FireEngine, so your code prints true and true.
Implementation of the Instanceof operator. Returns a Boolean if the Object parameter (which can be an expression) is an instance of a class type.
Input 1: An object or Expression returning an object.
Input 2: A Class or an Expression returning a Class
Returns: A Boolean that is the result of testing the object against the Class.
For more information please go throught the javadocs # http://docs.oracle.com/cd/E13155_01/wlp/docs103/javadoc/com/bea/p13n/expression/operator/Instanceof.html
For more detailed explanation with examples please go through the following web page : http://mindprod.com/jgloss/instanceof.html
In Java there are two types of bindings: static (the reference type)
and dynamic (the object type).
In your case:
Car car = new FireEngine();
Car is the static type and FireEngine is dynamic type. It means, you are actually working with a FireEngine (the object type). You can imagine Car as a special pointer with a car shape pointing to the real object that is your awesome FireEngine. If you read 'instanceof' you can understand it, this method tell you if an object is an instance of a class, not the reference. So the compiler will see: FireEngine (car) instanceOf FireEngine? Of course, let's return a true!
You can have a look to this post also: What is the 'instanceof' operator used for?
The statement
As I know, when A is child of B (Parent),
and a instanceof B is 'false' but result is different with what I thought.
is not correct. instanceof does not check for the child, it tests for the parent.

calling specific methods of generic types in ny equals() method

I am working on the equals() method for my sparse matrix class that I'm writing (part of a school project). I am constantly runnung into the issue that it won't let me use any methods or other members specific to my class, because that(the name I used for the parameter to equals) has to be of the generic type Object in order to override Objects's equals() method. Even beyond that, I need to be able to use some of the methods of my SparseMatrix's type parameter in order to really compare the equality, unless I can also figure ou. How can I write it to get around that obstacle?
I have a few ideas how I night do it, but none of them seem to work: I've triedcasting the parameter, I've tried overlading equals(), I've even tried some other stuff, but none of it seems to work.
This is what I have so far, but, as I said, I just can't get it to work.
public boolean equals(Object that) {
if (that instanceof SparseMatrix<?>) {
if (this.xSize != that.xSize ||
this.ySize != that.ySize)
return false;
/* make some more comparisons that depend on specific
* members of my matrix class, etc...*/
}
return false;
}
I have tried searching SO for this, and while I was able to find a few that seemed to be asking the same thing, I couldn't find any answers that actually explained how to do it.
When you have an object of a base class and you know which kind of subclass it is, you can convert it by downcasting it. Then you can call any methods specific to the subclass.
public boolean equals(Object o) {
if (o instanceof SparseMatrix<?>) {
SparseMatrix that = (SparseMatrix)o; // <-- downcast
if (this.xSize != that.xSize ||
this.ySize != that.ySize)
return false;
}
return false;
}
Should o not be an instance of SparseMatrix or a class which extends/implements SparseMatrix (you already checked that, but let's assume you didn't) you would get a ClassCastException.
public boolean equals(Object that) {
if (that !=null && that instanceof SparseMatrix<?>) {
SparseMatrix that = (SparshMatrix)o;
if (this.xSize != that.xSize ||
this.ySize != that.ySize)
return false;
}
return false;
}
Added Code to check for null value.
You need to have this method overrided in the class whose instances you want to compare.. And when you actually invoke equals on instances of that class, this overrided method will be invoked..
** Moved from comment: - Using instanceof you can only assure of what type your instance is, and not the parameterized type of that instance.. (You cannot findout whether you have ArrayList<String>, what you can find out is that it is ArrayList or ArrayList<?> to be precise) .. Reason for that is, generic type information is only available at compile time, and is erased through type erasure , so they are not available at runtime..

Why can't a "Class" variable be passed to instanceof?

Why doesn't this code compile?
public boolean isOf(Class clazz, Object obj){
if(obj instanceof clazz){
return true;
}else{
return false;
}
}
Why I can't pass a class variable to instanceof?
The instanceof operator works on reference types, like Integer, and not on objects, like new Integer(213). You probably want something like
clazz.isInstance(obj)
Side note: your code will be more concise if you write
public boolean isOf(Class clazz, Object obj){
return clazz.isInstance(obj)
}
Not really sure if you need a method anymore ,though.
instanceof can be used only with explicit class names (stated at compile time). In order to do a runtime check, you should do:
clazz.isInstance(obj)
This has a small advantage over clazz.isAssignableFrom(..) since it deals with the case obj == null better.
As others have mentioned, you cannot pass a class variable to instanceof because a class variable references an instance of an Object, while the right hand of instanceof has to be a type. That is, instanceof does not mean "y is an instance of Object x", it means "y is an instance of type X". In case you don't know the difference between an Object and a type, consider:
Object o = new Object();
Here, the type is Object, and o is a reference to the instance of the Object with that type. Thus:
if(o instanceof Object)
is valid but
if(o instanceof o)
is not because o on the right hand side is an Object, not a type.
More specific to your case, a class instance is not a type, it is an Object (which is created for you by the JVM). In your method, Class is a type, but clazz is an Object (well, a reference to an Object)
What you need is an way to compare an Object to a Class Object. It turns out that this is popular so this is provided to you as a method of the Class Object: isInstance().
Here is the Java Doc for isInstance, which explains this better:
public boolean isInstance(Object obj)
Determines if the specified Object is assignment-compatible with the
object represented by this Class. This method is the dynamic
equivalent of the Java language instanceof operator. The method
returns true if the specified Object argument is non-null and can be
cast to the reference type represented by this Class object without
raising a ClassCastException. It returns false otherwise.
Specifically, if this Class object represents a declared class, this
method returns true if the specified Object argument is an instance of
the represented class (or of any of its subclasses); it returns false
otherwise. If this Class object represents an array class, this method
returns true if the specified Object argument can be converted to an
object of the array class by an identity conversion or by a widening
reference conversion; it returns false otherwise. If this Class object
represents an interface, this method returns true if the class or any
superclass of the specified Object argument implements this interface;
it returns false otherwise. If this Class object represents a
primitive type, this method returns false.
Parameters: obj - the object to check
Returns: true if obj is an instance of this class
Since: JDK1.1
Firstly, instanceof requires that the operand on the right is an actual class (e.g. obj instanceof Object or obj instanceof Integer) and not a variable of type Class. Secondly, you have made a fairly common newbie mistake that you really should not do... the following pattern:
if ( conditional_expression ){
return true;
} else{
return false;
}
The above can be refactored into:
return conditional_expression;
You should always perform that refactoring, as it eliminates a redundant if...else statement. Similarly, the expression return conditional_expression ? true : false; is refactorable to the same result.

Categories

Resources