Различия в подписи при скрытии статического метода в подклассе

Недавно я играл с некоторым простым 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- Компилятор заставляет вас подчиняться определению функции родительского класса, чтобы другие программисты могли спать по ночам.