Как работает ключевое слово «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); } }
Над кодом работает нормально и никаких ошибок.
- Как преобразовать файл .pfx в хранилище ключей с помощью закрытого ключа?
- Как преобразовать java-программу в daemon с помощью jsvc?
- Невозможно применить к неспецифическому вложенному типу с дженериками
- Java разделяет строку на массив
- Получение контекста сервлетов, сеанса и запроса в внешнем контейнере POJO
Теперь измените переменную как static
:
private static final List foo;
Теперь это ошибка компиляции. Как этот final
действительно работает?
- Не удалось создать JAXBContext, создав мой wsdl
- Есть ли приемлемый эквивалент Java для zip () для Python?
- Сплит 1GB Xml-файл с использованием Java
- Самый простой способ прочитать файл в String?
- Ошибки округления?
- Понимание JSF как структуры MVC
- Как программно доказать, что StringBuilder не является streamобезопасным?
- Как вы знаете переменный тип в java?
Вы всегда можете инициализировать 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
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
можно использовать во многих контекстах. Финал может быть:
- переменная
- метод
- 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
при попытке добавить элемент в список.
- Поскольку конечная переменная нестатическая, ее можно инициализировать в конструкторе. Но если вы сделаете его статическим, его нельзя инициализировать конструктором (потому что конструкторы не являются статическими).
- Ожидается, что добавление к списку не прекратится, сделав окончательный список.
final
просто связывает ссылку на конкретный объект. Вы можете изменять «состояние» этого объекта, но не сам объект.
Прочтите все ответы.
Существует еще один случай пользователя, в котором может использоваться final
ключевое слово, т. Е. В аргументе метода:
public void showCaseFinalArgumentVariable(final int someFinalInt){ someFinalInt = 9; // won't compile as the argument is final }
Может использоваться для переменной, которая не должна изменяться.
Я подумал о написании обновленного и подробного ответа здесь.
ключевое слово final
может использоваться в нескольких местах.
- 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 во время выполнения не может быть применен. Поэтому время работы не беспокоится об этом. Это означает, что это экономит время обработки, что улучшит производительность.
- методы
final method
любого classа означает, что любой дочерний class, расширяющий этот class, не может переопределить этот окончательный метод (ы). Таким образом, поведение времени выполнения в этом сценарии также совершенно аналогично предыдущему поведению, которое я упоминал для classов.
- поля, локальные переменные, параметры метода
Если один из указанных выше как 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(); } }