Как работает ключевое слово «final» в Java? (Я все еще могу изменить объект.)

В Java мы используем final ключевое слово с переменными, чтобы указать его значения, которые нельзя изменить. Но я вижу, что вы можете изменить значение в конструкторе / методах classа. Опять же, если переменная static то это ошибка компиляции.

Вот код:

 import java.util.ArrayList; import java.util.List; class Test { private final List foo; public Test() { foo = new ArrayList(); foo.add("foo"); // Modification-1 } public static void main(String[] args) { Test t = new Test(); t.foo.add("bar"); // Modification-2 System.out.println("print - " + t.foo); } } 

Над кодом работает нормально и никаких ошибок.

Теперь измените переменную как static :

 private static final List foo; 

Теперь это ошибка компиляции. Как этот final действительно работает?

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

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

Java не имеет понятия о неизменности объекта; это достигается путем тщательного проектирования объекта и является далеким тривиальным делом.

Это любимый вопрос интервью . С этими вопросами интервьюер пытается выяснить, насколько хорошо вы понимаете поведение объектов в отношении конструкторов, методов, переменных classа (статических переменных) и переменных экземпляра.

 import java.util.ArrayList; import java.util.List; class Test { private final List foo; public Test() { foo = new ArrayList(); foo.add("foo"); // Modification-1 } public void setFoo(List foo) { //this.foo = foo; Results in compile time error. } } 

В приведенном выше случае мы определили конструктор для «Test» и дали ему метод «setFoo».

О конструкторе: конструктор может быть вызван только один раз для создания объекта с помощью new ключевого слова. Вы не можете вызывать конструктор несколько раз, потому что конструктор не предназначен для этого.

О методе: метод можно вызывать столько раз, сколько вы хотите (даже никогда), и компилятор знает об этом.

Сценарий 1

 private final List foo; // 1 

foo – переменная экземпляра . Когда мы создаем объект Test class, тогда переменная экземпляра foo будет скопирована внутри объекта classа Test . Если мы назначим foo внутри конструктора, то компилятор знает, что конструктор будет вызван только один раз, поэтому нет проблем с его назначением внутри конструктора.

Если мы назначим foo внутри метода, компилятор знает, что метод можно вызвать несколько раз, что означает, что значение нужно будет менять несколько раз, что не допускается для final переменной. Поэтому компилятор решает, что конструктор – хороший выбор! Вы можете присвоить значение конечной переменной только один раз.

Сценарий 2

 private static final List foo = new ArrayList(); 

foo теперь статическая переменная. Когда мы создаем экземпляр classа Test , foo не будет скопирован в объект, потому что foo является статическим. Теперь foo не является независимым свойством каждого объекта. Это свойство classа Test . Но foo можно увидеть несколькими объектами, и если каждый объект создается с помощью new ключевого слова, которое в конечном итоге вызовет конструктор Test который изменит значение во время создания нескольких объектов (помните, что static foo не копируется в каждом объекте, но разделяется между несколькими объектами.)

Сценарий 3

 t.foo.add("bar"); // Modification-2 

Выше Modification-2 из вашего вопроса. В приведенном выше случае вы не меняете первый ссылочный объект, но добавляете контент внутри foo который разрешен. Компилятор жалуется , если вы попытаетесь назначить new ArrayList() к foo ссылочной переменной.
Правило Если вы инициализировали final переменную, вы не можете ее изменить, чтобы ссылаться на другой объект. (В этом случае ArrayList )

конечные classы не могут быть подclassами
окончательные методы не могут быть переопределены. (Этот метод находится в суперclassе)
конечные методы могут переопределяться. (Прочтите это грамматически, этот метод находится в подclassе)

Конечное ключевое слово имеет множество способов:

  • Окончательный class нельзя подclassифицировать.
  • Окончательный метод не может быть переопределен подclassами
  • Окончательная переменная может быть инициализирована только один раз

Другое использование:

  • Когда внутри тела метода определен анонимный внутренний class, все переменные, объявленные окончательными в объеме этого метода, доступны изнутри внутреннего classа

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

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

Типы значений: для int s, double s и т. Д. Он гарантирует, что значение не может измениться,

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

Таким образом, final List foo; гарантирует, что foo всегда ссылается на один и тот же список, но содержимое указанного списка может меняться со временем.

Если вы ставите foo static, вы должны инициализировать его в конструкторе classа (или внутри, где вы его определяете), как в следующих примерах.

Конструктор classов (не экземпляр):

 private static final List foo; static { foo = new ArrayList(); } 

В очереди:

 private static final List foo = new ArrayList(); 

Проблема здесь заключается не в том, как работает final модификатор, а в том, как работает static модификатор.

final модификатор принудительно инициализирует вашу ссылку к моменту завершения вызова вашего конструктора (т. Е. Вы должны инициализировать его в конструкторе).

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

  • если foo является static , foo = new ArrayList() будет выполняться до того, как будет создан static{} конструктор static{} вы определили для своего classа
  • если foo не является static , foo = new ArrayList() будет выполняться до запуска вашего конструктора

Когда вы не инициализируете атрибут в строке, final модификатор принуждает вас инициализировать его и что вы должны сделать это в конструкторе. Если у вас также есть static модификатор, конструктор, которому вы должны будете инициализировать атрибут, является блок инициализации classа: static{} .

Ошибка, которую вы получаете в своем коде, связана с тем, что static{} запускается при загрузке classа до момента создания объекта этого classа. Таким образом, вы не инициализировали foo при создании classа.

Подумайте о static{} блоке static{} как конструкторе для объекта типа Class . Здесь вы должны выполнить инициализацию своих static final атрибутов static final classа (если не сделать inline).

Примечание:

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

Когда вы объявляете final объект, то, что вы получаете, является final ссылкой на этот объект, но сам объект не является постоянным.

То, что вы действительно достигаете при объявлении final атрибута, заключается в том, что после объявления объекта для вашей конкретной цели (например, для final List который вы объявили), этот и только тот объект будет использоваться для этой цели: вы не сможете для изменения List foo в другой List , но вы все равно можете изменить свой List , добавив / удалив элементы ( List вы используете, будет таким же, только с измененным содержимым).

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

1) Когда кто-то упоминает конечный объект, это означает, что ссылка не может быть изменена, но ее состояние (переменные экземпляра) можно изменить.

2) Неизменяемым объектом является тот, чье состояние не может быть изменено, но его ссылка может быть изменена. Пример:

  String x = new String("abc"); x = "BCG"; 

переменная переменной x может быть изменена, чтобы указать другую строку, но значение «abc» не может быть изменено.

3) Переменные экземпляра (нестатические поля) инициализируются при вызове конструктора. Таким образом, вы можете инициализировать значения для переменных внутри конструктора.

4) «Но я вижу, что вы можете изменить значение в конструкторе / методах classа». – Вы не можете изменить его внутри метода.

