The compiler doesn't prevent code from casting a type to an interface, unless it can establish for sure that the relationship is impossible.
If the target type is an interface, then it makes sense because a class extending Foo
can implement Map<String, String>
. However, note that this only works as Foo
is not final
. If you declared your class with final class Foo
, that cast would not work.
If the target type is a class, then in this case it would simply fail (try (HashMap<String, String>) this
), because the compiler knows for certain that the relationship between Foo
and HashMap
is impossible.
For reference, these rules are described in JLS-5.5.1 (T = target type - Map<String, String>
, S = source type - Foo
)
If T [target type] is an interface type:
If S is not a final class (§8.1.1), then, if there exists a supertype X of T, and a supertype Y of S, such that both X and Y are provably distinct parameterized types, and that the erasures of X and Y are the same, a compile-time error occurs.
Otherwise, the cast is always legal at compile time (because even if S does not implement T, a subclass of S might).
If S is a final class (§8.1.1), then S must implement T, or a compile-time error occurs.
Note the bold-italic comment in the quoted text.