Почему у меня возникает это InstantiationException в Java при доступе к конечным локальным переменным? - PullRequest
9 голосов
/ 25 мая 2010

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

Все выглядело нормально, но когда я пытался получить доступ к последней локальной переменной в коде, выдается исключение 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 ]

8 голосов
/ 25 мая 2010

Ну, это имеет смысл.

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

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

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

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

6 голосов
/ 25 мая 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? Компилятор скрывает тот факт, что это передается в качестве параметра синтетическому конструктору.

1 голос
/ 26 мая 2010

Спасибо вам и Божо, и Полигенубрикантам за поучительные ответы.

Итак, причина в (по моим собственным словам)

При использовании локальной конечной переменной компилятор создает конструктор с полями, используемыми анонимным внутренним классом, и вызывает его. Он также «вводит» поле со значениями.

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

Вот такой код:

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 :(

...