That's because List<? extends Object> might actually refer to a List<String> and thus the compiler has to trust the developer.
In the first case the compiler knows that the cast will mess with the assumptions other code will have (i.e. that you can put every kind of object into l and that l2 will only contain strings) and thus is able to refuse the cast.
The same (or something similar) happens with plain objects:
Integer i = new Integer(1);
String s = (String)i;
The above will fail because the compiler knows that an Integer object can't possibly be cast to String.
However, the following at least compiles since the compiler doesn't know the actual type of i (think of i coming from elsewhere in your code) and thus the compiler has to trust you know what you're doing.
Object i = new Integer(1);
String s = (String)i;