Перегрузка - это полиморфизм времени компиляции. В самом деле?
Я знаю синтаксическую разницу между переопределением и перегрузкой. И я также знаю, что переопределение - это полиморфизм во время выполнения, а перегрузка - это полиморфизм во время компиляции. Но мой вопрос: "Является ли перегрузка действительно полиморфизмом во время компиляции? Действительно ли вызов метода действительно решает во время компиляции?". Чтобы прояснить мою точку зрения, давайте рассмотрим пример класса.
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 ответа
Перегруженные методы все еще могут быть переопределены, если это то, что вы просите.
Перегруженные методы похожи на разные семейства, даже если они имеют одно и то же имя. Компилятор статически выбирает одно семейство с учетом сигнатуры, а затем во время выполнения отправляется наиболее конкретному методу в иерархии классов.
То есть диспетчеризация метода выполняется в два этапа:
- Первый делается во время компиляции с доступной статической информацией, компилятор выдаст
call
для сигнатуры, которая лучше всего соответствует вашим текущим параметрам метода из списка перегруженных методов в объявленном типе объекта, к которому вызывается метод. - Второй шаг выполняется во время выполнения, учитывая сигнатуру метода, который должен быть вызван (предыдущий шаг, помните?), JVM отправит его в наиболее конкретную переопределенную версию в фактическом типе объекта-получателя.
Если типы аргументов метода вообще не являются ковариантными, перегрузка эквивалентна искажению имен методов во время компиляции; Поскольку это эффективно разные методы, JVM никогда не будет их взаимозаменять в зависимости от типа получателя.
Каждый класс "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!");
}
}
Использование этого класса приветствия даст следующие результаты:
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)
метод.
Что такое полиморфизм?
Точность. для меня: если сущность может быть представлена более чем в одной форме, говорят, что эта сущность проявляет полиморфизм.
Теперь давайте применим это определение к конструкциям 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.
Надеюсь, это немного прояснится.
Перегрузка в этом отношении означает, что тип функции статически определяется во время компиляции, а не динамической диспетчеризации.
Что действительно происходит за кулисами, так это то, что для метода с именем "foo" с типами "A" и "B" создаются два метода ("foo_A" и "foo_B"). Какой из них должен быть вызван, определяется во время компиляции (foo((A) object)
или же foo((B) object)
результат в foo_A
вызывается или foo_B
). Таким образом, в некотором смысле это полиморфизм во время компиляции, хотя реальный метод (т.е. какую реализацию в иерархии классов следует использовать) определяется во время выполнения.
Я категорически возражаю против вызова перегрузки метода как полиморфизма времени компиляции.
Я согласен с тем, что перегрузка метода - это статическая привязка (время компиляции), но я не видел в этом полиморфизма.
Я попытался выразить свое мнение в своем вопросе, чтобы получить разъяснения. вы можете сослаться на эту ссылку.