Cast and Equality #
Downcast #
Java (as well as C# and C++) lets us change locally the type $c$ that the compiler associates to an object, using instead a subtype of $c$.
This is called a downcast.
Example. Consider the following classes:
In the code below, the operation
(Unicorn) unitis a downcast.It lets us call the instance method
regenof the classUnicornwith aUnit.Unit[] units = getUnits(); for (Unit unit: units){ ((Unicorn) unit).regen(); }
Warning. The cast in this example is not safe, because the array may contain butterflies. This would cause a
ClassCastExceptionat runtime.
Syntax. The keyword
instanceoflets us check whether a downcast is safe (or alternatively the methodgetClass()of the classObject, as illustrated below).
Example(continued). We can use
instanceofto check whether aUnitcan be downcast to aUnicorn, as follows:Unit[] units = getUnits(); for (Unit unit: units){ if(unit instanceof Unicorn){ ((Unicorn) unit).regen(); } }
Object equality #
As we saw in a previous chapter, a constructor in Java creates an object in memory and returns a 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
Citythat we saw earlier.public class City { String name; int zipCode; public City(String name, int zipCode){ this.name = name; this.zipCode = zipCode; } }The following outputs
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, 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.
If we want another behavior, we need to override it.
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 usage of the method
getClass()(inherited from the classObject) to retrieve the classes associated withoand the current object, - the (safe) cast from
ObjecttoCity, and - the recursive call to
equals(because the attributenamehas typeString).
Recursion #
Warning. Similarly to what we saw with the method toString, beware of naive (recursive) implementations of
equalsif 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.