Это плохая идея объявить окончательный статический метод? - PullRequest
36 голосов
/ 19 декабря 2009

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

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();
    }
}

... выведет:

в Фу
в баре

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

в Фу
в Фу

( Редактировать : компиляция завершается неудачно, когда вы помечаете метод как final, и запускается снова только после удаления Bar.method())

Считается ли плохой практикой объявлять статические методы как final, если это мешает подклассам преднамеренно или непреднамеренно переопределять метод?

( это - хорошее объяснение поведения final ..)

Ответы [ 10 ]

36 голосов
/ 19 декабря 2009

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

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

Я весьма удивлен вашим заявлением:

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

в Фу
в Фу

Нет, отметка метода как 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
  }
}
31 голосов
/ 20 декабря 2009

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

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

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

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

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

и вам следует

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

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

6 голосов
/ 20 декабря 2009

Если у меня есть метод 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? Остальное просто перенесите в какой-нибудь служебный класс. Не загромождайте «нормальные» классы методами public static. Таким образом, вам также не нужно отмечать их final.

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

4 голосов
/ 19 декабря 2009

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

3 голосов
/ 19 декабря 2009

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

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

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

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

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

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

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

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

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

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

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

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

  1. нет необходимости создавать интерфейс
  2. нет необходимости создавать абстрактный класс
  3. класс должен быть окончательным
  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);
    }
}
1 голос
/ 03 июля 2012

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

I догадываюсь , что проверка Idea / Netbeans предупреждает вас, потому что она думает, что вы хотите использовать ключевое слово final для оптимизации, и они думают, что вы не знаете об этом что он не нужен современным виртуальным машинам.

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

1 голос
/ 19 декабря 2009

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

0 голосов
/ 21 июля 2013

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

0 голосов
/ 19 декабря 2009

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

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

0 голосов
/ 19 декабря 2009

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

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