不能引用在不同方法中定义的内部类中的非最终variables
编辑:我需要改变几个variables的值,因为他们通过计时器运行几次。 我需要通过定时器每次迭代不断更新值。 我不能将值设置为final,因为这将阻止我更新值,但是我得到了我在下面的初始问题中描述的错误:
我以前写过以下内容:
我得到错误“不能引用在一个不同的方法定义的内部类中的非最终variables”。
这种情况发生在称为价格的双倍价格和称为priceObject的价格上。 你知道我为什么会遇到这个问题吗? 我不明白为什么我需要最后的宣言。 另外,如果你可以看到我正在做的是什么,我该怎么做才能解决这个问题。
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() { ... }
)看起来像是一种闭包。
编辑 – 看下面的评论 – 以下是不正确的解释,如KeeperOfTheSoul指出。
这就是为什么它不起作用:
lastPrice
和pricevariables是main()方法中的局部variables。 使用匿名类创build的对象可能会持续到main()
方法返回之前。
当main()
方法返回时,局部variables(如lastPrice
和price
)将从堆栈中清除,所以在main()
返回后它们将不再存在。
但是匿名类对象引用这些variables。 如果匿名类对象在清理之后尝试访问variables,情况就会变得非常糟糕。
通过使lastPrice
和price
final
,它们不再是真正的variables,而是常数。 然后,编译器就可以用匿名类中的lastPrice
和price
来代替常量的值(当然是在编译时),而且不会再有访问不存在的variables的问题了。
其他支持闭包的编程语言通过特别处理这些variables来做到这一点 – 确保在方法结束时不会被销毁,以便闭包仍然可以访问这些variables。
@Ankur:你可以这样做:
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); }
为了避免在匿名lastPrice
引用的javavariables中使用闭包的奇怪副作用,必须将其标记为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; } }
现在只需创build一个新的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); }
使用匿名类时,只能从包含类访问最终variables。 因此,您需要声明最终使用的variables(因为您正在更改lastPrice和price ,所以这不是您的select),也不要使用匿名类。
所以你的select是创build一个实际的内部类,你可以在其中传递variables并以正常的方式使用它们
要么:
有一个快速(在我看来丑陋)破解你的lastPrice和价格variables,这是宣布它是这样的
final double lastPrice[1]; final double price[1];
在你的匿名类中,你可以像这样设置值
price[0] = priceObject.getNextPrice(lastPrice[0]); System.out.println(); lastPrice[0] = price[0];
很好的解释,为什么你不能做你已经提供的东西。 作为解决scheme,可以考虑:
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 ); } }
似乎可能你可以做一个比这更好的devise,但想法是,你可以将更新的variables分组在一个类不变的类引用。
用匿名类,你实际上是在声明一个“无名”的嵌套类。 对于嵌套类,编译器使用一个构造函数生成一个新的独立公共类,该构造函数将把所有使用的variables作为参数(对于“命名”嵌套类,这总是原始/封闭类的一个实例)。 这是因为运行时环境没有嵌套类的概念,所以需要从嵌套类到独立类进行(自动)转换。
以此代码为例:
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); } }
正如你所看到的,独立类拥有对共享对象的引用,请记住,java中的所有东西都是按值传递的,所以即使EnclosingClass中的引用variables“shared”被更改,它指向的实例也不会被修改和所有其他引用variables(如匿名类中的variables:包含$ 1),都不会意识到这一点。 这是编译器强制你将这个“共享”variables声明为final的主要原因,所以这种types的行为不会让你进入已经运行的代码。
现在,当你在一个匿名类中使用一个实例variables的时候会发生这种情况(这是你应该做的,以解决你的问题,把你的逻辑移到一个“实例”方法或类的构造函数):
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); } }
这个编译好,因为编译器会修改代码,所以新生成的类Enclosing $ 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中的引用variables'shared'被重新分配,并且在调用Thread#run()之前发生,你会看到“other hello”打印两次,因为现在EnclosingClass $ 1#enclosingvariables会保留一个引用到声明它的类的对象,因此对EnclosingClass $ 1的实例可以看到该对象上任何属性的更改。
有关这个主题的更多信息,你可以看到这个优秀的博客文章(不是我写的): http : //kevinboone.net/java_inner.html
当我偶然发现这个问题时,我只是通过构造函数将对象传递给内部类。 如果我需要传递基元或不可变对象(如本例中那样),则需要一个包装类。
编辑:其实,我根本不使用匿名类,而是一个适当的子类:
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); }
由于Java语言规范如此,所以不能引用非最终variables。 从8.1.3开始:
“任何局部variables,forms方法参数或exception处理程序参数在内部类中使用但未声明,都必须声明为final。” 整段。
我只能看到你的代码的一部分 – 根据我调度局部variables的修改是一个奇怪的想法。 离开函数时,局部variables不复存在。 也许一个类的静态字段会更好?
我只是写了一些东西来处理 作者的意图 。 我发现最好的做法是让构造函数获取所有对象,然后在实现的方法中使用构造函数对象。
但是,如果您正在编写通用接口类,则必须传递一个对象,或者更好地传递一个对象列表。 这可以通过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)); } });
如果你想在一个匿名类的方法调用中改变一个值,那这个“value”其实就是一个Future
。 所以,如果你使用番石榴,你可以写
... final SettableFuture<Integer> myvalue = SettableFuture<Integer>.create(); ... someclass.run(new Runnable(){ public void run(){ ... myvalue.set(value); ... } } return myvalue.get();
我注意到的一个解决scheme是没有提到(除非我错过了,如果我确实请更正我),是使用类variables。 尝试在一个方法内运行一个新线程的问题: new Thread(){ Do Something }
。
从以下调用doSomething()
将工作。 你不一定要final
声明它,只需要改变variables的范围,使它不在内部类之前收集。 这当然是,除非你的过程是巨大的,改变范围可能会产生某种冲突。 我不想让我的变数决赛,因为它决不是最后的/常数。
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(); } }
如果variables需要是最终的,那么不能那么你可以把variables的值赋给另一个variables,并做出最后的决定,所以你可以用它来代替。
使用ClassName.this.variableName引用非最终variables
你可以在外部类声明variables。 在此之后,您将能够编辑内部类中的variables。 我有时在Android中编码时面临类似的问题,所以我声明variables为全局的,它适用于我。
你可以使lastPrice
, priceObject
和price
字段的匿名内部类?
主要关心的是在运行时是否可以parsing匿名类实例中的variables。 只要保证variables在运行时间范围内,就不一定要做一个variablesfinal。 例如,请参阅updateStatus()方法内的两个variables_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); } }); } } }
什么对我来说只是定义你的这个function以外的variables。
就在主函数声明之前
Double price; public static void main(String []args(){ -------- -------- }
将该variables声明为静态,并使用className.variable在必需的方法中引用它
只是另一个解释。 考虑下面的这个例子
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()完成了,我们把引用variables赋值为null,现在外部类对象有资格使用GC,但是内部类对象仍然存在与正在运行的Thread对象有(Has-A)关系。 没有现有的Outer类对象,现有的m1()方法没有机会,没有现有的m1()方法,就没有机会存在它的局部variables,但是如果Inner Class Object使用m1()方法的局部variables,那么一切都是自我解释的。
为了解决这个问题,我们必须创build一个局部variables的副本,然后必须使用Inner类对象复制到堆中,java只为finalvariables做什么,因为它们实际上不是variables,它们就像是常量(所有事情都只发生在编译时而不是在运行时)。
为了解决上述问题,不同的语言做出不同的决定。
对于Java来说,解决scheme就像我们在本文中看到的一样。
对于C#来说,解决scheme是允许通过引用的副作用和捕获是唯一的select。
对于C ++ 11来说,解决scheme是让程序员做出决定。 他们可以select通过价值或借鉴来捕捉。 如果按值捕获,则不会发生副作用,因为所引用的variables实际上是不同的。 如果通过引用捕获,可能会发生副作用,但程序员应该认识到这一点。
因为如果这个variables不是最终的,这会让人感到困惑,因为它的变化将不会在匿名类中find。
只要将variables'price'和'lastPrice'作为最终的。
– 编辑
糟糕,你也不需要分配给他们,显然,在你的function。 你将需要新的本地variables。 无论如何,我怀疑现在有人给你一个更好的答案。