9

我正在使用一些代码来制作“类似闭包”的构造(顺便说一句不起作用)

一切看起来都很好,但是当我尝试访问代码中的最终局部变量时,InstantiationException抛出了异常。

如果我通过完全删除它或改为使其成为类属性来删除对局部变量的访问,则不会发生异常。

文档说:InstantiationException

当应用程序尝试使用类 Class 中的 newInstance 方法创建类的实例,但无法实例化指定的类对象时抛出。实例化可能由于多种原因而失败,包括但不限于:

- 类对象表示抽象类、接口、数组类、原始类型或 void

- 该类没有空构造函数

还有什么其他原因可能导致此问题?

这是代码。注释/取消注释类属性/局部变量以查看效果(第 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

嗯,这是有道理的。

只有您的类的第一个实例_可以访问局部变量。后续实例不能,除非您向它们提供它(通过构造函数 arg)

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

输出 1.(a是您的匿名类的实例)

也就是说,我认为这不是实现闭包的好方法。初始化程序块至少被调用一次,不需要它。我假设您只是在玩耍,但请看一下lambdaj。或者等待 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$1final局部变量版本:

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引用。它没有空构造函数。

如果你仔细想想,这是有道理的。否则,这个版本InstantiationExceptionDemo$1要怎么获取field参考呢?编译器隐藏了 this 作为参数提供给合成构造函数的事实。

于 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 指出的那样,这不是创建闭包的好方法(不是一种方法,但不是一种好方法)。

这有两个问题。

1.- 初始化程序块在声明时被调用。

2.- 没有办法获取实际代码的参数(即actionPerformed中的actioneEvent)

如果我们可以延迟初始化块的执行,这将是一个不错的(就语法而言)闭包替代方案。

也许在 Java 7 中 :(

于 2010-05-25T20:09:50.393 回答