Я понимаю что в этом коде:
class Foo {
public static void method() {
System.out.println("in Foo");
}
}
class Bar extends Foo {
public static void method() {
System.out.println("in Bar");
}
}
.. статический метод в Bar
'скрывает' статический метод, объявленный в Foo
, в противоположность переопределению его в смысле полиморфизма.
class Test {
public static void main(String[] args) {
Foo.method();
Bar.method();
}
}
... произведет:
в Foo
в Панели
Переопределение method()
как final
в Foo
отключит способность к Bar
скрыть его, и повторное выполнение main()
произведет:
в Foo
в Foo
(Редактирование: Компиляция перестала работать при маркировке метода как final
, и только выполнения снова, когда я удаляю Bar.method()
)
Это считают плохой практикой для объявления статических методов как final
, если это останавливает подклассы от намеренно или непреднамеренно переопределение метода?
(это - хорошее объяснение какой поведение использования final
..)
Я не считаю плохой практикой отмечать метод static
как final
.
Как вы выяснили, final
предотвратит скрытие метода подклассами , что является очень хорошей новостью imho .
Я очень удивлен вашим утверждением:
Повторное определение метода () как final в Foo отключит способность Bar скрывать его, и повторный запуск main () выведет:
in Foo
в Foo
Нет, пометка метода как final
в Foo
предотвратит компиляцию Bar
. По крайней мере, в Eclipse я получаю:
Исключение в потоке "main" java.lang.Error: Неразрешенная проблема компиляции: невозможно переопределить последний метод из Foo
Кроме того, я думаю, что люди всегда должны вызывать static
, квалифицирующий их с именем класса даже внутри самого класса:
class Foo
{
private static final void foo()
{
System.out.println("hollywood!");
}
public Foo()
{
foo(); // both compile
Foo.foo(); // but I prefer this one
}
}
Статические методы - одна из самых сбивающих с толку функций Java. Есть лучшие практики, чтобы исправить это, и превращение всех статических методов в final
- одна из этих передовых практик!
Проблема со статическими методами заключается в том, что
, поэтому вы должны
final
, и вы не должны
NB: вторая версия вашей программы должна вывести из строя ошибку компиляции. Я полагаю, ваша IDE скрывает этот факт от вас!
Было бы неплохо пометить статические методы как окончательные, особенно если вы разрабатываете структуру, которую вы ожидаете от других. Таким образом, ваши пользователи не будут случайно скрывать ваши статические методы в своих классах. Но если вы разрабатываете фреймворк, возможно, вы захотите сначала избежать использования статических методов.
Если у меня есть общедоступный статический метод
, то он часто уже находится в так называемом служебном классе только с статическим
] методы. Самообъясняющие примеры: StringUtil
, SqlUtil
, IOUtil
и т. Д. Эти служебные классы сами по себе уже объявлены final
и поставляются с конструктором private
. Например,
public final class SomeUtil {
private SomeUtil() {
// Hide c'tor.
}
public static SomeObject doSomething(SomeObject argument1) {
// ...
}
public static SomeObject doSomethingElse(SomeObject argument1) {
// ...
}
}
Таким образом, вы не можете их переопределить.
Если ваш не является служебным классом, то я бы поставил под сомнение значение модификатора public
. Разве это не должно быть частным
? Иначе просто переместите его в какой-нибудь служебный класс. Не загромождайте «нормальные» классы общедоступными статическими
методами. Таким образом, вы тоже не Их нужно пометить final
.
Другой случай - это своего рода абстрактный фабричный класс, который возвращает конкретные реализации себя посредством общедоступного статического
метода. В таком случае имеет смысл пометить метод final
, вы не хотите, чтобы конкретные реализации могли переопределить метод.
Когда я создаю чистые служебные классы, Я объявляю это с помощью частного конструктора, поэтому они не могут быть расширены. При создании обычных классов я объявляю свои методы статическими, если они не используют какие-либо переменные экземпляра класса (или, в некоторых случаях, даже если бы они были, я бы передал аргументы в метод и сделал его статическим, это легче увидеть что делает метод). Эти методы объявлены статическими, но также являются частными - они нужны только для того, чтобы избежать дублирования кода или сделать код более понятным.
При этом я не помню, чтобы столкнулся с случаем, когда у вас есть класс, имеющий общедоступные статические методы, которые могут / должны быть расширены. Но, исходя из того, что здесь сообщалось,
Код не компилируется:
Test.java:8: method () в Bar не может переопределить метод () в Foo; отвергнутый метод статический final тогда нет возможности попытаться переопределить их.
Особые случаи:
Служебные классы (классы со всеми статическими методами):
- объявляют класс как final
- предоставляют ему частный конструктор для предотвращения случайного создания
Если вы хотите иметь статический метод в конкретном или абстрактном классе, который не является частным, вы, вероятно, захотите вместо этого создать служебный класс.
Классы значений (класс, который очень специализирован для хранения данных, например java.awt.Point, где в значительной степени хранятся значения x и y):
- нет необходимости создавать интерфейс
- нет необходимости создать абстрактный класс
- класс должен быть final
- неприватные статические методы подходят, особенно для конструирования, поскольку вы можете захотеть выполнить кэширование.
Если вы последуете приведенному выше совету, вы получите довольно гибкий код, который также имеет довольно четкое разделение ответственности.
Пример класса значений - это класс Location:
import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public final class Location implements Comparable<Location> { // should really use weak references here to help out with garbage collection private static final Map<Integer, Map<Integer, Location>> locations; private final int row; private final int col; static { locations = new HashMap<Integer, Map<Integer, Location>>(); } private Location(final int r, final int c) { if(r < 0) { throw new IllegalArgumentException("r must be >= 0, was: " + r); } if(c < 0) { throw new IllegalArgumentException("c must be >= 0, was: " + c); } row = r; col = c; } public int getRow() { return (row); } public int getCol() { return (col); } // this ensures that only one location is created for each row/col pair... could not // do that if the constructor was not private. public static Location fromRowCol(final int row, final int col) { Location location; Map<Integer, Location> forRow; if(row < 0) { throw new IllegalArgumentException("row must be >= 0, was: " + row); } if(col < 0) { throw new IllegalArgumentException("col must be >= 0, was: " + col); } forRow = locations.get(row); if(forRow == null) { forRow = new HashMap<Integer, Location>(col); locations.put(row, forRow); } location = forRow.get(col); if(location == null) { location = new Location(row, col); forRow.put(col, location); } return (location); } private static void ensureCapacity(final List<?> list, final int size) { while(list.size() <= size) { list.add(null); } } @Override public int hashCode() { // should think up a better way to do this... return (row * col); } @Override public boolean equals(final Object obj) { final Location other; if(obj == null) { return false; } if(getClass() != obj.getClass()) { return false; } other = (Location)obj; if(row != other.row) { return false; } if(col != other.col) { return false; } return true; } @Override public String toString() { return ("[" + row + ", " + col + "]"); } public int compareTo(final Location other) { final int val; if(row == other.row) { val = col - other.col; } else { val = row - other.row; } return (val); } }
Я столкнулся с одним недостатком в использовании методов final с использованием Spring AOP и MVC. Я пытался использовать Spring AOP, вставив крючки безопасности вокруг одного из методов в AbstractFormController, который был объявлен окончательным. Я думаю, что Spring использовала библиотеку bcel для внедрения в классы, и там было некоторое ограничение.
Обычно с утилитными классами - классами, использующими только статические методы - нежелательно использовать наследование. по этой причине можно определить класс как конечный, чтобы другие классы не расширяли его. Это исключит применение конечных модификаторов к методам класса утилиты.