Why cast after an instanceOf?
Asked Answered
S

12

45

In the example below (from my coursepack), we want to give to the Square instance c1 the reference of some other object p1, but only if those 2 are of compatible types.

if (p1 instanceof Square) {c1 = (Square) p1;}

What I don't understand here is that we first check that p1 is indeed a Square, and then we still cast it. If it's a Square, why cast?

I suspect the answer lies in the distinction between apparent and actual types, but I'm confused nonetheless...

Edit:
How would the compiler deal with:

if (p1 instanceof Square) {c1 = p1;}

Edit2:
Is the issue that instanceof checks for the actual type rather than the apparent type? And then that the cast changes the apparent type?

Sheff answered 15/11, 2010 at 16:12 Comment(3)
That's why he's asking a question delnan...Chem
Regarding the question in your edit, why not just try to compile it yourself? You don't need the SO community to act as a compiler for you.Outgrowth
@Mark Peters - point well taken, my interest is not really what would happen, but more how differently the compiler would parse that.Sheff
L
18

Keep in mind, you could always assign an instance of Square to a type higher up the inheritance chain. You may then want to cast the less specific type to the more specific type, in which case you need to be sure that your cast is valid:

Object p1 = new Square();
Square c1;

if(p1 instanceof Square)
    c1 = (Square) p1;
Lucianolucias answered 15/11, 2010 at 16:15 Comment(2)
That was what I didn't understand, thanks. I did a 2nd edit on my question to focus on that.Sheff
Would this compile without instanceof?Mussolini
D
40

Old code will not work correctly

The implied cast feature is justified after all but we have trouble to implement this FR to java because of backward-compatibility.

See this:

public class A {
    public static void draw(Square s){...} // with implied cast
    public static void draw(Object o){...} // without implied cast
    public static void main(String[] args) {
        final Object foo = new Square();
        if (foo instanceof Square) {
            draw(foo);
        }
    }
}

The current JDK would compile the usage of the second declared method. If we implement this FR in java, it would compile to use the first method!

🔴 JDK 14

We finally implemented this feature in JDK 14. As you might have noticed you can declare a new variable within the instanceof-linkage. This new variable has been defined by the value of a automatically downcast to the specified type.

if (any instanceof String s) {
  System.out.println(s);
}
Dialectal answered 9/8, 2015 at 9:28 Comment(5)
It is very seldom situation and it can be solved. The decision - what method to call - is done during the compilation. I would suggest to enforce the cast in this case and only in this case.Ferdy
We had this kind of issue when migrating to Java 8 (due to type inference) and it was really a pain. You cannot easily track it as the code still compiles, but behaves differently – we even had a case of infinite recursion because of this. In the end we had to change hundreds of method calls.Lice
What do you mean by "FR"? Also, by "impield" (which you typed 3 times), do you mean "implied"?Perry
"FR" means feature-request.Dialectal
If I were you I would make draw(Object o ) delegates to draw(Square s) after checking using instance of and cast it : if(o instanceof Square) draw ((Square) o);,anyway the example is a bit far away of OOD and OOP.Gupta
L
18

Keep in mind, you could always assign an instance of Square to a type higher up the inheritance chain. You may then want to cast the less specific type to the more specific type, in which case you need to be sure that your cast is valid:

Object p1 = new Square();
Square c1;

if(p1 instanceof Square)
    c1 = (Square) p1;
Lucianolucias answered 15/11, 2010 at 16:15 Comment(2)
That was what I didn't understand, thanks. I did a 2nd edit on my question to focus on that.Sheff
Would this compile without instanceof?Mussolini
V
14

The compiler does not infer that since you are in the block, you have done a successful check for the type of the object. An explicit cast is still required to tell the compiler that you wish to reference the object as a different type.

if (p1 instanceof Square) {
    // if we are in here, we (programmer) know it's an instance of Square
    // Here, we explicitly tell the compiler that p1 is a Square
    c1 = (Square) p1;
}

In C# you can do the check and the cast in 1 call:

c1 = p1 as Square;

This will cast p1 to a Square, and if the cast fails, c1 will be set to null.

Vargo answered 15/11, 2010 at 16:15 Comment(8)
well, I think the compiler can know that it is a Square.Edee
@Bozho, what do you mean? Not the current compiler. But I suppose it is possible.Vargo
well, from your answer it appeared that the compiler can't possibly know this. But it can, it's just not implemented yet (hence my answer)Edee
It could be possible theoretically, but would not respect Java syntax and thus is not permitted by the compiler. Anyway since generics are in the language I'm not sure it is really a good thing to use instanceof. It is better to manipulate only well-defined interfaces.Hahn
Yes, it's clearer. But I understood the question as - "I know a cast is needed, but wasn't it possible to avoid this"Edee
@Bozho, well, there is a difference between "possible now" and "perhaps possible someday". I answered the first question.Vargo
He didn't ask whether it's possible :) he asked why is he required to cast. And I think the answer is - because it's not yet implemented :)Edee
Or in C#, if (p1 is Square s) { /* s is a non-null instance of Square, within this scope */ }Androecium
V
13

Just to provide an update on this, Java 14 now provides pattern matching for instanceof, this allows you to check and cast in one fell swoop.

This (old way):

