web-dev-qa-db-ja.com

別のメソッドで定義された内部クラス内の非最終変数を参照できません

編集済み:タイマーを介して数回実行されるため、いくつかの変数の値を変更する必要があります。タイマーを繰り返すたびに値を更新し続ける必要があります。値をfinalに設定することはできません。値を更新できないためですが、以下の最初の質問で説明するエラーが発生しています。

私は以前に以下を書いていました:

「別のメソッドで定義された内部クラス内の非最終変数を参照できません」というエラーが表示されます。

これは、価格と呼ばれる二重と、価格オブジェクトと呼ばれる価格で発生します。この問題が発生する理由を知っていますか。最終宣言が必要な理由がわかりません。また、私が何をしようとしているのかを見ることができる場合、この問題を回避するには何をしなければなりませんか。

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

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

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

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

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

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

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

lastPriceおよびpricefinalを作成することにより、これらは実際には変数ではなく、定数になります。コンパイラーは、匿名クラスでのlastPriceおよび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);      
}
195
Jesper

匿名デリゲートによって参照されるJava変数のクロージャーによる奇妙な副作用を回避するには、最終としてマークする必要があるため、タイマータスク内でlastPriceと価格を参照するには、最終としてマークする必要があります。

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

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を作成し、タイマーから.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);
}
30
Chris Chilvers

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

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

または:

あなたのlastPricepriceの変数のための迅速な(そして私の意見ではい)ハックがあります。

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

あなたの匿名クラスでは、このような値を設定できます

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

既に提供しようとしていることを実行できない理由の良い説明。解決策として、次のことを検討してください。

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

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

13
Peter Cardona

匿名クラスを使用すると、実際には「名前のない」ネストされたクラスを宣言します。ネストされたクラスの場合、コンパイラーは、引数として使用するすべての変数を受け取るコンストラクターを持つ新しいスタンドアロンパブリッククラスを生成します(「名前付き」ネストクラスの場合、これは常に元のクラスまたは包含クラスのインスタンスです)。これは、ランタイム環境にネストされたクラスの概念がないため、ネストされたクラスからスタンドアロンクラスへの(自動)変換が必要になるためです。

たとえば、次のコードをご覧ください。

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'が変更されても、それが指すインスタンスは変更されません。 、およびそれを指す他のすべての参照変数(匿名クラス内の変数:Enclosing $ 1など)は、これを認識しません。これが、コンパイラがこの「共有」変数を最終として宣言することを強制する主な理由です。そのため、このタイプの動作は、すでに実行中のコードに反映されません。

さて、これは、匿名クラス内でインスタンス変数を使用すると発生します(問題を解決し、ロジックを「インスタンス」メソッドまたはクラスのコンストラクターに移動するために行うべきことです)。

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の参照変数「shared」が再割り当てされると、Thread#run()の呼び出しの前にこれが発生します。EnclosingClass$ 1#enclosing変数が参照を保持するため、「other hello」が2回出力されます。それが宣言されたクラスのオブジェクトに、そのオブジェクトの属性への変更はEnclosingClass $ 1のインスタンスに表示されます。

このテーマの詳細については、次の優れたブログ記事を参照してください(私は書いていません): http://kevinboone.net/Java_inner.html

10
emerino

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

編集:実際には、匿名クラスはまったく使用せず、適切なサブクラスを使用します。

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

私が気づいた解決策の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();
    }

}
2
James

ハンドルに沿って何かを書いたところです著者の意図コンストラクターテイクにすべてのオブジェクトを許可し、実装されたメソッドでそのコンストラクターオブジェクトを使用することが最善の方法であることがわかりました。

ただし、ジェネリックインターフェイスクラスを記述する場合は、オブジェクトを渡すか、オブジェクトのリストを渡す必要があります。これは、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));
        }
    });
2
momomo

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

2
Tadeusz Kopec

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

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

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

 return myvalue.get();
2
Earth Engine

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

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

1

className.this.variableNameを使用して、非最終変数を参照します

1
user3251651

ただ別の説明。以下の例を検討してください

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)関係を持つ内側のクラスオブジェクトはまだ存在します。既存のOuterクラスオブジェクトがなければ、既存のm1()メソッドは存在せず、既存のm1()メソッドがなければローカル変数が存在する可能性はありませんが、Inner Class Objectがm1()メソッドのローカル変数を使用する場合、すべてが自明です。

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

0
pks

主な関心事は、匿名クラスインスタンス内の変数を実行時に解決できるかどうかです。変数がランタイムスコープ内にあることが保証されている限り、変数を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);

                }

            });
        }
    }

}
0
WaiKit Kung

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

0
shweta

私のために働いたのは、あなたのこの関数の外で変数を定義することです。

メイン関数が宣言する直前、すなわち.

Double price;
public static void main(String []args(){
--------
--------
}
0
punkck

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

0
Greg Mattes