Почему у меня возникает это InstantiationException в Java при доступе к конечным локальным переменным?

Я играл с некоторым кодом, чтобы сделать закрытие, подобное конструкции (кстати, не работает)

Все выглядело нормально, но когда я попытался получить доступ к последней локальной переменной в коде, возникло исключение InstantiationException.

Если я удалю доступ к локальной переменной либо полностью удалив ее, либо вместо этого сделав ее атрибутом класса, исключения не произойдет.

В документе говорится: InstantiationException

Вызывается, когда приложение пытается создать экземпляр класса с помощью метода newInstance в классе Class, но не удается создать экземпляр указанного объекта класса. Создание экземпляра может завершиться неудачей по разным причинам, включая, помимо прочего:

- объект класса представляет абстрактный класс, интерфейс, класс массива, примитивный тип или пустоту

- в классе нет конструктора 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)

person OscarRyz    schedule 25.05.2010    source источник
comment
В какой строке возникает исключение?   -  person Michael    schedule 25.05.2010
comment
@Oscar: я запутался в синтаксисе анонимного внутреннего класса. Это должен быть конструктор?   -  person Uri    schedule 25.05.2010
comment
@Bozho: Разве блок инициализатора анонимного класса не должен быть статическим? (Я никогда не пробовал это).   -  person Uri    schedule 26.05.2010
comment
@Ури. Есть два типа блоков инициализатора, инициализатор класса и инициализатор экземпляра, как и во всем остальном, первый использует static, а первый — нет. В результате блок инициализатора выполняется перед конструктором. Он не используется широко, потому что на первом месте стоят конструкторы, а конструкция static {} является альтернативой инициализации ресурсов класса, поскольку конструкторов классов нет.   -  person OscarRyz    schedule 26.05.2010
comment
Это часто используется в свинг. Например: `JLabel label = new JLabel(Hola){{ setFont(new Font(Arial, Font.PLAIN, 32));}};`   -  person OscarRyz    schedule 26.05.2010
comment
@Oscar: я знаком с обоими типами инициализации, но не был уверен, какие типы подходят в случае анонимного класса. Обычно я просто определяю конструктор, который в ретроспективе, вероятно, является пустой тратой места.   -  person Uri    schedule 26.05.2010


Ответы (3)


Что ж, это имеет смысл.

Только ваш первый экземпляр класса _ имеет доступ к локальной переменной. Последующие экземпляры не могут, если вы не предоставите им это (через аргумент конструктора)

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

выводит 1. (a — это экземпляр вашего анонимного класса)

Тем не менее, я не думаю, что это хороший способ реализовать закрытие. Блок инициализатора вызывается по крайней мере один раз без необходимости в этом. Я предполагаю, что вы просто играете, но взгляните на lambdaj. Или дождитесь Java 7 :)

person Bozho    schedule 25.05.2010
comment
+1 Ммммм и да и нет. В случае обычных анонимных внутренних классов компилятор создает ссылку на локальный финал без необходимости иметь конструктор или аргумент в методе, я понимаю из вашего цикла, что компилятор создал его для меня. Вероятно, компилятор должен был установить его для меня также в блоке инициализатора. - person OscarRyz; 25.05.2010
comment
Насчет реализации замыканий, ну, это не только нехороший способ, потому что, ну.. он вообще не работает. Это был просто эксперимент. Для реального кода я бы использовал общепринятую идиому закрытия для Java, которая заключается в использовании анонимного внутреннего класса new ActionListener(){public void actionPerformed(ActionEvent e){}} - person OscarRyz; 25.05.2010

Вот отрывок из 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? Компилятор скрывает тот факт, что this передается в качестве параметра синтетическому конструктору.

person polygenelubricants    schedule 25.05.2010
comment
+1 На самом деле я не знал, как последняя локальная переменная была встроена в анонимные классы. Судя по тому, что вы пишете, они передаются в синтаксическом конструкторе, верно? Таким образом, отражающий вызов должен также передать еще один параметр. Отличный поучительный ответ. - person ewernli; 25.05.2010
comment
+1 Я не знал, что это сделал компилятор. I, на котором я мог отметить два ответа как принятые. Я знаю, что делать, я проголосую за другой ваш ответ: P;) - person OscarRyz; 26.05.2010

Спасибо и Божо, и 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 );
        }
    }
}

Конечно, как указывает Божо, это не очень хороший способ (не способ, но не хороший) для создания замыканий.

Есть две проблемы с этим.

1.- Блок инициализатора вызывается при его объявлении.

2.- Невозможно получить параметры фактического кода (например, actioneEvent в actionPerformed)

Если бы мы могли просто отложить выполнение блока инициализатора, это было бы хорошей (с точки зрения синтаксиса) альтернативой закрытия.

Возможно, в Java 7 :(

person OscarRyz    schedule 25.05.2010