The behavior you're seeing here is due to polymorphism and function overloading. If you have a toString() method in the base class and the derived class, it will always call the most derived version of the toString() method, which is in the derived class. It doesn't matter how you are holding the reference to the derived class (e.g. in a derived class reference, a base class reference, or an object reference).
This is true whether the base class implements the toString() method, or just declared it. It's also true for all other methods as well - toString() is not special aside from the fact that it's part of Object's basic contract.
So, you'll see the same behavior here even if you cast your derived class to Object and call toString() on that.
PS: You should annotate your overridden methods, like toString() using @Override in Java to make it more explicitly recognizable - a lot of tooling/IDEs work on this concept.