Существует разница между перегрузкой и переопределением. В вашем коде вы используете методы перегрузки (создавая методы с одинаковыми именами, но разными типами параметров). Перегрузка разрешается компилятором во время компиляции (в отличие от переопределения, которое разрешается во время выполнения).
Для a
компилятор видит, что его тип - A, поэтому он выбирает метод A.
Для c
компилятор видит, что его типы C, поэтому он выбирает метод с наиболее конкретной c сигнатурой, которая является C методом (String более специфична c, чем Object).
Для b
компилятор видит, что его тип - B, поэтому он выбирает метод B.
Для d
компилятор видит, что его тип - D, поэтому он выбирает метод с наиболее c подпись, которая является методом B (строка более конкретна c, чем Object).