9

私は「クロージャーのような」構造を作るためにいくつかのコードで遊んでいました(ところで機能していません)

すべてが正常に見えましたが、コード内の最後のローカル変数にアクセスしようとすると、例外InstantiationExceptionがスローされます。

ローカル変数へのアクセスを完全に削除するか、代わりにクラス属性にすることで削除しても、例外は発生しません。

ドキュメントによると:InstantiationException

アプリケーションがクラスClassのnewInstanceメソッドを使用してクラスのインスタンスを作成しようとしたが、指定されたクラスオブジェクトをインスタンス化できない場合にスローされます。インスタンス化は、次のようなさまざまな理由で失敗する可能性があります。

-クラスオブジェクトは、抽象クラス、インターフェイス、配列クラス、プリミティブ型、またはvoidを表します

-クラスにはnullaryコンストラクターがありません

この問題を引き起こした可能性のある他の理由は何ですか?

これがコードです。クラス属性/ローカル変数にコメント/コメントを外して、効果を確認します(5行目と10行目)。

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
class InstantiationExceptionDemo {
     //static JTextField field = new JTextField();// works if uncommented

    public static void main( String [] args ) {
        JFrame frame = new JFrame();
        JButton button = new JButton("Click");
        final JTextField field = new JTextField();// fails if uncommented

        button.addActionListener( new _(){{
            System.out.println("click " + field.getText());
        }});
    
        frame.add( field );
        frame.add( button, BorderLayout.SOUTH );
        frame.pack();frame.setVisible( true );
    
    }
}
class _ implements ActionListener {
    public void actionPerformed( ActionEvent e ){
        try {
            this.getClass().newInstance();
        } catch( InstantiationException ie ){
            throw new RuntimeException( ie );
        } catch( IllegalAccessException ie ){
            throw new RuntimeException( ie );
        }
    }
}

これはJavaのバグですか?

編集

ああ、忘れました、スタックトレース(スローされたとき)は次のとおりです:

Caused by: java.lang.InstantiationException: InstantiationExceptionDemo$1
at java.lang.Class.newInstance0(Class.java:340)
at java.lang.Class.newInstance(Class.java:308)
at _.actionPerformed(InstantiationExceptionDemo.java:25)
4

3 に答える 3

8

Well, that makes sense.

Only your first instance of the _ class has access to the local variable. Subsequent instances can't, unless you provide them with it (via constructor arg)

Constructor[] constructor = a.getClass().getDeclaredConstructors();
for (Constructor c : constructors) {
     System.out.println(c.getParameterTypes().length);
}

outputs 1. (a is the instance of your anonymous class)

That said, I don't think this is a good way to implement closures. The initializer block is called at least once, without the need of it. I assume you are just playing around, but take a look at lambdaj. Or wait for Java 7 :)

于 2010-05-25T18:02:48.923 に答える
6

バージョンの抜粋は次のjavap -c InstantiationExceptionDemo$1とおりです。static field

Compiled from "InstantiationExceptionDemo.java"
class InstantiationExceptionDemo$1 extends _{
InstantiationExceptionDemo$1();
  Code:
   0:   aload_0
   1:   invokespecial   #8;  //Method _."<init>":()V
   4:   getstatic       #10; //Field InstantiationExceptionDemo.field:
                             //Ljavax/swing/JTextField;

そしてjavap -c InstantiationExceptionDemo$1、これがfinalローカル変数バージョンのです:

Compiled from "InstantiationExceptionDemo.java"
class InstantiationExceptionDemo$1 extends _{
InstantiationExceptionDemo$1(javax.swing.JTextField);
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method _."<init>":()V
   4:   aload_1

したがって、原因があります。finalローカル変数バージョンJTextFieldには、コンストラクターに追加の引数である参照が必要です。nullaryコンストラクターはありません。

あなたがそれについて考えるならば、これは理にかなっています。それ以外の場合、このバージョンの参照はどのようInstantiationExceptionDemo$1に取得されfieldますか?コンパイラーは、これが合成コンストラクターへのパラメーターとして指定されているという事実を隠します。

于 2010-05-25T18:02:29.800 に答える
1

啓発的な答えをくれたBozhoとPolygenlubricantsの両方に感謝します。

だから、その理由は(私自身の言葉で)

ローカルのfinal変数を使用する場合、コンパイラーは匿名内部クラスで使用されるフィールドを使用してコンストラクターを作成し、それを呼び出します。また、フィールドに値を「挿入」します。

それで、私がしたことは、リフレクションを使用して正しいコンストラクターに正しい値をロードするように作成を変更することでした。

結果のコードは次のとおりです。

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.lang.reflect.*;

class InstantiationExceptionDemo {

    public static void main( String [] args ) {

        JFrame frame = new JFrame();
        final JButton reverse = new JButton("Reverse");
        final JButton swap    = new JButton("Swap");

        final JTextField fieldOne = new JTextField(20);
        final JTextField fieldTwo = new JTextField(20);

        // reverse the string in field one
        reverse.addActionListener( new _(){{
            StringBuilder toReverse = new StringBuilder();
            toReverse.append( fieldOne.getText() );
            toReverse.reverse();
            fieldOne.setText( toReverse.toString() );

            //fieldOne.setText( new StringBuilder( fieldOne.getText() ).reverse().toString() );
        }});

        // swap the fields 
        swap.addActionListener( new _(){{
            String temp = fieldOne.getText();
            fieldOne.setText( fieldTwo.getText() );
            fieldTwo.setText( temp  );
        }});

        // scaffolding
        frame.add( new JPanel(){{
            add( fieldOne );
            add( fieldTwo );
        }} );
        frame.add( new JPanel(){{
            add( reverse );
            add( swap );
        }}, BorderLayout.SOUTH );
        frame.pack();frame.setVisible( true );

    }
}
abstract class  _ implements ActionListener {
    public _(){}

    public void actionPerformed( ActionEvent e ){ 
        invokeBlock();
    }

    private void invokeBlock(){
    // does actually invoke the block but with a trick
    // it creates another instance of this same class
    // which will be immediately discarded because there are no more 
    // references to it. 
        try {
            // fields declared by the compiler in the anonymous inner class
            Field[] fields = this.getClass().getDeclaredFields();
            Class[] types= new Class[fields.length];
            Object[] values = new Object[fields.length];
            int i = 0;
            for( Field f : fields ){
                types[i] = f.getType();
                values[i] = f.get( this );
                i++;
            }
            // this constructor was added by the compiler
            Constructor constructor = getClass().getDeclaredConstructor( types );
            constructor.newInstance( values );

        } catch( InstantiationException ie ){
            throw new RuntimeException( ie );
        } catch( IllegalAccessException ie ){
            throw new RuntimeException( ie );
        }catch( InvocationTargetException ie ){
            throw new RuntimeException( ie );        
        } catch(NoSuchMethodException nsme){
            throw new RuntimeException( nsme );
        }
    }
}

もちろん、Bozhoが指摘しているように、これはクロージャーを作成するための良い方法ではありません(方法ではありませんが、良い方法ではありません)。

これには2つの問題があります。

1.-初期化ブロックは、宣言されたときに呼び出されます。

2.-実際のコードのパラメーターを取得する方法はありません(つまり、actionPerformedのactioneEvent)

イニシャライザブロックの実行を遅らせることができれば、これは(構文の観点から)優れたクロージャの代替手段になります。

おそらくJava7で:(

于 2010-05-25T20:09:50.393 に答える