Почему я должен использовать ключевое слово "final" для параметра метода в Java?

Я не могу понять, где ключевое слово 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;
}

Ответ 1

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

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

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

Ответ 2

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

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

Используйте ключевое слово 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.
}

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

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";  
}

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

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
 }

Ответ 3

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

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

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

EDIT: я должен был бы все это суммировать с помощью ссылки Monty Python; вопрос кажется несколько похожим на вопрос: "Что римляне сделали для нас?"

Ответ 4

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

Если вы создаете анонимный внутренний класс в своем методе и используете локальную переменную (такую ​​как параметр метода) внутри этого класса, компилятор заставляет вас сделать параметр 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 вместо этого ставит копии этих локальных переменных в скрытые переменные экземпляра в анонимный класс (вы можете увидеть их, если вы изучите код байта). Но если они не были окончательными, можно было бы ожидать, что анонимный класс и метод, видя изменения, другой, внесенный в переменную. Чтобы сохранить иллюзию, что существует только одна переменная, а не две копии, она должна быть окончательной.

Ответ 5

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

Значит ли это так много? Не совсем.

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

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

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

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

Ответ 6

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

Ответ 7

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

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

Мне бы, конечно, понравилось нечто вроде C/С++ const.

Ответ 8

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

Ответ 9

Я никогда не использую 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;
}

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

Ответ 10

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

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

Ответ 11

Еще одна причина добавления окончательных объявлений параметров заключается в том, что он помогает идентифицировать переменные, которые необходимо переименовать как часть рефакторинга "Метод извлечения". Я обнаружил, что добавление финала к каждому параметру перед началом рефакторинга большого метода быстро говорит мне, есть ли какие-либо проблемы, которые мне нужно решить, прежде чем продолжить.

Однако я обычно удаляю их как лишние в конце рефакторинга.

Ответ 12

Следите за тем, что сообщение Michel. Я сам сделал себе еще один пример, чтобы объяснить это. Надеюсь, это поможет.

public static void main(String[] args){
    MyParam myParam = thisIsWhy(new MyObj());
    myParam.setArgNewName();

    System.out.println(myParam.showObjName());
}

public static MyParam thisIsWhy(final MyObj obj){
    MyParam myParam = new MyParam() {
        @Override
        public void setArgNewName() {
            obj.name = "afterSet";
        }

        @Override
        public String showObjName(){
            return obj.name;
        }
    };

    return myParam;
}

public static class MyObj{
    String name = "beforeSet";
    public MyObj() {
    }
}

public abstract static class MyParam{
    public abstract void setArgNewName();
    public abstract String showObjName();
}

Из приведенного выше кода в методе thisIsWhy() мы фактически не присваивали [аргумент MyObj obj] реальная ссылка в MyParam. Вместо этого мы просто используем [аргумент MyObj obj] в методе внутри MyParam.

Но после того, как мы закончим метод thisIsWhy(), , должен ли существовать аргумент (объект) MyObj?

Похоже, должно быть, потому что мы видим, что в основном мы все еще вызываем метод showObjName(), и ему нужно достичь obj. MyParam все еще использует/достигает аргумента метода, даже если метод уже возвращался!

Как на самом деле реализовать Java, это сгенерировать копию, также является скрытой ссылкой аргумента MyObj obj внутри объекта MyParam (но это не формальное поле в MyParam, так что мы не можем видеть это)

Как мы называем "showObjName", он будет использовать эту ссылку для получения соответствующего значения.

Но если мы не поместили аргумент final, который приведет к ситуации, мы можем переназначить новую память (объект) на аргумент MyObj obj.

Технически нет никакого столкновения! Если нам разрешено это сделать, ниже будет ситуация:

  • Теперь у нас есть скрытая точка [MyObj obj] к [Память A в куче], теперь живущая в объекте MyParam.
  • У нас также есть еще один [MyObj obj], который является аргументом, указывающим на [Память B в куче], теперь живущим в этом методе IsWhy.

Нет столкновений, но "ПОДТВЕРЖДЕНИЕ!!" . Поскольку все они используют одно и то же "имя ссылки", которое является "obj" .

Чтобы этого избежать, установите его как "final", чтобы избежать программирования с помощью кода, "подверженного ошибкам".