Поведение финального статического метода

124

Я играл с модификаторами со статическим методом и обнаружил странное поведение.

Как мы знаем, статические методы нельзя переопределить, поскольку они связаны с классом, а не с экземпляром.

Итак, если у меня есть приведенный ниже фрагмент, он отлично компилируется

//Snippet 1 - Compiles fine
public class A {
    static void ts() {
    }
}

class B extends A {
    static void ts() {
    }
}

Но если я добавлю модификатор final к статическому методу в классе A, то компиляция завершится неудачно, ts () в B не может переопределить ts () в A; переопределенный метод является статическим окончательным .

Почему это происходит, когда статический метод вообще нельзя переопределить?

Хариш
источник
2
это кажется странным, +1 за вопрос, но до сих пор ни один из ответов не является удовлетворительным.
Rakesh Juyal
1
Это не отменено. Он все еще там в A.ts ().
Alex Feinman

Ответы:

166

Статические методы нельзя переопределить, но их можно скрыть. ts()Метод B не перекрывая (не подлежит полиморфизм) ts()А , но она будет скрыть. Если вы вызываете ts()B (НЕ A.ts()или B.ts()... просто ts()), будет вызываться один из B, а не A. Поскольку он не подвергается полиморфизму, вызов ts()в A никогда не будет перенаправлен на тот, который находится в B.

Ключевое слово finalотключит скрытие метода. Поэтому они не могут быть скрыты, и попытка сделать это приведет к ошибке компилятора.

Надеюсь это поможет.

NawaMan
источник
30
Чтобы, возможно, закончить свой ответ, и я считаю, что проблема здесь в основном в плохом сообщении об ошибке компилятора: он должен сказать, что B не может скрыть ts () в A. Объявление статического метода final означает, что он не может быть скрыт.
Шон Оуэн,
2
@ Шон Оуэн: Я тоже так думаю. Термин «скрыть» используется даже в спецификации Java, так почему бы не использовать его в сообщении компилятора.
NawaMan
2
Почему это вообще функция? В каком контексте это было бы полезно?
user253751
открытый класс Test {final static public void main (String ... srik) {System.out.println ("В основном методе"); (ц); } public static void ts () {Child c = new Child (); c.ts (); System.out.println («Тестовый ц»); }} открытый класс Child расширяет Test {public static void ts () {System.out.println ("Child ts"); }} Привет, пожалуйста, объясните мне, что происходит в этом сценарии
srikanth r
@SeanOwen Я тоже не думаю, что это правильно, компилятор должен сказать, что, поскольку A#tsон наследуется и такой метод уже существует B, просто наличие двух методов с одинаковой сигнатурой и другим модификатором ( final) не будет работать как перегрузка .. Я хотел бы придумать для этого простое сообщение
Юджин
13

статические методы нельзя переопределить

Это не совсем так. Пример кода действительно означает, что метод ts в B скрывает метод ts в A. Так что это не совсем переопределение. На Джаваранче есть хорошее объяснение.

Винсент Рамдхани
источник
4
Это правда, но не совсем точно. статические методы нельзя переопределить, но можно скрыть, если вы вызываете их по ссылке на экземпляр, а не по имени класса.
Джон Мерсье,
1
К сожалению, ваша ссылка больше не работает. Можно ли это исправить?
Матиас Бадер,
Ссылка javaranch не работает, но поиск в Google ключевых слов показал, что эта ссылка на кодовом ранчо
Sundeep
Я отредактировал сообщение, заменив мертвую ссылку ссылкой, опубликованной Sundeep.
MC Emperor
10

Статические методы принадлежат классу, а не экземпляру.

A.ts()и B.ts()всегда будут отдельными методами.

Настоящая проблема в том, что Java позволяет вам вызывать статические методы для объекта-экземпляра. Статические методы с той же сигнатурой родительского класса скрываются при вызове из экземпляра подкласса. Однако вы не можете переопределить / скрыть методы final .

Можно подумать, что в сообщении об ошибке будет использоваться слово «скрыто», а не «переопределено» ...

Powerlord
источник
6

Вы можете подумать о том, чтобы сделать статический метод final, учитывая следующее:

Имея следующие классы:

class A {
    static void ts() {
        System.out.print("A");
    }
}
class B extends A {
    static void ts() {
        System.out.print("B");
    }
}

Теперь "правильный" способ вызова этих методов был бы

A.ts();
B.ts();

что приведет к тому, ABно вы также можете вызвать методы для экземпляров:

A a = new A();
a.ts();
B b = new B();
b.ts();

что также приведет ABк.

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

A a = new B();
a.ts();

что бы напечатать A. Это может вас удивить, поскольку на самом деле у вас есть объект класса B. Но поскольку вы вызываете его из ссылки типа A, он вызовет A.ts(). Вы можете печатать Bс помощью следующего кода:

A a = new B();
((B)a).ts();

В обоих случаях объект, который у вас есть, действительно принадлежит классу B. Но в зависимости от указателя, указывающего на объект, вы будете вызывать метод from Aили from B.

Теперь предположим, что вы разработчик класса Aи хотите разрешить подклассы. Но вам действительно нужен метод ts(), когда бы он ни вызывался, даже из подкласса, который делал бы то, что вы хотите, а не был бы скрыт версией подкласса. Тогда вы можете сделать это finalи предотвратить его скрытие в подклассе. И вы можете быть уверены, что следующий код вызовет метод из вашего класса A:

B b = new B();
b.ts();

Хорошо, по общему признанию, это каким-то образом сконструировано, но в некоторых случаях это может иметь смысл.

Вы должны вызывать статические методы не в экземплярах, а непосредственно в классах - тогда у вас не будет этой проблемы. Также IntelliJ IDEA, например, покажет вам предупреждение, если вы вызовете статический метод в экземпляре, а также если вы сделаете статический метод final.

Матиас Бадер
источник
0

Метод ts () в B не отменяет метод ts () в A, это просто другой метод. Класс B не видит метод ts () в A, поскольку он статический, поэтому он может объявить свой собственный метод с именем ts ().

Однако, если метод является окончательным, компилятор обнаружит, что в A есть метод ts (), который не следует переопределять в B.

amischiefr
источник
Я не думаю, что это объясняет, почему «final» внезапно означает, что эти методы не могут сосуществовать. Как вы говорите, без «финала» проблем нет. Вы говорите, что это не отменяет, но затем говорите, что проблема в том, что B не может переопределить метод A.
Шон Оуэн,
Конечно, есть, я заявляю, что B не видит метод ts () в A (он «скрыт»), но модификатор final не «скрывает» методы от классов, которые расширяют другой. Но да ладно.
amischiefr
0

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

BalusC
источник
1
Так вы считаете, что статический метод в B заменяет метод в A?
Корай Тугай
@KorayTugay Мне просто интересно, смотрит ли компилятор сначала на потенциально переопределяемый метод (на мгновение игнорирует static), видит ли final при сбое. Просто дикая догадка
Евгений
Balus Я думаю, что это единственный некачественный ответ, который у вас есть в StackOverflow. Учитывая все ваши исключительные ответы, этот не принадлежит им. @BalusC
Корай Тугай
@KorayTugay: в то время у меня не было достаточно репутации, чтобы комментировать :) Если вы ответите, я удалю ответ, без проблем.
BalusC
0

Статический метод не может быть переопределен в Java, в отличие от нестатических методов. Но они наследуются как статические и нестатические элементы данных. Поэтому в родительском классе нельзя создать нестатический метод с таким же именем.

class Writer { 
    public static void doo(){
        System.out.println("sth");
    } 
}
class Author extends Writer{ 
    public void doo(){
        System.out.println("ok"); // error overridden method is static
    }
}

В finalключевых словах гарантирует , что метод конкретного орган будет запускать каждый раз, когда вызов метода. Теперь, если статический метод создается в дочернем классе с тем же именем и выполняется вызов метода, выполняется метод в подклассе, чего не должно быть, если final имеет префикс перед именем статического метода в родительском классе. , Следовательно, ключевое слово final ограничивает создание метода с тем же именем в дочернем классе.

Pratik Gupta
источник