为什么我在访问最终局部变量时在Java中有这个InstantiationException?
我正在玩一些代码来制作一个“封闭式”构造(不工作顺便说一句)
一切看起来都很好,但是当我尝试访问代码中的最终局部变量时,抛出exceptionInstantiationException
。
如果通过完全删除局部变量或通过使其成为类属性来删除对局部变量的访问,则不会发生exception。
该文档说: 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)
嗯,这是有道理的。
只有_
类的第一个实例才能访问局部变量。 后续实例不能,除非你提供它(通过构造函数arg)
Constructor[] constructor = a.getClass().getDeclaredConstructors(); for (Constructor c : constructors) { System.out.println(c.getParameterTypes().length); }
输出1.( a
是您的匿名类的实例)
也就是说,我不认为这是实现闭包的好方法。 初始化程序块至少被调用一次,而不需要它。 我假设你只是在玩耍,但看看lambdaj 。 或者等待Java 7 🙂
这是static field
版本的javap -c InstantiationExceptionDemo$1
的摘录:
Compiled from "InstantiationExceptionDemo.java" class InstantiationExceptionDemo$1 extends _{ InstantiationExceptionDemo$1(); Code: 0: aload_0 1: invokespecial #8; //Method _."":()V 4: getstatic #10; //Field InstantiationExceptionDemo.field: //Ljavax/swing/JTextField;
这是final
局部变量版本的javap -c InstantiationExceptionDemo$1
:
Compiled from "InstantiationExceptionDemo.java" class InstantiationExceptionDemo$1 extends _{ InstantiationExceptionDemo$1(javax.swing.JTextField); Code: 0: aload_0 1: invokespecial #8; //Method _."":()V 4: aload_1
所以这是你的原因: final
局部变量版本在构造函数中需要一个额外的参数,即JTextField
引用。 它没有无效的构造函数。
如果你考虑一下,这是有道理的。 否则,这个版本的InstantiationExceptionDemo$1
将如何获得field
引用? 编译器隐藏了这个作为合成构造函数的参数给出的事实。
感谢Bozho和Polygenlubricants的启发性答案。
所以,原因是(用我自己的话说)
使用本地最终变量时,编译器会创建一个构造函数,其中包含匿名内部类使用的字段并调用它。 它还使用值“注入”字段。
所以,我所做的是修改我的创建,使用reflection加载正确的构造函数。
这是结果代码:
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 🙁