Почему вызывается метод, который получает класс Отца в качестве параметра, а не метод, который получает дочерний класс в качестве параметра?

У меня есть класс с именем A и класс с именем B, который расширяет A. Играя с некоторыми методами для понимания полиморфного поведения, я столкнулся со странной ситуацией.

public class Main {
    public static void main(String[] args){
        B b = new B();
        A a = b;
        b.f1(a);
    }
}

public class A {
.
.
.
    public void f1(A a){
        if(a instanceof B)
            f1((B)a);
        else
            System.out.println("Nothing");
    }
.
.
.
}

public class B extends A {
.
.
.
    public void f1(B b){
        System.out.println("B::f1(B)");
    }
.
.
.
}

Я ожидал, что f1 в классе A будет вызываться первым (потому что a имеет тип A), что и произошло. Затем я ожидал строку f1((B)a); для вызова, так как a является экземпляром B. До сих пор все шло так, как ожидалось. Однако я думал, что следующим будет вызываться метод f1(B) в классе B. Вместо этого f1(A) в классе A вызывался снова и снова, вызывая исключение переполнения стека. Почему не вызвали f1(B) в классе B? Экземпляр B был вызывающим, и параметр был приведен к типу B.


person Ronen Hanukayev    schedule 06.02.2019    source источник
comment
Это связано с тем, что f1() является методом экземпляра, экземпляр B знает свой метод экземпляра, а не метод экземпляра A. То же верно и в обратном порядке   -  person Hari Krishna    schedule 06.02.2019
comment
Какой перегруженный метод вызывается в java?   -  person ernest_k    schedule 06.02.2019


Ответы (3)


f1(A a) — это метод экземпляра класса A. Он не знает методов подклассов A. Следовательно, он не может вызывать void f1(B b) класса B. Поэтому f1((B)a) снова выполняет void f1(A a).

Если вы хотите вызвать f1(B b), вам придется вызвать f1 для переменной экземпляра класса B:

public void f1(A a){
    if(a instanceof B) {
        B b = (B)a;
        b.f1(b);
    } else {
        System.out.println("Nothing");
    }
}
person Eran    schedule 06.02.2019
comment
О, теперь я понял. А как насчет обратного, могу ли я вызвать метод, написанный в классе A, из метода, написанного в классе B (конечно, при условии, что метод не переопределен в классе B)? - person Ronen Hanukayev; 06.02.2019
comment
@RonenHanukayev да, поскольку B может видеть все (общедоступные или защищенные) методы своего суперкласса A. - person Eran; 06.02.2019
comment
@Эран, пожалуйста, обратите внимание на мой ответ. Это может привести к исключению ClassCastException, поэтому необходимо указать вопросник. - person Davide Lorenzo MARINO; 06.02.2019
comment
@DavideLorenzoMARINO Как может быть выброшено исключение ClassCastException, если вы проверяете это a instanceof B перед выполнением какого-либо приведения? - person Eran; 06.02.2019
comment
О, я вижу... вы не использовали это, но вы вызвали f1 для экземпляра параметра. Интересный трюк. +1 - person Davide Lorenzo MARINO; 06.02.2019
comment
@Eran Хороший трюк, но в исходном вопросе f1 (B) следует вызывать для объекта this, а не для аргумента, поэтому, вероятно, ожидается ((B) this).f1(b);. - person Serge Ageyev; 06.02.2019

Ваш класс A понятия не имеет, что класс B где-то существует и имеет функцию B.f1(B b). На самом деле f1(A a) и f1(B b) — две разные функции. то, что вы хотите, вероятно, достичь, должно быть сделано немного по-другому:

public class A {
//...
    public void f1(A a) {
         System.out.println("Nothing");
    }
//...
}

public class B {
//...
    public void f1(B a) {
        // this is different function, because of another parameters
    }

    public void f1(A a) {
        if(a instanceof B)
            f1((B)a);
        else
            super.f1(a);
    }
//...
}
person Serge Ageyev    schedule 06.02.2019

Код должен привести вызывающую сторону метода следующим образом:

public class A {

    public void f1(A a){
        if(a instanceof B)
            ((B) this).f1(a);
        else
            System.out.println("Nothing");
    }
}

Вместо этого в вашем коде вы указываете только параметр. Это приводит к рекурсивному вызову одного и того же метода того же класса.

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

Важно Обратите внимание, что вызов приведения к вызывающему объекту в зависимости от параметра может привести к возникновению исключения приведения класса, например, в следующем контексте:

    B b = new B();
    A a = new A();
    a.f1(b);

Вы не можете быть уверены, что метод f1, получающий параметр B, вызывается для объекта B.

person Davide Lorenzo MARINO    schedule 06.02.2019
comment
Вероятно, ((B) this).f1((B)a); - это то, что нужно автору, поскольку f1(B b) - это то, что ожидается для аргумента типа B. Я думаю, было бы здорово проверить, что this на самом деле можно привести и к B :) - person Serge Ageyev; 06.02.2019