Nie można odwoływać się do nieokreślonej zmiennej w wewnętrznej klasie zdefiniowanej w innej metodzie


247

Edytowane: Muszę zmienić wartości kilku zmiennych, ponieważ działają one kilkakrotnie przez licznik czasu. Muszę aktualizować wartości przy każdej iteracji za pomocą timera. Nie mogę ustawić wartości na końcowe, ponieważ nie pozwoli mi to zaktualizować wartości, jednak pojawia się błąd, który opisuję w początkowym pytaniu poniżej:

Wcześniej napisałem, co jest poniżej:

Pojawia się błąd „nie można odwoływać się do nieokreślonej zmiennej w wewnętrznej klasie zdefiniowanej w innej metodzie”.

Dzieje się tak w przypadku podwójnej ceny o nazwie Cena i ceny o nazwie cena Obiekt. Czy wiesz, dlaczego mam ten problem? Nie rozumiem, dlaczego potrzebuję ostatecznej deklaracji. Również jeśli widzisz, co próbuję zrobić, co muszę zrobić, aby obejść ten problem.

public static void main(String args[]) {

    int period = 2000;
    int delay = 2000;

    double lastPrice = 0;
    Price priceObject = new Price();
    double price = 0;

    Timer timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            price = priceObject.getNextPrice(lastPrice);
            System.out.println();
            lastPrice = price;
        }
    }, delay, period);
}

Pytam o to, jak uzyskać zmienną w zegarze, którą mogę ciągle aktualizować.
Ankur

1
@Ankur: prosta odpowiedź brzmi „nie”. Ale możesz osiągnąć pożądany efekt za pomocą wewnętrznej klasy; patrz odpowiedź @ petercardona.
Stephen C

Odpowiedzi:


197

Java nie obsługuje prawdziwych zamknięć , nawet jeśli użycie anonimowej klasy, takiej jak używasz tutaj ( new TimerTask() { ... }), wygląda jak zamknięcie.

edycja - zobacz poniższe komentarze - poniższe informacje nie są poprawnym wyjaśnieniem, jak zauważa KeeperOfTheSoul.

Oto dlaczego to nie działa:

Zmienne lastPricei cena są zmiennymi lokalnymi w metodzie main (). Obiekt utworzony za pomocą klasy anonimowej może trwać do momentu main()powrotu metody.

Kiedy main()metoda zwróci, zmienne lokalne (takie jak lastPricei price) zostaną usunięte ze stosu, więc nie będą już istnieć po main()powrocie.

Ale anonimowy obiekt klasy odwołuje się do tych zmiennych. Sprawy potoczyłyby się bardzo źle, gdyby obiekt klasy anonimowej próbował uzyskać dostęp do zmiennych po ich oczyszczeniu.

Tworząc lastPricei price final, nie są już tak naprawdę zmiennymi, ale stałymi. Kompilator może wtedy po prostu zastąpić użycie lastPriceiw priceklasie anonimowej wartościami stałych (oczywiście w czasie kompilacji) i nie będzie już problemu z dostępem do nieistniejących zmiennych.

Inne języki programowania, które obsługują zamknięcia, robią to przez specjalne traktowanie tych zmiennych - upewniając się, że nie zostaną zniszczone po zakończeniu metody, aby zamknięcie nadal mogło uzyskać dostęp do zmiennych.

@Ankur: Możesz to zrobić:

public static void main(String args[]) {
    int period = 2000;
    int delay = 2000;

    Timer timer = new Timer();

    timer.scheduleAtFixedRate(new TimerTask() {
        // Variables as member variables instead of local variables in main()
        private double lastPrice = 0;
        private Price priceObject = new Price();
        private double price = 0;

        public void run() {
            price = priceObject.getNextPrice(lastPrice);
            System.out.println();
            lastPrice = price;
        }
    }, delay, period);      
}

34
Nie do końca prawda, Java generuje przechwytywanie dla danych zmiennych, aby uchwycić ich wartości w czasie wykonywania, po prostu chcieli uniknąć dziwnego efektu ubocznego, który jest możliwy w .Net, gdzie przechwytując wartość w delegacie, zmień wartość w metodzie zewnętrznej, a teraz delegat widzi nową wartość patrz stackoverflow.com/questions/271440/c-captured-variable-in-loop dla przykładu C # tego zachowania, którego Java chce uniknąć.
Chris Chilvers

14
To nie jest „dziwny efekt uboczny”, to normalne zachowanie, jakiego ludzie by się spodziewali - i którego Java nie jest w stanie zapewnić, ponieważ nie generuje przechwyceń. Aby obejść ten problem, zmienne lokalne używane w anonimowej klasie muszą być ostateczne.
Michael Borgwardt,

12
Jesper, prawdopodobnie powinieneś wyedytować niepoprawne fragmenty swoich odpowiedzi, a nie tylko komunikat, że powyższe jest niepoprawne.
James McMahon,

19
W rzeczywistości Java nie obsługuje zamknięć. Języki, które obsługują zamknięcia, robią to poprzez przechowywanie całego środowiska lokalnego (to znaczy zestawu zmiennych lokalnych zdefiniowanych w bieżącej ramce stosu) jako obiektu stosu. Java nie obsługuje tego (projektanci języka chcieli go zaimplementować, ale zabrakło mu czasu), więc jako obejście, za każdym razem, gdy tworzona jest instancja klasy lokalnej, wartości dowolnych zmiennych lokalnych, do których się ona odnosi, są kopiowane na stos . Jednak JVM nie może wówczas zsynchronizować wartości ze zmiennymi lokalnymi, dlatego muszą być ostateczne.
Taymon

64
Ta odpowiedź jest całkowicie myląca, ponieważ nie ma nikogo o nazwisku „KeeperOfTheSoul”, który skomentowałby to. Odpowiedź powinna zostać zmieniona.
Adam Parkin

32

Aby uniknąć dziwnych efektów ubocznych związanych z zamykaniem zmiennych java, do których odwołuje się anonimowy delegat, należy oznaczyć je jako ostateczne, więc aby odwoływać się lastPricei wyceniać w ramach zadania czasowego, muszą być oznaczone jako ostateczne.

To oczywiście nie zadziała, ponieważ chcesz je zmienić, w takim przypadku powinieneś spojrzeć na kapsułkowanie ich w klasie.

public class Foo {
    private PriceObject priceObject;
    private double lastPrice;
    private double price;

    public Foo(PriceObject priceObject) {
        this.priceObject = priceObject;
    }

    public void tick() {
        price = priceObject.getNextPrice(lastPrice);
        lastPrice = price;
    }
}

teraz po prostu utwórz nowy Foo jako ostateczny i zadzwoń .tick z timera.

public static void main(String args[]){
    int period = 2000;
    int delay = 2000;

    Price priceObject = new Price();
    final Foo foo = new Foo(priceObject);

    Timer timer = new Timer();
    timer.scheduleAtFixedRate(new TimerTask() {
        public void run() {
            foo.tick();
        }
    }, delay, period);
}

1
lub możesz po prostu poprowadzić Foo Runnable ..?
vidstige

18

Dostęp do zmiennych końcowych można uzyskać tylko z klasy zawierającej, gdy używana jest klasa anonimowa. Dlatego musisz zadeklarować, że zmienne są używane jako ostateczne (co nie jest dla ciebie opcją, ponieważ zmieniasz ostatnią cenę i cenę ) lub nie korzystasz z anonimowej klasy.

Zatem twoimi opcjami jest utworzenie rzeczywistej klasy wewnętrznej, w której możesz przekazać zmienne i używać ich w normalny sposób

lub:

Istnieje szybki (i moim zdaniem brzydki) hack dla ostatniej zmiennej ceny i ceny , która ma ją tak zadeklarować

final double lastPrice[1];
final double price[1];

a w swojej anonimowej klasie możesz ustawić taką wartość

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];

14

Dobre wyjaśnienia, dlaczego nie możesz zrobić tego, co próbujesz, już dostarczone. Jako rozwiązanie warto rozważyć:

public class foo
{
    static class priceInfo
    {
        public double lastPrice = 0;
        public double price = 0;
        public Price priceObject = new Price ();
    }

