Lambda: zmienne lokalne potrzebują finalnego, zmienne instancji nie

W lambdzie zmienne lokalne muszą być ostateczne, ale zmienne instancji nie. dlaczego tak jest?

Author: Not a bug, 2014-07-31

10 answers

Podstawową różnicą między polem a zmienną lokalną jest to, że zmienna lokalna jest kopiowane kiedy JVM tworzy instancję lambda. Z drugiej strony, pola mogą być dowolnie zmieniane, ponieważ zmiany w nich są propagowane do zewnętrznej instancji klasy (ich scope jest całą zewnętrzną klasą, jak zauważył Boris poniżej).

Najprostszy sposób myślenia o anonimowych klasach, zamknięciach i labmdach jest z zakresu zmiennej perspektywa; wyobraź sobie Konstruktor kopiujący dodany dla wszystkich zmiennych lokalnych, które przekazujesz do zamknięcia.

 61
Author: Adam Adamaszek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-07 08:54:35

W dokumencie projektu lambda: Stan Lambda v4

W Sekcji 7. Variable capture , mówi się, że....

Naszym zamiarem jest zabronienie przechwytywania zmiennych lokalnych. Na powodem jest to, że idiomy takie jak:

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

Są zasadniczo seryjne; dość trudno jest napisać ciała lambda takie, które nie mają warunków wyścigowych. Chyba, że jesteśmy skłonni wymuszać-najlepiej w czasie kompilacji - że taka funkcja nie można uciec wątku przechwytywania, ta funkcja może powodować więcej kłopotów niż rozwiązuje.

Edit:

Kolejną rzeczą, którą należy tutaj zauważyć jest to, że zmienne lokalne są przekazywane w konstruktorze klasy wewnętrznej, gdy uzyskujesz do nich dostęp wewnątrz swojej klasy wewnętrznej, i to nie będzie działać z niekończącą się zmienną, ponieważ wartość niekończących się zmiennych może zostać zmieniona po zbudowaniu.

Podczas gdy w przypadku zmiennej instancyjnej kompilator przekazuje referencję klasy i referencję klasy zostanie użyty do uzyskania dostępu do zmiennej instancji. Nie jest to więc wymagane w przypadku zmiennych instancji.

PS: warto wspomnieć, że anonimowe klasy mogą uzyskać dostęp tylko do końcowych zmiennych lokalnych( w Javie SE 7), podczas gdy w Javie SE 8 można skutecznie uzyskać dostęp do końcowych zmiennych również wewnątrz lambda, jak również wewnątrz klas.

 25
Author: Not a bug,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-12 21:03:42

W książce Java 8 w działaniu sytuacja ta jest wyjaśniona następująco:

Możesz zadawać sobie pytanie, dlaczego zmienne lokalne mają te ograniczenia. Po pierwsze, jest klucz różnica w sposobie implementacji zmiennych lokalnych I instancji za kulisami. Instancja zmienne są przechowywane na stercie, podczas gdy zmienne lokalne żyją na stosie. Gdyby lambda mogła dostęp do zmiennej lokalnej bezpośrednio i lambda zostały użyte w wątku, następnie wątek za pomocą lambda może spróbuj uzyskać dostęp do zmiennej po wątku, który przydzielił zmienną dealokowane. Dlatego Java implementuje dostęp do wolnej zmiennej lokalnej jako dostęp do jej kopii zamiast dostępu do pierwotnej zmiennej. Nie ma to znaczenia, jeśli zmienna lokalna jest przypisany tylko raz-stąd ograniczenie. Po drugie, ograniczenie to zniechęca również do typowych schematów programowania imperatywnego (które, jak wyjaśnij w późniejszych rozdziałach, zapobiegaj łatwej równoległości), które mutują zewnętrzną zmienna.

 17
Author: sedooe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-03 22:30:10

Ponieważ zmienne instancji są zawsze dostępne poprzez operację dostępu do pola w odniesieniu do jakiegoś obiektu, np. some_expression.instance_variable. Nawet jeśli nie uzyskujesz do niej jawnego dostępu poprzez notację kropkową, np. instance_variable, jest ona domyślnie traktowana jako this.instance_variable (lub jeśli jesteś w klasie wewnętrznej, która uzyskuje dostęp do zmiennej instancji klasy zewnętrznej, OuterClass.this.instance_variable, która znajduje się pod maską this.<hidden reference to outer this>.instance_variable).

Tak więc zmienna instancji nigdy nie jest bezpośrednio dostępna, a prawdziwa "zmienna", do której bezpośrednio uzyskujesz dostęp, to this (która jest "effectively final", ponieważ nie można go przypisać), lub zmienną na początku jakiegoś innego wyrażenia.

 14
Author: newacct,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-31 18:15:57

wprowadzenie kilku koncepcji dla przyszłych odwiedzających:

W zasadzie wszystko sprowadza się do tego, że kompilator powinien być w stanie deterministycznie stwierdzić, że ciało wyrażenia lambda nie działa na starej kopii zmiennych .

W przypadku zmiennych lokalnych, kompilator nie ma pewności, że ciało wyrażenia lambda nie działa na starej kopii zmiennej, chyba że ta zmienna jest ostateczna lub efektywnie ostateczna, więc zmienne lokalne powinny być albo ostateczne albo ostatecznie.

Teraz, w przypadku pól instancji, kiedy uzyskasz dostęp do pola instancji wewnątrz wyrażenia lambda, kompilator doda this do tej zmiennej access (jeśli nie zrobiłeś tego jawnie), a ponieważ {[0] } jest faktycznie ostateczna, więc kompilator jest pewien, że ciało wyrażenia lambda zawsze będzie miało najnowszą kopię zmiennej (proszę zauważyć, że wielowątkowość jest obecnie poza zakresem tej dyskusji). Tak więc w przypadku pól instancji kompilator może stwierdzić, że lambda ciało ma najnowszą kopię zmiennej instancji, więc zmienne instancji nie muszą być ostateczne lub faktycznie ostateczne. Proszę zapoznać się z poniższym zrzutem ekranu ze slajdu Oracle:

