247

編集:タイマーを介して数回実行されるため、いくつかの変数の値を変更する必要があります。タイマーを繰り返すたびに値を更新し続ける必要があります。値を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);
}
4

20 に答える 20

198

Java は真のクロージャーをサポートしていませんが、ここで使用しているような匿名クラスを使用することは ( new TimerTask() { ... }) 一種のクロージャーのように見えます。

編集-以下のコメントを参照してください - KeeperOfTheSoul が指摘しているように、以下は正しい説明ではありません。

これが機能しない理由です:

変数lastPriceと価格は、main() メソッドのローカル変数です。main()匿名クラスで作成したオブジェクトは、メソッドが戻るまで存続する場合があります。

main()メソッドが戻ると、ローカル変数 ( や など)lastPricepriceスタックからクリーンアップされるため、戻り後は存在しなくなりmain()ます。

ただし、匿名クラス オブジェクトはこれらの変数を参照します。変数がクリーンアップされた後に匿名クラス オブジェクトが変数にアクセスしようとすると、事態はひどく悪化します。

lastPriceとを作成することによりprice final、それらは実際には変数ではなく、定数になります。lastPriceコンパイラは、無名クラスでのandの使用をprice(もちろんコンパイル時に) 定数の値に置き換えるだけで、存在しない変数にアクセスする際の問題はなくなります。

クロージャーをサポートする他のプログラミング言語は、それらの変数を特別に扱うことによってそれを行います-メソッドが終了したときにそれらが破棄されないようにすることで、クロージャーが引き続き変数にアクセスできるようにします。

@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);      
}
于 2009-08-19T13:22:46.583 に答える
32

匿名デリゲートによって参照される Java 変数のクロージャによる奇妙な副作用を回避するには、finallastPriceとしてマークする必要があります。

それらを変更したいので、これは明らかにうまくいきません。この場合、それらをクラス内にカプセル化することを検討する必要があります。

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);
}
于 2009-08-19T13:23:08.643 に答える
18

匿名クラスを使用する場合は、含まれているクラスからのみ final 変数にアクセスできます。したがって、使用されている変数を final として宣言する必要があります ( lastPricepriceを変更しているため、これはオプションではありません)。または、匿名クラスを使用しないでください。

したがって、オプションは、変数を渡して通常の方法で使用できる実際の内部クラスを作成することです

また:

lastPriceprice変数には、そのように宣言するための簡単な(そして私の意見では醜い)ハックがあります

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

匿名クラスでは、次のように値を設定できます

price[0] = priceObject.getNextPrice(lastPrice[0]);
System.out.println();
lastPrice[0] = price[0];
于 2009-08-19T13:24:27.127 に答える
14

あなたがやろうとしていることができない理由についての良い説明はすでに提供されています。解決策として、多分考えてみてください:

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

おそらくそれよりも優れた設計を行うことができるようですが、変更されないクラス参照内で更新された変数をグループ化できるという考えです。

于 2009-08-19T13:31:54.580 に答える
7

この問題に遭遇したときは、コンストラクターを介してオブジェクトを内部クラスに渡すだけです。(この場合のように)プリミティブまたは不変オブジェクトを渡す必要がある場合は、ラッパークラスが必要です。

編集:実際、私は匿名クラスをまったく使用していませんが、適切なサブクラスを使用しています:

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);
    }
于 2009-08-19T13:36:34.903 に答える
2

匿名クラス内のメソッド呼び出しで値を変更する場合、その「値」は実際にはFuture. したがって、グアバを使用する場合は、次のように書くことができます

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

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

 return myvalue.get();
于 2013-05-16T02:54:27.223 に答える
2

私が気づいた 1 つの解決策は、クラス変数の使用です (見逃していない限り、修正してください)。メソッド内で新しいスレッドを実行しようとして、この問題が発生しました: new Thread(){ Do Something }

以下から呼び出すdoSomething()と動作します。必ずしも宣言する必要はありませんfinal。変数のスコープを変更して、内部クラスの前に収集されないようにするだけです。もちろん、プロセスが巨大で、スコープを変更すると何らかの競合が発生する可能性がある場合を除きます。変数は決して最終/定数ではないため、変数を最終にしたくありませんでした。

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

}
于 2014-12-23T18:38:53.753 に答える
2

私はちょうど著者の意図に沿って何かを処理するために何かを書いた. 最善の方法は、コンストラクターにすべてのオブジェクトを取得させてから、実装されたメソッドでそのコンストラクター オブジェクトを使用することであることがわかりました。

ただし、ジェネリック インターフェイス クラスを作成している場合は、オブジェクトを渡すか、オブジェクトのリストを渡す必要があります。これは 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/閉鎖.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));
        }
    });
于 2011-10-05T12:23:16.820 に答える
2

Java 言語仕様にそう記載されているため、非 final 変数を参照することはできません。8.1.3 から:
「内部クラスで宣言されていないローカル変数、仮メソッド パラメーター、または例外ハンドラー パラメーターは、final として宣言する必要があります。」段落全体。
私はあなたのコードの一部しか見ることができません - 私によると、ローカル変数の変更をスケジュールすることは奇妙な考えです. 関数を終了すると、ローカル変数は存在しなくなります。クラスの静的フィールドの方が良いでしょうか?

于 2009-08-19T13:30:37.340 に答える
1

外部クラスの外で変数を宣言するだけです。この後、内部クラス内から変数を編集できるようになります。Androidでのコーディング中に同様の問題に直面することがあるので、変数をグローバルとして宣言すると、うまくいきます。

于 2015-09-02T10:06:33.200 に答える
1

変数を final にする必要がある場合は、その変数の値を別の変数に代入し、THAT final にして代わりに使用できるようにすることができます。

于 2009-08-19T13:39:12.293 に答える
0

変数を静的として宣言し、必要なメソッドで className.variable を使用して参照します。

于 2014-09-23T05:20:27.570 に答える
0

、、、および匿名の内部クラスのフィールドlastPriceを作成できますか?priceObjectprice

于 2009-08-19T13:29:31.820 に答える
0

ちょうど別の説明。以下の例を考えてみましょう

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 を null に割り当てます。これで、アウター クラス オブジェクトは GC の対象になりますが、実行中のスレッド オブジェクトと (Has-A) 関係を持つインナー クラス オブジェクトはまだ存在します。外部クラス オブジェクトが存在しない場合、m1() メソッドが存在する可能性はなく、m1() メソッドが存在しない場合、そのローカル変数が存在する可能性はありませんが、内部クラス オブジェクトが m1() メソッドのローカル変数を使用する場合、すべてが自明です。 .

これを解決するには、ローカル変数のコピーを作成してから、内部クラス オブジェクトを使用してヒープにコピーする必要があります。実際には変数ではないため、最終変数に対してのみ Java が行うことは、定数のようなものです (すべてはコンパイル時にのみ発生します)。実行時ではありません)。

于 2016-07-05T07:37:31.573 に答える
0

主な関心事は、匿名クラス インスタンス内の変数を実行時に解決できるかどうかです。変数がランタイム スコープ内にあることが保証されている限り、変数を final にする必要はありません。たとえば、updateStatus() メソッド内の 2 つの変数 _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);

                }

            });
        }
    }

}
于 2013-05-23T12:43:16.900 に答える
-1

上記の問題を解決するために、異なる言語は異なる決定を下します。

Java の場合、解決策はこの記事で説明したとおりです。

C# の場合、解決策は副作用を許可することであり、参照によるキャプチャが唯一のオプションです。

C++11 の場合、解決策はプログラマーが決定できるようにすることです。値または参照によるキャプチャを選択できます。値でキャプチャする場合、参照される変数が実際には異なるため、副作用は発生しません。参照によるキャプチャの場合、副作用が発生する可能性がありますが、プログラマはそれを認識する必要があります。

于 2011-10-19T02:08:47.637 に答える
-2

変数がfinalでない場合は混乱を招くため、変数への変更は匿名クラスで取得されません。

変数「price」と「lastPrice」をfinalにするだけです。

- 編集

おっと、そしてあなたはまた、明らかにあなたの関数でそれらに割り当てる必要はありません。新しいローカル変数が必要になります。とにかく、誰かが今までにあなたにもっと良い答えを与えたのではないかと思います。

于 2009-08-19T13:14:40.597 に答える