Apex, like Java and other Object-Oriented Languages before it, are strongly-typed languages. This means that the compiler can check if an object is compatible with an assignment or operation before the code runs. This reduces the odds of deploying code that has no chance of ever working correctly. For example, you can't write:
Integer i = 5;
String s = i;
Because the compiler knows that an Integer is not a String. We need to manually convert it (e.g.
JSON.deserialize returns an
Object, which is the parent class of all other classes. This allows
JSON.deserialize to be able to technically deserialize any type of object (some objects are inherently not deserializable/serializable). However, the compiler can't know that the returned
Object is actually a
The compiler doesn't make special exceptions for any methods, as this would be specific behavior and introduce complexities into the compiler. Instead, the developer must help the compiler by telling it what the return type actually is.
This is what casting does. It bypasses the compiler's inferred type checks, and tells it what we know what we're doing, and that object being cast is indeed a specific type.
There are still certain types of casts that are disallowed, since they would be nonsensical (e.g. you can't cast an Integer to a String), but if the the source data type and the cast type are related to each other, the cast is allowed.
Of course, if the wrong type is used, a runtime error can occur, but this casting mechanism is what allows developers to help the compiler determine if an assignment is allowed, by allowing the type of a variable to change into a new, related type.
As a super-simple version that demonstrates the problem, consider:
Decimal d1 = 5.0;
Object o1 = d1;
// Decimal d2 = o1; // Compile error, Object is not a Decimal
Decimal d2 = (Decimal)o1; // Okay!
In summary, we can't skip the cast because while all
Decimal objects are
Object, not all
Decimal, so we must specifically tell the compiler that we are certain the result is a