Incremental Java
Implementing equals() using instanceof

equals()

In Java, == does handle equality (if it's comparing two objects). Two handles are considered equal if they are holding the same balloon.

However, this kind of equality is too strong for certain situations. For example, consider:

Rectangle one = new Rectangle( 3, 5 ) ;
Rectangle two = new Rectangle( 3, 5 ) ;
if ( one == two )
   System.out.println( "SAME" ) ;
else
   System.out.println( "DIFFERENT" ) ;
What does it print? It prints "DIFFERENT". That's because there are two balloons (objects). one has one handle to a Rectangle object. one has a handle to a different Rectangle object. These two objects have the same width and height, but they are still two separate objects.

Here's a real world example. You go to a store. You buy two of the same candy bars. While they are similar, they are still distinct candy bars. If you eat one, the other isn't eaten. In Java, == checks for exactly the same object, not for same values.

== is often called reference equality or handle equality. If we just compare width and height and check that they are the same, then we are doing value equality. In general, equals() should do value equality.

However, Java has no idea what it means for objects that you've written to be considered "equal". The default behavior for equals() is handle equality, i.e., ==.

Overriding equals()

You need to override equals() if you want to use it for value equality. The method signature for equals() is:
public boolean equals( Object obj ) ;
The problem is the parameter, obj. We expect a Rectangle object to be passed. Inheritance allows the object use to pass a Rectangle object to an Object parameter, since Rectangle is a descendant class of Object.

But even if obj is a handle to a Rectangle object, we still need to dynamic cast obj to get back the Rectangle.

instanceof

Of course, objects of any type can be passed to equals(). We shouldn't cast to Rectangle without checking if we really have a Rectangle. If it isn't a Rectangle, an error (called an exception) occurs, and the program quits.

Java has an operator called instanceof which checks if an object has a certain type. This operator takes an object as the left operand and a type as the right operand, and evaluates to true if the object has the type of the right operand, and false otherwise.

Object Has Many Types

Because of inheritance and interfaces, an object can have many types. For example, suppose you have an object called bob of type Human. Human is a child class of Mammal. Mammal is a child class of Animal. Animal is a subclass of Object. Human may also implement an interface Walkable.

So bob has type Human, Mammal, Animal, Object, and Walkable. Its most specific type is Human, but nevertheless, you could say that it is all of these types. Thus, you can create variables of any of these types and can assign bob to such a variable.

instanceof

instanceof can check if bob has all these types.
public static void main( String [] args )
{
   Human bob = new Human() ;
   if ( bob instanceof Human )
     System.out.println( "bob is an instance of a Human" ) ;
   if ( bob instanceof Mammal )
     System.out.println( "bob is an instance of a Mammal" ) ;
   if ( bob instanceof Animal )
     System.out.println( "bob is an instance of a Animal" ) ;
   if ( bob instanceof Object )
     System.out.println( "bob is an instance of a Object" ) ;
   if ( bob instanceof Walkable )
     System.out.println( "bob is an instance of a Walkable" ) ;
}
All five println() statements print out.

We can use this to check if an Object variable is an instanceof a Rectangle. If it is, then we can safely cast it to a Rectangle.

Implementing equals()

Remember our goal: we wish to implement equals() for Rectangle which gives us value equality. Specifically, two Rectangle objects should be considered equal if their widths and heights are the same.

This is easy to implement now that we can use instanceof.

// Overrides Object equals()
public boolean equals( Object obj )  
{
   if ( obj instanceof Rectangle )
   {
      // Dynamic cast obj to Rectangle
      // We're guaranteed success, because of the 
      // instanceof check
      Rectangle rect = (Rectangle) obj ;
      return rect.width == width && rect.height == height ;
   }
   else // obj is not a Rectangle, so return false
   {
      return false ;
   }
}
Notice that we do a dynamic cast and save obj to a Rectangle variable. In the if body, both rect and obj have a handle to a Rectangle object. However, you can only call Object methods with obj while you can call Rectangle methods with rect. We need to access Rectangle instance variables, so that's why we save obj to a Rectangle object.

With instanceof, we can check that obj is a Rectangle object, so that doing the dynamic cast (see code above) doesn't fail.

When you have an if-else, and you don't have a return statement afterwards, then you must have a return statement in both the if body and the else body. The compiler complains if there is a possible "path" (a sequence of execution of statements) that doesn't end in a return statement.