Tutaj wpisz opis obrazka

Należy również pamiętać, że jeśli uzyskujesz dostęp do pola instancji w wyrażeniu lambda i jest ono wykonywane w środowisku wielowątkowym, możesz potencjalnie uruchomić problem.

 12
Author: hagrawal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-14 21:31:16

Wygląda na to, że pytasz o zmienne, które możesz odwołać z ciała lambda.

Z JLS §15.27.2

Jakakolwiek zmienna lokalna, parametr formalny lub parametr wyjątku użyta, ale nie zadeklarowana w wyrażeniu lambda, musi być zadeklarowana jako ostateczna lub faktycznie ostateczna (§4.12.4), lub błąd w czasie kompilacji występuje w przypadku próby użycia.

Więc nie musisz deklarować zmiennych jako final musisz tylko upewnić się, że są / align = "left" / Jest to ta sama zasada co dotyczy klas anonimowych.

 9
Author: Boris the Spider,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-07-31 09:33:21

W wyrażeniach Lambda można efektywnie używać zmiennych końcowych z otaczającego zakresu. W praktyce oznacza to, że nie jest obowiązkowe deklarowanie zmiennej final, ale upewnij się, że nie zmienisz jej stanu w lambda expresssion.

Można również użyć tego w zamknięciach i użycie "this" oznacza obiekt zamykający, ale nie samą lambdę, ponieważ zamknięcia są funkcjami anonimowymi i nie mają z nimi skojarzonej klasy.

Więc kiedy używasz dowolnego pola (powiedzmy private Integer i;) z zamkniętej klasy, która nie jest zadeklarowana jako ostateczna i nie jest efektywnie ostateczna, nadal będzie działać, ponieważ kompilator robi sztuczkę w Twoim imieniu i wstawia "this" (this.i).

private Integer i = 0;
public  void process(){
    Consumer<Integer> c = (i)-> System.out.println(++this.i);
    c.accept(i);
}
 6
Author: nasioman,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-19 16:08:28

Oto przykład kodu, ponieważ tego też się nie spodziewałem, spodziewałem się, że nie będę w stanie zmodyfikować niczego poza moją lambdą

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

 }

Wyjście: Odd = true

 5
Author: losty,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-21 08:05:35

Tak, możesz zmienić zmienne składowe instancji, ale nie możesz zmienić samej instancji, tak jak wtedy, gdy obsługujesz zmienne.

Coś takiego jak wspomniano:

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

Podstawową zasadą ograniczenia zmiennych lokalnych jest o danych i ważności obliczeń

Jeśli lambda, oceniana przez drugi wątek, miała możliwość mutacji zmiennych lokalnych. Nawet umiejętność czytania wartość zmiennych lokalnych z innego wątku wprowadzałaby konieczność synchronizacji lub użycia volatile w celu uniknięcia odczytu starych danych.

Ale jak wiemy, główny cel lambdas

Wśród różnych powodów tego, najbardziej palącym dla platformy Java jest to, że ułatwiają one dystrybucję przetwarzania zbiorów w wielu wątkach.

Zupełnie w przeciwieństwie do zmiennych lokalnych, lokalna instancja może być mutowana, ponieważ jest dzielona globalnie. Możemy to lepiej zrozumieć poprzez różnicę stosu i stosu :

Gdy obiekt jest tworzony, jest zawsze przechowywany w przestrzeni sterty, a pamięć stosu zawiera odniesienie do niego. Pamięć stosu zawiera tylko lokalne zmienne prymitywne i zmienne odniesienia do obiektów w przestrzeni sterty.

Podsumowując, są dwa punkty myślę, że naprawdę ważne:

  1. Naprawdę trudno zrobić instancję skutecznie final , co może spowodować wiele bezsensownych obciążeń (Wystarczy wyobrazić sobie klasę głęboko zagnieżdżoną);

  2. Sama instancja jest już globalnie współdzielona, a lambda jest również współdzielona między wątkami, więc mogą one poprawnie współpracować, ponieważ wiemy, że mamy do czynienia z mutacją i chcemy przekazać tę mutację dookoła;

    {62]}

Punkt równowagi tutaj jest jasne: jeśli wiesz, co robisz, możesz to zrobić łatwo, ale jeśli nie, to domyślne ograniczenie pomoże uniknąć podstępnych błędów.

P. S. Jeśli synchronizacja wymagana w instance mutation, możesz użyć bezpośrednio stream reduction methods lub jeśli w instance mutation występuje problem z zależnością, nadal możesz używać thenApply lub thenCompose W Function while mapping lub podobnych metod.

 4
Author: Hearen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-12 05:17:41

Po pierwsze, istnieje kluczowa różnica w tym, jak zmienne lokalne i instancyjne są implementowane za kulisami. Zmienne instancji są przechowywane na stercie, podczas gdy zmienne lokalne są przechowywane na stosie. Jeśli lambda może uzyskać bezpośredni dostęp do zmiennej lokalnej, a lambda została użyta w wątku, to wątek korzystający z lambda może spróbować uzyskać dostęp do zmiennej po tym, jak wątek, który przydzielił zmienną, ją dealokował.

W skrócie: aby upewnić się, że inny wątek nie nadpisuje oryginału wartość, lepiej jest zapewnić dostęp do zmiennej copy zamiast oryginalnej.

 0
Author: Sambhav,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-01-18 17:37:06