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


115

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

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)?

Відповіді:


96

Я очікую, що вибір методу врахує реальний (а не задекларований) тип параметра. Я щось пропускаю?

Так. Ваші сподівання неправильні. У Java диспетчеризація динамічного методу відбувається лише для об'єкта, на який викликається метод, а не для типів параметрів перевантажених методів.

Посилаючись на специфікацію мови Java :

Коли метод викликається (§15.12), кількість фактичних аргументів (та будь-яких явних аргументів типу) та типи аргументів часу компіляції використовуються під час компіляції для визначення підпису методу, який буде викликаний ( §15.12.2). Якщо метод, який слід викликати, це метод екземпляра, то фактичний метод, на який слід викликати, визначатиметься під час виконання, використовуючи динамічний пошук методу (§15.12.4).


4
Чи можете ви пояснити цитату, яку ви цитували, будь ласка. Два речення, здається, суперечать одне одному. У наведеному вище прикладі використовуються методи екземплярів, проте метод, який викликається, явно не визначається під час виконання.
Алекс Worden

15
@Alex Worden: тип часу компіляції параметрів методу використовується для визначення підпису методу, який потрібно викликати, у цьому випадку foo(Object). Під час виконання клас об'єкта, за допомогою якого використовується метод, визначає, яка реалізація цього методу викликається, беручи до уваги, що це може бути екземпляр підкласу заявленого типу, що перекриває метод.
Майкл Боргвардт

86

Як було сказано, роздільна здатність перевантаження виконується під час компіляції.

Java Puzzlers має прекрасний приклад для цього:

Головоломка 46: Справа заплутаного конструктора

Ця головоломка представляє два заплутані конструктори. Основний метод викликає конструктор, але який? Вихід програми залежить від відповіді. Що друкує програма, чи це навіть законно?

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].

У нашій програмі обидва конструктори доступні та застосовні. Конструктор Confusion (Object) приймає будь-який параметр, переданий Confusion (подвійний []) , тому Confusion (Object) менш конкретний. (Кожен подвійний масив - це Об'єкт , але не кожен Об'єкт - це подвійний масив .) Отже, найбільш специфічний конструктор є заплутаним (подвійний []) , що пояснює вихід програми.

Така поведінка має сенс, якщо ви передаєте значення типу double [] ; це протиінтуїтивно, якщо ви переходите до нуля . Ключовим моментом для розуміння цієї головоломки є те, що тест, для якого методу чи конструктора найбільш конкретний, не використовує фактичні параметри : параметри, що з’являються у виклику. Вони використовуються лише для визначення того, які перевантаження застосовуються. Після того, як компілятор визначає, які перевантаження застосовні та доступні, він вибирає найбільш конкретну перевантаження, використовуючи лише формальні параметри: параметри, що відображаються в декларації.

Щоб викликати конфузний (об'єкт) конструктор з нульовим параметром, напишіть новий конфузний ((об’єкт) нуль) . Це гарантує, що застосовується лише заплутаність (об'єкт) . Більш загально, щоб змусити компілятор вибрати конкретну перевантаження, привласнити фактичні параметри до оголошених типів формальних параметрів.


4
Я сподіваюся, що не пізно сказати - "одне з найкращих пояснень на SOF". Дякую :)
TheLostMind

5
Я вважаю, якби ми також додали конструктор "private Confusion (int [] iArray)", він би не зміг скласти, чи не так? Тому що зараз є два конструктори з однаковою специфікою.
Risser

Якщо я використовую динамічні типи повернення як функцію введення, він завжди використовує менш конкретні ... сказав, що метод, який можна використовувати для всіх можливих повернених значень ...
kaiser

16

Можливість диспетчеризації виклику методу, заснованому на типах аргументів, називається багаторазовою розсилкою . У Java це робиться за допомогою шаблону відвідувачів .

Однак, оскільки ви маєте справу з Integers і Strings, ви не можете легко включити цей шаблон (ви просто не можете змінювати ці класи). Таким чином, гігант switchна час роботи об'єкта стане вашою зброєю вибору.


11

У Java метод виклику (як у тому, який підпис методу використовувати) визначається під час компіляції, тому він відповідає типу часу компіляції.

Типовою схемою для вирішення цього питання є перевірка типу об'єкта в методі за допомогою підпису Object та делегування методу із закликом.

    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 і реалізувати якусь схему стратегії, щоб делегувати відповідне обробку для кожного типу об’єкта.


4

У мене була подібна проблема з викликом правильного конструктора класу під назвою "Параметр", який міг би прийняти кілька основних типів Java, таких як String, Integer, Boolean, Long та ін. Враховуючи масив об'єктів, я хочу перетворити їх у масив моїх об'єктів 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;
}

Ніяких некрасивих instanceof, переключень висловлювань або шаблону відвідувача не потрібно :)


2

Java дивиться на тип посилання, намагаючись визначити, який метод викликати. Якщо ви хочете змусити свій код, ви виберете правильний метод, ви можете оголосити свої поля як екземпляри конкретного типу:

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

Ви також можете призначити парами як тип парамуму:

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

1

Якщо між кількістю та типом аргументів, зазначених у виклику методу, і підписом методу перевантаженого методу є точне відповідність, то це метод, який буде викликаний. Ви використовуєте посилання на об'єкт, тому java вирішує під час компіляції, що для парами Object існує метод, який приймає безпосередньо Object. Тому він називав цей метод 3 рази.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.