5) Статическая переменная инициализируется во время загрузки classа. Поэтому вы не можете инициализировать внутри конструктора, это нужно сделать еще до этого. Поэтому вам нужно назначать значения статической переменной во время самой декларации.

Стоит упомянуть некоторые простые определения:

Классы / Методы

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

переменные

Как только final переменная была инициализирована, она всегда содержит одно и то же значение.

final основном избегают перезаписывать / подписываться ничем (подclassы, переменная «переназначить»), в зависимости от случая.

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

final – зарезервированное ключевое слово в Java для ограничения пользователя и его можно применять к переменным-членам, методам, classам и локальным переменным. Заключительные переменные часто объявляются с ключевым словом static в Java и рассматриваются как константы. Например:

 public static final String hello = "Hello"; 

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

Например:

 public class ClassDemo { private final int var1 = 3; public ClassDemo() { ... } } 

Примечание . Класс, объявленный как final, не может быть расширен или унаследован (т. Е. Не может быть подclassа суперclassа). Также полезно отметить, что методы, объявленные как final, не могут быть переопределены подclassами.

Преимущества использования ключевого слова final рассматриваются в этом streamе .

final ключевое слово в java используется для ограничения пользователя. Ключевое слово java final можно использовать во многих контекстах. Финал может быть:

  1. переменная
  2. метод
  3. class

