Как диспетчеризация метода Java работает с обобщенными и абстрактными классами? - PullRequest
7 голосов
/ 17 января 2009

Сегодня я столкнулся с ситуацией, когда Java не вызывал ожидаемый мной метод. Вот минимальный тестовый пример: (Извините, это кажется надуманным - сценарий «реального мира» существенно сложнее и делает гораздо больше смысла с точки зрения «почему, черт возьми, вы сделали бы с этой ?» точки зрения.)

Меня особенно интересует, почему это происходит, меня не волнуют предложения по редизайну. У меня такое чувство, что это в Java Puzzlers, но у меня нет под рукой моей копии.

См. Конкретный вопрос в комментариях в Test .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

Ответы [ 3 ]

6 голосов
/ 17 января 2009

Нет, тип значения недоступен во время компиляции. Имейте в виду, что javac скомпилирует только одну копию кода, который будет использоваться для всех возможных T. Учитывая это, единственным возможным типом, который компилятор может использовать в вашем методе getValue (), является Object.

C ++ отличается, потому что он в конечном итоге создаст несколько скомпилированных версий кода по мере необходимости.

2 голосов
/ 30 июля 2011

Josh Bloch's Effective Java отлично обсуждает путаницу, возникающую из-за того, что диспетчеризация работает по-разному для перегруженных и переопределенных (в подклассе) методов. Выбор из перегруженных методов --- предмет этого вопроса --- определяется во время компиляции; выбор из переопределенных методов выполняется во время выполнения (и, следовательно, позволяет узнать конкретный тип объекта.)

Книга намного понятнее моего комментария: см. «Пункт 41: Используйте перегрузку разумно»

2 голосов
/ 17 января 2009

Поскольку решение о том, что makeString() использовать, принимается во время компиляции и, исходя из того факта, что T может быть чем угодно, должно быть Object версией. Думаю об этом. Если бы вы сделали Test<String>, это должно было бы вызвать Object версию. Таким образом, все экземпляры Test<T> будут использовать makeString(Object).

Теперь, если вы сделали что-то вроде:

public abstract class Test<T extends Integer> {
  ...
}

все может быть иначе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...