Cast and equality

Cast and Equality #

Cast #

Java (as well as C# and C++) provides mechanisms to change the type associated with an object $o$, using either a supertype of $o$ (this is an upcast), or a subtype of $o$ (this is a downcast).

Upcast #

Explicit upcasts are uncommon, but may still be useful in some scenarios, e.g. to disambiguate two method calls.

Implicit upcasts on the other hand are very frequent, when the type of an object cannot be determined at compile time.

Example. Consider the following classes:

Now consider the following program, where the method getUnits produces an array of units that depends on the player’s input. The type of objects in this array (Unicorn or Butterfly) cannot be determined at compile time. Thanks to the implicit upcast, they can nonetheless be referred to as (underspecified) units.

Unit[] units = getUnits();
for (Unit unit: units){
    ...
}

Downcast #

Downcasting in Java is frequent for objects whose type cannot be determined at compile time. However, this may not be safe. For instance, in the above example, downcasting a unit from Unit to Unicorn may cause a ClassCastException (at runtime), because this unit is a instance of Butterfly. This is why downcasting is often used in combination with the instanceof operator. For instance the above example can be modifed as follows:

Unit[] units = getUnits();
for (Unit unit: units){
    if(unit instanceof Unicorn){
        ((Unicorn) unit).regen();
    }
}

Here the operation (Unicorn) unit is a downcast. The output of this operation has type Unicorn, thus allowing the call to the method regen.

Object equality #

As we saw in a previous chapter, a constructor in Java creates an object in memory and returns a (fresh) reference to that object. Since two objects have different locations in memory, their respective references must differ, even if the content of the two objects is identical.

Example. Consider (a simplified version of) the class City that we saw earlier.

 public class City {

    String name;
    int zipCode;

    public City(String name, int zipCode){
        this.name = name;
        this.zipCode = zipcode;
    }
 }

The following program will output false:

City city1 = new City("Florence", 50100);
City city2 = new City("Florence", 50100);
System.out.println(city1 == city2);

However, in some scenarios, it may be useful to compare the content of two objects, rather than their references. Java provides a native method called equals for this purpose.

Like the method toString that we saw earlier, the equals is an instance method of the native Java class Object, which is an (implicit) superclass of every other class. So every (user-defined of native) class inherits equals.

Here is the source code of Object.equals:

public boolean equals(Object obj) {
    return (this == obj);
}

In other words, by default, this method behaves like the == operator. In order to use this method to check whether two objects are equal, it has to be overriden.

For instance, here is a prototypical implementation of the method equals within our class City:

@Override
public boolean equals(Object o) {
    if (this == o) { // same reference
        return true;
    }
    if (o == null || getClass() != o.getClass())
        return false; // o is null or has a different type
    }
    City downcastObject = (City) o;
    return zipCode == downcastObject.zipCode &&
        name.equals(downcastObject.name);
}

Hint. Your IDE can generate such a method.

Note in this example:

  • the (safe) downcast from Object to City, and
  • the recursive call to equals (because the attribute name has type String).

Recursion #

Warning. Similarly to what we saw with the method toString, beware of naive (recursive) implementations of equals if your program can create an object that refers to itself (directly or indirectly).

Built-in implementations #

Several native Java classes have their own implementation of equals. We will encounter several of them during this course, notably for the class String and for the implementations of the interface Set.

The method hashCode #

The method equals is usually overridden together with another method of the class Object, called hashCode. In particular, this is needed for the method equals of the class HashSet to behave correctly. We will explore this topic later in this course, when we introduce the notion of a hash table.