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
orButterfly
) 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.
This allows 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
toCity
, and - the recursive call to
equals
(because the attributename
has typeString
).
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.