2010-05-25 11 views
8

que estaba jugando con algo de código para hacer un "cierre como" construir (no funciona por cierto)¿Por qué tengo esta InstantiationException en Java cuando accedo a las variables locales finales?

Todo se veía bien, pero cuando traté de acceder a una variable local final en el código, la excepción es lanzada InstantiationException.

Si elimino el acceso a la variable local ya sea eliminándola por completo o convirtiéndola en atributo de clase, no ocurre ninguna excepción.

El doctor dice: InstantiationException

inicia cuando una aplicación intenta crear una instancia de una clase utilizando el método newInstance en la clase clase, pero la clase de objeto especificado no se pueden crear instancias. La instanciación puede fallar por una variedad de razones, incluyendo pero no limitado a:

- la clase de objeto representa una clase abstracta, una interfaz, una clase de matriz, un tipo primitivo, o vacío

- la clase no tiene constructor nullary

¿Qué otra razón podría haber causado este problema?

Aquí está el código. comentar/descomentar el atributo de clase/variable local para ver el efecto (líneas: 5 y 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); 
     } 
    } 
} 

¿Este es un error en Java?

edición

Ah, me olvidaba, el StackTrace (cuando se lanza) es:

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

¿En qué línea es la excepción lanzada en – Michael

+0

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

+0

@Oscar: Estoy confundido acerca de la sintaxis de la interna anónima clase. ¿Se supone que es el constructor? – Uri

Respuesta

8

Bueno, eso tiene sentido.

Solo su primera instancia de la clase _ tiene acceso a la variable local.instancias posteriores no puede, a menos que se les proporcione con ella (a través de arg constructor)

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

salidas 1. (a es la instancia de la clase anónima)

Dicho esto, no creo que esto es una buena forma de implementar cierres. El bloque de inicialización se llama al menos una vez, sin necesidad de ello. Supongo que solo estás jugando, pero echa un vistazo al lambdaj. O esperar a que Java 7 :)

+0

+1 para clavarlo también – ewernli

+0

+1 Mmmhh sí y no. En el caso de las clases internas anónimas regulares, el compilador crea la referencia de la final local sin tener que tener un constructor o un argumento en el método, entiendo por su ciclo que el compilador lo creó para mí. Probablemente el compilador debería haberlo configurado también para mí en el bloque de inicializadores – OscarRyz

+0

Acerca de la implementación de cierres, bueno, no solo "no es una buena manera" porque, bueno ... no funciona en absoluto. Esto fue solo un experimento. Para el código real, utilizaría el modismo de cierre "aceptado" para Java, que es, utilizando una clase interna anónima * nuevo ActionListener() {public void actionPerformed (ActionEvent e) {}} * – OscarRyz

6

He aquí un extracto de la javap -c InstantiationExceptionDemo$1 de la versión 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; 

Y aquí está el javap -c InstantiationExceptionDemo$1 de la final versión de variable local:

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 

Así que su causa: la versión final variable local necesita un argumento adicional, la referencia JTextField, en el constructor. No tiene constructor nullary.

Esto tiene sentido si lo piensas. De lo contrario, ¿cómo va a obtener esta versión de InstantiationExceptionDemo$1 la referencia field? El compilador oculta el hecho de que esto se da como un parámetro para el constructor sintético.

+0

1 Yo no sabía en realidad cómo la variable local finales fueron "inline" en las clases anónimas. Según lo que escribes, se pasan en el constructor sintáctico, ¿verdad? Entonces, la invocación reflexiva también necesita pasar un parámetro más. Una gran respuesta instructiva. – ewernli

+0

1 Yo no sabía que el compilador lo hizo. Yo que podría marcar dos respuestas como aceptadas.Sé qué hacer, votaré otra de tus respuestas: P;) – OscarRyz

1

Gracias a los dos Bozho y Polygenlubricants de las respuestas esclarecedoras.

Por lo tanto, la razón es (en mis propias palabras)

Cuando se utiliza una última variable local, el compilador crea un constructor con los campos utilizados por la clase interna anónima y lo invoca. También "inyecta" el campo con los valores.

Por lo tanto, lo que hice, fue modificar mi creación para cargar el constructor de la derecha con los valores correctos que utilizan la reflexión.

Este es el código resultante:

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

Por supuesto, como señala Bozho, esto no es una buena manera (no se trata de una manera, pero no una buena) para crear cierres.

Hay dos problemas con esto.

1.- El bloque de inicialización se invoca cuando se declara.

2.- No hay manera de obtener los parámetros del código real (es decir, en actioneEvent actionPerformed)

Si pudiéramos retrasar la ejecución del bloque de inicialización esto haría un buen (en términos de sintaxis) alternativa de cierre.

Tal vez en Java 7 :(

Cuestiones relacionadas