Когда следует использовать final для параметров метода и локальных переменных?


171

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

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

Это то, что я должен попытаться вспомнить?


Вот связанное сообщение, чтобы просмотреть: stackoverflow.com/questions/137868/…
mattlant


1
Я голосую только потому, что не знал, что можно использовать final в качестве модификатора параметров, прежде чем читать это. Спасибо!
Кип

Ответы:


178

Одержимость над:

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

Рассмотрите, но используйте разумно:

  • Заключительные занятия - дизайн Framework / API - единственный случай, когда я рассматриваю это.
  • Финальные методы - в основном такие же, как финальные классы. Если вы используете шаблоны шаблонных методов, такие как сумасшедшие, и помечаете вещи как финальные, вы, вероятно, слишком полагаетесь на наследование и недостаточно на делегирование.

Проигнорируйте, если не чувствуете анальный

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

  • Редактировать: обратите внимание, что один случай использования, когда конечные локальные переменные на самом деле очень полезны, как упомянуто @ adam-gent, - это когда значение присваивается переменной в ветвях if/ else.


8
Джошуа Блох утверждает, что все классы должны быть определены как окончательные, если они не предназначены для наследования. Я с ним согласен; Я добавляю final к каждому классу, который реализует интерфейс (чтобы иметь возможность создавать модульные тесты). Также отметьте в качестве окончательного все защищенные / классовые методы, которые не будут переопределены.
rmaruszewski

61
При всем уважении к Джошу Блоху (а это немалая сумма) я не согласен с общим делом. В случае создания API обязательно заблокируйте его. Bui внутри вашего собственного кода, возведение стен, которые вам позже придется разрушать, - пустая трата времени.
Алекс Миллер

9
Это определенно не «пустая трата времени», особенно потому, что оно совсем не стоит времени… В приложении я обычно делаю почти все классы finalпо умолчанию. Вы можете не заметить преимущества, если не используете действительно современную Java IDE (то есть IDEA).
Rogério

9
IDEA имеет (из коробки) сотни проверок кода, и некоторые из них могут обнаружить неиспользуемый / ненужный код в finalклассах / методах. Например, если последний метод объявляет, что выбрасывает проверенное исключение, но никогда не выдает его, IDEA сообщит вам об этом, и вы можете удалить исключение из throwsпредложения. Иногда вы также можете найти целые методы, которые не используются, что можно обнаружить, если их нельзя переопределить.
Рожерио

8
@rmaruszewski Пометка классов как окончательных не позволяет большинству фальшивых фреймворков имитировать их, что затрудняет тестирование кода. Я только сделал бы окончательный класс, если было бы критически важно, чтобы он не был расширен.
Майк Риландер

44

Это то, что я должен попытаться вспомнить?

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


3
Отличный совет с Save Action, не знал об этом.
здравомыслие

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

4
Это действительно проблема для вас? Как часто вы на самом деле имели ошибку, которая возникла в результате этого?
Алекс Миллер

1
+1 за полезный совет в Eclipse. Я думаю, что мы должны использовать final как можно больше, чтобы избежать ошибок.
Изумрудный

Вы можете сделать это и в IntelliJ?
Корай Тугай

15

Преимущества «final» во время разработки по меньшей мере так же значительны, как и преимущества времени выполнения. Он рассказывает будущим редакторам кода о ваших намерениях.

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

Маркировка переменной "final" (и присвоение ее в конструкторе) полезна для внедрения зависимостей. Это указывает на «коллаборационную» природу переменной.

Маркировка метода «final» полезна в абстрактных классах. Он четко определяет, где находятся точки расширения.


11

Я использую finalвсе время, чтобы сделать Java более выразительным. Видите, условия Java ( if,else,switch) не основаны на выражениях, которые я всегда ненавидел, особенно если вы привыкли к функциональному программированию (например, ML, Scala или Lisp).

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

Позвольте мне привести Вам пример:

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

Теперь, если добавить еще один caseоператор и не устанавливать, nameкомпилятор не удастся. Компилятор также потерпит неудачу, если вы не нарушите каждый случай (если вы установили переменную). Это позволяет вам сделать Java очень похожим на letвыражения Lisp и делает так, чтобы ваш код не имел больших отступов (из-за лексических переменных области видимости).

И, как заметил @Recurse (но, по-видимому, -1 ме), вы можете сделать предыдущее без создания String name finalошибки, чтобы получить ошибку компилятора (которую я никогда не говорил, что вы не можете), но вы можете легко заставить ошибку компилятора исчезнуть после установки имени параметра оператор, который отбрасывает семантику выражения или, что еще хуже, забывая, breakчто нельзя вызвать ошибку (несмотря на то, что говорит @Recurse) без использования final:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Из-за имени настройки ошибки (помимо того, что я забыл breakеще одну ошибку) я теперь могу случайно сделать это:

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

Последняя переменная требует единственной оценки того, какое имя должно быть. Подобно тому, как функция, имеющая возвращаемое значение, всегда должна возвращать значение (игнорируя исключения), блок переключателя имени должен будет разрешать имя и, таким образом, привязываться к этому блоку переключателя, что упрощает рефакторинг кусков кода (т.е. Eclipe refactor: метод extract) ,

Выше в OCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

В match ... with ...оценивает как функция , т.е. выражения. Обратите внимание, как выглядит наш оператор switch.

