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

В Java мы используем ключевое слово final с переменными, чтобы указать его значения, которые нельзя изменить. Но я вижу, что вы можете изменить значение в конструкторе/методах класса. Опять же, если переменная 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?

Ответ 1

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

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

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

Ответ 2

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

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 - это переменная instance. Когда мы создаем объект класса Test, тогда переменная экземпляра foo будет скопирована внутри объекта класса Test. Если мы назначим foo внутри конструктора, то компилятор знает, что конструктор будет вызываться только один раз, поэтому нет проблем с его назначением внутри конструктора.

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

Сценарий 2

private static final List foo = new ArrayList();

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

Сценарий 3

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

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

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

Ответ 3

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

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

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

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

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

Ответ 4

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

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

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

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

Ответ 5

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

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

private static final List foo;

static
{
   foo = new ArrayList();
}

Инлайн:

private static final List foo = new ArrayList();

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

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

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

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

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

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

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

Боковое примечание:

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

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

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

Ответ 6

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

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

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

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

ref variable x можно изменить, чтобы указать другую строку, но значение "abc" не может быть изменено.

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

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

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

Ответ 7

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

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

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

переменные

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

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

Ответ 8

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

Ответ 9

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

public static final String hello = "Hello";

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

Например:

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

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

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

Ответ 10

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

  1. переменная
  2. метод
  3. класс

Ключевое слово 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  

Итоговый класс Java:

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

Пример конечного класса

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

Ответ 11

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

Ответ 12

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

    private static final List foo;

    static {
        foo = new ArrayList();
    }

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

Ответ 13

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

foo = new ArrayList();

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

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

Ответ 14

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

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

public void showCaseFinalArgumentVariable(final int someFinalInt){

   someFinalInt = 9; // won't compile as the argument is final

}

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

Ответ 15

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

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

  1. классы

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

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

  1. методы

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

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

Ответ 16

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

Ответ 17

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

foo = new ArrayList();

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

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

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

foo = new ArrayList();

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


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

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

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

Ответ 18

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

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

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
   }
}

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

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

final class Base { }
class Derived extends Base { } //Compiler Error:cannot inherit from final Base

public class Main {
   public static void main(String args[]) {
   }
}

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

//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();
    }
}