Почему вывод такой?

class Another {
    public void method(Object o) {
        System.out.println("This is in method which takes object");
    }
    public void method(String s) {
        System.out.println("This is method which takes string");
    }
}

public class NewClass {
    public static void main(String args[]) {
        Another an = new Another();
        an.method(null);
    }
}

Когда я пытаюсь выполнить это, я получаю

Это метод, который принимает строку

в качестве вывода. Почему не "Это в методе, который берет объект"? Объект также может быть нулем, а строка также может быть нулевой, почему он не вызывает первый метод?

Ответы

Ответ 1

В соответствии с Спецификацией Java Language в таких случаях перегруженных методов, в которых оба метода могут обрабатывать предоставленные аргументы, метод с более конкретным аргументом выбран. Поскольку String более специфичен, чем Object (String extends Object), выбирается и вызывается void method(String).

Ответ 2

Это точно указано в JLS:

JLS 15.12.2.5 Выбор наиболее конкретного метода

Если более чем один метод-член доступен и применим к вызову метода, необходимо выбрать его для предоставления дескриптора для отправки времени выполнения. Язык программирования Java использует правило, в котором выбран наиболее специфический метод.

A String is-a Object, но не все Object есть-a String. Следовательно, String более специфичен, чем Object, поэтому почему в приведенном выше примере выбрана перегрузка String.


Стоит отметить, что этот точный вопрос (по сути) появился в замечательном Java Puzzlers (настоятельно рекомендуется), в частности Puzzle 46: Случай смущающего конструктора

Процесс разрешения перегрузки Java работает в два этапа. На первом этапе выбираются все доступные или применимые методы или конструкторы. Вторая фаза выбирает наиболее конкретные методы или конструкторы, выбранные на первом этапе. Один метод или конструктор менее специфичен, чем другой, если он может принимать любые параметры, переданные другому

Ключом к пониманию этой головоломки является то, что тест, для которого метод или конструктор наиболее специфичен, не использует фактические параметры: параметры, появляющиеся в вызове. Они используются только для определения того, какие перегрузки применимы. Когда компилятор определяет, какие перегрузки применимы и доступны, он выбирает наиболее специфическую перегрузку, используя только формальные параметры: параметры, отображаемые в объявлении.


Я закрою цитату из Effective Java 2nd Edition, Пункт 41: разумно использовать перегрузку:

Правила, определяющие, какая перегрузка выбрана, чрезвычайно сложны. Они занимают тридцать три страницы в спецификации языка, и лишь немногие программисты понимают все их тонкости.

Излишне говорить, что эта книга также очень рекомендуется.

См. также


При явном литье

Итак, как я могу вызвать перегрузку Object с аргументом null?

Простой: введите null, чтобы напечатать Object. Существует несколько сценариев, где это полезно/необходимо, и это один из них.

Что делать, если у меня есть перегрузка, которая принимает, скажем, Integer?

Затем вызов метода неоднозначен, и возникает ошибка времени компиляции.

JLS 15.12.2.5 Выбор наиболее конкретного метода

Возможно, что ни один из методов не является наиболее конкретным, поскольку существуют два или более метода, которые являются максимально конкретными. В этом случае [... с некоторыми исключениями] мы говорим, что вызов метода неоднозначен и возникает ошибка времени компиляции.

Чтобы устранить неоднозначность, снова вы можете применить null (или другое выражение) к требуемому типу перегрузки.

См. также

Ответ 3

Компилятор всегда найдет самое узкое совпадение при разрешении перегруженных вызовов методов.

Btw соглашение Java должно использовать имена верхнего регистра для классов, т.е. Another.

Ответ 4

Я довольно уверен, что с Java 6 (я думаю, что Java 5 выложил ошибку компилятора) в таком случае, он будет выбирать класс нижнего уровня для атрибута null. (поскольку String является объектом, он ниже в иерархии)