Перегрузка - это полиморфизм времени компиляции.В самом деле? - PullRequest
24 голосов
/ 02 декабря 2011

Я знаю синтаксическую разницу между переопределением и перегрузкой.И я также знаю, что переопределение - это полиморфизм во время выполнения, а перегрузка - это полиморфизм во время компиляции.Но мой вопрос: «Является ли перегрузка действительно полиморфизмом во время компиляции? Действительно ли вызов метода действительно решает во время компиляции?».Чтобы прояснить мою точку зрения, давайте рассмотрим пример класса.

public class Greeter {
    public void greetMe() {
        System.out.println("Hello");
    }

    public void greetMe(String name) {
        System.out.println("Hello " + name);
    }

    public void wishLuck() {
        System.out.println("Good Luck");
    }
}

Поскольку все методы greetMe(), greetMe(String name), wishLuck() являются общедоступными, все они могут быть переопределены (включая перегруженный), верно?Например,

public class FancyGreeter extends Greeter {
    public void greetMe() {
        System.out.println("***********");
        System.out.println("*  Hello  *");
        System.out.println("***********");
    }
}

Теперь рассмотрим следующий фрагмент:

Greeter greeter = GreeterFactory.getRandomGreeter();
greeter.greetMe();

Метод getRandomGreeter() возвращает случайный объект Greeter.Он может либо вернуть объект Greeter, либо любой из его подклассов, например FancyGreeter или GraphicalGreeter, или любой другой.getRandomGreeter() создаст объекты либо с помощью new, либо динамически загрузит файл класса и создаст объект с помощью отражения (я думаю, что это возможно с помощью отражения), либо любым другим возможным способом.Все эти методы Greeter могут быть или не быть переопределены в подклассах.Таким образом, компилятор не может узнать, переопределен ли конкретный метод (перегружен или нет).Правильно?Кроме того, в Википедии сказано: Виртуальные функции :

В Java все нестатические методы по умолчанию являются "виртуальными функциями".Только методы, помеченные ключевым словом final, которые нельзя переопределить, наряду с закрытыми методами, которые не наследуются, не являются виртуальными.

Поскольку виртуальные функции разрешаются во время выполнения с использованием динамического методадиспетчеризация, и поскольку все не частные, не финальные методы являются виртуальными (независимо от того, перегружены они или нет), они должны быть разрешены во время выполнения.Правильно?

Тогда, как перегрузка все еще может быть решена во время компиляции?Или есть что-то, что я неправильно понял, или я скучаю?

Ответы [ 4 ]

14 голосов
/ 02 декабря 2011

Каждый класс Greeter имеет 3 виртуальных метода: void greetMe(), void greetMe(String) и void wishLuck().

Когда вы вызываете greeter.greetMe(), компилятор может определить, какой из трех виртуальных методовдолжен вызываться из сигнатуры метода - т.е.void greetMe(), поскольку он не принимает аргументов.Какая конкретная реализация метода void greetMe() вызывается, зависит от типа экземпляра greeter и определяется во время выполнения.

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

class Greeter {
    public void greetMe(Object obj) {
        System.out.println("Hello Object!");
    }

    public void greetMe(String str) {
        System.out.println("Hello String!");
    }
}

Использование этого класса greeter даст следующие результаты:

Object obj = new Object();
String str = "blah";
Object strAsObj = str;

greeter.greetMe(obj); // prints "Hello Object!"
greeter.greetMe(str); // prints "Hello String!"
greeter.greetMe(strAsObj); // prints "Hello Object!"

Компилятор выберетметод с наиболее конкретным соответствием, использующий тип времени компиляции, поэтому второй пример работает и вызывает метод void greetMe(String).

Последний вызов является наиболее интересным: несмотря на то, чтотип времени strAsObj String, он был приведен как Object, так что компилятор видит его так.Итак, самое близкое соответствие, которое компилятор может найти для этого вызова, - это метод void greetMe(Object).

12 голосов
/ 02 декабря 2011

Перегруженные методы все еще могут быть переопределены, если это то, что вы просите.

Перегруженные методы похожи на разные семейства, даже если они имеют одно и то же имя. Компилятор статически выбирает одно семейство с учетом сигнатуры, а затем во время выполнения отправляется наиболее конкретному методу в иерархии классов.

То есть диспетчеризация метода выполняется в два этапа:

  • Первый выполняется во время компиляции с доступной статической информацией, компилятор выдаст call для сигнатуры, которая лучше всего соответствует вашим текущим параметрам метода среди списка перегруженных методов в объявленном типе объекта метода вызывается.
  • Второй шаг выполняется во время выполнения, учитывая сигнатуру метода, который должен быть вызван (предыдущий шаг, помните?), JVM отправит его в наиболее конкретную переопределенную версию в фактическом типе объекта-получателя.

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

10 голосов
/ 02 декабря 2011

Что такое полиморфизм?

Точность. мне : если сущность может быть представлена ​​более чем в одной форме, говорят, что эта сущность проявляет полиморфизм.

Теперь давайте применим это определение к конструкциям Java:

1) Перегрузка оператора - это полиморфизм времени компиляции.

Например, оператор + может использоваться для добавления двух чисел ИЛИ для объединения двух строк. это пример полиморфизма, строго говоря, полиморфизм во время компиляции.

2) Перегрузка метода - полиморфизм времени компиляции.

Например, метод с одинаковым именем может иметь более одной реализации. это также полиморфизм во время компиляции.

It's compile-time because before execution of program compiler decides the flow of program i.e which form will be used during run-time.

3) Переопределение метода - это полиморфизм времени выполнения.

Например, метод с одной и той же сигнатурой может иметь более одной реализации. это полиморфизм времени выполнения.

4) Вместо базового класса вместо производного класса используется полиморфизм времени выполнения.

Например, ссылка interface может указывать на любого из ее разработчика.

It's run-time because the flow of program can't be known before execution i.e. only during run-time it can be decided that which form will be used.

Надеюсь, это немного прояснится.

3 голосов
/ 02 декабря 2011

Перегрузка в этом отношении означает, что тип функции статически определяется во время компиляции, а не динамической диспетчеризации.

Что действительно происходит за кадром, так это для метода с именем "foo" с типамиA и B создаются два метода («foo_A» и «foo_B»).Какой из них должен быть вызван, определяется во время компиляции (foo((A) object) или foo((B) object) приводит к вызову foo_A или foo_B).Таким образом, в некотором смысле этот является полиморфизмом во время компиляции, хотя реальный метод (т.е. какую реализацию в иерархии классов следует использовать) определяется во время выполнения.

...