Вопрос о перегрузке Java и динамическом связывании - PullRequest
14 голосов
/ 14 апреля 2011

В приведенном ниже коде как первое и второе операторы печати распечатывают SubObj ??Вершина и подпункт указывают на один и тот же подкласс?

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";}
    public String f(Object o) {return "SubObj";}
}

public class Test {
    public static void main(String[] args) {  
        Sub sub = new Sub();
        Top top = sub;
        String str = "Something";
        Object obj = str;


        System.out.println(top.f(obj));
        System.out.println(top.f(str));
        System.out.println(sub.f(obj));
        System.out.println(sub.f(str));
    }
}

Код выше возвращает результат ниже результата.

SubObj
SubObj
SubObj
Sub

Ответы [ 6 ]

11 голосов
/ 25 апреля 2011

Поскольку вы уже понимаете случаи 1, 3 и 4, давайте рассмотрим случай 2.

(Обратите внимание - я ни в коем случае не эксперт по внутренней работе JVM или компиляторов, но этонасколько я понимаю. Если кто-то читает это как эксперт по JVM, не стесняйтесь редактировать этот ответ на любые несоответствия, которые вы можете найти.)

Метод в подклассе с тем же именем, но с другой сигнатурой известенкак метод перегрузки.Перегрузка метода использует статическое связывание, что в основном означает, что соответствующий метод будет вынужден быть «выбранным» (то есть связанным) во время компиляции.Компилятор не имеет ни малейшего представления о типе времени выполнения (или фактическом типе) ваших объектов.Поэтому, когда вы пишете:

                         // Reference Type  // Actual Type
    Sub sub = new Sub(); // Sub                Sub
    Top top = sub;       // Top                Sub

, компилятор только «знает», что top имеет тип Top (он же ссылочный тип).Поэтому, когда вы позже напишите:

    System.out.println(top.f(str)); // Prints "subobj"

, компилятор «видит» вызов top.f как ссылку на метод f класса Top.Он «знает», что str имеет тип String, который расширяет Object.Так как 1) вызов top.f ссылается на метод f класса Top, 2) в классе Top нет метода f, который принимает параметр String, и 3) поскольку str является подклассом Object, метод f класса Topявляется единственным допустимым выбором во время компиляции.Таким образом, компилятор неявно переводит str в свой родительский тип, Object, так что он может быть передан методу Top Top.(Это отличается от динамического связывания, где разрешение типов в приведенной выше строке кода будет отложено до времени выполнения, чтобы разрешаться JVM, а не компилятором.)

Затем во время выполнения, в приведенной выше строкекода, top понижается JVM до его фактического типа, sub.Тем не менее, аргумент str был отклонен компилятором для типа Object.Поэтому теперь JVM должна вызвать метод f в классе sub, который принимает параметр типа Object.

Следовательно, в приведенной выше строке кода выводится «subobj», а не «sub».

Другой очень похожий пример см. В разделе: Динамическое связывание Java и переопределение методов

Обновление. Обнаружена эта подробная статья о внутренней работе JVM:

http://www.artima.com/underthehood/invocationP.html

Я прокомментировал ваш код, чтобы прояснить, что происходит:

class Top {
    public String f(Object o) {return "Top";}
}

class Sub extends Top {
    public String f(String s) {return "Sub";} // Overloading = No dynamic binding
    public String f(Object o) {return "SubObj";} // Overriding = Dynamic binding
}

public class Test {
    public static void main(String[] args) {  

                                  // Reference Type     Actual Type
        Sub sub = new Sub();      // Sub                Sub
        Top top = sub;            // Top                Sub
        String str = "Something"; // String             String
        Object obj = str;         // Object             String

                                        // At Compile-Time:      At Run-Time:
        // Dynamic Binding
        System.out.println(top.f(obj)); // Top.f (Object)   -->  Sub.f (Object)

        // Dynamic Binding
        System.out.println(top.f(str)); // Top.f (Object)   -->  Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(obj)); // Sub.f (Object)        Sub.f (Object)

        // Static Binding
        System.out.println(sub.f(str)); // Sub.f (String)        Sub.f (String)
    }
}
6 голосов
/ 14 апреля 2011

Это потому, что все вызовы методов в Java: виртуальные (по умолчанию).

То есть разрешение начинается с фактический объект (не тип выражения ) и «обрабатывает» цепочку наследования (за фактический тип объекта ) до тех пор, пока не будет найден первый соответствующий метод.Невиртуальные методы начинаются с выражения типа .(Маркировка метода как final делает его не виртуальным.)

Однако точная сигнатура метода определяется во время компиляции (Java не поддерживаетmulti-dispatch, single-dispatch изменяется только во время выполнения в зависимости от объекта-получателя) - это объясняет, почему Sub.f(String) приводит к «Sub», например, в то время как Top.f(String) «связывается» с методом, совпадающим с Top.f(Object) дажеесли вызывается для подтипа Top.(Это была самая подходящая подпись, определенная во время компиляции).Сама виртуальная рассылка, такая же.

Счастливого кодирования.

2 голосов
/ 14 апреля 2011

Это связано с видимым типом объекта.Во время компиляции Java выполняет проверку типа на основе типа, который вы объявляете своим объектом, а не конкретного типа, который вы создаете.

У вас есть тип Top с методом f (Object).Поэтому, когда вы говорите:

 System.out.println(top.f(obj));

Компилятор Java заботится только о том, чтобы верх объекта имел тип Top, и единственный доступный метод принимает Object в качестве параметра.Затем во время выполнения он вызывает метод f (Object) фактического экземпляра объекта.

Следующий вызов интерпретируется аналогичным образом.

Следующие два вызова интерпретируются так, как вы ожидаете.

1 голос
/ 14 апреля 2011

В наследовании объект базового класса может ссылаться на экземпляр производного класса.

Так работает Top top = sub;.

  1. Для System.out.println(top.f(obj));:

    Объект top пытается использовать метод f() класса Sub.Теперь, когда в классе Sub есть два метода f(), для переданного аргумента выполняется проверка типа.Поскольку типом является Object, вызывается второй f() метод класса Sub.

  2. Для System.out.println(top.f(str));:

    Вы можете интерпретировать так же, как(1) то есть тип String, поэтому вызывается первая функция f().

  3. Для System.out.println(sub.f(obj));:

    Это просто, как вы звонитеметод самого класса Sub.Теперь, поскольку в классе Sub есть два перегруженных метода, здесь также выполняется проверка типа для переданного аргумента.Поскольку переданный аргумент имеет тип Object, вызывается второй метод f().

  4. Для System.out.println(sub.f(str));:

    Аналогично 3. здесьпередан тип String, поэтому вызывается первая f() функция класса Sub.

Надеюсь, это поможет.

1 голос
/ 14 апреля 2011

Да, они оба указывают на Sub класс. Проблема в том, что top знает только о

f(Object o)

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

Но sub знает обе подписи и должен выбирать по типу параметра.

0 голосов
/ 14 апреля 2011

in

Sub sub = new Sub();
Top top = sub;

Вы создали экземпляр sub, затем навели его на верх, что позволяет узнать только о методах, существующих в top.метод, который существует сверху, является общедоступным String f(Object o) {return "Top";}

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

другогоМожно сказать, что вы получили подтип

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

...