Где может использоваться модификатор final
Перейти к содержимому

Где может использоваться модификатор final

  • автор:

Что означает модификатор final в полях классов?

Я заметил, что профессиональные разработчики нередко объявляют поля классов в джаве как final, например:

@Component public class LinkResolver implements GraphQLResolver  < private final UserRepository userRepository; public LinkResolver(UserRepository userRepository) < this.userRepository = userRepository; >public User postedBy(Link link) < if (link.getUserId() == null) < return null; >return userRepository.findById(link.getUserId()); > > 

Для чего это делается? Я видел, как в проекте final объявляются не только поля сервисов и репозиториев, но и поля дата-классов. Расскажите об этом больше, пожалуйста.

Отслеживать
18.5k 4 4 золотых знака 31 31 серебряный знак 45 45 бронзовых знаков
задан 5 мар 2019 в 9:57
2,427 2 2 золотых знака 30 30 серебряных знаков 47 47 бронзовых знаков
Чтобы можно было только один раз инициализировать переменную, например только в конструкторе
5 мар 2019 в 10:00

4 ответа 4

Сортировка: Сброс на вариант по умолчанию

  1. Удобно чисто визуально. Если видишь, что переменная final , то точно знаешь, что она не будет меняться.
  2. Без final никак, если ты локальную переменную собираешься использовать в анонимных классах/замыканиях.
  3. final — это подсказка компилятору. Если он видит финальные поля, то может произвести определённые оптимизации кода.

Отслеживать
ответ дан 5 мар 2019 в 10:03
Suvitruf — Andrei Apanasik Suvitruf — Andrei Apanasik
32.2k 15 15 золотых знаков 60 60 серебряных знаков 93 93 бронзовых знака
А final, примененное к полям, может иметь какое-то значение при наследовании?
5 мар 2019 в 10:29
@typemoon принципиальной нет. Поля всё также остаются неизменяемыми.
5 мар 2019 в 10:31
в ответе кроется одно коварство, некоторые новички путают final с иммутабельностью.
5 мар 2019 в 10:45
4. final-поля безопасно публикуются в многопоточном окружении
5 мар 2019 в 10:46
@StrangerintheQ ещё про рефлексию можно написать )
5 мар 2019 в 11:20

Многа букав: По-умолчанию ставятся максимальные ограничения. Снимать ограничение (final) с поля имеет смысл только если на это есть причина (поле изменяется).

С помощью final отмечаются поля, которые инициализируются только один раз. Отмечать такое поле как final технически необязательно, но если этого не сделать, то:

  • останется возможность ошибки: разработчик опечатается и переприсвоит значение;
  • при чтении кода возникнут вопросы: «Где это поле изменяется? И как это повлияет на остальной код?». Модификатор final говорит разработчику что лишние сценарии рассматривать не требуется.

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

По аналогичной причине поля, которые не используются вне класса, объявляются как private . Поле с доступом по-умолчанию оставит лишние сценарии использования и, вместе с ними, простор для ошибок и вопросов («Из какого класса к нему идет обращение и зачем?»).

Многие IDE и средства анализа кода (например, PMD) отслеживают поля и переменные, которые могут быть отмечены как final и выдают соответствующие предупреждения.

На практике поля, как важную часть классов, везде где возможно отмечают как final. На локальных переменных и аргументах методов же обычно «экономят», т.к. срок жизни у них короче и риск побочных эффектов меньше. Для них наоборот используют final только там где это явно необходимо (использование в анонимных классах). Например, в Вашем коде аргументы конструктора ( userRepository ) и метода ( link ) могли бы быть отмечены как final , но не отмечены для простоты кода.

Вот так final…

Java-университет

Вот так final… - 1

В java есть ключевое слово – final . Оно может применяться к классам, методам, переменным (в том числе аргументам методов). Для класса это означает, что класс не сможет иметь подклассов, т.е. запрещено наследование. Это полезно при создании immutable (неизменяемых) объектов, например, класс String объявлен, как final .

 public final class String < >class SubString extends String < //Ошибка компиляции >

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

 public class SuperClass < public final void printReport()< System.out.println("Report"); >> class SubClass extends SuperClass < public void printReport()< //Ошибка компиляции System.out.println("MyReport"); >> 

