SOLIDhard5-8 years

Precisely why can `javac` catch a stronger precondition on an overloaded method's parameter types but not a stronger precondition on an override's *behavior* — and what, exactly, is `invokevirtual` doing at the bytecode level that makes an LSP violation a purely run-time phenomenon?

javac's override check operates purely on signatures — parameter types, a return type that's the same or a covariant subtype, no new checked exceptions the base didn't declare — because those are the only facts about a method that exist as static, inspectable metadata in the class file before the method ever runs. Behavior — what the method actually does when it runs — isn't a fact the compiler has access to at all; it can see that save(Order) returns Order and throws nothing new, but it cannot see that the body unconditionally throws UnsupportedOperationException, because reading a method body for behavioral intent is not what a signature check does or can do. invokevirtual (the bytecode instruction behind a normal method call) doesn't encode which method body to run at compile time at all — it encodes a signature to look up, at run time, against whatever the object's actual class turns out to be, via that class's method table. The lookup happens after the object exists, using its real class, which is precisely why the override's real behavior — including a violation like an unexpected throw — is invisible until that lookup happens and the wrong body runs.

The lesson behind it →
More on SOLID