Перегруженный метод выбора на основе параметра 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: Случай смущающего конструктора

Эта головоломка представляет вам два конструктора Confusing. Основной метод вызывает конструктор, но какой? Выход программы зависит от ответа. Что печатает программа или она даже юридический?

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 []), поэтому Смущение (объект) менее специфично. (Каждый двойной массив - это Object, но не каждый Object является двойным массивом). Поэтому наиболее специфичным конструктором является Confusing (double []), что объясняет вывод программы.

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

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

Ответ 3

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

Однако, поскольку вы имеете дело с Integer и String s, вы не можете легко включить этот шаблон (вы просто не можете изменять эти классы). Таким образом, гигантское 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 и реализовать какой-то шаблон стратегии для делегирования соответствующей обработки для каждого типа объекта.

Ответ 5

У меня была аналогичная проблема с вызовом правого конструктора класса под названием "Параметр", который мог бы принимать несколько базовых типов Java, таких как String, Integer, Boolean, Long и т.д. Учитывая массив объектов, я хочу их преобразовать в массив объектов Parameter, вызывая наиболее специфичный конструктор для каждого объекта во входном массиве. Я также хотел определить конструктор Parameter (Object o), который бы выбрал исключение IllegalArgumentException. Я, конечно, нашел, что этот метод вызывается для каждого объекта в моем массиве.

Решением, которое я использовал, было поиск конструктора через отражение...

public Parameter[] convertObjectsToParameters(Object[] objArray) {
    Parameter[] paramArray = new Parameter[objArray.length];
    int i = 0;
    for (Object obj : objArray) {
        try {
            Constructor<Parameter> cons = Parameter.class.getConstructor(obj.getClass());
            paramArray[i++] = cons.newInstance(obj);
        } catch (Exception e) {
            throw new IllegalArgumentException("This method can't handle objects of type: " + obj.getClass(), e);
        }
    }
    return paramArray;
}

Нет уродливых экземпляров, операторов операторов или шаблона посетителя!:)

Ответ 6

Java ищет тип ссылки при попытке определить, какой метод вызывать. Если вы хотите заставить свой код выбрать "правильный" метод, вы можете объявить свои поля как экземпляры определенного типа:

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

Вы также можете указать свои параметры как тип параметра:

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

Ответ 7

Если существует точное соответствие между количеством и типами аргументов, указанными в вызове метода, и сигнатурой метода перегруженного метода, то это метод, который будет вызываться. Вы используете ссылки на объекты, поэтому java решает во время компиляции, что для параметра Object существует метод, который принимает непосредственно Object. Так он назвал этот метод 3 раза.