Как диспетчеризация метода Java работает с обобщенными и абстрактными классами?
-
19-08-2019 - |
Вопрос
Сегодня я столкнулся с ситуацией, когда Java не вызывал ожидаемый мной метод. Вот минимальный тестовый пример: (Извините, это кажется надуманным - сценарий «реального мира» существенно сложнее и делает гораздо больше смысла из "!", почему, черт возьми, вы сделали бы это ? & "точка зрения.)
Меня особенно интересует, почему это происходит, меня не волнуют предложения по редизайну. У меня такое ощущение, что это в Java Puzzlers, но у меня нет под рукой моей копии.
См. конкретный вопрос в комментариях в разделе Test < T > .getValue () ниже:
public class Ol2 {
public static void main(String[] args) {
Test<Integer> t = new Test<Integer>() {
protected Integer value() { return 5; }
};
System.out.println(t.getValue());
}
}
abstract class Test<T> {
protected abstract T value();
public String getValue() {
// Why does this always invoke makeString(Object)?
// The type of value() is available at compile-time.
return Util.makeString(value());
}
}
class Util {
public static String makeString(Integer i){
return "int: "+i;
}
public static String makeString(Object o){
return "obj: "+o;
}
}
Вывод этого кода:
obj: 5
Решение
Нет, тип значения недоступен во время компиляции. Имейте в виду, что javac скомпилирует только одну копию кода, который будет использоваться для всех возможных T. Учитывая это, единственным возможным типом, который компилятор может использовать в вашем методе getValue (), является Object.
C ++ отличается, потому что он в конечном итоге создаст несколько скомпилированных версий кода по мере необходимости.
Другие советы
Потому что решение о том, что makeString()
использовать, принимается во время компиляции и, исходя из того факта, что T может быть чем угодно, должно быть версией Object
. Думаю об этом. Если вы это сделали Test<String>
, вам придется вызвать версию Test<T>
. Поэтому все экземпляры makeString(Object)
будут использовать <=>.
Теперь, если вы сделали что-то вроде:
public abstract class Test<T extends Integer> {
...
}
все может быть иначе.
Эффективная Java Джоша Блоха отлично обсуждает путаницу, возникающую из-за того, что диспетчеризация работает по-разному для перегруженных и переопределенных (в подклассе) методов. Выбор среди методов перегруженных - предмет этого вопроса - определяется во время компиляции; выбор среди методов переопределенных выполняется во время выполнения (и, следовательно, позволяет узнать конкретный тип объекта.)
Книга намного понятнее моего комментария: см. " Пункт 41: Используйте разумную перегрузку "