void outputValue(Object obj) {
    if (obj instanceof String) {                      // Compare
        String aString = (String) obj;                // New variable & explicit casting
        System.out.println(aString.toUpperCase());    // Access member
    }
}

Can be simplified to this:

void outputValue(Object obj) {
    if (obj instanceof String aString) {              // Compare and cast (if true)
        System.out.println(aString.toUpperCase());    // Access member
    }
}
Venireman answered 29/5, 2020 at 10:54 Comment(0)
O
5

There's a difference between measuring if some object will fit in a box, and actually putting it in the box. instanceof is the former, and casting is the latter.

Outgrowth answered 15/11, 2010 at 16:27 Comment(1)
Assuming its a method's variable where no other thread can access, I always can put something in a box that fits instanceof!!!!Dialectal
E
4

Because this particular syntactic sugar is not yet added to the language. I think it was proposed for Java 7, but it doesn't seem to have entered project coin

Edee answered 15/11, 2010 at 16:15 Comment(3)
I hope it doesn't get added. Instanceof and cast is most often a design smell (should be using polymorphism). Making it more elegant will just aggravate the problem.Outgrowth
@Mark Peters - instanceof is a pretty valid usecase sometimes, although it is often misused.Edee
I agree it has valid uses (though they are few and far between). I just don't see the merit in adding syntactic sugar to make it easier (or nicer). Also, generics can eliminate the need for many of the valid uses.Outgrowth
S
3

As Leroy mentioned, Java 14 introduces pattern matching for instanceof. So, you can combine both instanceof check and typecast altogether in a single expression:

if (p1 instanceof Square) {
    c1 = (Square) p1;
}

can be rewritten as

if (p1 instanceof Square c1) {
    // use c1
}

This feature is finalized in Java 16 (JEP 394). For the below versions, refer this link to enable this preview feature from IDEs such as IntelliJ, Eclipse, and STS.

Salzman answered 26/10, 2021 at 9:11 Comment(0)
U
1

E.g. If you hand over p1 as of type Object, the compiler wouldn't know that it is in fact an instance of Square, so that Methods etc. wouldn't be accessible. The if simply checks for a certain type to return true/false, but that doesn't change the type of the variable p1.

Upsweep answered 15/11, 2010 at 16:16 Comment(0)
C
1

The test is done to prevent from ClassCastExceptions at runtime:

Square c1 = null;
if (p1 instanceof Square) {
   c1 = (Square) p1;
} else {
   // we have a p1 that is not a subclass of Square
}

If you're absolutly positive that p1 is a Square, then you don't have to test. But leave this to private methods...

Cornett answered 15/11, 2010 at 16:17 Comment(1)
you may want to invoke certain methods on it in different cases.Edee
V
1

The variable p1 has whatever type it started with - let's say Shape. p1 is a Shape, and only a Shape, no matter that its current contents happen to be a Square. You can call - let's say - side() on a Square, but not on a Shape. So long as you are identifying the entity in question via the variable p1, whose type is Shape, you can't call side() on it, because of the type of the variable. The way Java's type system works, if you can call p1.side() when you happen to know it's a Square, you can always call p1.side(). But p1 can hold not just Square Shapes, but also (say) Circle Shapes, and it would be an error to call p1.side() when p1 held a Circle. So you need another variable to represent the Shape which you happen to know is a Square, a variable whose type is Square. That's why the cast is necessary.

Variorum answered 15/11, 2010 at 16:26 Comment(0)
S
1

Not to be obnoxious, but you have to tell the compiler what you want to do because the alternative would be for it to guess what you're trying to do. Sure, you might think, "If I'm checking the type of an object, OBVIOUSLY that must mean that I want to cast it to that type." But who says? Maybe that's what you're up to and maybe it isn't.

Sure, in a simple case like

if (x instanceof Integer)
{
  Integer ix=(Integer) x;
  ...

My intent is pretty obvious. Or is it? Maybe what I really want is:

if (x instanceof Integer || x instanceof Double)
{
  Number n=(Number) x;
... work with n ...

Or what if I wrote:

if (x instanceof Integer || x instanceof String)

What would you expect the compiler to do next? What type should it assume for x?

RE the comments that instanceof is obsolete or otherwise a bad idea: It can certainly be mis-used. I recently worked on a program where the original author created six classes that all turned out to be pages and pages long, but identical to each other, and the only apparent reason for having them was so he could say "x instanceof classA" versus "x instanceof classB", etc. That is, he used the class as a type flag. It would have been better to just have one class and add an enum for the various types. But there are also plenty of very good uses. Perhaps the most obvious is something like:

public MyClass
{
  int foo;
  String bar;
  public boolean equals(Object othat)
  {
    if (!(othat instanceof MyClass))
      return false;
    MyClass that=(MyClass) othat;
    return this.foo==that.foo && this.bar.equals(that.bar); 
  }
  ... etc ...
}

How would you do that without using instanceof? You could make the parameter be of type MyClass instead of Object. But then there's be no way to even call it with a generic Object, which could be highly desirable in many cases. Indeed, maybe I want a collection to include, say, both Strings and Integers, and I want comparisons of unlike types to simply return false.

Soberminded answered 15/11, 2010 at 17:59 Comment(0)
M
0

If c1 is declared as a type of Square then casting is required. If it is a declared as an Object then casting is not needed.

Mertiemerton answered 15/11, 2010 at 16:15 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.