    public static void main ( String args[] )
    {

        int period = 2000;
        int delay = 2000;

        final priceInfo pi = new priceInfo ();
        Timer timer = new Timer ();

        timer.scheduleAtFixedRate ( new TimerTask ()
        {
            public void run ()
            {
                pi.price = pi.priceObject.getNextPrice ( pi.lastPrice );
                System.out.println ();
                pi.lastPrice = pi.price;

            }
        }, delay, period );
    }
}

Wygląda na to, że prawdopodobnie mógłbyś zrobić lepszy projekt, ale pomysł polega na tym, że możesz grupować zaktualizowane zmienne wewnątrz odwołania do klasy, które się nie zmienia.


11

Dzięki anonimowym klasom faktycznie deklarujesz „bezimienną” klasę zagnieżdżoną. W przypadku klas zagnieżdżonych kompilator generuje nową autonomiczną klasę publiczną z konstruktorem, który weźmie wszystkie zmienne, których używa jako argumenty (w przypadku klas nazwanych zagnieżdżonych jest to zawsze instancja klasy oryginalnej / zamykającej). Dzieje się tak, ponieważ środowisko wykonawcze nie ma pojęcia o zagnieżdżonych klasach, dlatego konieczna jest (automatyczna) konwersja z zagnieżdżonej do samodzielnej klasy.

Weźmy na przykład ten kod:

public class EnclosingClass {
    public void someMethod() {
        String shared = "hello"; 
        new Thread() {
            public void run() {
                // this is not valid, won't compile
                System.out.println(shared); // this instance expects shared to point to the reference where the String object "hello" lives in heap
            }
        }.start();

        // change the reference 'shared' points to, with a new value
        shared = "other hello"; 
        System.out.println(shared);
    }
}

To nie zadziała, ponieważ to właśnie robi kompilator pod maską:

public void someMethod() {
    String shared = "hello"; 
    new EnclosingClass$1(shared).start();

    // change the reference 'shared' points to, with a new value
    shared = "other hello"; 
    System.out.println(shared);
}

Oryginalną anonimową klasę zastępuje pewna samodzielna klasa generowana przez kompilator (kod nie jest dokładny, ale powinien dać ci dobry pomysł):

public class EnclosingClass$1 extends Thread {
    String shared;
    public EnclosingClass$1(String shared) {
        this.shared = shared;
    }

    public void run() {
        System.out.println(shared);
    }
}

Jak widać, samodzielna klasa zawiera odniesienie do obiektu współdzielonego, pamiętaj, że wszystko w java jest przekazywane przez wartość, więc nawet jeśli zmienna odniesienia „shared” w EnclosingClass zostanie zmieniona, instancja, na którą wskazuje, nie jest modyfikowana , i wszystkie inne zmienne odniesienia do niego wskazujące (jak ta w klasie anonimowej: Załączanie 1 $), nie będą tego świadome. Jest to główny powód, dla którego kompilator zmusza cię do zadeklarowania tych „współdzielonych” zmiennych jako ostatecznych, aby tego rodzaju zachowanie nie przekształciło się w już działający kod.

Oto, co dzieje się, gdy używasz zmiennej instancji w anonimowej klasie (to powinieneś zrobić, aby rozwiązać problem, przenieść logikę do metody „instancji” lub konstruktora klasy):

public class EnclosingClass {
    String shared = "hello";
    public void someMethod() {
        new Thread() {
            public void run() {
                System.out.println(shared); // this is perfectly valid
            }
        }.start();

        // change the reference 'shared' points to, with a new value
        shared = "other hello"; 
        System.out.println(shared);
    }
}

To kompiluje się dobrze, ponieważ kompilator zmodyfikuje kod, tak aby nowa generowana klasa Enclosing $ 1 zawierała odwołanie do instancji EnclosingClass, w której została utworzona instancja (jest to tylko reprezentacja, ale powinna zacząć działać):

public void someMethod() {
    new EnclosingClass$1(this).start();

    // change the reference 'shared' points to, with a new value
    shared = "other hello"; 
    System.out.println(shared);
}

public class EnclosingClass$1 extends Thread {
    EnclosingClass enclosing;
    public EnclosingClass$1(EnclosingClass enclosing) {
        this.enclosing = enclosing;
    }

