لماذا أواجه هذا instantiationException في Java عند الوصول إلى المتغيرات المحلية النهائية؟
-
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 :(