Как можно использовать неэффективную конечную переменную в лямбда [дубликат]

Указатель NULL - это тот, который указывает на никуда. Когда вы разыскиваете указатель p, вы говорите «дайте мне данные в месте, хранящемся в« p ». Когда p является нулевым указателем, местоположение, хранящееся в p, является nowhere, вы говорите «Дайте мне данные в месте« нигде ». Очевидно, он не может этого сделать, поэтому он выбрасывает NULL pointer exception.

В общем, это потому, что что-то не было правильно инициализировано.

48
задан Not a bug 31 July 2014 в 10:52
поделиться

9 ответов

Фундаментальное различие между полем и локальной переменной состоит в том, что локальная переменная скопирована , когда JVM создает экземпляр лямбда. С другой стороны, поля могут быть изменены свободно, потому что изменения к ним распространяются также на экземпляр внешнего класса (их область - это весь внешний класс, как показано Борисом ниже).

Самый простой способ мышления об анонимных классах, замыканиях и labmdas - это перспектива scope scope ; представьте, что конструктор копирования добавлен для всех локальных переменных, которые вы передаете в закрытие.

40
ответ дан Adam Adamaszek 17 August 2018 в 22:26
поделиться
  • 1
    Правильно, тогда конструктору анонимного класса не нужно копировать переменную экземпляра, потому что она может просто ссылаться на нее. Хорошее объяснение! – Gerard 31 July 2014 в 10:49
  • 2
    Будущие посетители - DO READ - stackoverflow.com/a/51850049/4691279 – hagrawal 14 August 2018 в 21:33

Похоже, вы спрашиваете о переменных, которые вы можете ссылаться на тело лямбда.

Из JLS §15.27.2

] Любой параметр локальной переменной, формальный параметр или исключение, но не объявленный в выражении лямбда, должен быть объявлен окончательным или быть фактически окончательным (§4.12.4), или ошибка времени компиляции возникает при попытке использования.

Поэтому вам не нужно объявлять переменные как final, вам просто нужно убедиться, что они «эффективно окончательны». Это то же правило, что и для анонимных классов.

8
ответ дан Boris the Spider 17 August 2018 в 22:26
поделиться
  • 1
    Да, но переменные экземпляра можно ссылаться и назначать в лямбда, что удивительно для меня. Ограничения final ограничены только локальными переменными. – Gerard 31 July 2014 в 10:35
  • 2
    @Gerard Поскольку переменная экземпляра имеет область действия всего класса. Это точно та же логика, что и для анонимного класса - есть много учебников, объясняющих логику. – Boris the Spider 31 July 2014 в 10:39

Включение некоторых концепций для будущих посетителей:

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

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

Теперь, в случае полей экземпляра, когда вы обращаетесь к полю экземпляра внутри выражения лямбда, тогда компилятор добавит this к этому доступу к переменной (если вы не сделали это явно) и поскольку this является фактически окончательным, поэтому компилятор уверен, что тело выражения лямбда всегда будет иметь последнюю копию переменной (обратите внимание, что многопоточность выходит за рамки области сейчас для этого обсуждения). Итак, в случае полей экземпляра компилятор может сказать, что тело лямбда имеет последнюю копию переменной экземпляра, поэтому переменные экземпляра не обязательно должны быть окончательными или фактически окончательными. Пожалуйста, см. Снимок экрана с помощью слайда Oracle:

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

0
ответ дан hagrawal 17 August 2018 в 22:26
поделиться

ДА, вы можете изменить переменные-члены экземпляра, но вы НЕ МОЖЕТЕ изменить сам экземпляр так же, как при обработке переменных.

Что-то вроде этого, как упоминалось:

    class Car {
        public String name;
    }

    public void testLocal() {
        int theLocal = 6;
        Car bmw = new Car();
        bmw.name = "BMW";
        Stream.iterate(0, i -> i + 2).limit(2)
        .forEach(i -> {
//            bmw = new Car(); // LINE - 1;
            bmw.name = "BMW NEW"; // LINE - 2;
            System.out.println("Testing local variables: " + (theLocal + i));

        });
        // have to comment this to ensure it's `effectively final`;
//        theLocal = 2; 
    }

Основной принцип ограничения локальных переменных - это данные и достоверность вычислений

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

Но, как мы знаем, Основная цель lambdas

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

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

Всякий раз, когда создается объект, он всегда сохраняется в пространстве кучи, а в стеке содержится ссылка на него ,

Так что, чтобы суммировать, есть две точки, которые, я думаю, действительно имеют значение:

  1. Очень сложно сделать экземпляр эффективным окончательным , что может вызвать много бессмысленного бремени (просто представьте себе глубоко вложенный класс);
  2. сам экземпляр уже глобально разделен и lambda также разделяется между потоками, поэтому они могут работать вместе правильно, поскольку мы знаем, что мы обрабатываем мутацию и хотим передать эту мутацию;

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