    public void run() {
        System.out.println(enclosing.shared);
    }
}

W ten sposób, gdy zmienna referencyjna „współdzielona” w EnclosingClass zostanie ponownie przypisana, a dzieje się to przed wywołaniem wątku # run (), zobaczysz dwa razy wydrukowane „other hello”, ponieważ teraz zmienna otaczająca EnclosingClass $ 1 zachowa referencję do obiektu klasy, w której został zadeklarowany, więc zmiany dowolnego atrybutu tego obiektu będą widoczne dla instancji klasy EnclosingClass $ 1.

Aby uzyskać więcej informacji na ten temat, możesz zobaczyć ten znakomity post na blogu (nie napisany przeze mnie): http://kevinboone.net/java_inner.html


Co jeśli zmienna lokalna „współużytkowana” jest obiektem zmiennym? Według twojego wyjaśnienia stwierdzenie, że „ostateczny” też nie pomoże, prawda?
sactiw

Zadeklarowanie „współużytkowanej” jako ostatecznej pozwoli ci zmodyfikować stan obiektu, do którego odwołują się zmienne końcowe, ale w tym konkretnym przykładzie to nie zadziała, ponieważ nie będziesz w stanie zmienić wartości „współużytkowanej” zmiennej (która jest to, czego chciał OP), będziesz mógł używać go w anonimowych klasach, ale jego wartość się nie zmieni (ponieważ zostanie uznana za ostateczną). Ważne jest, aby zauważyć różnicę między zmiennymi a rzeczywistymi wartościami, które przechowują (które mogą być prymitywami lub referencjami do obiektów w stercie).
emerino

>>> ale jego wartość się nie zmieni Myślę, że brakuje ci punktu, tzn. jeśli końcowa zmienna odniesienia wskazuje na obiekt podlegający zmianom, może być nadal aktualizowana, jednak klasa anonimowa tworzy płytką kopię, więc zmiany są odzwierciedlane w anonimowym klasa. Innymi słowy, stan jest zsynchronizowany, co jest tutaj pożądane. W tym przypadku OP potrzebuje możliwości zmodyfikowania wspólnej zmiennej (typ pierwotny) i aby ją osiągnąć, OP musi zawinąć wartość w obiekt zmienny i udostępnić ten obiekt zmienny.
sactiw

1
Oczywiście OP może owinąć potrzebną wartość pod zmienny obiekt, zadeklarować zmienną jako końcową i użyć jej zamiast tego. Może jednak uniknąć użycia dodatkowego obiektu, deklarując zmienną jako atrybut bieżącej klasy (jak wskazano i wyjaśniono w odpowiedzi). Wymuszanie zmiennych obiektów (takich jak używanie tablic tylko w celu modyfikowania wartości wspólnej zmiennej) nie jest dobrym pomysłem.
emerino

7

Kiedy natknę się na ten problem, po prostu przekazuję obiekty do klasy wewnętrznej przez konstruktora. Jeśli muszę przekazać prymitywy lub niezmienne obiekty (jak w tym przypadku), potrzebna jest klasa opakowania.

Edycja: Właściwie wcale nie używam anonimowej klasy, ale odpowiednią podklasę:

public class PriceData {
        private double lastPrice = 0;
        private double price = 0;

        public void setlastPrice(double lastPrice) {
            this.lastPrice = lastPrice;
        }

        public double getLastPrice() {
            return lastPrice;
        }

        public void setPrice(double price) {
            this.price = price;
        }

        public double getPrice() {
            return price;
        }
    }

    public class PriceTimerTask extends TimerTask {
        private PriceData priceData;
        private Price priceObject;

        public PriceTimerTask(PriceData priceData, Price priceObject) {
            this.priceData = priceData;
            this.priceObject = priceObject;
        }

        public void run() {
            priceData.setPrice(priceObject.getNextPrice(lastPrice));
            System.out.println();
            priceData.setLastPrice(priceData.getPrice());

        }
    }

    public static void main(String args[]) {

        int period = 2000;
        int delay = 2000;

        PriceData priceData = new PriceData();
        Price priceObject = new Price();

        Timer timer = new Timer();

        timer.scheduleAtFixedRate(new PriceTimerTask(priceData, priceObject), delay, period);
    }

2

Nie można odwoływać się do zmiennych nieokreślonych, ponieważ tak określa specyfikacja języka Java. Od 8.1.3:
„Każda zmienna lokalna, parametr metody formalnej lub parametr procedury obsługi wyjątków użyty, ale nie zadeklarowany w klasie wewnętrznej, musi zostać zadeklarowany jako ostateczny.” Cały akapit.
Widzę tylko część twojego kodu - według mnie planowanie modyfikacji zmiennych lokalnych jest dziwnym pomysłem. Zmienne lokalne przestają istnieć po opuszczeniu funkcji. Może lepsze byłyby pola statyczne klasy?


2

Właśnie napisałem coś, aby poradzić sobie z czymś zgodnie z intencją autorów . Znalazłem najlepszą rzeczą do zrobienia było pozwolenie, aby konstruktor wziął wszystkie obiekty, a następnie w zaimplementowanej metodzie wykorzystał te obiekty konstruktora.

Jeśli jednak piszesz ogólną klasę interfejsu, musisz przekazać obiekt lub, lepiej, listę obiektów. Można to zrobić przez Object [] lub jeszcze lepiej, Object ..., ponieważ łatwiej jest wywoływać.

Zobacz mój przykładowy kawałek poniżej.

List<String> lst = new ArrayList<String>();
lst.add("1");
lst.add("2");        

SomeAbstractClass p = new SomeAbstractClass (lst, "another parameter", 20, true) {            

    public void perform( ) {                           
        ArrayList<String> lst = (ArrayList<String>)getArgs()[0];                        
    }

};

public abstract class SomeAbstractClass{    
    private Object[] args;

    public SomeAbstractClass(Object ... args) {
        this.args = args;           
    }      

    public abstract void perform();        

    public Object[] getArgs() {
        return args;
    }

}

Zapoznaj się z tym postem na temat zamknięć Java, które obsługują to od razu: http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html

Wersja 1 obsługuje przekazywanie nie-końcowych zamknięć z autocastingiem:
https://github.com/MSeifeddo/Closure-implementation-for-Java-5-6-and-7/blob/master/org/mo/closure/v1/ Closure.java

    SortedSet<String> sortedNames = new TreeSet<String>();
    // NOTE! Instead of enforcing final, we pass it through the constructor
    eachLine(randomFile0, new V1<String>(sortedNames) {
        public void call(String line) {
            SortedSet<String> sortedNames = castFirst();  // Read contructor arg zero, and auto cast it
            sortedNames.add(extractName(line));
        }
    });

2

Jeśli chcesz zmienić wartość w wywołaniu metody w klasie anonimowej, ta „wartość” jest w rzeczywistości Future. Więc jeśli używasz Guava, możesz pisać

...
final SettableFuture<Integer> myvalue = SettableFuture<Integer>.create();
...
someclass.run(new Runnable(){

    public void run(){
        ...
        myvalue.set(value);
        ...
    }
 }

 return myvalue.get();

2

Jedno z rozwiązań, które zauważyłem, nie jest wspomniane (chyba że je przegapiłem, jeśli to zrobię, proszę mnie poprawić), jest użycie zmiennej klasy. Wpadł na ten problem, próbujący uruchomić nowy wątek w metodzie: new Thread(){ Do Something }.

Dzwoń doSomething()z następujących elementów będzie działać. Nie musisz go deklarować final, wystarczy zmienić zakres zmiennej, aby nie była ona gromadzona przed klasą wewnętrzną. Dzieje się tak, chyba że oczywiście Twój proces jest ogromny, a zmiana zakresu może spowodować konflikt. Nie chciałem, aby moja zmienna była ostateczna, ponieważ w żaden sposób nie była ostateczna / stała.

public class Test
{

    protected String var1;
    protected String var2;

    public void doSomething()
    {
        new Thread()
        {
            public void run()
            {
                System.out.println("In Thread variable 1: " + var1);
                System.out.println("In Thread variable 2: " + var2);
            }
        }.start();
    }

}

1

Jeśli zmienna musi być ostateczna, nie może być, możesz przypisać wartość zmiennej do innej zmiennej i sprawić, że będzie ona ostateczna, abyś mógł jej użyć.



1

możesz po prostu zadeklarować zmienną poza klasą zewnętrzną. Następnie będziesz mógł edytować zmienną z klasy wewnętrznej. Czasami napotykam podobne problemy podczas kodowania w Androidzie, dlatego deklaruję zmienną jako globalną i działa ona dla mnie.


To tak naprawdę nie odpowiada na pytanie ... To, dlaczego jesteś poniżany.
Stuart Siegler,


0

Głównym problemem jest to, czy zmienną wewnątrz anonimowej instancji klasy można rozwiązać w czasie wykonywania. Nie jest konieczne, aby zmienna była ostateczna, o ile gwarantowane jest, że zmienna znajduje się w zakresie wykonawczym. Na przykład zobacz dwie zmienne _statusMessage i _statusTextView w metodzie updateStatus ().

public class WorkerService extends Service {

Worker _worker;
ExecutorService _executorService;
ScheduledExecutorService _scheduledStopService;

TextView _statusTextView;


@Override
public void onCreate() {
    _worker = new Worker(this);
    _worker.monitorGpsInBackground();

    // To get a thread pool service containing merely one thread
    _executorService = Executors.newSingleThreadExecutor();

    // schedule something to run in the future
    _scheduledStopService = Executors.newSingleThreadScheduledExecutor();
}

@Override
public int onStartCommand(Intent intent, int flags, int startId) {

    ServiceRunnable runnable = new ServiceRunnable(this, startId);
    _executorService.execute(runnable);

    // the return value tells what the OS should
    // do if this service is killed for resource reasons
    // 1. START_STICKY: the OS restarts the service when resources become
    // available by passing a null intent to onStartCommand
    // 2. START_REDELIVER_INTENT: the OS restarts the service when resources
    // become available by passing the last intent that was passed to the
    // service before it was killed to onStartCommand
    // 3. START_NOT_STICKY: just wait for next call to startService, no
    // auto-restart
    return Service.START_NOT_STICKY;
}

@Override
public void onDestroy() {
    _worker.stopGpsMonitoring();
}

@Override
public IBinder onBind(Intent intent) {
    return null;
}

class ServiceRunnable implements Runnable {

    WorkerService _theService;
    int _startId;
    String _statusMessage;

    public ServiceRunnable(WorkerService theService, int startId) {
        _theService = theService;
        _startId = startId;
    }

    @Override
    public void run() {

        _statusTextView = MyActivity.getActivityStatusView();

        // get most recently available location as a latitude /
        // longtitude
        Location location = _worker.getLocation();
        updateStatus("Starting");

        // convert lat/lng to a human-readable address
        String address = _worker.reverseGeocode(location);
        updateStatus("Reverse geocoding");

        // Write the location and address out to a file
        _worker.save(location, address, "ResponsiveUx.out");
        updateStatus("Done");

        DelayedStopRequest stopRequest = new DelayedStopRequest(_theService, _startId);

        // schedule a stopRequest after 10 seconds
        _theService._scheduledStopService.schedule(stopRequest, 10, TimeUnit.SECONDS);
    }

    void updateStatus(String message) {
        _statusMessage = message;

        if (_statusTextView != null) {
            _statusTextView.post(new Runnable() {

                @Override
                public void run() {
                    _statusTextView.setText(_statusMessage);

                }

            });
        }
    }

}

0

To, co zadziałało, to po prostu zdefiniowanie zmiennej poza tą funkcją.

Tuż przed deklaracją funkcji głównej tj

Double price;
public static void main(String []args(){
--------
--------
}

To nie zadziała, deklarujesz zmienną instancji, aby z niej skorzystać, musisz utworzyć instancję w swojej głównej metodzie. Powinieneś być bardziej szczegółowy lub po prostu dodać modyfikator statyczny do zmiennej „price”.
emerino

0

Zadeklaruj zmienną jako statyczną i odwołaj się do niej w wymaganej metodzie za pomocą className.variable


Non-static parameter cannot be referenced from a static context
Stephen

@Shweta lokalnych zmiennych i parametrów metody nie można uznać za „statyczne”, ponadto chodzi o sposób, w jaki został zaimplementowany, aby umożliwić klasom w metodach (lokalne klasy anonimowe) kontynuowanie dostępu do zmiennych lokalnych i parametrów metody nawet po metodzie powrócił, tzn. wykonuje ich „ostateczne” kopie i wykorzystuje je jako zmienne instancji.
sactiw

0

To kolejne wyjaśnienie. Rozważ ten przykład poniżej

public class Outer{
     public static void main(String[] args){
         Outer o = new Outer();
         o.m1();        
         o=null;
     }
     public void m1(){
         //int x = 10;
         class Inner{
             Thread t = new Thread(new Runnable(){
                 public void run(){
                     for(int i=0;i<10;i++){
                         try{
                             Thread.sleep(2000);                            
                         }catch(InterruptedException e){
                             //handle InterruptedException e
                         }
                         System.out.println("Thread t running");                             
                     }
                 }
             });
         }
         new Inner().t.start();
         System.out.println("m1 Completes");
    }
}

Tutaj Wyjście będzie

m1 Ukończono

Wątek t działa

Wątek t działa

Wątek t działa

................

Teraz metoda m1 () kończy się i przypisujemy zmienną referencyjną o wartości null, teraz obiekt klasy zewnętrznej kwalifikuje się do GC, ale nadal istnieje obiekt klasy wewnętrznej, który ma (Has-A) związek z działającym obiektem wątku. Bez istniejącego obiektu klasy zewnętrznej nie ma szans na istnienie metody m1 (), a bez istniejącej metody m1 () nie ma szansy na istnienie jej zmiennej lokalnej, ale jeśli obiekt klasy wewnętrznej korzysta z zmiennej lokalnej metody m1 (), wszystko jest oczywiste .

Aby rozwiązać ten problem, musimy utworzyć kopię zmiennej lokalnej, a następnie skopiować ją na stos z obiektem klasy wewnętrznej. To, co robi Java, tylko dla zmiennej końcowej, ponieważ tak naprawdę nie są zmienne, są jak stałe (wszystko dzieje się tylko w czasie kompilacji nie w czasie wykonywania).


-1

Aby rozwiązać powyższy problem, różne języki podejmują różne decyzje.

w przypadku Javy rozwiązanie jest takie, jakie widzimy w tym artykule.

w przypadku C # rozwiązaniem jest dopuszczenie efektów ubocznych i jedyną opcją jest przechwytywanie przez odniesienie.

w przypadku C ++ 11 rozwiązaniem jest umożliwienie programistom podjęcia decyzji. Mogą wybrać przechwytywanie według wartości lub referencji. W przypadku przechwytywania według wartości nie wystąpiłyby żadne skutki uboczne, ponieważ przywoływana zmienna jest w rzeczywistości inna. W przypadku przechwytywania przez odniesienie mogą wystąpić działania niepożądane, ale programista powinien to zrozumieć.


-2

Ponieważ jest mylące, jeśli zmienna nie jest ostateczna, ponieważ zmiany w niej nie zostaną wykryte w klasie anonimowej.

Po prostu ustaw zmienne „cena” i „ostatnia cena” jako ostateczne.

-- Edytować

Ups, musisz też nie przypisywać do nich, oczywiście, w swojej funkcji. Będziesz potrzebował nowych zmiennych lokalnych. W każdym razie podejrzewam, że ktoś udzielił ci lepszej odpowiedzi.


2
jest to nie tylko mylące - jest wręcz niepoprawne, więc kompilator na to nie pozwala.
Chii

Ale jak mogę zmienić wartości, kiedy trzeba?
Ankur,

Nie tylko dlatego, że jest mylące; dzieje się tak, ponieważ Java nie obsługuje zamknięć. Zobacz moją odpowiedź poniżej. @Ankur: Możesz ustawić zmienne jako elementy składowe anonimowego obiektu klasy zamiast zmiennych lokalnych w main ().
Jesper

On je modyfikuje, więc nie mogą być ostateczne.
Robin,

Gdyby cena i ostatnia cena były ostateczne, przypisania do nich nie zostałyby skompilowane.
Greg Mattes,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.