I live in C++ land and we call it dynamic binding. In Java, all objects use dynamic binding so it’s kind of inconsequential but it makes a big difference in C++.
Downcasting/dynamic binding are tools used for polymorphism. As with your example, you can create a reference to a subclass, while that reference’s static type is actually the base class. With static binding, a lookup for a member function (method) would look only in the class that the reference is staticly bound too.
Consider a base class called “Vehicle”. It seems reasonable that we might make several more classes, like “Boat” or “Car” or “Rocket” and they would all have some sort of accelerate command. However, the actual process of acceleration is much different between each vehicle type, so we want only to call the acceleration behavior defined in that object’s particular subclass.
Suppose we have an array of Vehicles, where the static type of the array is Vehicle. However, we know that the list could contain objects of any subclass of Vehicle—Boat, Plane, or even types that haven’t been defined yet but could be added in the future, like Rocket.
If you wanted to iterate through this array and call the accelerate() member function on each one, you would want it to call the implementation of that function from the actual subclass of that object, not the base class Vehicle. In order to do this, function lookup is done at run time, and it looks for the definition in the dynamic type of the object, that being the actual subclass.
In C++, by default the static type is used, in which case the static type is an array of pointers to Vehicle type objects, so the compiler would by default look for the implementation of that function in Vehicle, unless it was declared virtual, which tells the compiler to delay lookup until run time. I know you don’t need to know that, but it makes the distinction a little clearer in the context of the language that makes you hold it’s hand when you want dynamic binding.
Downcasting in Java is when you cast a base class into a subclass and lookup is done at runtime. Let’s say you have a reference to an object of Dervived type whose static type is Base. You can convert that type to a Derived, a more specific class, using downcasting, using a type cast.
Base a = new Derived();
Derived b = (Derived)a;