编辑: 我需要改变几个变量的值,因为他们通过计时器运行几次。我需要在计时器的每次迭代中不断更新值。我不能将值设置为final,因为这将阻止我更新值,但是我得到了我在下面的初始问题中描述的错误:

我之前写过如下内容:

我得到错误“不能引用在不同方法中定义的内部类中的非最终变量”。 这发生在名为price的double和名为priceObject的price上。你知道我为什么会有这个问题吗?我不明白为什么我要做最后申报。如果你能看到我在做什么,我要怎么做才能解决这个问题。

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

当前回答

Java不支持真正的闭包,即使使用像您在这里使用的匿名类(new TimerTask(){…})看起来像是一种闭包。

编辑-请看下面的评论-以下是一个不正确的解释,正如keperofsoul指出的那样。

这就是为什么它不起作用:

变量lastPrice和price是main()方法中的局部变量。使用匿名类创建的对象可能会持续到main()方法返回之后。

当main()方法返回时,局部变量(如lastPrice和price)将从堆栈中清除,因此在main()返回后它们将不再存在。

但是匿名类对象引用了这些变量。如果匿名类对象在变量被清理后试图访问它们,那么事情将会发生严重的错误。

通过使lastPrice和price成为final,它们不再是真正的变量,而是常量。然后,编译器可以将匿名类中lastPrice和price的使用替换为常量的值(当然是在编译时),这样就不会再有访问不存在的变量的问题了。

其他支持闭包的编程语言是通过特殊处理这些变量来实现的——确保它们在方法结束时不会被销毁,这样闭包仍然可以访问这些变量。

你可以这样做:

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

其他回答

使用匿名类时,只能从包含类中访问最终变量。因此,您需要声明正在使用的变量为final(这对您来说不是一个选项,因为您正在更改lastPrice和price),或者不要使用匿名类。

因此,您的选择是创建一个实际的内部类,您可以在其中传递变量并以正常方式使用它们

or:

对于你的lastPrice和price变量,有一个快速(在我看来很难看)的方法,就是像这样声明它

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

在匿名类中,你可以这样设置值

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

主要的问题是匿名类实例中的变量是否可以在运行时解析。只要保证变量在运行时范围内,就不一定要使变量为final。例如,请参阅updateStatus()方法中的两个变量_statusMessage和_statusTextView。

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

                }

            });
        }
    }

}

为了解决上述问题,不同的语言做出了不同的决定。

对于Java,解决方案就像我们在本文中看到的那样。

对于c#,解决方案是允许副作用和引用捕获是唯一的选择。

对于c++ 11,解决方案是让程序员来做决定。他们可以选择通过值或引用来捕获。如果通过值捕获,则不会发生副作用,因为引用的变量实际上是不同的。如果通过引用捕获,可能会产生副作用,但程序员应该意识到这一点。

你能创建匿名内部类的lastPrice, priceObject和price字段吗?

使用匿名类,实际上是在声明一个“无名称”嵌套类。对于嵌套类,编译器生成一个新的独立的公共类,其中带有一个构造函数,该构造函数将使用所有变量作为参数(对于“命名”嵌套类,这始终是原始/外围类的实例)。这样做是因为运行时环境没有嵌套类的概念,因此需要从嵌套类(自动)转换到独立类。

以下面的代码为例:

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

这是行不通的,因为这是编译器在底层所做的:

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

原来的匿名类被编译器生成的一些独立的类所取代(代码不精确,但应该给你一个很好的想法):

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

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

As you can see, the standalone class holds a reference to the shared object, remember that everything in java is pass-by-value, so even if the reference variable 'shared' in EnclosingClass gets changed, the instance it points to is not modified, and all other reference variables pointing to it (like the one in the anonymous class: Enclosing$1), will not be aware of this. This is the main reason the compiler forces you to declare this 'shared' variables as final, so that this type of behavior won't make it into your already running code.

现在,这是当你在匿名类中使用实例变量时发生的事情(这是你应该做的来解决你的问题,将你的逻辑移动到一个“实例”方法或类的构造函数):

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

这可以很好地编译,因为编译器会修改代码,这样新生成的类EnclosingClass $1将持有它被实例化的EnclosingClass实例的引用(这只是一个表示,但应该会让你继续下去):

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

就像这样,当EnclosingClass中的引用变量“shared”被重新赋值时,这发生在调用Thread#run()之前,你会看到“other hello”打印了两次,因为现在EnclosingClass$1#封闭变量将保持对它声明的类对象的引用,因此对该对象上的任何属性的更改将对EnclosingClass$1的实例可见。

关于这个主题的更多信息,你可以看到这篇优秀的博客文章(不是我写的):http://kevinboone.net/java_inner.html