Why is the type of the object checked and afterwards a new object created via type-cast?
No new object is created.
In Java, objects are accessed only via references; they cannot be accessed directly. References have types, whereas objects have classes. The type of a non-null
reference will always be a supertype of the class of the object to which it refers, but often the reference's type is not the exact class of the object.
// Why is this cast necessary? I know (already) that is of type Complex. So I has all members Complex has.
Java performs compile-time checking of object accesses based on the types of the references through which the accesses are performed. Only the fields and methods defined for the type of a reference are accessible via that reference, even though the object to which it refers may be of a class that provides other fields and methods as well. So yes, you have all the methods and fields of class Complex
, but you need to cast to access them.
Thus, in the equality-testing idiom you present, the cast is performed to obtain access to the fields that belong to objects of class Complex
but not necessarily to objects of other classes. This does not create a new object, but rather a new reference to the same object. Neither does the declaration of variable c
create any objects. It gives you local storage for a reference and a name for the reference stored there. Afterward, c
and o
refer to the same object.
Can someone provide an example, why it is done in the shown way?
The cast is necessary, as already discussed, but the instanceof
test is not, because Java checks casts at runtime anyway. But an incompatible cast will elicit a ClassCastException
, and the contract of equals(Object)
specifies that the method should return false
under those circumstances, not throw an exception. Therefore, this would be an alternative:
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
try {
// The casts are necessary for accessing members re and im
// defined by class Complex:
return Double.compare(re, ((Complex)o).re) == 0
&& Double.compare(im, ((Complex)o).im) == 0;
} catch (ClassCastException e) {
return false;
}
}
Is that better than the original? Not to my eye. Exception handling adds extra cognitive load, and now you have to cast twice, inline. And Java casts have a runtime cost, so if you're lucky, the compiler will optimize that pair of casts to an equivalent of the original code's ...
Complex c = (Complex) o;
... anyway.
Or maybe, then, we can manually ensure that the cast is done only once instead of leaving that to the compiler or JIT:
@Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
Complex c;
try {
c = (Complex) o;
} catch (ClassCastException e) {
return false;
}
return Double.compare(re, c.re) == 0
&& Double.compare(im, c.im) == 0;
}
Is that better than the original? Again, not to my eye. We still have the cognitive load (and runtime cost) of exception handling, whereas the instanceof
check that the original code uses to avoid that is simple and natural in context.