P.S. Если требуется синхронизация в мутации экземпляра, вы можете напрямую использовать методы сокращения потока или, если есть проблема с зависимостями в мутации экземпляра, вы все равно можете использовать thenApply или thenCompose в Функция , тогда как mapping или методы аналогичны.

1
ответ дан Hearen 17 August 2018 в 22:26
поделиться

Вот пример кода, так как я этого тоже не ожидал, я ожидал, что не смогу изменить что-либо за пределами лямбда

 public class LambdaNonFinalExample {
    static boolean odd = false;

    public static void main(String[] args) throws Exception {
       //boolean odd = false; - If declared inside the method then I get the expected "Effectively Final" compile error
       runLambda(() -> odd = true);
       System.out.println("Odd=" + odd);
    }

    public static void runLambda(Callable c) throws Exception {
       c.call();
    }

 }

. Вывод: Odd = true

4
ответ дан losty 17 August 2018 в 22:26
поделиться

В документе проекта lambda: Состояние лямбда v4

В разделе 7. Захват переменной. Упоминается, что ....

Мы намерены запретить захват изменяемых локальных переменных. Причина в том, что подобные идиомы:

int sum = 0;
list.forEach(e -> { sum += e.size(); });

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

Изменить:

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

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

PS: Следует отметить, что анонимные классы могут получить доступ только к конечным локальным переменным (в JAVA SE 7), тогда как в Java SE 8 вы можете эффективно конечные переменные также внутри лямбда, а также внутренние классы.

16
ответ дан m0nhawk 17 August 2018 в 22:26
поделиться

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

Вы также можете использовать это в закрытии, а использование «this» означает объект-объект, но не сама лямбда как закрытие - это анонимные функции, и у них нет связанного с ними класса.

Итак, когда вы используете какое-либо поле (допустим, private Integer i;) из класса-оболочки, который не объявлен окончательным, а не эффективно завершающий, он все равно будет работать, когда компилятор делает трюк от вашего имени и вставляет «this» (this.i).

private Integer i = 0;
public  void process(){
    Consumer<Integer> c = (i)-> System.out.println(++this.i);
    c.accept(i);
}
5
ответ дан nasioman 17 August 2018 в 22:26
поделиться

Поскольку переменные экземпляра всегда доступны через операцию доступа к полю при ссылке на какой-либо объект, то есть some_expression.instance_variable. Даже если вы явно не обращаетесь к нему через точечную нотацию, например instance_variable, она неявно рассматривается как this.instance_variable (или если вы находитесь во внутреннем классе, обращаясь к переменной экземпляра внешнего класса, OuterClass.this.instance_variable, которая находится под hood this.<hidden reference to outer this>.instance_variable).

Таким образом, переменная экземпляра никогда не получает прямого доступа, и реальная «переменная», к которой вы напрямую обращаетесь, - this (которая является «фактически окончательной», поскольку она не может быть назначена ) или переменная в начале некоторого другого выражения.

9
ответ дан newacct 17 August 2018 в 22:26
поделиться

В книге Java 8 in Action эта ситуация объясняется следующим образом:

Возможно, вы спрашиваете себя, почему локальные переменные имеют эти ограничения. Во-первых, есть ключевое различие в том, как реализуются экземпляры и локальные переменные за кулисами. Переменные экземпляра хранятся в куче, тогда как локальные переменные находятся в стеке. Если лямбда могла напрямую обращаться к локальной переменной, а лямбда использовалась в потоке, то поток, использующий лямбда, мог бы попытаться получить доступ к переменной после того, как поток, выделенный этой переменной, освободил ее. Следовательно, Java реализует доступ к свободной локальной переменной в качестве доступа к ее копии, а не к исходной переменной. Это не имеет значения, если локальная переменная назначается только один раз, следовательно, ограничение. Во-вторых, это ограничение также препятствует типичным императивным шаблонам программирования (которые, как мы объясняем в последующих главах, предотвращают легкую параллелизацию), которые мутируют внешнюю переменную.

8
ответ дан sedooe 17 August 2018 в 22:26
поделиться
  • 1
    Я действительно думаю, что есть некоторые проблемы в Java 8 в Action в этой точке. Если локальная переменная здесь относится к переменным, созданным в методе, но к которым обращаются lambdas, а многопоточность достигается с помощью ForkJoin, тогда будет копия для разных потоков и мутации в lambdas приемлемо теоретически, и в этом случае он может быть мутирован . Но здесь здесь разные, локальные переменные , используемые в лямбда, для параллелизации , достигнутые parallelStream, и эти локальные переменные разделяются разными нити, которые основаны на lambdas . – Hearen 12 May 2018 в 05:50
  • 2
    Таким образом, первая точка на самом деле не правильная, нет так называемой копии , она разделяется между потоками в parallelStream. И совместное использование изменяемых переменных среди потоков опасно, как Вторая точка . Именно поэтому мы предотвращаем его и внедряем встроенные методы в Stream для обработки этих случаев. – Hearen 12 May 2018 в 05:51
Другие вопросы по тегам:

Похожие вопросы: