Приведение отражения и диспетчеризация перегруженных методов в Java

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

У меня есть несколько классов, которые реализуют общий интерфейс.

public interface Inter{}
public class Inter1 implements Inter{}
public class Inter2 implements Inter{}

В отдельном классе у меня есть список типов Inter, который я использую для хранения и удаления типов Inter1 и Inter2 на основе пользовательского ввода.

java.util.ArrayList<Inter> inters = new java.util.ArrayList<Inter>();

У меня также есть семейство перегруженных методов, которые имеют дело с тем, как каждая реализация взаимодействует друг с другом, наряду с реализацией по умолчанию для 2 "Inter".

void doSomething(Inter in1, Inter in2){
    System.out.println("Inter/Inter");     
}
void doSomething(Inter1 in1, Inter1 in2){
    System.out.println("Inter1/Inter11");    
}
void doSomething(Inter2 in1, Inter1 in2){
    System.out.println("Inter2/Inter1");    
}

Методы периодически вызываются так:

for(int i = 0; i < inters.size() - 1; i++){
    for(int o = i+1; o < inters.size(); o++){
        Inter in1 = inters.get(i);    Inter in2 = inters.get(o);

        doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2));

        System.out.println("Class 1: " + in1.getClass().getName());
        System.out.println("Class 2: " + in2.getClass().getName());
    }
}

Пример вывода из этого:

Inter/Inter
Class 1: Inter
Class 2: Inter
Inter/Inter
Class 1: Inter
Class 2: Inter1
Inter/Inter
Class 1: Inter1
Class 2: Inter1

Глядя на вывод, становится ясно, что doSomething(Inter in1, Inter in2) вызывается даже в тех случаях, когда должны быть вызваны другие методы. Интересно, что выведенные имена классов являются правильными.

Почему в java есть перегрузка статического метода, когда типы классов определяются во время выполнения с использованием отражения? Есть ли способ заставить Java сделать это? Я знаю, что могу использовать отражение и Class.getMethod() и method.invoke(), чтобы получить нужные мне результаты, но было бы гораздо лучше сделать это с помощью приведения.

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


person Adno    schedule 28.03.2012    source источник


Ответы (2)


Мне кажется, мы говорим о том, что происходит с:

doSomething(in1.getClass().cast(in1), in2.getClass().cast(in2));

Основываясь на вашем удивлении, что тип, который выводится всегда Inter, кажется, вы немного запутались в том, что здесь происходит. В частности, вы, кажется, думаете, что in1.getClass().cast(in1) и in2.getClass().cast(in2) должны вызывать другую перегрузку из-за разного типа времени выполнения. Однако это неправильно.

Разрешение перегрузки метода происходит статически. Это означает, что это происходит на основе объявленных типов двух аргументов метода. Поскольку и in1, и in2 объявлены как Inter, очевидно, что выбран метод void doSomething(Inter in1, Inter in2).

Вывод здесь заключается в том, что in1 объявляется как Inter. Это означает, что in1.getClass() по существу совпадает с Inter.class для целей статического анализа — getClass просто возвращает Class<? extends Inter>. Следовательно, приведения бесполезны, и вы получите только первую перегрузку.

person Kirk Woll    schedule 28.03.2012
comment
Спасибо, я надеялся, что это не так, потому что это довольно раздражает. Я просто не могу не чувствовать, что такие вещи должны быть динамичными. Это делает все намного сложнее... Ну ладно, думаю, мне просто придется использовать API отражения, как бы беспорядочно это ни выглядело. - person Adno; 28.03.2012

Спецификация языка Java (JLS) в разделе 15.12 Выражение вызова метода подробно объясняет процесс, которому следует компилятор, чтобы выбрать правильный метод для вызова.

Там вы заметите, что это задача во время компиляции. JLS говорит в подразделе 15.12.2:

На этом шаге используется имя метода и типы выражений аргументов для поиска доступных и применимых методов. Таких методов может быть несколько, и в этом случае выбирается наиболее конкретный.

В вашем случае это означает, что, поскольку вы передаете два объекта типа Integer, наиболее конкретным методом является тот, который получает именно это.

Чтобы проверить характер времени компиляции, вы можете выполнить следующий тест.

Объявите такой класс и скомпилируйте его.

public class ChooseMethod {
   public void doSomething(Number n){
    System.out.println("Number");
   }
}

Объявите второй класс, который вызывает метод первого, и скомпилируйте его.

public class MethodChooser {
   public static void main(String[] args) {
    ChooseMethod m = new ChooseMethod();
    m.doSomething(10);
   }
}

Если вы вызываете main, вывод говорит Number.

Теперь добавьте второй более конкретный метод в класс ChooseMethod и перекомпилируйте его (но не перекомпилируйте другой класс).

public void doSomething(Integer i) {
 System.out.println("Integer");
}

Если вы снова запустите main, вывод по-прежнему будет Number.

В основном, потому что это было решено во время компиляции. Если вы перекомпилируете класс MethodChooser (тот, что с основным) и снова запустите программу, вывод будет Integer.

Таким образом, если вы хотите принудительно выбрать один из перегруженных методов, тип аргументов должен соответствовать типу параметров во время компиляции, а не только во время выполнения, как вы ожидаете в этом упражнении.

person Edwin Dalorzo    schedule 28.03.2012