Когда следует использовать final для параметров метода и локальных переменных? - PullRequest
157 голосов
/ 30 сентября 2008

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

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

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

Ответы [ 16 ]

163 голосов
/ 30 сентября 2008

Одержимость более:

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

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

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

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

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

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

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

15 голосов
/ 30 сентября 2008

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

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

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

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

9 голосов
/ 01 октября 2008

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

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

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

8 голосов
/ 17 сентября 2013

Я все время использую 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 в Лиспе и делает так, чтобы ваш код не имел больших отступов (из-за лексических переменных области видимости).

И, как заметил @Recurse (но, по-видимому, -1 ме), вы можете выполнить предыдущее, не делая String name final, чтобы получить ошибку компилятора (чего я никогда не говорил, что вы не могли), но вы могли бы легко сделать Ошибка компилятора исчезает после имени оператора switch, который отбрасывает семантику выражения или, что еще хуже, забывая 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"]))
6 голосов
/ 01 октября 2008

Это полезно в параметрах, чтобы избежать случайного изменения значения параметра и внести небольшую ошибку. Я использую, чтобы игнорировать эту рекомендацию, но потратив около 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 ) { ...

 }

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

6 голосов
/ 30 сентября 2008

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

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

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

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


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

5 голосов
/ 30 сентября 2008

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

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

4 голосов
/ 30 сентября 2008

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

2 голосов
/ 21 июня 2013

Существует много вариантов использования переменной 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

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

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

Есть два способа сделать урок финальным. Первый - использовать ключевое слово 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 является хорошим примером того, как неявные конечные классы могут вызывать проблемы.

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

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