Различия в подписи при скрытии статического метода в подклассе
Недавно я играл с некоторым простым Java-кодом, используя методы main
, чтобы быстро протестировать код, который я написал. Я попал в ситуацию, когда у меня было два класса, похожие на те, что:
public class A {
public static void main(String[] args) {
// code here
}
}
public class B extends A {
public static void main(String[] args) throws IOException {
// code here
}
}
Я был очень удивлен, что код прекратил компиляцию, и Eclipse жаловался, что Exception IOException is not compatible with throws clause in A.main(String[])
.
Ну, оба метода статичны, а функция main
в B
просто скрывает одну из A
, поэтому я думал, что между ними нет никакой связи. В статических методах у нас нет полиморфизма, и вызов связан с реализацией конкретного метода во время компиляции, поэтому я не могу понять, почему main
in B
не может передать исключение, которое не объявлено в подписи main
в A
.
Почему разработчики Java решили принудительно использовать ограничение, подобное этому, и в каких ситуациях это может вызвать проблемы, если ограничение не было принудительно применено компилятором?
Ответы
Ответ 1
Для чего это стоит, вот соответствующая часть JLS, которая применяет это правило.
Сначала §8.4.8.2. Скрытие (по методам класса) дает определение для скрытия метода, которое применяется здесь:
Если класс C объявляет или наследует метод static
m
, то m
, как говорят, скрывает любой метод m'
, где сигнатура m
является поднаклейкой (§8.4.2) of подпись m'
, в суперклассах и суперинтерфейсах C, которые в противном случае были бы доступны для кода в C.
Затем §8.4.8.3. Требования к переопределению и скрытию гласят:
Метод, который переопределяет или скрывает другой метод, включая методы, реализующие методы abstract
, определенные в интерфейсах, не может быть объявлен для того, чтобы выдавать более проверенные исключения, чем переопределенный или скрытый метод.
Точнее, предположим, что B - класс или интерфейс, а A - суперкласс или суперинтерфейс B, а объявление метода m2
в B переопределяет или скрывает объявление метода m1
в A. Затем:
-
Если m2
имеет предложение throws
, в котором упоминаются все проверенные типы исключений, тогда m1
должно иметь предложение throws
, или возникает ошибка времени компиляции.
-
Для каждого проверяемого типа исключения, указанного в предложении throws
m2
, тот же класс исключений или один из его супертипов должен произойти в стирании (§4.6) предложения throws
m1
; в противном случае возникает ошибка времени компиляции.
Другими словами, сообщение об ошибке не является некоторым недосмотром в компиляторе или неправильной интерпретацией спецификации; JLS прилагает особые усилия, чтобы упомянуть, что конфликты кластера throws
являются ошибкой при скрытии метода (т.е. со статическими методами). Существует эквивалентный язык для этой версии в каждой версии JLS до 1.0.
Однако я не могу окончательно ответить на ваш вопрос о том, почему ограничение присутствует в этом случае. Я не могу представить себе ситуацию, в которой требуется ограничение, поскольку проблема, с которой вызывается статическая реализация метода, всегда полностью разрешается во время компиляции, в отличие, например, от методов.
Я бы поставил небольшую сумму денег, что тот, кто первым поставил это ограничение в langspec, просто был слишком осторожен, полагая, что было бы безопаснее предотвратить что-то, чем разрешить, а затем позже обнаружить, что это вызывает проблемы. Дизайн Java-языка имеет/не лишен достаточной доли недостатков (проверенные исключения являются одним из них), и это может быть достоверно другим, но это всего лишь предположение.
Ответ 2
Я никогда не осознавал этого факта, но, проанализировав его точно, я не вижу причин, по которым компилятор должен применять такое ограничение. Хотя этого не произошло, не было бы возможной некогерентности при сопоставлении метода main
, поскольку связывание статического метода также является статическим. В этом случае не допускается переопределение.
Ответ 3
Дело в том, что мы говорим о статических методах, на которые на самом деле не должно влиять полиморфизм. Однако мне кажется, что статические методы относятся к объекту Class, который, как и любой другой объект, должен подчиняться полиморфизму, правильно?
Ответ 4
Ключевое слово 'extends' означает, что вы наследуете методы класса A, когда вы объявляете класс B. Поскольку ваш класс A является родительским классом класса B, основная декларация метода все еще находится в классе A, несмотря на то, что вы переопределяют его в классе B.
Когда вы имеете дело со статическими методами, метод родительского класса не перезаписывается подклассом, он просто скрыт.
http://docs.oracle.com/javase/tutorial/java/IandI/override.html
Может предоставить более подробную информацию, но вот выдержка:
Различие между скрытием статического метода и переопределением метода экземпляра имеет важные последствия:
Версия метода переопределенного экземпляра, который вызывается, - это один в подклассе. Версия скрытого статического метода, который получает вызывается, будет ли он вызван из суперкласса или подкласс.
оригинальный ответ
Это ограничение вынуждает компилятор упростить наследование в более сложных иерархиях.
Возьмем метод toString(), например. Он наследуется каждым объектом в Java, представьте, если бы вы смогли переопределить объявление этого метода, чтобы позволить подклассам создавать разные исключения. Это рецепт катастрофы с точки зрения обработки всех этих потенциально новых исключений.
TL;DR- Компилятор заставляет вас подчиняться определению функции родительского класса, чтобы другие программисты могли спать по ночам.