2012-07-11 12 views
9

Jeśli mam zmienną int x = 1, powiedzmy, i deklaruję możliwość uruchomienia w wątku głównym, i chcę przekazać x do metodzie run(), należy ją oznaczyć jako final. Czemu?Dlaczego zmienne przekazane do uruchomienia muszą być ostateczne?

final int x = 0;//<----must be final... 
private class myRun implements Runnable { 

    @Override 
    public void run() { 
     x++;// 
    } 

} 
+1

Ponieważ tak definiuje się język. Prawdopodobnie, aby zmienne * zmiennych * nie były modyfikowane we wspomnianej metodzie w * anonimowej klasie wewnętrznej *. (Sądzę, że to również upraszcza implementację: tylko wartości * muszą być skopiowane z użyciem proxy do anonimowego typu, a oryginalne zmienne nie muszą być zachowywane, tak jak byłoby to wymagane przy semantyce pełnego zamknięcia.) –

+0

Jeśli to nie miało to miejsca, zmienne mogą być modyfikowane w dowolnym momencie bez ostrzeżenia. – jahroy

Odpowiedz

8

Bo jeśli są one w stanie zmienić, może to spowodować wiele problemów, rozważ to:

public void count() 
{ 
    int x; 

    new Thread(new Runnable() 
    { 
     public void run() 
     { 
      while(x < 100) 
      { 
       x++; 
       try 
       { 
        Thread.sleep(1000); 
       }catch(Exception e){} 
      } 
     } 
    }).start(); 

    // do some more code... 

    for(x = 0;x < 5;x++) 
     for(int y = 0;y < 10;y++) 
      System.out.println(myArrayElement[x][y]); 
} 

Jest to trudny przykład, ale można zobaczyć, gdzie może wystąpić wiele niewyjaśnionych błędów. Dlatego zmienne muszą być ostateczne. Oto prosta poprawka do powyższego problemu:

public void count() 
{ 
    int x; 

    final int w = x; 

    new Thread(new Runnable() 
    { 
     public void run() 
     { 
      int z = w; 

      while(z < 100) 
      { 
       z++; 
       try 
       { 
        Thread.sleep(1000); 
       }catch(Exception e){} 
      } 
     } 
    }).start(); 

    // do some more code... 

    for(x = 0;x < 5;x++) 
     for(int y = 0;y < 10;y++) 
      System.out.println(myArrayElement[x][y]); 
} 

Jeśli chcesz uzyskać pełniejsze wyjaśnienie, jest to trochę jak zsynchronizowane. Java chce uniemożliwić ci odwoływanie się do jednego obiektu z wielu wątków. Tutaj jest trochę o synchronizacji:

Nadzieja to pomogło!

+1

Nie spowoduje to wiele problemów - wiele języków ma pełne zamknięcia :-) Fakt, że Runnable (i prawdopodobnie wątki) jest w temacie tutaj jest ortogonalne, aby umożliwić modyfikowanie zmiennych lokalnych z anonimowych typów wewnętrznych. Nawet jeśli mówimy o czymś niezwiązanym z wątkami, będą to te same zasady językowe. Ten sam problem z gwintowaniem wynikałby również ze zmiennych członkowskich. –

+1

W twoim "Oto prosta poprawka dla powyższego problemu:", czy w ++ nie da ci błędu kompilacji ?! Nie można ponownie przypisać w po jej inicjalizacji. – mskw

+0

@mskw Nie mogę uwierzyć, że nikt nic nie powiedział do tej pory, dzięki za znalezienie tego. – John

6

Bo tak mówi specyfikacja języka. According to Guy Steele, uzasadnieniem tego wyboru jest to, że programiści oczekują deklaracji int x = 0 w metodzie, której wynikiem będzie pamięć przydzielana w stosie, ale jeśli można zwrócić z tej metody metodę new myRun() (lub w inny sposób pozwolić, aby myRun utrzymywał się po powrocie funkcji) i można go później zmodyfikować, a następnie x musi zostać przydzielony sterty, aby uzyskać semantykę, jakiej można się spodziewać.

Mogli to zrobić, a w rzeczywistości inne języki zrobiły to w ten sposób. Jednak projektanci Javy postanowili zamiast tego wymagać, aby oznaczyć x jako final, aby uniknąć konieczności implementacji do sterty-alokować coś, co wygląda jak pamięć przydzielana stackowi.

(Należy zauważyć: to nie jest specyficzne dla Runnable Dotyczy to każdej anonimowej klasy wewnętrznej.).

+0

Ach, tak, masz rację, źle odczytałem pytanie. – jacobm

+0

+1 dla "it [* is not *] specific to Runnable" –

1

Wielkim "problemem" z wielowątkowością, a także całym powodem jego używania, jest to, że wiele rzeczy dzieje się w tym samym czasie. Nagle wartość dowolnej zmiennej, do której wątek uzyskuje dostęp, która nie jest lokalna dla wątku, może się zmienić w dowolnym momencie. Tak więc, można rzecz, jesteś po prostu drukuje numery 1-10 z tego kodu:

int x = 0; //supposing that this was allowed to be non-final... 
    private class myRun implements Runnable{ 

    @Override 
    public void run() { 
     for (int i=0; i<10; i++) { 
      System.Out.Println(x++); 
     } 
    } 
} 

Jednak w rzeczywistości, jeśli inny kod w tej klasie zmienia wartość X, może skończyć się drukowanie 230498 - 230508 Wartość x może zmienić zmianę w środku pętli. Jeśli nie możesz polegać na x posiadającej określoną wartość lub zachowującej wcześniej przypisaną jej wartość, używanie jej w kodzie staje się daremne. Dlaczego używałbyś zmiennej, gdyby jej zawartość mogła się zmienić po kropli kapelusza?

Zamiast tylko zabraniać ci używania go w ogóle, Java wymaga, abyś go zrobił final. Możesz po prostu "obiecać", aby nigdy nie zmieniać wartości x z innego wątku, ale dlaczego nie uczynić go w pierwszej kolejności final i pozwolić kompilatorowi pomóc?Oczywiście, masz dostęp tylko do wartości początkowej przypisanej do x, ale tylko możliwość uzyskania dostępu do wartości początkowej zmiennej jest lepsza niż niemożność jej użycia w ogóle, co skutecznie odcięłoby zdolność wątku do wykorzystania danych z reszty twojej klasy.

Powiązane problemy