在不同方法中定义的内部类无法引用非最终变量。

251

编辑:

我需要在计时器运行多次的过程中更改多个变量的值。我需要在每次通过计时器迭代时更新这些值。我不能将这些值设置为final,因为那样会防止我更新它们,然而我正在遇到下面描述的错误:

我之前写的是:

我遇到了“无法引用在不同方法中定义的内部类中的非最终变量”的错误。

这发生在名为price和名为priceObject的double变量上。你知道我为什么会遇到这个问题吗?我不明白为什么需要有一个final声明。如果你能看出我试图做什么,那么我需要怎么做才能解决这个问题。

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

我所问的是如何在计时器中获取一个可以不断更新的变量。 - Ankur
1
@Ankur:简单的回答是“不行”。但是你可以使用内部类来实现所需的效果;请参见@petercardona的答案。 - Stephen C
@StephenC 抱歉,但是现在这个petercardona是谁? - wlnirvana
我很惊讶你需要问那个问题... - Stephen C
20个回答

2
您不能引用非final变量,因为Java语言规范是这样规定的。来自8.1.3:
“任何在内部类中使用但未声明的局部变量、形式方法参数或异常处理程序参数都必须声明为final。”整段。
我只能看到您代码的一部分-根据我的理解,调度修改局部变量是一个奇怪的想法。当您离开函数时,局部变量就不存在了。也许类的静态字段会更好?

1
使用 ClassName.this.variableName 引用非 final 变量。

1
如果需要将变量定义为 final,但是实际上无法这样做,则可以将该变量的值赋给另一个变量,并将该变量定义为 final,以便您可以使用它。

0

再解释一下。请考虑下面的例子

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

这里的输出结果将会是:

m1 完成

线程 t 正在运行

线程 t 正在运行

线程 t 正在运行

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

现在方法m1()已经完成,我们将引用变量o赋值为null,现在外部类对象符合GC的条件,但内部类对象仍然存在,并且与正在运行的Thread对象具有(Has-A)关系。如果不存在外部类对象,则不存在m1()方法的可能性,如果不存在m1()方法,则不存在其局部变量的可能性,但是如果内部类对象使用m1()方法的局部变量,则一切都是不言自明的。

为了解决这个问题,我们必须创建局部变量的副本,然后将它们与内部类对象一起复制到堆中,Java只对final变量执行此操作,因为它们实际上不是变量,而是常量(所有操作仅在编译时发生,而不是在运行时)。


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

                }

            });
        }
    }

}

0

对我有效的方法是在函数外部定义变量。

就在主函数之前声明即可。

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

那样行不通,你正在声明一个实例变量,要使用它,你需要在主方法内创建一个实例。你应该更具体一些,或者只是将“price”变量添加静态修饰符。 - emerino

0

将变量声明为静态的,并在所需方法中使用 className.variable 引用它。


Non-static parameter cannot be referenced from a static context - tread
@Shweta 本地变量和方法参数不能被声明为“静态”,更重要的是,它是通过实现方式来允许方法内部的类(本地匿名类)在方法返回后继续访问本地变量和方法参数,即将它们的“final”副本作为实例变量使用。 - sactiw

0
你能够使匿名内部类的 lastPrice, priceObjectprice 字段吗?

-1
为了解决上述问题,不同的编程语言做出了不同的决策。
对于Java,解决方案如本文所示。
对于C#,解决方案是允许副作用并且只能通过引用来捕获。
对于C++11,解决方案是允许程序员做出决策。他们可以选择按值或按引用捕获。如果按值捕获,则不会发生副作用,因为引用的变量实际上是不同的。如果按引用捕获,则可能会发生副作用,但程序员应该意识到这一点。

-2

如果变量不是final的话,会很容易混淆,因为它的更改不会在匿名类中被捕获。

只需将变量'price'和'lastPrice'设置为final即可。

-- 编辑

哎呀,显然,在您的函数中也不能对它们进行赋值。您需要新的局部变量。无论如何,我猜现在有人已经给您提供了更好的答案。


2
不仅仅是令人困惑 - 它是明显错误的,因此编译器不允许它。 - Chii
那么当我需要更改这些值时,我该怎么做呢? - Ankur
不仅因为它很混乱;这是因为Java不支持闭包。请参见下面的答案。@Ankur:您可以将变量作为匿名类对象的成员变量,而不是main()中的局部变量。 - Jesper
他正在修改它们,所以它们不能是最终的。 - Robin
如果price和lastPrice是final的,对它们的赋值将无法编译。 - Greg Mattes
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接