Для переменных примитивного типа это означает, что однажды присвоенное значение не может быть изменено. Для ссылочных переменных это означает, что после присвоения объекта, нельзя изменить ссылку на данный объект. Это важно! Ссылку изменить нельзя, но состояние объекта изменять можно. С java 8 появилось понятие — effectively final . Применяется оно только к переменным (в том числе аргументам методов). Суть в том, что не смотря на явное отсутствие ключевого слова final , значение переменной не изменяется после инициализации. Другими словами, к такой переменной можно подставить слово final без ошибки компиляции. effectively final переменные могут быть использованы внутри локальных классов ( Local Inner Classes ), анонимных классов ( Anonymous Inner Classes ), стримах (Stream API).

 public void someMethod() < // В примере ниже и a и b - effectively final, тк значения устанавливаютcя однажды: int a = 1; int b; if (a == 2) b = 3; else b = 4; // с НЕ является effectively final, т.к. значение изменяется int c = 10; c++; Stream.of(1, 2).forEach(s->System.out.println(s + a)); //Ок Stream.of(1, 2).forEach(s-> System.out.println(s + c)); //Ошибка компиляции > 
  1. Что можно сказать про массив, когда он объявлен final ?
  2. Известно, что класс String — immutable , класс объявлен final , значение строки хранится в массиве char , который отмечен ключевым словом final .
 public final class String implements java.io.Serializable, Comparable, CharSequence < /** The value is used for character storage. */ private final char value[]; 
  1. Т.к. массив – это объект, то final означает, что после присвоения ссылки на объект, уже нельзя ее изменить, но можно изменять состояние объекта.
 final int[] array = ; array[0] = 9; //ок, т.к. изменяем содержимое массива – array = new int[5]; //ошибка компиляции 
 import java.lang.reflect.Field; class B < public static void main(String[] args) throws Exception < String value = "Old value"; System.out.println(value); //Получаем поле value в классе String Field field = value.getClass().getDeclaredField("value"); //Разрешаем изменять его field.setAccessible(true); //Устанавливаем новое значение field.set(value, "JavaRush".toCharArray()); System.out.println(value); /* Вывод: * Old value * JavaRush */ >> 

Обратите внимание, что если бы мы попытались изменить подобным образом финальную переменную примитивного типа, то ничего бы не вышло. Предлагаю вам самостоятельно в этом убедить: создать Java класс, например, с final int полем и попробовать изменить его значение через Reflection API. Всем удачи!

Финальные классы в Java: зачем они нужны?

Есть множество элементов в Java, которые могут вызвать недоумение у новичков. Один из них — final классы. Сам по себе модификатор final означает, что значение не может быть изменено после инициализации. Но что это значит в контексте класса?

Введение в финальные классы

Прежде всего, представьте себе простой класс Animal , который имеет метод makeSound() . Теперь, если мы хотим создать новый класс Dog , который наследуется от Animal , мы можем переопределить метод makeSound() так, чтобы он выводил «Woof!».

class Animal < void makeSound() < System.out.println(". "); >> class Dog extends Animal < @Override void makeSound() < System.out.println("Woof!"); >>

Такова основа полиморфизма — один из ключевых принципов объектно-ориентированного программирования. Но что, если мы хотим, чтобы наш класс Animal всегда выводил «…» при вызове makeSound() , и не хотим, чтобы другие классы могли изменять это поведение? Вот тут-то и приходит на помощь модификатор final .

final class Animal < void makeSound() < System.out.println(". "); >>

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

Значение финальных классов в объектно-ориентированном программировании

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

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

Заключение

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

Обзор всех модификаторов в Java

Модификаторы это ключевые слова в Java, которые "изменяют и регулируют" работу классов, методов и переменных.

27 авг. 2022 · 9 минуты на чтение

Все члены класса в языке Java имеют модификаторы. Модификаторы — это ключевые слова, которые "изменяют и регулируют" работу классов, методов и переменных.

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

Спонсор поста

Прочие модификаторы

Начнём наш разбор с модификаторов, которые нельзя объединить в какую-то группу. Этакие модификаторы одиночки 🙂

Модификатор final

С английского "final" можно перевести как "последний, окончательный". Этот модификатор тем или иным образом защищает от изменений переменные, методы и классы.

Переменная final

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

Для final переменных на уровне класса значение устанавливается сразу или через конструктор.

public class Foo < private final int a = 10; private final Bar bar; public Foo(Bar bar) < this.bar = bar; >> class Bar

В примере выше переменную a мы устанавливаем сразу, а переменную bar в конструкторе.

Изменить значения этих переменных не выйдет (строка 13 и 14). Однако данные внутри объекта могут быть изменены (строка 16). Таким образом, состояние полей объекта, на который ссылается final переменная, изменяемо.

