Действительно ли это - плохая идея объявить заключительный статический метод?

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

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 ..)

37
задан brasskazoo 23 March 2018 в 06:27
поделиться

8 ответов

Я не считаю плохой практикой отмечать метод 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
  }
}
37
ответ дан 27 November 2019 в 04:27
поделиться

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

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

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

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

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

, и вы не должны

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

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

31
ответ дан 27 November 2019 в 04:27
поделиться

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

1
ответ дан 27 November 2019 в 04:27
поделиться

Если у меня есть общедоступный статический метод , то он часто уже находится в так называемом служебном классе только с статическим ] методы. Самообъясняющие примеры: 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 , вы не хотите, чтобы конкретные реализации могли переопределить метод.

6
ответ дан 27 November 2019 в 04:27
поделиться

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

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

0
ответ дан 27 November 2019 в 04:27
поделиться

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

Test.java:8: method () в Bar не может переопределить метод () в Foo; отвергнутый метод статический final тогда нет возможности попытаться переопределить их.

Особые случаи:

Служебные классы (классы со всеми статическими методами):

  1. объявляют класс как final
  2. предоставляют ему частный конструктор для предотвращения случайного создания

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

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

  1. нет необходимости создавать интерфейс
  2. нет необходимости создать абстрактный класс
  3. класс должен быть final
  4. неприватные статические методы подходят, особенно для конструирования, поскольку вы можете захотеть выполнить кэширование.

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

Пример класса значений - это класс 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);
    }
}
3
ответ дан 27 November 2019 в 04:27
поделиться

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

0
ответ дан 27 November 2019 в 04:27
поделиться

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

4
ответ дан 27 November 2019 в 04:27
поделиться
Другие вопросы по тегам:

Похожие вопросы: