- PR -

無名インナクラスにデータを渡す方法

投稿者投稿内容
うのきち
ベテラン
会議室デビュー日: 2003/02/17
投稿数: 55
投稿日時: 2003-10-16 13:31
現在のJava VMのスレッドモデルからくる制約です。
ローカル変数は、スレッドの私有領域に置いても良いことになっています。従って、別のスレッドからは変更が見えません(より正確には「見えないようにJavaVMを実装しても構いません」)。インナークラスのインスタンスを利用するスレッドと、ローカル変数を変更するスレッドが別のスレッドであった場合、これを確実にに行う方法が存在しません。
以下から、Inner class specification 1.1を入手して、inner classに関するところを読んでみて下さい。すいません英文です。

http://developer.java.sun.com/developer/qow/archive/81/index.html

該当部分を引用しますと、

In fact, if a local variable or parameter in one class is referred to by another (inner) class, it must be declared final. Because of potential synchronization problems, there is by design no way for two objects to share access to a changeable local variable

和文だと、ASCIIの実践Javaという本が、割と詳しく書いてあります。

引用:

unibonさんの書き込み (2003-10-16 10:03) より:
unibon です。こんにちわ。
私は、今までとりあえずコンパイルエラーを解消するために final を付けてきました。
(ちなみに、オブジェクト型変数ならば単に変数に final を付けるだけでよいですが、
基本型変数だと、final 用の変数を別に設ける必要がある場合もあるので面倒です。)

が、なんで final を付けなければならないのかの必然性が良く分かりません。
final がなくても、静的なコンテキスト(static/lexical な context)から分かりそうな気もします。
もしかしたら、Java のコンパイラ(javac 等)が楽をしたいためだけのために、
そのような仕様になっているような気がするのですが、
この予想は正しいでしょうか。


Wata
ぬし
会議室デビュー日: 2003/05/17
投稿数: 279
投稿日時: 2003-10-16 13:34
引用:

unibonさんの書き込み (2003-10-16 10:03) より:
私は、今までとりあえずコンパイルエラーを解消するために final を付けてきました。
が、なんで final を付けなければならないのかの必然性が良く分かりません。


私はfinalは必然だと思います。
無名インナークラスのインスタンスは、参照するローカル変数よりも生存期間が
長くなるため、必然的にローカル変数の値を暗黙的なインスタンスフィールドに
コピーして持たなければなりません。

しかし、非final変数の参照を許可すると、どの時点でのコピーなのかが曖昧に
なります。
例えば、非final変数の参照を許可されるとした場合、
コード:
public static void main(String[] args) {
   String str = "abc";

   Object o = new Object() {
      public String toString() {
         return str;
      }
   };

   System.out.println(o);   // 1
   
   str = "def";
   
   System.out.println(o);   // 2
}


このメソッドは1と2においてどちらも"abc"を出力することになると思いますが、
これはあまり直感的な動作とはいえないと思います。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-10-16 19:29
unibon です。こんにちわ。

引用:

うのきちさんの書き込み (2003-10-16 13:31) より:
以下から、Inner class specification 1.1を入手して、inner classに関するところを読んでみて下さい。すいません英文です。

http://developer.java.sun.com/developer/qow/archive/81/index.html


ありがとうございます。上記 URL を辿っていくと、
JDK 1.1.8 の Documentation の一部になりますね。
(ちなみに、たとえば、ローカルのインストールディレクトリ内の、
C:\jdk1.1.8\docs\guide\innerclasses\spec\innerclasses.doc1.html
などのパスになります。)
ご紹介されている箇所の前後にも参考になる記述がありそうなのですが、
どうもすぐには理解できませんでした。
しばらく時間をかけて読んでみます。

#JDK のドキュメントだから日本語化になったものがあるかと期待しているのですが、
#JDK 1.1.x の時代のものはなっていないのだったかもしれません。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-10-16 19:48
unibon です。こんにちわ。

引用:

Wataさんの書き込み (2003-10-16 13:34) より:
例えば、非final変数の参照を許可されるとした場合、
コード:
public static void main(String[] args) {
   String str = "abc";

   Object o = new Object() {
      public String toString() {
         return str;
      }
   };

   System.out.println(o);   // 1
   
   str = "def";
   
   System.out.println(o);   // 2
}


このメソッドは1と2においてどちらも"abc"を出力することになると思いますが、
これはあまり直感的な動作とはいえないと思います。


ありがとうございます。
上記のコードを下記のように変形してみたのですが、
こう変形すると final がなくてもコンパイルできるし動きます。
私は、上と下は等価(もちろん完全な等価ではありませんが、
ローカル変数 str の扱いが同じ)だと思っているので、
なぜ上だと final が必要で下だと不要なのかが分からないのです。
たぶん、この、等価だという私の前提が正しくないんですよね。

コード:
public class Hoge {

    public static void main(String[] args) {
        String str = "abc";

        class Bar {
            private final String foo; // この final は単に immutable を示すだけでなくても良い。
            public Bar(String aFoo) {
                foo = aFoo;
            }
            public String toString() {
                return foo;
            }
        };
        Bar o = new Bar(str);

        System.out.println(o); // 1

        str = "def";

        System.out.println(o); // 2
    }

}

Wata
ぬし
会議室デビュー日: 2003/05/17
投稿数: 279
投稿日時: 2003-10-16 21:51
引用:

unibonさんの書き込み (2003-10-16 19:48) より:
上記のコードを下記のように変形してみたのですが、
こう変形すると final がなくてもコンパイルできるし動きます。
私は、上と下は等価(もちろん完全な等価ではありませんが、
ローカル変数 str の扱いが同じ)だと思っているので、
なぜ上だと final が必要で下だと不要なのかが分からないのです。
たぶん、この、等価だという私の前提が正しくないんですよね。


違いはstrのコピーが明示的に行われるか、暗黙的に行われるかだと思います。
unibonさんの示したコードの 2 の位置で"def"が出力されると思う人はいないでしょう。
# 少なくともjavaの参照型について理解していればね...
うのきち
ベテラン
会議室デビュー日: 2003/02/17
投稿数: 55
投稿日時: 2003-10-17 10:43
引用:

unibonさんの書き込み (2003-10-16 19:48) より:

こう変形すると final がなくてもコンパイルできるし動きます。
私は、上と下は等価(もちろん完全な等価ではありませんが、
ローカル変数 str の扱いが同じ)だと思っているので、
なぜ上だと final が必要で下だと不要なのかが分からないのです。
たぶん、この、等価だという私の前提が正しくないんですよね。

コード:
public class Hoge {

    public static void main(String[] args) {
        String str = "abc";

        class Bar {
            private final String foo; // この final は単に immutable を示すだけでなくても良い。
            public Bar(String aFoo) {
                foo = aFoo;
            }
            public String toString() {
                return foo;
            }
        };
        Bar o = new Bar(str);

        System.out.println(o); // 1

        str = "def";

        System.out.println(o); // 2
    }

}





この場合はロジック自体が「ローカル変数(参照)の変更がインナークラスに見えない」ロジックなので、finalは不要です。参照変数がコピーされているのが明らかですよね。

もともとのコードは、toString()メソッドがローカル変数を参照しているので、あのコードを見た人は「toString()の度に、ローカル変数が参照される」と思うでしょう。でも、残念ながら、今のJavaのメモリーモデルでは、ローカル変数を複数スレッドで共有することが出来ないので、こういうロジックは実現できないのです。
unibon
ぬし
会議室デビュー日: 2002/08/22
投稿数: 1532
お住まい・勤務地: 美人谷        良回答(20pt)
投稿日時: 2003-10-17 12:03
unibon です。こんにちわ。

その後、探していたら、
http://java-house.jp/ml/topics/topics.html#language-innerclass
の中の
http://java-house.jp/ml/archive/j-h-b/021015.html#body
に解説がありました。
#すみません。最初にここを見ておけば良かったのですが。

私なりの解釈としては、
あるメソッド A 内のインナークラスから、
そのメソッド A 内のローカル変数(今回の場合は row, col, str 等)を参照する場合、
インナークラスの中からこれらの変数を見た場合に、
変数が値渡しなのか参照渡しなのかの解釈にブレが生じるため、
final だけを許しておく仕様にしておけば値渡しであることが明らかになるので良い、
ということだろうと思いました。
#ここでの「値渡し」や「参照渡し」は Visual Basic の ByVal や ByRef をイメージしています。
ただ、Java のメソッド呼び出しの際の変数の渡し方は値渡しだけなのだから、
それからの類推で行けばインナークラス内からの変数の参照も、
final を付けなくても常に値渡しだけにする、
という決めにしても良いような気もします
(final 用に別に変数を作らないといけないのが面倒だし、
面倒だけでなく、誤りを誘発する原因にもなりうると考えるので)。

スキルアップ/キャリアアップ(JOB@IT)