Аргументы и локальные переменные метода также могут быть final . Тогда изменить их тоже не выйдет (строки 4, 5).

public void method(final int a) < final long b = 10; a = 10; // error b = 20; // error >

Помните, что final переменную класса объявляют сразу или в конструкторе. Но на локальную переменную метода это правило не распространяется. Вы можете объявить её, но не инициализировать до момента ее использования.

public void method() < final long b; System.out.println(b); // error >

Такое поведение позволяет инициализировать final переменную по условию. Например, так:

public void method(int a) < final long b; if (a >10) < b = 20; >else < b = 30; >b = 50; // error >
Метод final

Для методов final означает запрет на переопределение в наследниках.

class Foo < final void method() < System.out.println("test"); >> class Bar extends Foo < final void method() < // error System.out.println("test"); >>

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

Класс final

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

final class Foo < >class Bar extends Foo < // error >

Модификатор static

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

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

Этот модификатор не получится применить к конструктору, а также к обычному классу, но к вложенному классу применить static можно.

package p1; class Foo < static String field = "Test"; static String getString() < return "Test two"; >> class Main < public static void main(String[] args) < System.out.println(Foo.field); System.out.println(Foo.getString()); > >

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

Также у вас не получится сделать наследника, так как при наследовании вызывается конструктор класса родителя.

Зачем нужен такой конструктор?

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

Первый — это статические фабричные методы для создания объекта. О них мы говорили в разделе модификатора static . Таким образом, мы скрываем конструктор и оставляем только фабричные методы.

public class Foo < private Foo(int a) < >public static Foo of(int a) < return new Foo(a); >public static Foo doubleCreate(int a) < return new Foo(a * a); >>

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

Класс private

Обратите внимание, что перед самим Foo стоит модификатор доступа public . Поставить private попросту не выйдет. Однако, мы сможем поставить модификатор private у вложенного класса:

public class Foo < private String field; private class Bar < >>
Метод private

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

class Foo < private void privateMethod() < >public void method() < >> class Bar < void method(Foo foo) < foo.method(); // success foo.privateMethod(); // error >>

Приватные методы недоступны для вызова даже у наследника.

class Foo < private void privateMethod() < >> class ChildFoo < void method() < this.privateMethod(); // error >>

Не имеет смысла объявлять метод private final так как private метод не виден в наследниках, соответственно не может быть предопределен.

Переменная private

Повторяет особенности private-метода: у других классов, в том числе у наследников, нет доступа к этому полю.

Модификатор доступа default-package

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

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

Рассмотрим пример использования. У нас есть класс Foo и Bar в одном пакете p1 и класс DifferentPackage в другом пакете.

package p1; public class Foo < int defaultField; Foo(int i, int i2) < >void defaultMethod() < >>
package p1; public class Bar < public void testMethod(Foo foo) < foo.defaultField = 2; // success foo.defaultMethod(); // success foo = new Foo(1); // success >>

В случае с классом Bar никаких ошибок не будет, мы сможем получить доступ и к конструктору, и к методу, и к полю. Но если мы попробуем то же самое сделать в классе DifferentPackage , то столкнёмся с ошибками.

package p2; public class DifferentPackage < public void testMethod(Foo foo) < foo.defaultField = 2; // error foo.defaultMethod(); // error foo = new Foo(1); // error >>

Модификатор доступа protected

Этот модификатор доступа обладает свойствами модификаторов private и default-package . А также позволяет наследникам обращаться к членам класса родителя.

Посмотрим на примере. У нас есть класс Foo в пакете p1 и пакет p2 с классом наследником ChildFoo и классом DifferentPackage .

package p1; public class Foo < protected int defaultField; protected Foo(int i) < >protected void defaultMethod() < >>

C классом DifferentPackage всё так же, как и в прошлом примере. А для ChildFoo теперь есть доступ к полям/методам/конструктору своего родителя.

package p2; import p1.Foo; public class ChildFoo extends Foo < protected ChildFoo(int i) < super(i); >void method() < System.out.println(defaultField); defaultMethod(); >>

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

Модификатор доступа public

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

Шпаргалка для модификаторов доступа переменной

Визуально модификаторы доступа переменной класса можно представить таким образом:

Резюмирую

Модификаторы неотъемлемая базовая составляющая языка Java. Без полного понимания работы всех модификаторов будет сложно продолжать изучать этот язык.

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

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

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *