编辑: 我需要改变几个变量的值,因为他们通过计时器运行几次。我需要在计时器的每次迭代中不断更新值。我不能将值设置为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);
}

因为如果变量不是final,就会令人困惑,因为匿名类中不会提取对它的更改。

只需要让变量“price”和“lastPrice”成为final。

——编辑

很明显,在你的函数中,你也不需要给它们赋值。您需要新的局部变量。不管怎样,我怀疑现在已经有人给了你一个更好的答案。


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

为了避免java闭包中由匿名委托引用的变量的奇怪副作用,必须将其标记为final,因此要在计时器任务中引用lastPrice和price,它们需要标记为final。

这显然不适合您,因为您希望更改它们,在这种情况下,您应该考虑将它们封装在一个类中。

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

现在只需创建一个新的Foo作为final,并从计时器调用.tick。

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

使用匿名类时,只能从包含类中访问最终变量。因此,您需要声明正在使用的变量为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];

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


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


对于为什么你不能做你正在尝试做的事情,已经给出了很好的解释。作为解决方案,也许可以考虑:

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

似乎你可以做一个比这更好的设计,但其思想是你可以将更新的变量分组在一个不变的类引用中。


当我偶然发现这个问题时,我只是通过构造函数将对象传递给内部类。如果需要传递原语或不可变对象(如本例),则需要包装器类。

编辑:实际上,我根本不使用匿名类,而是一个适当的子类:

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

如果变量要求为final,那么你可以将变量的值分配给另一个变量,并使其为final,这样你就可以使用它。


我只是写了一些东西来处理作者的意图。 我发现最好的办法是让构造函数取所有的对象,然后在你实现的方法中使用构造函数对象。

但是,如果您正在编写一个泛型接口类,那么您必须传递一个Object,或者最好是一个Object列表。这可以通过Object[]完成,甚至更好,Object…因为这样更容易打电话。

请看下面的例子。

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

}

请参阅这篇关于Java闭包的文章,它支持开箱即用: http://mseifed.blogspot.se/2012/09/closure-implementation-for-java-5-6-and.html

版本1支持通过自动转换传递非最终闭包: 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));
        }
    });

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

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

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

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


如果您想在匿名类中的方法调用中更改一个值,该“值”实际上是一个Future。所以,如果你用番石榴,你可以写

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

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

 return myvalue.get();

主要的问题是匿名类实例中的变量是否可以在运行时解析。只要保证变量在运行时范围内,就不一定要使变量为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);

                }

            });
        }
    }

}

对我来说有用的是定义函数外面的变量。

就在主函数declare之前,即。

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

使用ClassName.this.variableName来引用非最终变量


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


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

以下面的代码为例:

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


我注意到一个没有提到的解决方案(除非我错过了,如果我错过了,请纠正我)是使用类变量。尝试在一个方法中运行一个新线程时遇到了这个问题:new thread (){Do Something}。

从下面调用doSomething()将工作。你不一定要声明它为final,只需要改变变量的作用域,这样它就不会在内部类之前被收集。当然,除非您的流程非常庞大,改变范围可能会产生某种冲突。我不想让我的变量成为final,因为它绝不是final/constant。

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

}

你可以在外部类之外声明变量。在此之后,您将能够从内部类中编辑变量。我有时会遇到类似的问题,而在android编码,所以我声明变量为全局,它为我工作。


这是另一种解释。考虑下面这个例子

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完成

线程运行

线程运行

线程运行

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

现在方法m1()完成,我们将引用变量o赋值为空,现在外部类对象有资格进行GC,但内部类对象仍然存在,与正在运行的线程对象有(has - a)关系。没有外部类对象,就不可能存在m1()方法,没有m1()方法,就不可能存在它的局部变量,但如果内部类对象使用m1()方法的局部变量,那么一切都是不言自明的。

为了解决这个问题,我们必须创建一个局部变量的副本,然后必须用内部类对象将其复制到堆中,java只对final变量做了什么,因为它们实际上不是变量,它们像常量(一切都发生在编译时,而不是在运行时)。