Warum habe ich diese InstantiationException in Java, wenn die abschließenden lokale Variablen zugreifen?

StackOverflow https://stackoverflow.com/questions/2906954

  •  04-10-2019
  •  | 
  •  

Frage

ich mit einigem Code spiele einen „Verschluss wie“ zu machen Konstrukt (nicht btw Arbeits)

sah alles in Ordnung, aber wenn ich versuche, eine endgültige lokale Variable in dem Code zuzugreifen, wird die Ausnahme InstantiationException geworfen.

Wenn ich den Zugriff auf die lokale Variable entfernen, entweder indem sie sie komplett zu entfernen oder durch Klassenattribut stattdessen machen, geschieht keine Ausnahme.

Der Doc sagt: InstantiationException

Geworfen, wenn eine Anwendung versucht, eine Instanz einer Klasse zu erstellen, die newInstance Methode in der Klasse zu Klasse, aber das angegebene Klasse Objekt nicht instanziiert werden kann. Die Instanziierung kann für eine Vielzahl von Gründen fehlschlagen, einschließlich, aber nicht beschränkt auf:

- das Klasse-Objekt stellt eine abstrakte Klasse, eine Schnittstelle, eine Array-Klasse, eine primitive Art, oder nichtig

- hat die Klasse keine nullary Konstruktor

Was anderer Grund haben könnte dieses Problem verursacht?

Hier ist der Code. Kommentar / Kommentar- der Klasse Attribut / lokale Variable den Effekt zu sehen (Linien: 5 und 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 );
        }
    }
}

Ist das ein Fehler in Java?

Bearbeiten

Oh, ich habe vergessen, die stacktrace (wenn sie geworfen werden) ist:

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)
War es hilfreich?

Lösung

Well, that makes sense.

Only your first instance of the _ class has access to the local variable. Subsequent instances can't, unless you provide them with it (via constructor arg)

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

outputs 1. (a is the instance of your anonymous class)

That said, I don't think this is a good way to implement closures. The initializer block is called at least once, without the need of it. I assume you are just playing around, but take a look at lambdaj. Or wait for Java 7 :)

Andere Tipps

Here's an excerpt of the javap -c InstantiationExceptionDemo$1 of the static field version:

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;

And here's the javap -c InstantiationExceptionDemo$1 of the final local variable version:

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

So there's your cause: the final local variable version needs an extra argument, the JTextField reference, in the constructor. It has no nullary constructor.

This makes sense if you think about it. Else, how is this version of InstantiationExceptionDemo$1 going to get the field reference? The compiler hides the fact that this is given as a parameter to the synthetic constructor.

Thank you both Bozho and Polygenlubricants for the enlightening answers.

So, the reason is ( in my own words )

When using a local final variable, the compiler creates a constructor with the fields used by the anonymous inner class and invokes it. It also "inject" field with the values.

So, what I did, was to modify my creation to load the right constructor with the correct values using reflection.

This is the resulting code:

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 );
        }
    }
}

Of course, as Bozho points out, this is not a good way ( not it is a way, but not a good one ) to create closures.

There are two problems with this.

1.- The initializer block is invoked when it is declared.

2.- There is no way get the parameters of the actual code ( ie actioneEvent in actionPerformed )

If we could just delay the execution of the initializer block this would make a nice ( in terms of syntax ) closure alternative.

Perhaps in Java 7 :(

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top