Pourquoi ai-je cette InstantiationException en Java lors de l'accès des variables locales finales?

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

  •  04-10-2019
  •  | 
  •  

Question

Je jouais avec un code pour faire une « fermeture comme » construction (ne fonctionne pas BTW)

Tout avait l'air bien, mais quand j'ai essayé d'accéder à une variable locale finale dans le code, le InstantiationException d'exception est levée.

Si je supprime l'accès à la variable locale soit en supprimant complètement ou en en faisant l'attribut de classe au lieu, aucune exception ne se produit.

Le doc dit: InstantiationException

  

quand une tente Jeté d'application pour créer une instance d'une classe en utilisant la méthode newInstance en classe de classe, mais l'objet de classe spécifié ne peut pas être instanciée. L'instanciation peut échouer pour diverses raisons, y compris mais sans s'y limiter:

     

- l'objet de classe représente une classe abstraite, une interface, une classe de tableau, un type primitif, ou un vide

     

- la classe n'a pas de constructeur arité

Quelle autre raison pourrait avoir causé ce problème?

Voici le code. commentaire / décommenter l'attribut class / variable locale pour voir l'effet (lignes 5 et 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 );
        }
    }
}

Est-ce un bug dans Java?

modifier

Oh, j'ai oublié, le stacktrace (quand jeté) est:

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)
Était-ce utile?

La solution

Eh bien, ce sens.

Seul votre première instance de la classe _ a accès à la variable locale. les instances suivantes ne peuvent pas, à moins que vous leur fournissez (via constructeur arg)

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

Sorties 1. (a est l'instance de votre classe anonyme)

Cela dit, je ne pense pas que ce soit une bonne façon de mettre en œuvre des fermetures. Le bloc initialiseur est appelé au moins une fois, sans le besoin. Je suppose que vous êtes juste à jouer, mais jeter un oeil à lambdaj . Ou attendre Java 7:)

Autres conseils

Voici un extrait du javap -c InstantiationExceptionDemo$1 de la version 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;

Et voici la javap -c InstantiationExceptionDemo$1 de la version variable 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

Alors il y a votre cause: la version variable locale de final a besoin d'un argument supplémentaire, la référence JTextField, dans le constructeur. Il n'a pas de constructeur arité.

Cela est logique si on y pense. Sinon, comment cette version de InstantiationExceptionDemo$1 va obtenir la référence de field? Le compilateur cache le fait que ceci est donné en tant que paramètre au constructeur synthétique.

Merci à vous deux Bozho et Polygenlubricants pour les réponses éclairantes.

Alors, la raison en est (dans mes propres mots)

Lorsque vous utilisez une variable finale locale, le compilateur crée un constructeur avec les champs utilisés par la classe interne anonyme et invoque. Il a également « inject » champ avec les valeurs.

Alors, ce que je faisais, était de modifier ma création pour charger le constructeur à droite avec les valeurs correctes à l'aide de la réflexion.

Ceci est le code qui en résulte:

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

Bien sûr, comme le souligne Bozho sur, ce n'est pas une bonne façon (pas un moyen, mais pas un bon) pour créer des fermetures.

Il y a deux problèmes avec cela.

Le bloc 1.- initialiseur est invoqué quand il est déclaré.

Il n'y a pas 2.- moyen d'obtenir les paramètres du code réel (c.-à-actioneEvent en actionPerformed)

Si nous pouvions retarder l'exécution du bloc initialiseur cela ferait une belle (en termes de syntaxe) de remplacement de fermeture.

Peut-être dans Java 7 :(

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top