Вот пример в Схеме (Ракетка или Цыпленок):

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
За исключением того, что java-компилятор уже выдаст вам ошибку «имя потенциально неинициализировано» и откажется компилировать с финальной версией или без нее.
Рекурс

4
Да, но без финала вы можете сбросить его в любое время. Я действительно видел, как это случилось, когда разработчик превратил else if (...)в if(...)и, таким образом, сбросил переменную. Я показал ему, что никогда бы не случилось с окончательной переменной. В основном finalвынуждает вас назначать переменную один раз и только один раз ... так: P
Адам Гент

9

Я нашел параметры метода маркировки и локальные параметры, которые finalполезны в качестве помощи при рефакторинге, когда рассматриваемый метод представляет собой непонятный беспорядок длиной в несколько страниц. Обильно finalпосыпайте, посмотрите, какие ошибки «не может быть назначено конечной переменной» выдает компилятор (или ваша IDE), и вы можете просто узнать, почему переменная с именем «data» обнуляется, даже если несколько (устаревших) комментариев клянутся, что могут не бывает

Затем вы можете исправить некоторые ошибки, заменив повторно используемые переменные новыми переменными, объявленными ближе к месту использования. Затем вы обнаружите, что можете заключить целые части метода в фигурные скобки, и внезапно вы оказались на расстоянии одного нажатия IDE от «Извлечь метод», и ваш монстр стал более понятным.

Если ваш метод уже не является неисправимым, я полагаю, что было бы целесообразно сделать материал окончательным, чтобы не дать людям превратить его в указанное крушение; но если это короткий метод (см .: не несущественный), то вы рискуете добавить много подробностей. В частности, сигнатуры Java-функций достаточно сложны, чтобы уместиться в 80 символов, без добавления еще шести на аргумент!


1
Последний пункт очень актуален, хотя я давно отказался от ограничения в 80 символов, так как разрешение экрана немного изменилось за последние 10 лет. Я могу легко разместить на экране строку из 300 символов без прокрутки. Тем не менее, удобочитаемость, конечно, лучше без finalкаждого параметра.
Бримбориум

8

Ну, это все зависит от вашего стиля ... если вам нравится видеть финал, когда вы не будете изменять переменную, используйте его. Если вам НЕ НРАВИТСЯ видеть это ... тогда оставьте это.

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

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

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


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


6

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

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

Конечно, в идеальном мире этого не произойдет, но ... ну ... иногда вам приходится поддерживать чужой код. :(


2
Этот метод имеет более серьезные проблемы, чем пропущенный финал. Довольно редко, хотя и не невозможно, есть веская причина, по которой метод настолько загроможден, что могут возникать ошибки такого рода. Небольшая мысль, вложенная в имена переменных, будет иметь большое значение для подобных аварий.
Икаганович

2
Если у вас есть «сотни строк кода» в одном методе, вы можете разбить его на несколько более мелких методов.
Стив Куо

5

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

Если вы пишете какой-то одноразовый код, то нет, не пытайтесь идентифицировать все константы и сделать их окончательными.


4

Я буду использовать final столько, сколько смогу. Это будет помечено, если вы непреднамеренно измените поле. Я также установил параметры метода в финал. При этом я обнаружил несколько ошибок в коде, который я взял на себя, когда они пытаются «установить» параметр, забывая про передачу Java по значению.


2

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

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

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


2

Существует много вариантов использования переменной final. Здесь только несколько

Конечные Константы

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

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

Конечные переменные

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

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

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

Конечные Константы

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

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

Финальные Коллекции

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

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

в противном случае, если вы не установите его как неизменяемый:

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

Финальные классы и Финальные методы не могут быть соответственно расширены или перезаписаны.

РЕДАКТИРОВАТЬ: РАССМОТРЕТЬ ЗАКЛЮЧИТЕЛЬНУЮ ПРОБЛЕМУ КЛАССА ОТНОСИТЕЛЬНО ЭНКАПУЛЯЦИИ

Есть два способа сделать урок финальным. Первый - использовать ключевое слово final в объявлении класса:

public final class SomeClass {
  //  . . . Class contents
}

Второй способ сделать класс final - объявить все его конструкторы как частные:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

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

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

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

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


1

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


1

Если у вас есть внутренние (анонимные) классы, и для метода требуется доступ к переменной содержащего метода, эта переменная должна быть финальной.

Кроме того, то, что вы сказали, правильно.


Теперь Java 8 предлагает гибкость с эффективными конечными переменными.
Равиндра Бабу

0

Используйте finalключевое слово для переменной, если вы делаете эту переменную какimmutable

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

С выпуском java 8 у нас появилась еще одна концепция под названием " effectively final variable". Неокончательная переменная может превращаться в конечную переменную.

локальные переменные, на которые ссылается лямбда-выражение, должны быть окончательными или эффективно окончательными

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

До Java 7 вы не можете использовать не окончательную локальную переменную внутри анонимного класса, но из Java 8 вы можете

Посмотрите на эту статью


-1

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

final int CM_PER_INCH = 2.54;

Вы бы объявили переменную final, потому что сантиметр на дюйм не изменяется.

Если вы попытаетесь переопределить конечное значение, переменная будет объявлена ​​первой. Например:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

Ошибка компиляции выглядит примерно так:

local variable is accessed from inner class, must be declared final

Если ваша переменная не может быть объявлена ​​финальной или вы не хотите объявлять ее финальной, попробуйте следующее:

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

Это напечатает:

Hello World!
A String
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.