Это плохая идея объявить окончательный статический метод?

Я понимаю, что в этом коде:

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 есть..)

Ответ 1

Я не считаю плохую практику отмечать метод static как final.

Как вы узнали, final предотвратит скрытие метода подклассами , что является очень хорошей новостью imho.

Я очень удивлен вашим выражением:

Переопределяющий метод() как final в Foo отключит способность Bar скрывать его, а повторный запуск main() будет выводить:

в 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
  }
}

Ответ 2

Статические методы - одна из наиболее запутанных функций Java. Лучшая практика заключается в том, чтобы исправить это, и создание всех статических методов final является одной из этих лучших практик!

Проблема со статическими методами заключается в том, что

  • они не являются методами класса, но глобальные функции с префиксом classname
  • Странно, что они "унаследованы" к подклассам
  • Удивительно, что они не могут быть переопределены, но скрыты.
  • полностью нарушено, что они могут быть вызваны с экземпляром в качестве приемника

поэтому вы должны

  • всегда называют их своим классом в качестве получателя
  • всегда вызывать их с объявляющим классом только в качестве получателя
  • всегда делают их (или объявляющий класс) final

и вы должны

  • никогда не вызывайте их с экземпляром в качестве получателя
  • никогда не называть их подклассом их объявляющего класса в качестве получателя
  • никогда не переопределять их в подклассах

 

NB: вторая версия вашей программы должна завершиться ошибкой компиляции. Я полагаю, что ваша IDE скрывает этот факт от вас!

Ответ 3

Если у меня есть метод public static, он часто уже находится в так называемом классе утилиты только с методами static. Самоочевидными примерами являются 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. Разве это не должно быть private? Else просто переместите его в какой-то класс утилиты. Не кладите "нормальные" классы с помощью методов public static. Таким образом, вам также не нужно отмечать их final.

Другой случай - это своего рода абстрактный класс factory, который возвращает конкретные реализации self через метод public static. В этом случае было бы совершенно целесообразно отметить метод final, вы не хотите, чтобы конкретные реализации могли переопределить метод.

Ответ 4

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

Ответ 5

Код не компилируется:

Test.java:8: метод() в баре не может переопределить метод() в Foo; переопределены метод является статическим окончательным     public static void method() {

Сообщение вводит в заблуждение, поскольку статический метод по определению никогда не может быть переопределен.

Я делаю следующее при кодировании (не 100% все время, но ничего здесь не "неправильно":

(Первый набор "правил" выполняется для большинства вещей - некоторые специальные случаи рассматриваются после)

  • создать интерфейс
  • создать абстрактный класс, реализующий интерфейс
  • создавать конкретные классы, расширяющие абстрактный класс
  • создавать конкретные классы, которые реализуют интерфейс, но не расширяют абстрактный класс
  • всегда, если возможно, сделать все переменные/константы/параметры интерфейса

Поскольку интерфейс не может иметь статические методы, вы не завершаете проблему. Если вы собираетесь создавать статические методы в абстрактном классе или конкретных классах, они должны быть частными, тогда нет способа их переопределить.

Специальные случаи:

Классы полезности (классы со всеми статическими методами):

  • объявить класс окончательным
  • предоставить ему частный конструктор для предотвращения случайного создания

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

Классы значений (класс, который очень специализирован для существенного хранения данных, например java.awt.Point, где он в значительной степени поддерживает значения x и y):

  • Не нужно создавать интерфейс
  • Не нужно создавать абстрактный класс
  • класс должен быть окончательным
  • Неличные статические методы в порядке, особенно для построения, поскольку вы можете выполнять кеширование.

Если вы будете следовать приведенному выше совету, вы получите довольно гибкий код, который также имеет довольно чистое разделение обязанностей.

Класс класса примера - это класс 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);
    }
}

Ответ 6

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

Ответ 7

Большая часть этой проблемы final относится ко времени, когда виртуальные машины были довольно глупыми/консервативными. Тогда, если вы отметили метод final, это означало (между прочим), что VM может встроить его, избегая вызовов методов. Это не случай с долгого (или длинного двойного: P) времени: http://java.sun.com/developer/technicalArticles/Networking/HotSpot/inlining.html.

I Угадать, что инспекция "Идея/Netbeans" предупреждает вас, потому что она думает, что вы хотите использовать ключевое слово final для оптимизации, и они думают, что вы не знаете о том, что она не нужна современные ВМ.

Только мои два цента...

Ответ 8

Я столкнулся с одним из последствий использования конечных методов с использованием Spring AOP и MVC. Я пытался использовать Spring AOP, помещенный в блокировки безопасности вокруг одного из методов в AbstractFormController, который был объявлен окончательным. Я думаю, что Spring использовал библиотеку bcel для инъекций в классах, и там было некоторое ограничение.

Ответ 9

Когда я создаю классы чистой утилиты, я объявляю тогда с помощью частного конструктора, чтобы они не могли быть расширены. При создании обычных классов я объявляю свои методы статическими, если они не используют какие-либо переменные экземпляра класса (или, в некоторых случаях, даже если бы они были, я передавал бы аргументы в методе и делал бы его статическим, его было легче увидеть что делает этот метод). Эти методы объявляются статическими, но также частными - они существуют только для избежания дублирования кода или для облегчения понимания кода.

Это, как говорится, я не помню, чтобы работать в случае, когда у вас есть класс, который имеет общедоступные статические методы и который может/должен быть расширен. Но, основываясь на том, что было сообщено здесь, я бы объявил свои статические методы окончательными.

Ответ 10

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