简体   繁体   中英

Why does the declared type of an object matter at runtime?

In a statically-typed language like Java, from what I've learned, type declaration is essentially for compile-time catching of errors, an obvious advantage over dynamically typed languages. But looking at the times when Java does late binding, we get errors like ClassCastException , showing how the declared types are relevant somehow at runtime. But why does the declared type actually matter?

For example:

public class TestClass 
{
   public static void main(String[] args) 
   {
       Animal d = new Animal();
      ((Dog)d).bark(); //ClassCastException because an Animal is not a dog, which would make sense to throw at compile-time, but not at runtime.
   }
}

class Dog extends Animal{}

class Animal
{
   void bark()
   {
       System.out.println("Woof");
   }
}

I know this is a super bad example because it's an unnessesary cast, but I'm just giving an example of it. We try to avoid runtime exceptions at all times, so why can't Java disregard the cast and call method bark on the actual object type, which in this case is Animal ? I've been reading about duck typing and it seems like a similar adaption of this could work in Java (ie if this object woofs, then let's treat it like a dog!) or any statically-typed language, because at runtime, Java seems to act dynamically.

Edit: Now that I'm thinking about it more, this question is about the need for runtime type checking. Why does it need to happen? It seems bad that static typed languages halt with a runtime exception on a cast which could be ignored.

why can't Java disregard the cast and call method bark on the actual object type, which in this case is Animal?

That would be semantically wrong thing to do. What if Dog and Amimal has different versions of Bark method?

Let's say you have a non virtual method Bark in your base class. When you call Bark on Dog it should call Dog.Bark ; similarly when you call Bark on Animal it should call Animal.Bark .

If the compiler disregards the cast as you said, it would end up calling the wrong method.

Note that in C# by default all methods are non virtual as opposed to java. So this argument makes more sense in the context of C#.

Of course duck typing could work in Java. A lot of things could work in Java, but this is the way it's designed. The declared type matters exactly because Java is statically typed and not something else.

Static typing is not compile time only. Certainly a big portion of it is to verify that the code can run correctly. But it's not enough. Since it's not always possible to verify that the types are always correct (such as casting an Object parameter to a specific class), there needs to be runtime type checking as well. If it were static at compile time and duck at runtime, it could create very confusing and hard to debug situations.

I just noticed that this question has the tag as well as . My answer only covers the Java side, although some of the arguments would apply to both.

The issue isn't with the declared type, but rather with the cast. In the general case, it isn't knowable what the runtime type of an object is other than the declared type, so making a cast has to insert runtime checks (a checkcast bytecode) to ensure that it is the correct type. Casting is, by necessity, a runtime operation, and when you do it you're telling the compiler to throw out it's own safety and trust that you know what you're doing.

You can see this by looking at the bytecode generated by your example:

public static void main(java.lang.String[]);
    Code:
       0: new           #2                  // class Animal
       3: dup           
       4: invokespecial #3                  // Method Animal."":()V
       7: astore_1      
       8: aload_1       
       
      12: invokevirtual #5                  // Method Dog.bark:()V
      15: return

If you look at the JLS section for casting , you can see that it says:

the type of the operand expression must be converted to the type explicitly named by the cast operator.

Meaning this is a well-define operation. It doesn't matter that it's not a needed cast here: you're still requiring the compiler to do the cast, so it does.

Can you explain why runtime type checking has to happen in a static-typed language

Yes, here are a few reasons

  1. Compiled class Dog was replaced by someone, from other project and new class doesn't even extend Animal.
  2. Compiled class Dog is loaded via Dserialization and some wrong version of of Dog was packed.

Exception in thread "main" java.lang.ClassCastException: com.example.com.example.Animal cannot be cast to com.example.com.example.Dog at com.example.com.example.Test.main(Test.java:12)

As you can see error, The compiler fails to detect the type casting error, because down casting "((Dog)d)" is a valid casting if the reference Dog is a Animal object. so it throw runtime error instead compile time.

The compiler doesn't know which actual object being referenced. It only knows the reference type. The actual object comes into picture at runtime. So, the compiler wouldn't give you compiler error for that.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM