Почему у меня возникает это InstantiationException в Java при доступе к конечным локальным переменным?
Я играл с некоторым кодом, чтобы сделать конструкцию "как замыкание" (кстати, не работает)
Все выглядело хорошо, но когда я пытался получить доступ к последней локальной переменной в коде, исключение InstantiationException
брошен
Если я удаляю доступ к локальной переменной, либо удаляя ее полностью, либо вместо этого назначая ей атрибут класса, исключений не происходит.
В документе говорится: InstantiationException
Брошенный, когда приложение пытается создать экземпляр класса, используя метод newInstance в классе Class, но указанный объект класса не может быть создан. Инстанцирование может завершиться неудачей по ряду причин, включая, но не ограничиваясь:
- объект класса представляет абстрактный класс, интерфейс, класс массива, примитивный тип или 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)
3 ответа
Ну, это имеет смысл.
Только ваш первый экземпляр _
класс имеет доступ к локальной переменной. Последующие экземпляры не могут, если вы не предоставите им это (через конструктор arg)
Constructor[] constructor = a.getClass().getDeclaredConstructors();
for (Constructor c : constructors) {
System.out.println(c.getParameterTypes().length);
}
выходы 1. (a
это экземпляр вашего анонимного класса)
Тем не менее, я не думаю, что это хороший способ реализации замыканий. Блок инициализатора вызывается хотя бы один раз, без необходимости. Я предполагаю, что вы просто играете вокруг, но взгляните на лямбдадж. Или подожди Java 7:)
Вот выдержка из 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
ссылка, в конструкторе. У него нет нулевого конструктора.
Это имеет смысл, если вы думаете об этом. Остальное как поживает эта версия InstantiationExceptionDemo$1
собираюсь получить field
ссылка? Компилятор скрывает тот факт, что это задано в качестве параметра для синтетического конструктора.
Спасибо вам и Божо, и Полигенубрикантам за поучительные ответы.
Итак, причина в том (по моим собственным словам)
При использовании локальной конечной переменной компилятор создает конструктор с полями, используемыми анонимным внутренним классом, и вызывает его. Он также "вводит" поле со значениями.
Итак, что я сделал, так это изменил свое творение, чтобы загрузить правильный конструктор с правильными значениями, используя отражение.
Это результирующий код:
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 );
}
}
}
Конечно, как указывает Божо, это не хороший способ (не способ, но не хороший) для создания замыканий.
Есть две проблемы с этим.
1.- Блок инициализатора вызывается при его объявлении.
2.- Нет способа получить параметры реального кода (т.е. actioneEvent в actionPerformed)
Если бы мы могли просто отложить выполнение блока инициализатора, это сделало бы хорошую (с точки зрения синтаксиса) альтернативу закрытия.
Возможно в Java 7:(