2010-05-25 8 views
8

stavo giocando con un po 'di codice per fare un "chiusura come" costruire (non funzionante btw)Perché sto avendo questo InstantiationException in Java quando si accede alle variabili locali finali?

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

Se rimuovo l'accesso alla variabile locale rimuovendolo del tutto o rendendolo invece attributo di classe, non si verifica alcuna eccezione.

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 è possibile creare un'istanza. L'istanziazione può fallire per vari motivi, tra cui, ma non limitati a:

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

- la classe non ha un costruttore nullario

Quale altro motivo potrebbe aver causato questo problema?

Ecco il codice. commentare/commentare l'attributo di classe/variabile locale per vedere l'effetto (righe: 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?

modificare

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) 
+0

Quale linea è l'eccezione gettato su – Michael

+0

25:?. 'This.getClass() newInstance()' – OscarRyz

+0

@Oscar: Sono confuso circa la sintassi del interna anonima classe: è questo il costruttore? – Uri

risposta

8

Bene, questo ha 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 è un buon modo per attuare chiusure. Il blocco di inizializzazione viene chiamato almeno una volta, senza che sia necessario. Presumo che tu stia solo giocando, ma dai uno sguardo allo lambdaj. Oppure aspetta Java 7 :)

+0

+1 per inchiodarlo pure – ewernli

+0

+1 Mmmhh si e no. Nel caso di classi interne anonime regolari il compilatore crea il riferimento della finale locale senza dover avere un costruttore o un argomento nel metodo, ho capito dal tuo ciclo, che, il compilatore lo ha creato per me. Probabilmente il compilatore avrebbe dovuto impostarlo anche nel blocco di inizializzazione – OscarRyz

+0

Circa l'implementazione delle chiusure, beh, non solo "non è un buon modo" perché, beh .. non funziona affatto. Questo era solo un esperimento. Per codice reale vorrei usare l'idioma di chiusura "accettato" per Java che è, usando una classe interna anonima * nuovo ActionListener() {public void actionPerformed (ActionEvent e) {}} * – OscarRyz

6

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; 

E 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 versione variabile locale final ha bisogno di un argomento in più, il riferimento JTextField, nel costruttore. Non ha costruttori nullari.

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

+0

+1 Non sapevo in realtà come la variabile locale finale fosse "in linea" in classi anonime. Secondo ciò che scrivi, sono passati al costruttore sintattico, giusto? Quindi, l'invocazione riflessiva deve quindi passare anche un parametro in più. Una grande risposta istruttiva. – ewernli

+0

+1 Non sapevo che il compilatore l'avesse fatto. Io che potrei contrassegnare due risposte come accettate.So cosa fare, invierò un'altra delle tue risposte: P;) – OscarRyz

1

Grazie a entrambi Bozho e Polygenlubrificanti 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. Inoltre "inserisce" il campo con i valori.

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

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 inizializzatore 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 blocco di inizializzazione questo sarebbe fare un bel (in termini di sintassi) alternativa di chiusura.

Forse in Java 7 :(

Problemi correlati