Cannot refer to a non-final variable inside an inner class defined in a different method
编辑:我需要改变几个变量的值,因为它们通过计时器运行几次。我需要通过计时器不断更新每次迭代的值。我无法将值设置为final,因为这将阻止我更新值,但是我在下面的初始问题中得到了我描述的错误:
我以前写过以下内容:
I am getting the error"cannot refer to a non-final variable inside an inner class defined in a different method".
This is happening for the double called price and the Price called priceObject. Do you know why I get this problem. I do not understand why I need to have a final declaration. Also if you can see what it is I am trying to do, what do I have to do to get around this problem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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不支持真正的闭包,即使使用像你在这里使用的匿名类(EDCOX1,0),看起来还是一种闭包。
编辑-见下面的评论-下面的解释不正确,正如Keeperof指出的。
这就是它不起作用的原因:
变量
当
但是匿名类对象引用了这些变量。如果匿名类对象在清理完变量后试图访问这些变量,那么情况会非常糟糕。
通过使
其他支持闭包的编程语言通过专门处理这些变量来实现这一点——确保在方法结束时不会破坏这些变量,这样闭包仍然可以访问这些变量。
@你可以这样做:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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变量中出现的异常副作用,必须标记为最终的,因此要引用EDCOX1×1和在它们需要标记为最终的定时器任务内的价格。
这显然对您不起作用,因为您希望更改它们,在本例中,您应该考虑将它们封装在类中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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并调用。从计时器中选择。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
使用匿名类时,只能从包含类访问最终变量。因此,您需要声明使用的变量是final(这对您来说不是一个选项,因为您正在更改lastprice和price),或者不要使用匿名类。
因此,您的选项是创建一个实际的内部类,在该类中,您可以传入变量并以正常方式使用它们。
或:
对于你方最后的价格和价格变量,有一个快速的(我认为是丑陋的)攻击,它是这样宣布的。
1 2 | final double lastPrice[1]; final double price[1]; |
在匿名类中,可以这样设置值
1 2 3 |
关于为什么你不能做你想做的事情的好的解释已经提供了。作为解决方案,可以考虑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | 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 ); } } |
似乎您可以做一个更好的设计,但其思想是您可以将更新的变量分组到一个不改变的类引用中。
对于匿名类,实际上是声明一个"无名称"嵌套类。对于嵌套类,编译器生成一个新的独立公共类,该类带有一个构造函数,它将使用所有变量作为参数(对于"命名的"嵌套类,这始终是原始/封闭类的实例)。这是因为运行时环境没有嵌套类的概念,所以需要从嵌套类到独立类进行(自动)转换。
以该代码为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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); } } |
这不起作用,因为编译器就是这样做的:
1 2 3 4 5 6 7 8 |
最初的匿名类被编译器生成的一些独立类替换(代码不准确,但应该给您一个好主意):
1 2 3 4 5 6 7 8 9 10 |
如您所见,独立类持有对共享对象的引用,请记住Java中的所有内容都是按值传递的,因此即使在封闭类中的引用变量"共享"被更改,它指向的实例也不会被修改,并且指向它的所有其他引用变量(如匿名类中的一个:包围1美元),Wi我不知道。这是编译器强制您将这个"共享"变量声明为最终变量的主要原因,这样这种类型的行为就不会进入已经在运行的代码中。
现在,这就是在匿名类中使用实例变量时发生的情况(这是解决问题、将逻辑移动到"实例"方法或类的构造函数时应该做的事情):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
这编译得很好,因为编译器将修改代码,以便包含$1的新生成的类将包含对它被实例化的enclosingClass实例的引用(这只是一种表示,但应该让您继续):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | 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); } } |
像这样,当enclosing class中的引用变量'shared'被重新分配,并且这种情况发生在对thread run()的调用之前,您将看到打印了两次"other hello",因为现在enclosing class$1 enclosing变量将保留对声明它的类的对象的引用,因此对该对象上任何属性的更改都将可见。le到enclosingclass$1的实例。
关于这个主题的更多信息,你可以看到这个优秀的博客帖子(不是我写的):http://kevinboone.net/java_inner.html
当我偶然发现这个问题时,我只是通过构造函数将对象传递给内部类。如果我需要传递原语或不可变的对象(在本例中),就需要一个包装类。
编辑:实际上,我根本不使用匿名类,而是使用适当的子类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | 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); } |
我只是写了一些东西来处理作者的意图。我发现最好的做法是让构造函数获取所有对象,然后在实现的方法中使用该构造函数对象。
但是,如果您正在编写一个通用接口类,那么您必须传递一个对象,或者更好地传递一个对象列表。这可以由对象[]或更好的对象来完成,对象…因为打电话比较容易。
请看下面我的示例文章。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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-和.html
版本1支持通过自动铸造的非最终封口:https://github.com/mseifeddo/closing-implementation-for-java-5-6-and-7/blob/master/org/mo/closing/v1/closing.java
1 2 3 4 5 6 7 8 | 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)); } }); |
我注意到的一个解决方案没有提到(除非我错过了它,如果我做了,请纠正我),那就是使用一个类变量。在方法:
从以下位置调用EDOCX1[1]将有效。您不必声明它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
您不能引用非最终变量,因为Java语言规范是这样说的。从81.3:"在内部类中使用但未声明的任何局部变量、形式方法参数或异常处理程序参数必须声明为final。"整段。我只能看到您的代码的一部分-根据我的计划,局部变量的修改是一个奇怪的想法。当您离开函数时,局部变量将不再存在。也许类的静态字段会更好?
如果要更改匿名类中方法调用的值,则该"值"实际上是一个
1 2 3 4 5 6 7 8 9 10 11 12 13 | ... final SettableFuture<Integer> myvalue = SettableFuture<Integer>.create(); ... someclass.run(new Runnable(){ public void run(){ ... myvalue.set(value); ... } } return myvalue.get(); |
如果变量必须是final,则不能是final,则可以将变量的值赋给另一个变量,并使其成为final,以便可以使用它。
使用classname.this.variablename引用非最终变量
您可以在外部类之外声明变量。之后,您将能够从内部类中编辑变量。在android中编码时,我有时会遇到类似的问题,所以我将变量声明为全局变量,它对我很有用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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赋给空,现在外部类对象可以用于gc,但是内部类对象仍然存在,并且与正在运行的线程对象具有(has-a)关系。如果没有现有的外部类对象,就不可能存在现有的m1()方法;如果没有现有的m1()方法,就不可能存在其局部变量,但如果内部类对象使用m1()方法的局部变量,则一切都是自解释的。
为了解决这个问题,我们必须创建一个局部变量的副本,然后必须复制到具有内部类对象的堆中,Java只对最终变量做什么,因为它们实际上不是变量,它们就像常量(在编译时发生的一切都不只是在运行时)。
主要关注的是匿名类实例中的变量是否可以在运行时解决。只要确保变量在运行时范围内,就不必使变量成为最终变量。例如,请参见updateStatus()方法中的两个变量"状态消息"和"状态文本视图"。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | 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); } }); } } } |
对我有用的只是在你的函数之外定义变量。
在主函数声明之前,即
你能把
将变量声明为静态变量,并使用classname.variable在所需方法中引用它
为了解决上述问题,不同的语言会做出不同的决定。
对于Java,解决方案与我们在本文中看到的一样。
对于C,解决方案是允许副作用,而参考捕获是唯一的选择。
对于C++ 11,解决方案是允许程序员做出决定。它们可以选择通过值或引用进行捕获。如果按值捕获,则不会产生任何副作用,因为引用的变量实际上是不同的。如果通过引用捕获,可能会发生副作用,但是程序员应该认识到它。
因为如果变量不是最终的,它会令人困惑,因为对它所做的更改不会在匿名类中被获取。
只需将变量'price'和'lastprice'设为最终值。
--编辑
很明显,在您的函数中,您还需要不分配给它们。您将需要新的局部变量。不管怎样,我怀疑现在有人给了你一个更好的答案。