Ключевое слово final может применяться с переменными, final переменная, которая не имеет значения, называется пустой final переменной или неинициализированной final переменной. Его можно инициализировать только в конструкторе. Чистая final переменная может быть также static которая будет инициализирована только в static блоке.

Конечная переменная Java:

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

Пример final переменной

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

 class Bike9{ final int speedlimit=90;//final variable void run(){ speedlimit=400; // this will make error } public static void main(String args[]){ Bike9 obj=new Bike9(); obj.run(); } }//end of class 

Итоговый class Java:

Если вы сделаете какой-либо class final , вы не сможете его продлить .

Пример конечного classа

 final class Bike{} class Honda1 extends Bike{ //cannot inherit from final Bike,this will make error void run(){ System.out.println("running safely with 100kmph"); } public static void main(String args[]){ Honda1 honda= new Honda(); honda.run(); } } 

Окончательный метод Java:

Если вы сделаете какой-либо метод окончательным, вы не сможете его переопределить .

Пример final метода (run () в Honda не может переопределить run () в Bike)

 class Bike{ final void run(){System.out.println("running");} } class Honda extends Bike{ void run(){System.out.println("running safely with 100kmph");} public static void main(String args[]){ Honda honda= new Honda(); honda.run(); } } 

поделился с: http://www.javatpoint.com/final-keyword

Когда вы делаете его статическим окончанием, он должен быть инициализирован в статическом блоке инициализации

  private static final List foo; static { foo = new ArrayList(); } public Test() { // foo = new ArrayList(); foo.add("foo"); // Modification-1 } 

final ключевое слово указывает, что переменная может быть инициализирована только один раз. В коде вы выполняете только одну инициализацию финала, чтобы условия выполнялись. Это утверждение выполняет одиночную инициализацию foo . Обратите внимание, что final ! = Immutable, это означает только, что ссылка не может измениться.

 foo = new ArrayList(); 

Когда вы объявляете foo как static final переменная должна быть инициализирована при загрузке classа и не может полагаться на экземпляр (ака вызов конструктору) для инициализации foo поскольку статические поля должны быть доступны без экземпляра classа. Нет гарантии, что конструктор будет вызван до использования статического поля.

Когда вы выполняете свой метод под static final сценарием, class Test загружается до создания экземпляра t в это время нет экземпляра foo означающего, что он не был инициализирован, поэтому foo установлен по умолчанию для всех объектов, которые являются null . На этом этапе я предполагаю, что ваш код NullPointerException при попытке добавить элемент в список.

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

Прочтите все ответы.

Существует еще один случай пользователя, в котором может использоваться final ключевое слово, т. Е. В аргументе метода:

 public void showCaseFinalArgumentVariable(final int someFinalInt){ someFinalInt = 9; // won't compile as the argument is final } 

Может использоваться для переменной, которая не должна изменяться.

Я подумал о написании обновленного и подробного ответа здесь.

ключевое слово final может использоваться в нескольких местах.

  1. classы

final class означает, что ни один другой class не может распространять этот последний class. Когда Java Run Time ( JRE ) знает ссылку на объект в типе конечного classа (скажем F), он знает, что значение этой ссылки может быть только в типе F.

Пример:

 F myF; myF = new F(); //ok myF = someOther; //someOther cannot be in type of a child class of F. //because F cannot be extended. 

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

  1. методы

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

  1. поля, локальные переменные, параметры метода

Если один из указанных выше как final , это означает, что значение уже завершено, поэтому значение не может быть изменено .

Пример:

Для полей локальные параметры

 final FinalClass fc = someFC; //need to assign straight away. otherwise compile error. final FinalClass fc; //compile error, need assignment (initialization inside a constructor Ok, constructor can be called only once) final FinalClass fc = new FinalClass(); //ok fc = someOtherFC; //compile error fc.someMethod(); //no problem someOtherFC.someMethod(); //no problem 

Для параметров метода

 void someMethod(final String s){ s = someOtherString; //compile error } 

Это просто означает, что значение final ссылочного значения не может быть изменено. т.е. допускается только одна инициализация. В этом сценарии во время выполнения, поскольку JRE знает, что значения не могут быть изменены, он загружает все эти окончательные значения (конечных ссылок) в кеш L1 . Потому что ему не нужно снова и снова загружать из основной памяти . В противном случае он загружается в кэш L2 и время от времени загружает из основной памяти. Таким образом, это также улучшает производительность.

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

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

Прежде всего, место в вашем коде, где вы инициализируете (т.е. присваиваете в первый раз) foo, находится здесь:

 foo = new ArrayList(); 

foo – это объект (с типом List), поэтому он является ссылочным типом, а не типом значения (например, int). Таким образом, он содержит ссылку на ячейку памяти (например, 0xA7D2A834), где хранятся элементы списка. Линии, подобные этому

 foo.add("foo"); // Modification-1 

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

 foo = new ArrayList(); 

Это даст вам ошибку компиляции.


Теперь, с учетом этого, подумайте о том, что происходит, когда вы добавляете ключевое слово static .

Если у вас нет статического ключевого слова, каждый объект, который создает экземпляр classа, имеет свою собственную копию foo. Следовательно, конструктор присваивает значение пустой, свежей копии переменной foo, что отлично.

Однако, когда у вас есть статическое ключевое слово, в памяти есть только одно foo, связанное с classом. Если вам нужно создать два или более объектов, конструктор будет пытаться повторно назначить один foo каждый раз, нарушая последнее ключевое слово.

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

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

 class Main { public static void main(String args[]){ final int i = 20; i = 30; //Compiler Error:cannot assign a value to final variable i twice } } 

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

Заключительные classы Окончательный class не может быть расширен (унаследован)

 final class Base { } class Derived extends Base { } //Compiler Error:cannot inherit from final Base public class Main { public static void main(String args[]) { } } 

Конечные методы Окончательный метод не может быть переопределен подclassами.

 //Error in following program as we are trying to override a final method. class Base { public final void show() { System.out.println("Base::show() called"); } } class Derived extends Base { public void show() { //Compiler Error: show() in Derived cannot override System.out.println("Derived::show() called"); } } public class Main { public static void main(String[] args) { Base b = new Derived();; b.show(); } } 
  • Числовой текстовый фильтр для целых чисел в JavaFX 8 с TextFormatter и / или UnaryOperator
  • Вычисление частоты каждого слова в предложении в java
  • Что означает «изменчивость» в Java?
  • POI. Как установить значение ячейки в Date и применить формат даты по умолчанию Excel?
  • Как аннотировать поле автоинкремента MYSQL с аннотациями JPA
  • Управление зависимостями DLL с Maven
  • java native Тайм-аут процесса
  • Поворот изображения в java на указанный угол
  • Не удается загрузить файл свойств из каталога ресурсов
  • Hibernate 3.5.x: NoSuchMethodError: javax.persistence.OneToMany.orphanRemoval
  • Можно ли добавить Легенду к сюжету в JFreeChart?
  • Interesting Posts

    Каковы некоторые из «лучших» кросс-платформенных инструментов C ++ UI сегодня?

    Проверьте, является ли строка действительным целым числом

    Цепь команды Fish с помощью `&&` или `||`

    Как действительно работает команда «ping»?

    В чем преимущества использования ExecutorService?

    Хостинг веб-сайта у себя дома: порт блокирования ISP 80?

    Почему Spring’s ApplicationContext.getBean считается плохим?

    В чем разница между sparse_softmax_cross_entropy_with_logits и softmax_cross_entropy_with_logits?

    Угловая ошибка DI – ИСКЛЮЧЕНИЕ: Не удается разрешить все параметры

    VirtualBox – Мост через внутренний и мостовой интерфейс

    Как обновить новую версию eclipse вместо использования нового загруженного пакета

    Pandas получают самые высокие n записей в каждой группе

    Можно ли отключить кэширование записи на запоминающем устройстве USB на Mac OS X?

    Android: переключение камеры при нажатии кнопки

    Преобразование с плавающей запятой 32-бит в 16 бит

    Давайте будем гением компьютера.