لماذا أواجه هذا instantiationException في Java عند الوصول إلى المتغيرات المحلية النهائية؟

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

  •  04-10-2019
  •  | 
  •  

سؤال

كنت ألعب مع بعض التعليمات البرمجية لأصنع بنية "إغلاق مثل" (لا أعمل راجع للشغل)

بدا كل شيء على ما يرام ولكن عندما حاولت الوصول إلى متغير محلي نهائي في الكود ، الاستثناء InstantiationException هذا خطئ.

إذا قمت بإزالة الوصول إلى المتغير المحلي إما عن طريق إزالته تمامًا أو بجعله سمة فئة بدلاً من ذلك ، فلن يحدث استثناء.

يقول المستند: instantiationException

يتم طرحه عندما يحاول التطبيق إنشاء مثيل لفئة باستخدام طريقة NewInstance في فئة الفصل ، ولكن لا يمكن إنشاء كائن الفئة المحدد. يمكن أن تفشل إنشاء إنشاء مجموعة متنوعة من الأسباب بما في ذلك على سبيل المثال لا الحصر:

- يمثل كائن الفئة فئة مجردة أو واجهة أو فئة صفيف أو نوع بدائي أو باطل

- الفصل ليس له مُنشئ لاخذ

ما السبب الآخر الذي يمكن أن تسبب هذه المشكلة؟

ها هو الرمز. التعليق / UNFOMMENT سمة الفصل / المتغير المحلي لرؤية التأثير (الخطوط: 5 و 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 );
        }
    }
}

هل هذا خطأ في جافا؟

تعديل

أوه ، لقد نسيت ، stacktrace (عند إلقاء) هو:

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)
هل كانت مفيدة؟

المحلول

حسنًا ، هذا منطقي.

فقط مثالتك الأولى من _ الفصل لديه الوصول إلى المتغير المحلي. لا يمكن للحالات اللاحقة ، إلا إذا قمت بتزويدهم بها (عبر ARG CONTRUCTOR)

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

المخرجات 1. (a هو مثيل صفك المجهول)

ومع ذلك ، لا أعتقد أن هذه طريقة جيدة لتنفيذ عمليات الإغلاق. تسمى كتلة التهيئة مرة واحدة على الأقل ، دون الحاجة إليها. أفترض أنك تلعب فقط ، لكن ألق نظرة على لامداج. أو انتظر Java 7 :)

نصائح أخرى

إليك مقتطفات من javap -c InstantiationExceptionDemo$1 التابع 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;

وهنا javap -c InstantiationExceptionDemo$1 التابع 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

لذلك هناك قضيتك: final يحتاج الإصدار المتغير المحلي إلى وسيطة إضافية ، JTextField المرجع ، في المنشئ. ليس لديها مُنشئ لاخار.

هذا منطقي إذا فكرت في الأمر. آخر ، كيف يتم هذا الإصدار من InstantiationExceptionDemo$1 سوف تحصل على field المرجعي؟ يخفي المترجم حقيقة أن هذا يعطى كمعلمة إلى المنشئ الاصطناعي.

شكرا لك كل من بوزو و polygenlubricants للإجابات المنير.

لذلك ، السبب هو (بكلماتي الخاصة)

عند استخدام متغير نهائي محلي ، يقوم المترجم بإنشاء مُنشئ مع الحقول المستخدمة من قبل الفئة الداخلية المجهولة ويحتجه. كما أنه "حقن" الحقل مع القيم.

لذلك ، ما فعلته ، هو تعديل إنشائبي لتحميل المُنشئ المناسب بالقيم الصحيحة باستخدام الانعكاس.

هذا هو الكود الناتج:

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

بالطبع ، كما يشير Bozho ، هذه ليست طريقة جيدة (ليست طريقة ، ولكنها ليست جيدة) لإنشاء عمليات إغلاق.

هناك مشكلتان في هذا.

1.- يتم استدعاء كتلة التهيئة عند إعلانها.

2.- لا توجد طريقة للحصول على معلمات الكود الفعلي (أي Actionevent في ActionPerformed)

إذا تمكنا من تأخير تنفيذ كتلة التهيئة ، فهذا من شأنه أن يجعل بديلًا لطيفًا (من حيث بناء الجملة).

ربما في جافا 7 :(

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top