downcasting в java + вызывающий метод с переменными аргументами - PullRequest
2 голосов
/ 05 апреля 2020

Когда я вызываю a.displayName("Test"), он вызывает метод класса Icecream. displayName(String...s) метод принимает переменные аргументы. Выходные данные -

test Icecream
test Faloodeh 
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

Но когда я изменяю метод на displayName(String s) (я закомментировал этот раздел в коде), он вызывает метод класса Faloodeh. Новый вывод -

test Faloodeh 
test Faloodeh 
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

Я хотел бы знать, почему это происходит.

class Icecream{
    public void displayName(String...s){
        System.out.println(s[0]+" "+"Icecream");
    }
    /*public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    */
    public void describe(String s) {
        System.out.println(s+" "+"Icecream: Ice cream");
    }
}
class Faloodeh extends Icecream {
    public void displayName (String s){
        System.out.println(s+" "+"Faloodeh ");
    }

    public void describe (String s) {
        System.out.println(s+" "+"Faloodeh:  Faloodeh");
    }
}
 class Test {
    public static void main(String arg[]) {
       Icecream a=new Faloodeh ();
       Faloodeh b=( Faloodeh)a;
        a.displayName("test");
        b.displayName("test");
        a.describe("test");
        b.describe("test");
    }
}

** Редактировать- ** Спасибо за ответы. Пожалуйста, помогите мне с еще одним сомнением. Я изменил код на -

class Icecream{
    public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    /*public void displayName(String s){
        System.out.println(s+" "+"Icecream");
    }
    */
    public void describe(String s) {
        System.out.println(s+" "+"Icecream: Ice cream");
    }
}
class Faloodeh extends Icecream {
    public void displayName (String...s){
        System.out.println(s+" "+"Faloodeh ");
    }

    public void describe (String s) {
        System.out.println(s+" "+"Faloodeh:  Faloodeh");
    }
}
 class Test {
    public static void main(String arg[]) {
       Icecream a=new Faloodeh ();
       Faloodeh b=( Faloodeh)a;
        a.displayName("test");
        b.displayName("test");
        a.describe("test");
        b.describe("test");
    }
}

Теперь это дает следующий вывод:

test Icecream
test Icecream
test Faloodeh:  Faloodeh
test Faloodeh:  Faloodeh

Как вы все объяснили, здесь b является объектом класса Faloodeh. И displayName(String...s) класса Фалуде не получает переопределения. По-прежнему в выводе отображается test Icecream Почему так?

Ответы [ 2 ]

5 голосов
/ 05 апреля 2020

Ключевым моментом здесь является то, что изменение displayName(String... s) на displayName(String s) вызывает метод displayName(String s) в Faloodeh на переопределение метод в его суперклассе.

Icecream.displayName(String... s) и Faloodeh.displayName(String s) имеют разные подписи, поэтому они не перекрывают друг друга. Но изменение первого на принятие String только приводит к тому, что они имеют одинаковую сигнатуру, что приводит к переопределению.

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

  1. Найдите класс для поиска применимых методов. Это основано на типе времени компиляции объекта, для которого вы вызываете метод. В этом случае a. a тип времени компиляции Icecream, поэтому будут рассматриваться только методы Icecream. Обратите внимание, что он не находит метод displayName в Faloodeh, потому что тип времени компиляции a равен Icecream.
  2. Определите, какую перегрузку метода для вызова на основе переданных вами аргументов , Здесь есть только один выбор. Как и до, так и после изменения, displayName является единственной перегрузкой, которая совместима с переданными вами аргументами.
  3. Определите, какую реализацию метода вызывать, основываясь на типе времени выполнения объекта, для которого вы вызвали метод. a Тип выполнения Faloodeh. До изменения displayName не переопределяется в Faloodeh, поэтому он вызывает реализацию суперкласса. После изменения displayName становится переопределенным, поэтому вызывается реализация в Faloodeh.

Относительно вашего редактирования:

В этом случае, так как тип времени компиляции b - это Faloodeh, класс для поиска - Faloodeh (шаг 1). Однако есть 2 метода, которые соответствуют аргументам, которые вы дали (Шаг 2):

  • displayName(String...), который объявлен в Faloodeh, и;
  • displayName(String), который является наследуется.

В такой ситуации компилятор всегда поддерживает перегрузку без переменной арности - displayName(String). Это четко указано в JLS §15.12.2 . В частности, этап 2 дополнительно разделен на три дополнительных подэтапа. Первый подэтап пытается найти метод, не разрешая методы переменной арности, и если какой-либо подэтап находит какой-либо метод, остальные подэтапы пропускаются.

1 голос
/ 05 апреля 2020

Ваши тесты показывают, что вам весело с полиморфизмом. Итак, как вы, возможно, знаете, в Java мы можем сказать, что ваш объект имеет два типа:

  • Тип stati c: основанный на объявлении вашей переменной: Icecream a = .... Объект a имеет тип компиляции (то есть stati c) Icecream.
  • Динамический c класс: в зависимости от влияния этой переменной: ... a = new Faloodeh().

Когда пишет код, допустим, что вы используете только тип stati c. Это означает, что компилятор знает о классах / полях / методах, которые вы можете использовать, и позволяет вам их использовать. По этой причине вы можете написать код:

Icecream a = new Icecream();
a.displayName("test");

И не можете написать:

Icecream a = new Icecream();
a.unknownMethod("test");

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

Faloodeh b = new Faloodeh();
b.displayName("test");

Таким образом, в основном, когда вы объявляете и реализуете метод в классе, его потомки могут переопределить поведение, повторно внедрив метод. Что вы и сделали с методом void describe(String s).

Но метод displayName хитрый, потому что вы называете его одинаково, но на самом деле они не одинаковы, потому что они оба являются аргументами брать. Давайте попробуем быть компилятором. Я скомпилировал оба ваших класса, и вот что я создал:

Icecream => displayName | varargs
Icecream => describe | String
Fadooleh => displayName | String
Faloodeh => describe | String

Теперь, при запуске кода, давайте посмотрим, какие методы вы на самом деле будете вызывать со строками в вашем main:

Icecream a = new Faloodeh();
Faloodeh b = (Faloodeh)a;
a.displayName("test"); => Icecram => displayName | varargs
b.displayName("test"); => Fadooleh => displayName | String
a.describe("test"); => Faloodeh => describe | String
b.describe("test"); => Faloodeh => describe | String

Почему первая строка вызывает метод displayName(String...), а не displayName(String)? Поскольку статически компилятор видел, что вы используете тип Icecream для вызова метода displayName, и динамически существует только один метод displayName с переменными, этот метод никогда не был переопределен Faloodeh. Следовательно, вы вызываете напрямую метод Icecream displayName.

Если вы раскомментируете метод displayName(String s) в Icecream, тогда у вас есть Faloodeh, вступающий во владение, потому что динамически Faloodeh имеет свою собственную реализацию на displayName(String) Вот почему его называют.

Надеюсь, это поможет. Больше информации о полиморфизме: https://www.tutorialspoint.com/java/java_polymorphism.htm

** Редактировать ** Это почти та же причина. У обоих ваших классов статически есть метод displayName(String), а у другого - displayName(String...).

. При использовании b.displayName("test") он сначала будет соответствовать displayName(String), реализованному только в вашем Icecream объекте. , Следовательно, поведение.

Это будет невозможно, например:

Icecream a = new Faloodeh()
a.displayName("test", "test");

Поскольку Icecream ничего не знает о методе с именем displayName(String...).

...