Почему я должен использовать ключевое слово "final" для параметра метода в Java? - PullRequest
354 голосов
/ 01 февраля 2009

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

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

Обеспечение того, что некоторые данные остаются постоянными, не так сильно, как кажется.

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

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

Рассмотрим простой пример теста ниже. Этот тест проходит, хотя метод изменил значение заданной ссылки, но не имеет никакого эффекта.

public void testNullify() {
    Collection<Integer> c  = new ArrayList<Integer>();      
    nullify(c);
    assertNotNull(c);       
    final Collection<Integer> c1 = c;
    assertTrue(c1.equals(c));
    change(c);
    assertTrue(c1.equals(c));
}

private void change(Collection<Integer> c) {
    c = new ArrayList<Integer>();
}

public void nullify(Collection<?> t) {
    t = null;
}

Ответы [ 12 ]

217 голосов
/ 02 февраля 2009

Иногда приятно явно (для удобства чтения), что переменная не изменяется. Вот простой пример, где использование final может спасти некоторые возможные головные боли:

public void setTest(String test) {
    test = test;
}

Если вы забыли ключевое слово this в установщике, то переменная, которую вы хотите установить, не будет установлена. Однако, если вы использовали ключевое слово final для параметра, то ошибка будет обнаружена во время компиляции.

213 голосов
/ 30 апреля 2012

Остановить переназначение переменной

Хотя эти ответы интеллектуально интересны, я не прочитал короткий простой ответ:

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

Независимо от того, является ли переменная статической, переменной-членом, локальной переменной или переменной / параметром, эффект полностью одинаков.

Пример

Давайте посмотрим эффект в действии.

Рассмотрим этот простой метод, в котором двум переменным ( arg и x ) могут быть одновременно назначены разные объекты.

// Example use of this method: 
//   this.doSomething( "tiger" );
void doSomething( String arg ) {
  String x = arg;   // Both variables now point to the same String object.
  x = "elephant";   // This variable now points to a different String object.
  arg = "giraffe";  // Ditto. Now neither variable points to the original passed String.
}

Пометить локальную переменную как final . Это приводит к ошибке компилятора.

void doSomething( String arg ) {
  final String x = arg;  // Mark variable as 'final'.
  x = "elephant";  // Compiler error: The final local variable x cannot be assigned. 
  arg = "giraffe";  
}

Вместо этого давайте пометим переменную параметра как final . Это также приводит к ошибке компилятора.

void doSomething( final String arg ) {  // Mark argument as 'final'.
  String x = arg;   
  x = "elephant"; 
  arg = "giraffe";  // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}

Мораль истории:

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

Никогда не переназначать аргументы

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

В ретроспективе

Как отмечено в других ответах ... Учитывая первоначальную цель разработки Java, заключающуюся в том, чтобы помочь программистам избежать глупых ошибок, таких как чтение за концом массива, Java должна была быть спроектирована так, чтобы автоматически приводить все переменные параметра / аргумента как «final». Другими словами, Аргументы не должны быть переменными . Но задним числом является видение 20/20, и дизайнеры Java были заняты в то время.

Еще один случай, добавленный для полноты

public class MyClass {
    private int x;
    //getters and setters
}

void doSomething( final MyClass arg ) {  // Mark argument as 'final'.

   arg =  new MyClass();  // Compiler error: The passed argument variable arg  cannot be re-assigned to another object.

   arg.setX(20); // allowed
  // We can re-assign properties of argument which is marked as final
 }
126 голосов
/ 01 февраля 2009

Да, исключая анонимные классы, читаемость и декларацию намерений, это почти бесполезно. Эти три вещи бесполезны?

Лично я склонен не использовать final для локальных переменных и параметров, если только я не использую переменную в анонимном внутреннем классе, но я, безусловно, вижу смысл тех, кто хочет прояснить, что само значение параметра само по себе не изменится (даже если объект, на который он ссылается, изменит свое содержимое). Для тех, кто считает, что это улучшает читабельность, я думаю, что это вполне разумно.

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

РЕДАКТИРОВАТЬ: Я действительно должен был подвести итог всего этого со ссылкой на Монти Пайтона; вопрос кажется чем-то похожим на вопрос: «Что римляне когда-либо делали для нас?»

75 голосов
/ 01 февраля 2009

Позвольте мне объяснить немного об одном случае, когда у вас есть для использования final, о котором Джон уже упоминал:

Если вы создаете анонимный внутренний класс в своем методе и используете локальную переменную (например, параметр метода) внутри этого класса, то компилятор заставит вас сделать параметр final:

public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
    return new Iterator<Integer>(){
        int index = from;
        public Integer next()
        {
            return index++;
        }
        public boolean hasNext()
        {
            return index <= to;
        }
        // remove method omitted
    };
}

Здесь параметры from и to должны быть окончательными, чтобы их можно было использовать внутри анонимного класса.

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

Итак, вместо этого Java помещает копии этих локальных переменных как скрытые переменные экземпляра в анонимный класс (вы можете увидеть их, если изучите байт-код). Но если они не были окончательными, можно ожидать, что анонимный класс и метод увидят изменения, которые другой вносит в переменную. Чтобы сохранить иллюзию, что существует только одна переменная, а не две копии, она должна быть окончательной.

25 голосов
/ 01 февраля 2009

Я использую final все время по параметрам.

Это так много добавляет? Не совсем.

Я бы ее выключил? Нет.

Причина: я обнаружил 3 ошибки, когда люди написали неаккуратный код и не смогли установить переменную-член в методах доступа. Все ошибки оказалось трудно найти.

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

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

18 голосов
/ 01 февраля 2009

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

8 голосов
/ 01 февраля 2009

Лично я не использую final для параметров метода, потому что это добавляет слишком много беспорядка в списки параметров. Я предпочитаю следить за тем, чтобы параметры метода не изменялись через что-то вроде Checkstyle.

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

Я бы, конечно, хотел что-то более сильное, например, C / C ++ const.

4 голосов
/ 29 октября 2013

Поскольку Java передает копии аргументов, я чувствую, что их актуальность довольно ограничена. Я предполагаю, что это происходит из эры C ++, где вы могли запретить изменение ссылочного содержимого, выполнив const char const *. Я чувствую, что такого рода вещи заставляют вас верить, что разработчик глуп, как f ***, и должен быть защищен от действительно каждого символа, который он печатает. При всей скромности я должен сказать, что я пишу очень мало ошибок, хотя я и пропускаю final, если только я не хочу, чтобы кто-то отвергал мои методы и прочее ... Может, я просто разработчик старой школы ... : -О

1 голос
/ 21 июня 2016

Краткий ответ: final немного помогает, но ... вместо этого используйте защитное программирование на стороне клиента.

Действительно, проблема с final состоит в том, что он только заставляет ссылку оставаться неизменной, к счастью позволяя мутировать объекты-ссылки, которые не были изменены, без ведома вызывающей стороне. Следовательно, наилучшей практикой в ​​этом отношении является защитное программирование на стороне вызывающего, создание глубоко неизменных экземпляров или глубоких копий объектов, которые могут быть ограблены недобросовестными API.

1 голос
/ 07 января 2013

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

private String getString(String A, int i, String B, String C)
{
    if (i > 0)
        A += B;

    if (i > 100)
        A += C;

    return A;
}

Играя адвоката дьявола, что именно не так с этим делать?

...