Вопрос-ответ

How does the "final" keyword in Java work? (I can still modify an object.)

Как работает ключевое слово "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 в java 1.8.
В конце я объясню об этом effectively final в java 1.8.

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 ключевого слова. Вы не можете вызывать constructor несколько раз, потому что конструкторы не предназначены для этого.

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

Сценарий 1

private final List foo;  // 1

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


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

Сценарий 2

private static final List foo = new ArrayList();

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

Сценарий 3

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

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

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

конечные классы не могут быть подклассами

методыfinal нельзя переопределить. (Этот метод относится к суперклассу)

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

Теперь давайте посмотрим, что фактически является final в java 1.8?

public class EffectivelyFinalDemo { //compile code with java 1.8
public void process() {
int thisValueIsFinalWithoutFinalKeyword = 10; //variable is effectively final

//to work without final keyword you should not reassign value to above variable like given below
thisValueIsFinalWithoutFinalKeyword = getNewValue(); // delete this line when I tell you.

class MethodLocalClass {
public void innerMethod() {
//below line is now showing compiler error like give below
//Local variable thisValueIsFinalWithoutFinalKeyword defined in an enclosing scope must be final or effectively final
System.out.println(thisValueIsFinalWithoutFinalKeyword); //on this line only final variables are allowed because this is method local class
// if you want to test effectively final is working without final keyword then delete line which I told you to delete in above program.
}
}
}

private int getNewValue() {
return 0;
}
}

Приведенная выше программа выдаст ошибку в java 1.7 или <1.8, если вы не используете ключевое слово final. Фактически final является частью локальных внутренних классов метода. Я знаю, что вы редко использовали бы такой эффективный final в локальных классах методов, но к собеседованию мы должны быть готовы.

Ответ 2

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

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

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

Ответ 3

Ключевое словоFinal имеет множество способов использования:


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

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

  • Конечная переменная может быть инициализирована только один раз

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


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

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

Ответ 4

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

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

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

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

java