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

当前回答

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

以下面的代码为例:

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

其他回答

为了避免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);
}

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

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

以下面的代码为例:

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

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

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

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

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