Вы создаете фрагмент кода, который может «путешествовать». Код в {}
, принадлежащий вашему объявлению new Date()
, запускается не там, где вы его написали; он прикреплен к этому объекту даты, который вы сделали, и идет вместе с ним. Этот объект даты может путешествовать: он может храниться в поле. Может быть, он будет запущен через 18 дней в совершенно другом потоке. Виртуальная машина понятия не имеет, поэтому ее нужно подготовить к тому, что это произойдет.
Допустим, она знает: что должно произойти с вашей переменной «счетчик»?
Обычно локальные переменные хранятся «в стеке» и уничтожаются при выходе из метода. Но в этом случае мы бы уничтожили переменную, к которой ваш код путешествия все еще имеет доступ, так что это значит, что через 18 дней, когда будет вызван ваш код compareTo даты?
Допустим, что виртуальная машина незаметно «обновляет» переменные; вместо того, чтобы объявлять ее в стеке, как обычно, она объявляет ее в куче, чтобы переменная могла выжить после выхода из метода.
Хорошо. Что, если compareTo вызывается в другом потоке? Можно ли теперь пометить локальную переменную как «изменчивую»? Можно ли утверждать, что в java даже локальные переменные могут отображать состояние гонки?
Это критический вызов; что-то, что должны решить разработчики языка.
Java Разработчики языка решили: против тихого обновления в кучу и против , позволяя местным жителям потенциально подвергаться множественному доступ к потоку.
Следовательно, любая локальная переменная, к которой вы обращаетесь в любом кодовом блоке, который может «перемещаться» *, должна быть объявлена либо [A] final
, либо [B] действовать так, как если бы это могло быть, в котором case java сделает его окончательным для вас.
Изменение состоит в том, что counter
, сама переменная, не меняет во втором фрагменте: это ссылка на array, и эта ссылка никогда не меняется. Фактически, вы сами добавили уровень косвенного обращения и доступ к куче: в куче существуют массивы.
Как бы то ни было, я считаю использование AtomicX более читаемым. Поэтому, если вам нужен int, который можно изменять в коде перемещения, не используйте new int[1]
; сделать new AtomicInteger
. Если вам нужна изменяемая строка, используйте new AtomicReference<String>()
, а не new String[1]
.
NB: Да, в this Speci c код, переменная счетчика используется только, даже операция сортировки в этом методе и счетчик var могут go исчезнуть после завершения этого метода, но компилятор не выполняет такого чрезвычайно глубокого анализа, чтобы выяснить это, он использует гораздо более простое правило: хочу получить доступ локальная переменная из внешнего скрипта в коде "путешествия"? Не разрешено - если он (фактически) не является окончательным.
*) Код перемещения - это что-либо внутри определения локального или анонимного класса метода и что-либо в лямбда-выражении. Итак:
void method() {
class MethodLocalClassDef {
// anything here is considered 'travelling'
}
Object o = new Object() {
// this is an anonymous class def,
// and anything in here is 'travelling'
};
Runnable r = () -> {
// this is a lambda, and considered 'travelling'
};
}