Почему Java не допускает переопределения статических методов?
Почему невозможно переопределить статические методы?
Если возможно, пожалуйста, используйте пример.
Переведено автоматически
Ответ 1
Переопределение зависит от наличия экземпляра класса. Суть полиморфизма заключается в том, что вы можете создать подкласс класса, и объекты, реализующие эти подклассы, будут иметь разное поведение для одних и тех же методов, определенных в суперклассе (и переопределяемых в подклассах). Статический метод не связан ни с одним экземпляром класса, поэтому данная концепция неприменима.
На это повлияли два соображения, лежащие в основе дизайна Java. Одна из проблем заключалась в производительности: в адрес Smalltalk было много критики по поводу того, что он слишком медленный (сборка мусора и полиморфные вызовы являются частью этого), и создатели Java были полны решимости избежать этого. Другим решением было то, что целевой аудиторией Java были разработчики C ++. Создание статических методов таким образом, как они работают, было знакомо программистам на C ++, а также было очень быстрым, потому что не нужно было ждать, пока среда выполнения определит, какой метод вызывать.
Ответ 2
Лично я думаю, что это недостаток в дизайне Java. Да, да, я понимаю, что нестатические методы привязаны к экземпляру, в то время как статические методы привязаны к классу и т.д. и т.п. Тем не менее, рассмотрим следующий код:
public class RegularEmployee {
private BigDecimal salary;
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
public static BigDecimal getBonusMultiplier() {
return new BigDecimal(".02");
}
public BigDecimal calculateBonus() {
return salary.multiply(getBonusMultiplier());
}
/* ... presumably lots of other code ... */
}
public class SpecialEmployee extends RegularEmployee {
public static BigDecimal getBonusMultiplier() {
return new BigDecimal(".03");
}
}
Этот код не будет работать так, как вы могли бы ожидать. А именно, специальные сотрудники получают бонус в размере 2%, как и обычные сотрудники. Но если вы удалите "статические" методы, то специальные сотрудники получат бонус в размере 3%.
(По общему признанию, этот пример является плохим стилем кодирования в том смысле, что в реальной жизни вы, вероятно, хотели бы, чтобы множитель бонуса находился где-нибудь в базе данных, а не был жестко запрограммирован. Но это просто потому, что я не хотел перегружать пример большим количеством кода, не имеющего отношения к делу.)
Мне кажется вполне вероятным, что вы, возможно, захотите сделать getBonusMultiplier статическим. Возможно, вы хотите иметь возможность отображать множитель бонуса для всех категорий сотрудников без необходимости иметь экземпляр сотрудника в каждой категории. Какой смысл искать такие примеры экземпляров? Что, если мы создаем новую категорию сотрудников и ей еще не назначены сотрудники? Вполне логично, что это статическая функция.
Но это не работает.
И да, да, я могу придумать любое количество способов переписать приведенный выше код, чтобы заставить его работать. Я хочу сказать не то, что это создает неразрешимую проблему, а то, что это создает ловушку для неосторожного программиста, потому что язык ведет себя не так, как, я думаю, ожидал бы разумный человек.
Возможно, если бы я попытался написать компилятор для языка ООП, я бы быстро понял, почему реализовать его так, чтобы статические функции могли быть переопределены, было бы сложно или невозможно.
Или, возможно, есть какая-то веская причина, по которой Java ведет себя таким образом. Кто-нибудь может указать на преимущество такого поведения, на какую-то категорию проблем, которые благодаря этому облегчаются? Я имею в виду, не надо просто указывать мне на спецификацию языка Java и говорить "смотрите, это задокументировано, как это ведет себя". Я это знаю. Но есть ли веская причина, по которой это ДОЛЖНО вести себя именно так? (Помимо очевидного "заставить это работать правильно было слишком сложно" ...)
Обновить
@VicKirk: Если вы имеете в виду, что это "плохой дизайн", потому что он не соответствует тому, как Java обрабатывает статику, мой ответ: "Ну да, конечно". Как я уже говорил в своем первоначальном посте, это не работает. Но если вы имеете в виду, что это плохой дизайн в том смысле, что было бы что-то принципиально неправильное в языке, где это работало, т. Е. Где статику можно было бы переопределять точно так же, как виртуальные функции, что это каким-то образом внесло бы двусмысленность или было бы невозможно эффективно реализовать или что-то подобное, я отвечу: "Почему? Что не так с этой концепцией? "
Я думаю, что приведенный мной пример - это очень естественное желание. У меня есть класс, в котором есть функция, которая не зависит ни от каких данных экземпляра, и которую я, возможно, вполне обоснованно захочу вызывать независимо от экземпляра, а также из метода экземпляра. Почему это не должно работать? Я сталкивался с такой ситуацией довольно много раз за эти годы. На практике я обходил это, делая функцию виртуальной, а затем создавая статический метод, единственное назначение которого в жизни - быть статическим методом, который передает вызов виртуальному методу с фиктивным экземпляром. Это кажется очень обходным способом добраться туда.
Ответ 3
Короткий ответ таков: это вполне возможно, но Java этого не делает.
Вот некоторый код, который иллюстрирует текущее положение дел в Java:
Файл Base.java
:
package sp.trial;
public class Base {
static void printValue() {
System.out.println(" Called static Base method.");
}
void nonStatPrintValue() {
System.out.println(" Called non-static Base method.");
}
void nonLocalIndirectStatMethod() {
System.out.println(" Non-static calls overridden(?) static:");
System.out.print(" ");
this.printValue();
}
}
Файл Child.java
:
package sp.trial;
public class Child extends Base {
static void printValue() {
System.out.println(" Called static Child method.");
}
void nonStatPrintValue() {
System.out.println(" Called non-static Child method.");
}
void localIndirectStatMethod() {
System.out.println(" Non-static calls own static:");
System.out.print(" ");
printValue();
}
public static void main(String[] args) {
System.out.println("Object: static type Base; runtime type Child:");
Base base = new Child();
base.printValue();
base.nonStatPrintValue();
System.out.println("Object: static type Child; runtime type Child:");
Child child = new Child();
child.printValue();
child.nonStatPrintValue();
System.out.println("Class: Child static call:");
Child.printValue();
System.out.println("Class: Base static call:");
Base.printValue();
System.out.println("Object: static/runtime type Child -- call static from non-static method of Child:");
child.localIndirectStatMethod();
System.out.println("Object: static/runtime type Child -- call static from non-static method of Base:");
child.nonLocalIndirectStatMethod();
}
}
Если вы запустите это (я сделал это на Mac, из Eclipse, используя Java 1.6), вы получите:
Object: static type Base; runtime type Child.
Called static Base method.
Called non-static Child method.
Object: static type Child; runtime type Child.
Called static Child method.
Called non-static Child method.
Class: Child static call.
Called static Child method.
Class: Base static call.
Called static Base method.
Object: static/runtime type Child -- call static from non-static method of Child.
Non-static calls own static.
Called static Child method.
Object: static/runtime type Child -- call static from non-static method of Base.
Non-static calls overridden(?) static.
Called static Base method.
Здесь единственные случаи, которые могут вызвать удивление (и о которых идет речь), по-видимому, являются первым случаем:
"Тип времени выполнения не используется для определения того, какие статические методы вызываются, даже при вызове с экземпляром объекта (obj.staticMethod()
)".
и последний случай:
"При вызове статического метода из объектного метода класса выбирается статический метод, доступный из самого класса, а не из класса, определяющего тип объекта во время выполнения".
Вызов с помощью экземпляра объекта
Статический вызов разрешается во время компиляции, тогда как вызов нестатического метода разрешается во время выполнения. Обратите внимание, что, хотя статические методы наследуются (от родительского элемента), они не переопределяются (дочерним элементом). Это могло бы стать сюрпризом, если бы вы ожидали иного.
Вызов из объектного метода
Вызовы метода объекта разрешаются с использованием типа во время выполнения, но вызовы статического метода (класса) разрешаются с использованием объявленного типа во время компиляции.
Изменение правил
Чтобы изменить эти правила, чтобы последний вызов в примере вызывался Child.printValue()
, статические вызовы должны были бы быть снабжены типом во время выполнения, а не компилятором, разрешающим вызов во время компиляции с помощью объявленного класса объекта (или контекста). Тогда статические вызовы могли бы использовать (динамическую) иерархию типов для разрешения вызова, точно так же, как это делают сегодня вызовы объектных методов.
Это было бы легко выполнимо (если бы мы изменили Java :-O) и вовсе не является необоснованным, однако в нем есть некоторые интересные соображения.
Основное соображение заключается в том, что нам нужно решить, какие вызовы статических методов должны это делать.
На данный момент в Java есть такая "особенность" языка, при которой obj.staticMethod()
вызовы заменяются ObjectClass.staticMethod()
вызовами (обычно с предупреждением). [Примечание: ObjectClass
это тип времени компиляции obj
.] Они были бы хорошими кандидатами для переопределения таким образом, принимая тип времени выполнения obj
.
Если бы мы это сделали, это затруднило бы чтение тел методов: статические вызовы в родительском классе потенциально могли бы динамически "перенаправляться". Чтобы избежать этого, нам пришлось бы вызывать статический метод с именем класса - и это делает вызовы более очевидными с помощью иерархии типов во время компиляции (как сейчас).
Другие способы вызова статического метода более сложны: this.staticMethod()
должны означать то же самое, что и obj.staticMethod()
, принимая тип времени выполнения this
. Однако это может вызвать некоторые проблемы с существующими программами, которые вызывают (по-видимому, локальные) статические методы без оформления (что, возможно, эквивалентно this.method()
).
Так что насчет неподтвержденных вызововstaticMethod()
? Я предлагаю им сделать то же, что и сегодня, и использовать контекст локального класса, чтобы решить, что делать. В противном случае возникнет большая путаница. Конечно, это означает, что method()
означало бы, this.method()
если method
был нестатический метод, и ThisClass.method()
если method
был статический метод. Это еще один источник путаницы.
Другие соображения
Если бы мы изменили это поведение (и сделали статические вызовы потенциально динамически нелокальными), мы, вероятно, захотели бы пересмотреть значение final
, private
и protected
как квалификаторов для static
методов класса. Тогда нам всем пришлось бы привыкнуть к тому факту, что методы private static
и public final
не переопределяются и, следовательно, могут быть безопасно разрешены во время компиляции и "безопасны" для чтения как локальные ссылки.
Ответ 4
На самом деле мы были неправы.
Несмотря на то, что Java не позволяет переопределять статические методы по умолчанию, если вы внимательно изучите документацию классов классов и методов в Java, вы все равно можете найти способ эмулировать переопределение статических методов следующим обходным путем:
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
class RegularEmployee {
private BigDecimal salary = BigDecimal.ONE;
public void setSalary(BigDecimal salary) {
this.salary = salary;
}
public static BigDecimal getBonusMultiplier() {
return new BigDecimal(".02");
}
public BigDecimal calculateBonus() {
return salary.multiply(this.getBonusMultiplier());
}
public BigDecimal calculateOverridenBonus() {
try {
// System.out.println(this.getClass().getDeclaredMethod(
// "getBonusMultiplier").toString());
try {
return salary.multiply((BigDecimal) this.getClass()
.getDeclaredMethod("getBonusMultiplier").invoke(this));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
}
return null;
}
// ... presumably lots of other code ...
}
final class SpecialEmployee extends RegularEmployee {
public static BigDecimal getBonusMultiplier() {
return new BigDecimal(".03");
}
}
public class StaticTestCoolMain {
static public void main(String[] args) {
RegularEmployee Alan = new RegularEmployee();
System.out.println(Alan.calculateBonus());
System.out.println(Alan.calculateOverridenBonus());
SpecialEmployee Bob = new SpecialEmployee();
System.out.println(Bob.calculateBonus());
System.out.println(Bob.calculateOverridenBonus());
}
}
Результирующий вывод:
0.02
0.02
0.02
0.03
чего мы пытались достичь :)
Даже если мы объявим третью переменную Carl как RegularEmployee и присвоим ей экземпляр SpecialEmployee , у нас все равно будет вызов метода RegularEmployee в первом случае и вызов метода SpecialEmployee во втором
RegularEmployee Carl = new SpecialEmployee();
System.out.println(Carl.calculateBonus());
System.out.println(Carl.calculateOverridenBonus());
просто посмотрите на консоль вывода:
0.02
0.03
;)