Вопрос-ответ

Overloaded method selection based on the parameter's real type

Перегруженный выбор метода на основе реального типа параметра

Я экспериментирую с этим кодом:

interface Callee {
public void foo(Object o);
public void foo(String s);
public void foo(Integer i);
}

class CalleeImpl implements Callee
public void foo(Object o) {
logger.debug("foo(Object o)");
}

public void foo(String s) {
logger.debug("foo(\"" + s + "\")");
}

public void foo(Integer i) {
logger.debug("foo(" + i + ")");
}
}

Callee callee = new CalleeImpl();

Object i = new Integer(12);
Object s = "foobar";
Object o = new Object();

callee.foo(i);
callee.foo(s);
callee.foo(o);

Это выводится foo(Object o) три раза. Я ожидаю, что при выборе метода будет учитываться реальный (а не объявленный) тип параметра. Я что-то упускаю? Есть ли способ изменить этот код так, чтобы он печатал foo(12), foo("foobar") и foo(Object o)?

Переведено автоматически
Ответ 1

Я ожидаю, что при выборе метода будет учитываться реальный (а не объявленный) тип параметра. Я что-то упускаю?


ДА. Ваши ожидания неверны. В Java динамическая отправка метода происходит только для объекта, на котором вызывается метод, а не для типов параметров перегруженных методов.

Цитируем спецификацию языка Java:


Когда вызывается метод (§15.12), количество фактических аргументов (и любых аргументов явного типа) и типы аргументов во время компиляции используются во время компиляции для определения сигнатуры метода, который будет вызван (§15.12.2). Если метод, который должен быть вызван, является методом экземпляра, фактический вызываемый метод будет определен во время выполнения с использованием динамического поиска метода (§ 15.12.4).


Ответ 2

Как упоминалось ранее, разрешение перегрузки выполняется во время компиляции.

УJava Puzzlers есть хороший пример для этого:

Головоломка 46: случай с запутанным конструктором

Эта головоломка представляет вам два запутанных конструктора. Метод main вызывает конструктор, но какой именно? Результат программы зависит от ответа. Что программа печатает, или это вообще законно?

public class Confusing {

private Confusing(Object o) {
System.out.println("Object");
}

private Confusing(double[] dArray) {
System.out.println("double array");
}

public static void main(String[] args) {
new Confusing(null);
}
}

Решение 46: случай запутанного конструктора

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

В нашей программе доступны и применимы оба конструктора. Конструктор Confusing(Object) принимает любой параметр, переданный в Confusing(double[]), поэтому Confusing(Object) менее специфичен. (Каждый двойной массив является объектом, но не каждый объект является двойным массивом.) Поэтому наиболее специфичный конструктор сбивает с толку (double[]), что объясняет вывод программы.

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

Чтобы вызвать конструктор Confusing(Object) с параметром null, напишите new Confusing((Object)null). Это гарантирует, что применим только вводящий в заблуждение (объект). В более общем плане, чтобы заставить компилятор выбрать конкретную перегрузку, приведите фактические параметры к объявленным типам формальных параметров.

Ответ 3

Возможность отправлять вызов метода на основе типов аргументов называется множественной отправкой. В Java это делается с помощью шаблона Visitor.

Однако, поскольку вы имеете дело с Integers и Strings , вы не можете легко включить этот шаблон (вы просто не можете изменять эти классы). Таким образом, гигантская switch нагрузка на объект во время выполнения будет вашим любимым оружием.

Ответ 4

В Java вызываемый метод (как и сигнатура метода, которую использовать) определяется во время компиляции, поэтому он соответствует типу во время компиляции.

Типичный способ обойти это - проверить тип объекта в методе с помощью сигнатуры объекта и делегировать методу с помощью приведения.

    public void foo(Object o) {
if (o instanceof String) foo((String) o);
if (o instanceof Integer) foo((Integer) o);
logger.debug("foo(Object o)");
}

Если у вас много типов, и это неуправляемо, то перегрузка метода, вероятно, неправильный подход, скорее открытый метод должен просто принимать Object и реализовывать какой-то шаблон стратегии для делегирования соответствующей обработки для каждого типа объекта.

java oop