Кастинг на отражение и изпращане на претоварен метод в 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