ClassCastException في Java Foreach Loop
-
28-09-2019 - |
سؤال
في أي ظروف يمكن أن تحدث classcastexception في الكود أدناه:
import java.util.Arrays;
import java.util.List;
public class Generics {
static List getObjects() {
return Arrays.asList(1, 2, 3);
}
public static void main(String[] args) {
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
}
}
كان لدينا حالة مماثلة في بيئة الإنتاج (الممارسة السيئة ، وأنا أعلم) وقدم العميل سجلًا مع ClassCastException على السطر مع التعليق ، لكن لا يمكنني إعادة إنتاجه. أي أفكار؟
أعلم أن JVM ينشئ تكرارًا في الخلفية عند استخدام Foreach ، ولكن هل يمكن أن يقوم بإنشاء جهاز تكرار خام في بعض الحالات وواحد محدد في حالات أخرى؟
تحديث:لقد ألقيت إلقاء نظرة على رمز Bytecder الذي تم إنشاؤه وعلى Windows ، باستخدام JDK 1.6.0_21-B07 لا checkcast صنع. مثير للإعجاب :)
هذه هي الطريقة الرئيسية:
public static void main(java.lang.String[]); Code: 0: invokestatic #34; //Method getObjects:()Ljava/util/List; 3: astore_1 4: aload_1 5: invokeinterface #36, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 10: astore_3 11: goto 28 14: aload_3 15: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 20: astore_2 21: getstatic #48; //Field java/lang/System.out:Ljava/io/PrintStream; 24: aload_2 25: invokevirtual #54; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 28: aload_3 29: invokeinterface #60, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 34: ifne 14 37: return
شكرا جميع على الإجابات!
تحديث 2: لقد تضللت مع Eclipse IDE التي تستخدمها المترجم الخاص لذلك في الواقع ، فإن رمز Bytecode أعلاه هو الذي تم إنشاؤه باستخدام برنامج التحويل البرمجي الكسوف. نظرة هنا كيفية تجميع الكود يدويًا مع Eclipse. في الختام ، يولد برنامج التحويل البرمجي Eclipse رمز بايت مختلف من برنامج التحويل البرمجي للشمس في بعض الحالات ، بغض النظر عن المنصة ، وهي الحالة الموضحة هنا واحدة.
المحلول
لا ينبغي أن هذا الرمز دائماً اقذف ال ClassCastException
؟ إنه بالنسبة لي باستخدام برنامج التحويل البرمجي Sun Java 6 ووقت التشغيل (على Linux). أنت تلقي Integer
ق String
س. سيكون التكرار الذي تم إنشاؤه Iterator<String>
, ، ولكن بعد ذلك يحاول الوصول إلى العنصر الأول ، وهو أمر Integer
, ، وهكذا فشل.
يصبح هذا أكثر وضوحًا إذا قمت بتغيير صفيفك مثل ذلك:
return Arrays.asList("one", 2, 3);
الآن تعمل الحلقة فعليًا للعنصر الأول ، لأن العنصر الأول هو String
ونحن نرى الإخراج. ثم Iterator<String>
يفشل في الثانية ، لأنها ليست سلسلة.
يعمل الكود الخاص بك إذا كنت تستخدم فقط عام List
بدلا من واحد معين:
List list = getObjects();
for (Object o : list) {
System.out.println(o);
}
... أو بالطبع ، إذا كنت تستخدم List<Integer>
, ، لأن المحتويات Integer
س. ما تفعله الآن يؤدي إلى تحذير مترجم - Note: Generics.java uses unchecked or unsafe operations.
- ولسبب وجيه.
هذا التعديل يعمل أيضًا:
for (Object o : (List)list)
... من المفترض لأنك في هذه المرحلة تتعامل مع Iterator
, ، ليس Iterator<String>
.
قال Bozho إنه لا يرى هذا الخطأ على Windows XP (لم يذكر أي برنامج التحويل البرمجي ووقت التشغيل ، لكنني أخمن Sun's) ، وأنت تقول أنك لا ترى ذلك (أو غير موثوق) ، لذلك من الواضح أن هناك البعض حساسية التنفيذ هنا ، ولكن خلاصة القول هي: لا تستخدم List<String>
للتفاعل مع أ List
من Integer
س. :-)
هذا هو الملف الذي أقوم بتجميعه:
import java.util.Arrays;
import java.util.List;
public class Generics {
static List getObjects() {
return Arrays.asList("one", 2, 3);
}
public static void main(String[] args) {
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
}
}
ها هي المجموعة:
tjc@forge:~/temp$ javac Generics.java Note: Generics.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details.
ها هو المدى:
tjc@forge:~/temp$ java Generics one Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at Generics.main(Generics.java:12)
السطر 12 هو for
بيان. لاحظ أنه قام بإخراج العنصر الأول ، لأنني غيرت ذلك إلى أ String
. لم يخرج الآخرين. (وقبل أن أجري هذا التغيير ، فشل على الفور.)
إليك المترجم الذي أستخدمه:
tjc@forge:~/temp$ which javac /usr/bin/javac tjc@forge:~/temp$ ll /usr/bin/javac lrwxrwxrwx 1 root root 23 2010-09-30 16:37 /usr/bin/javac -> /etc/alternatives/javac* tjc@forge:~/temp$ ll /etc/alternatives/javac lrwxrwxrwx 1 root root 33 2010-09-30 16:37 /etc/alternatives/javac -> /usr/lib/jvm/java-6-sun/bin/javac*
إليكم التفكيك ، الذي يوضح checkcast
:
tjc@forge:~/temp$ javap -c Generics Compiled from "Generics.java" public class Generics extends java.lang.Object{ public Generics(); Code: 0: aload_0 1: invokespecial #1; //Method java/lang/Object."":()V 4: return static java.util.List getObjects(); Code: 0: iconst_3 1: anewarray #2; //class java/io/Serializable 4: dup 5: iconst_0 6: ldc #3; //String one 8: aastore 9: dup 10: iconst_1 11: iconst_2 12: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 15: aastore 16: dup 17: iconst_2 18: iconst_3 19: invokestatic #4; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 22: aastore 23: invokestatic #5; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List; 26: areturn public static void main(java.lang.String[]); Code: 0: invokestatic #6; //Method getObjects:()Ljava/util/List; 3: astore_1 4: aload_1 5: invokeinterface #7, 1; //InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator; 10: astore_2 11: aload_2 12: invokeinterface #8, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 17: ifeq 40 20: aload_2 21: invokeinterface #9, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 26: checkcast #10; //class java/lang/String 29: astore_3 30: getstatic #11; //Field java/lang/System.out:Ljava/io/PrintStream; 33: aload_3 34: invokevirtual #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 37: goto 11 40: return }
مرة أخرى ، رغم ذلك ، يجب أن يكون خلاصة القول: لا تستخدم أ List<String>
للتفاعل مع أ List
التي تحتوي على أشياء ليست String
س. :-)
نصائح أخرى
لا يمكنني إعادة إنتاجه أيضًا ، لكنني اكتشفت الأخطاء التالية التي يجب تصحيحها:
- صنع
getObjects()
إرجاعList<Integer>
, ، بدلا من النوع الخام - لا تتوقع
List<String>
, ، لكن أList<Integer>
في حين أن - عند التكرار ، حلقة
for (Integer o : list)
المشكلة هي الطريقة static List getObjects() {
إرجاع عام (غير معلمة) List
. وأنت تقوم بتعيينها إلى List<String>
. يجب أن يعطي هذا الخط تحذيرًا للمترجم ، ويجب أن يشير ذلك إلى مشكلة.
هذا الجزء :
List<String> list = getObjects();
for (Object o : list) { // ClassCastException?
System.out.println(o);
}
سيتم تبسيطها
List<String> list = getObjects();
for (Iterator<String> iterator = list.iterator(); iterator.hasNext();) {
Object o = iterator.next();
System.out.println(o);
}
لكن تنفيذ التكرار سيحاول الإلقاء عند الاتصال next()
طريقة إرسال المحتوى بواسطة Iterator
في String
.
لهذا السبب لديك CCE.
حلول :
إما استخدام الأدوية الجيلية في كل مكان أو لا تستخدمها ، ولكن من المهم حقًا أن تكون متسقًا. إذا كنت قد عادت List<Integer>
أو أ List<? super Integer>
كنت قد رأيت هذه المشكلة في وقت التجميع.