Подтвердить что ты не робот

Вопрос о перегрузке Java и динамической привязке

В приведенном ниже коде, как выводятся первые и вторые операторы печати SubObj? Сделайте верхнюю и нижнюю точки для одного и того же класса Sub?

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
4b9b3361

Ответ 1

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

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

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

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

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

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

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

Затем во время выполнения, в приведенной выше строке кода, верхняя строка опущена JVM на фактический тип, под. Однако аргумент 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)
    }
}

Ответ 2

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

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

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

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

Ответ 3

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

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

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

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

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

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

Ответ 4

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

f(Object o)

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

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

Ответ 5

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

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

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

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

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

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

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

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

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

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

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

Ответ 6

в

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

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

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

Другой способ поставить это в том, что вы получили

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