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

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

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

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

——编辑

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

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

__abc0 - __abc1

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

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

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

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

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

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

你可以这样做:

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价格),或者不要使用匿名类。

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

或者:

对于你的lastPrice价格变量,有一个快速(在我看来很难看)的hack,就是这样声明它

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

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

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

你能在匿名内部类中创建lastPricepriceObjectprice字段吗?

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

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

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,这样你就可以使用它。

我刚刚写了一些东西到处理沿着作者的意图。 我发现最好的方法是让构造函数take所有的对象,然后在你实现的方法中使用构造函数对象。< / p >

但是,如果您正在编写一个泛型接口类,那么您必须传递一个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 < / p > 版本1支持通过自动转换传递非最终闭包 https://github.com/MSeifeddo/Closure-implementation-for-Java-5-6-and-7/blob/master/org/mo/closure/v1/Closure.java < / p >

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

正如你所看到的,独立类持有一个对共享对象的引用,记住java中的一切都是值传递的,所以即使EnclosingClass中的引用变量'shared'被改变了,它所指向的实例也没有被修改,所有指向它的其他引用变量(比如匿名类:EnclosingClass $1)都不会意识到这一点。这就是编译器强迫你将这个“共享”变量声明为final的主要原因,这样这种类型的行为就不会出现在你已经运行的代码中。

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

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编码,所以我声明变量为全局,它为我工作。