То, что вы должны понимать, называется Стирание универсальных методов
Это означает, что параметр типа метода преобразуется в Object, если он не связан
или
это первый связанный класс, когда он связанный .
Связанный означает, что, оставаясь на вашем примере, вы объявили отношениепараметр типа T для другого класса, как вы это сделали:
// 1. method
public <T extends Dog> String helpMethod(T dog)
// 2. method
public <T extends Animal> String helpMethod(T animal)
T равен ограничен Dog в методе 1., и привязан кЖивотное в 2. метод.
Итак, когда дело доходит до компиляции, Java заменяет T на Dog в методе 1. и на Animal в методе 2.
Здесь нет двусмысленности,это два разных метода.
Однако, если вы объявите 3. метод:
// 3. method
public <T> String helpMethod(T dog)
Тогда T будет несвязанным , вы не объявилисвязь между T и другим классом, поэтому, когда дело доходит до компиляции, Java изменяет T на Object .
Теперь, если вы попытаетесь объявить 4. метод:
// 4. method
public String helpMethod(Object dog)
Будет ошибка компиляции , как метод 3. во время стирания типаиметь точно такую же сигнатуру метода как ваш 4. метод.
Компилятор не может решить, какой метод вы хотите вызвать, и выдает ошибку.
Учитывая вышесказанное, короткий ответ :
В вашем коде вы используете два разных метода, нет двусмысленности, поэтому ошибки не возникает.
Если вы хотите увидеть эту ошибку компиляции, вы должны объявить другой универсальный метод, сигнатура которого после компиляции будет такой же, как существующий универсальный / неуниверсальный метод в вашем классе.