2

Why is the type of the object checked and afterwards a new object created via type-cast?

Can someone provide an example, why it is done in the shown way?

Please see my comments in the snippet.

 @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }
 
        // After this check I know for sure that the object is an instance of Complex.
        if (!(o instanceof Complex)) {
            return false;
        }
        
        // Why is this cast necessary? I know (already) that is of type Complex. So I has all members Complex has.
        Complex c = (Complex) o;
       
        return Double.compare(re, c.re) == 0
                && Double.compare(im, c.im) == 0;
    }
cluster1
  • 4,968
  • 6
  • 32
  • 49
  • 1
    In the second if-statement `if (!(o instanceof Complex))`, you are checking that the input `o` is NOT an instance of the class `Complex`. Thus, you are sure about its type only after passing that condition, and then you can cast `o`. – Federico Pellegatta Sep 20 '22 at 07:21
  • *Why is this cast necessary?* - take out the cast and you will see that it does not compile. – Scary Wombat Sep 20 '22 at 07:21
  • 1
    "*new object created via type-cast*" - is wrong, there is no new object being created, actually nothing is changed at runtime, you are just *telling* the compiler that the instance referenced by `o` is actually an instance of ``Complex` (the compiler will not really *believe* you and introduce code to check if it is an instance of `Complex` (and throw an ClassCastException if not) - but the instance is not changed at all – user16320675 Sep 20 '22 at 07:32

4 Answers4

2

Just to be clear in your question you are asking two things: Why is the type of the object checked and afterwards a new object created via type-cast?

You do not create new object after the type cast. See to create new object you must use new keyword in Java. In your row there is not:

Complex c = (Complex) o;

In this case you simply cast one class to another. You do not change the object just the Java compiler knows that you are using another class.

The second question is inside your comment and it is related to: Why is this cast necessary? I know (already) that is of type Complex. So I has all members Complex has

Yes in older version of Java you need to additionally cast as you did.

From Java 14 on you can make instanceof and cast together. For more information look here:

https://openjdk.org/jeps/305

From the spec this is the design:

if (obj instanceof String s) {
     // can use s here
} else {
     // can't use s here
}
Level_Up
  • 789
  • 5
  • 19
1

Because the Java compiler simply does not go that far in optimizing your code. I would assume it is possible to implement such an optimization (although it sounds easier said than done, it probably needs a good amount of work to make o count as Complex after the if check), but javac has been always built around as little optimizations as possible.

M A
  • 71,713
  • 13
  • 134
  • 174
1

People indeed found it unreasonable to need a explicit cast. And came up with the following "improvement." But this still is just syntactic suggar. From Object to Complex would need a cast. That at compile time this is clear, does not promote an Object variable to a Complex variable, as in some other languages.

In this case there is a weird usage:

    if (!(o instanceof Complex c)) {
        return false;
    }       
    // c known here, weird as it is.
    ...

One would only expect @Level_Up's solution.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
1

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.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157