Когда использовать ключевое слово final на полях класса?
Я понимаю смысл использования «финальных» полей для констант. Иногда я встречаю, что программисты используют ключевое слово final на обычных полях (не константах). Вот пример с книги «Spring в действии»:
@Controller
@RequestMapping("/design")
@SessionAttributes("order")
public class DesignTacoController {
private final IngredientRepository ingredientRepo;
@Autowired
public DesignTacoController(IngredientRepository ingredientRepo) {
this.ingredientRepo = ingredientRepo;
}
@GetMapping
public String showDesignForm(Model model) {
List<Ingredient> ingredients = new ArrayList<>();
ingredientRepo.findAll().forEach(i -> ingredients.add(i));
Type[] types = Ingredient.Type.values();
for (Type type : types) {
model.addAttribute(type.toString().toLowerCase(),
filterByType(ingredients, type));
}
return "design";
}
...
}
Почему поле |ingredientRepo| с ключевым словом final, ведь это не константа?
Из этого всего мне постигли вопросы про использование final на полях класса:
В каких случаях нужно помечать финальными обычные поля класса? Какое преимущество мы получим?
Ответы (4 шт):
Если Вы хотите, чтобы никто не изменил поле, используйте final. Нельзя изменить только ссылку на объект, но свойства объекта могут быть изменены.
Если поле объявлено как final, то оно должно быть инициализировано при объявлении или в конструкторе.
Если кто-то попытается изменить значение final поля, то получит ошибку компилятора "cannot assign a value to final variable.."
Конкретно в Spring (в связке с Lombok) часто используется final для полей класса, которые нужно заинжектить.
То есть поле помечается модификаторами private final, а над классом ставится аннотация @RequiredArgsConstructor. В этом случае финализированные поля - обязательное требование! Без этого бины не будут автоматически инжектиться.
Подробнее про эту конструкцию:
Есть несколько причин использовать final для полей класса, которые не являются константами.
- Зная, что это поле не меняется никогда, компилятор может сгенерировать более оптимальный код.
- Это служит документацией для тех, кто будет читать ваш код.
- Поле final используется для безопасной публикации изменений в многопоточном окружении. Спецификация языка java (если более точно то java memory model) гарантирует, что final поле включая все другие поля доступные через final поле можно безопасно читать в других потоках не используя явной синхронизации. Безопасно в данном случае означает видеть полностью сконструированный и проинициализированный объект, не подверженный проблемам возникающих из-за переупорядочивания операций записи и чтения. Об этом чуть ниже подробней.
По последнему пункту, думаю, нужны пояснения.
Вот что говорит по этому поводу спецификация языка java:
final fields also allow programmers to implement thread-safe immutable objects without synchronization. A thread-safe immutable object is seen as immutable by all threads, even if a data race is used to pass references to the immutable object between threads. This can provide safety guarantees against misuse of an immutable class by incorrect or malicious code. final fields must be used correctly to provide a guarantee of immutability.
An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.
И пример из той же спецификации:
class FinalFieldExample {
final int x;
int y;
static FinalFieldExample f;
public FinalFieldExample() {
x = 3;
y = 4;
}
static void writer() {
f = new FinalFieldExample();
}
static void reader() {
if (f != null) {
int i = f.x; // гарантированно увидит 3
int j = f.y; // может увидеть 0
}
}
}
И комментарий к нему (перевод мой):
В классе FinalFieldExample есть final поле типа int поле
xи не-final поле типа inty. Один поток исполняем метод writer, а другой исполняет метод reader.Так как метод
writerзаписывает в f после того, как конструктор объекта закончил выполнения, гарантируется что методreaderувидит правильно инициализированное значениеf.x: будет прочитано значение 3. Полеf.yне final, так что нет гарантии, что методreaderувидит значение 4 присвоенное ему.
То есть если для поля не использовать final, то другой поток (который не вызывал конструктор и обращается к полю) может увидеть не до конца сконструированное состояние объекта. Чтоб этого не случилось нужно будет или делать явную синхронизацию или, что более просто, использовать final.
В случаях, когда требуется их инициализировать один раз при создании объекта класса. Причем значение можно присвоить только один раз.
После того, как объект создан, значение изменить нельзя, то есть в методах можно только читать это значение.
Если все переменные класса финальные, то он становится немутируемым и не должен содержать сеттеров.
Вот что можно добавить:
Когда переменная объявляется с ключевым словом
final, ее значение не может быть изменено. Это также означает, что вы должны инициализировать эту переменную, иначе компилятор выдаст ошибку во время компиляции.Финальная переменная может быть инициализирована только один раз, либо с помощью инлайн инициализатора, либо с помощью оператора присваивания.
Есть три способа инициализировать финальную переменную:
- Вы можете инициализировать финальную переменную при ее объявлении. Этот подход является наиболее распространенным.
Конечная переменная называется пустой финальной переменной, если она не инициализирована при объявлении.
Пустая конечная переменная может быть инициализирована:
внутри блока инициализатора экземпляра;
внутри конструктора.
Если в классе более одного конструктора, тогда она должна быть инициализирован во всех из них, иначе будет выдана ошибка во время компиляции.
Пустая финальная статическая переменная может быть инициализирована внутри статического блока.