Perché ho questo InstantiationException in Java quando si accede a variabili locali finali?

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

  •  04-10-2019
  •  | 
  •  

Domanda

stavo giocando con un po 'di codice per fare una "chiusura come" costrutto (non funzionante btw)

Tutto sembrava bene, ma quando ho cercato di accedere a una variabile locale finale nel codice, il InstantiationException viene generata un'eccezione.

Se rimuovo l'accesso alla variabile locale sia eliminando del tutto o rendendo attributo class invece, non fa eccezione succede.

Il dottore dice: InstantiationException

  

generata quando un'applicazione tenta di creare un'istanza di una classe utilizzando il metodo newInstance in classe classe, ma l'oggetto della classe specificata non può essere istanziato. L'esemplificazione può fallire per una serie di motivi, tra cui, ma non limitati a:

     

- l'oggetto classe rappresenta una classe astratta, un'interfaccia, una classe matrice, un tipo primitivo, o vuoto

     

- la classe non ha costruttore nullaria

Quale altro motivo potrebbe aver causato questo problema?

Ecco il codice. commento / decommentare l'attributo class variabili / locale per vedere l'effetto (linee: 5 e 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 );
        }
    }
}

Si tratta di un bug in Java?

modifica

Ah, dimenticavo, lo stacktrace (quando torta) è:

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)
È stato utile?

Soluzione

Bene, che ha un senso.

Solo la prima istanza della classe _ ha accesso alla variabile locale. istanze successive non può, a meno che non li forniscono con esso (via costruttore arg)

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

Uscite 1. (a è l'istanza della classe anonima)

Detto questo, non credo che questo sia un buon modo per implementare le chiusure. Il blocco di inizializzazione viene convocata almeno una volta, senza la necessità di esso. Presumo si sta solo giocando intorno, ma dare un'occhiata a lambdaj . Oppure attendere per Java 7:)

Altri suggerimenti

Ecco un estratto del javap -c InstantiationExceptionDemo$1 della versione 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;

Ed ecco la javap -c InstantiationExceptionDemo$1 della versione variabile locale 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

Quindi non c'è la vostra causa: la final locale versione variabile ha bisogno di un argomento in più, il riferimento JTextField, nel costruttore. Non ha costruttore nullaria.

Questo ha senso se ci pensate. Altrimenti, come è questa versione di InstantiationExceptionDemo$1 sta per ottenere il riferimento field? Le pelli compilatore il fatto che questo è dato come parametro al costruttore sintetico.

Grazie sia Bozho e Polygenlubricants per le risposte illuminanti.

Quindi, il motivo è (con parole mie)

Quando si utilizza una variabile finale locale, il compilatore crea un costruttore con i campi utilizzati dalla classe interna anonima e l'invoca. E 'anche "iniettare" il campo con i valori.

Quindi, quello che ho fatto, è stato quello di modificare la mia creazione per caricare il costruttore giusto con i valori corretti utilizzando la riflessione.

Questo è il codice risultante:

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

Naturalmente, come sottolinea Bozho, questo non è un buon modo (non è un modo, ma non è una buona) per creare chiusure.

Ci sono due problemi con questo.

1.- Il blocco di inizializzazione viene richiamato quando viene dichiarato.

2.- Non c'è modo ottenere i parametri del codice vero e proprio (cioè actioneEvent in actionPerformed)

Se solo potessimo ritardare l'esecuzione del inizializzatore bloccare questo sarebbe fare un bel (in termini di sintassi) chiusura alternativa.

Forse in Java 7 :(

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top