المنطقيين والمشغلين الشرطيين و Autoboxing
-
28-09-2019 - |
سؤال
لماذا هذا الرمي NullPointerException
public static void main(String[] args) throws Exception {
Boolean b = true ? returnsNull() : false; // NPE on this line.
System.out.println(b);
}
public static Boolean returnsNull() {
return null;
}
في حين أن هذا لا
public static void main(String[] args) throws Exception {
Boolean b = true ? null : false;
System.out.println(b); // null
}
?
الحل هو بالمناسبة لاستبداله false
بواسطة Boolean.FALSE
لتجنب null
يجري غير مرغوب فيه ل boolean
-ما غير ممكن. لكن هذا ليس هو السؤال. السؤال هو لماذا؟ هل هناك أي مراجع في JLS التي تؤكد هذا السلوك ، وخاصة من الحالة الثانية؟
المحلول
الفرق هو أن النوع الصريح من returnsNull()
تؤثر الطريقة على الكتابة الثابتة للتعبيرات في وقت الترجمة:
E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)
E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)
انظر مواصفات لغة Java ، القسم 15.25 المشغل الشرطي؟ :
بالنسبة إلى E1 ، هناك أنواع المعاملات الثانية والثالثة
Boolean
وboolean
على التوالي ، لذلك ينطبق هذا البند:إذا كان أحد المعاملات الثانية والثالثة من النوع المنطقي ونوع الآخر من النوع المنطقي ، فإن نوع التعبير الشرطي هو منطقية.
منذ نوع التعبير
boolean
, ، يجب إكراه المعامل الثانيboolean
. يقوم المترجم بإدراج رمز Hunboxing التلقائي إلى المعامل الثاني (قيمة الإرجاعreturnsNull()
) لجعله نوعboolean
. هذا بالطبع يسبب NPE منnull
عاد في وقت التشغيل.بالنسبة إلى E2 ، هناك أنواع من المعاملات الثانية والثالثة
<special null type>
(ليسBoolean
كما في E1!) وboolean
على التوالي ، لذلك لا ينطبق شرط كتابة محدد (اذهب اقرأ 'م!) ، لذلك ينطبق جملة "خلاف ذلك" النهائي:خلاف ذلك ، فإن المعاملات الثانية والثالثة هي من النوعين S1 و S2 على التوالي. دع T1 هو النوع الذي ينتج عن تطبيق تحويل الملاكمة على S1 ، ودع T2 هو النوع الذي ينتج عن تطبيق تحويل الملاكمة على S2. نوع التعبير الشرطي هو نتيجة تطبيق تحويل الالتقاط (§5.1.10) إلى LUB (T1 ، T2) (§15.12.2.7).
- S1 ==
<special null type>
(نرى §4.1) - S2 ==
boolean
- T1 == Box (S1) ==
<special null type>
(انظر العنصر الأخير في قائمة تحويلات الملاكمة في §5.1.7) - T2 == Box (S2) == `Boolean
- lub (T1 ، T2) ==
Boolean
لذا فإن نوع التعبير الشرطي هو
Boolean
ويجب إكراه المعامل الثالثBoolean
. يقوم المترجم بإدراج رمز التفتيش التلقائي للمعامل الثالث (false
). لا يحتاج المعامل الثاني إلى عدم وجود تلقائي كما فيE1
, ، لذلك لا يوجد npe-unboxingnull
يتم إرجاع.- S1 ==
يحتاج هذا السؤال إلى تحليل نوع مماثل:
نصائح أخرى
الخط:
Boolean b = true ? returnsNull() : false;
يتحول داخليًا إلى:
Boolean b = true ? returnsNull().booleanValue() : false;
لأداء unboxing. هكذا: null.booleanValue()
سوف تسفر عن NPE
هذا هو واحد من المزالق الرئيسية عند استخدام Autoboxing. تم توثيق هذا السلوك بالفعل في 5.1.8 JLS
تحرير: أعتقد أن إلغاء اللاصطناع يرجع إلى أن المشغل الثالث من النوع المنطقي ، مثل (إضافة ممثل ضمني):
Boolean b = (Boolean) true ? true : false;
من مواصفات لغة جافا ، القسم 15.25:
- إذا كان أحد المعاملات الثانية والثالثة من النوع المنطقي ونوع الآخر من النوع المنطقي ، فإن نوع التعبير الشرطي هو منطقية.
لذا ، يحاول المثال الأول الاتصال Boolean.booleanValue()
من أجل التحويل Boolean
إلى boolean
حسب القاعدة الأولى.
في الحالة الثانية ، يكون المعامل الأول من النوع الفارغ ، عندما لا يكون الثاني من النوع المرجعي ، لذلك يتم تطبيق تحويل Autoboxing:
- خلاف ذلك ، فإن المعاملات الثانية والثالثة هي من النوعين S1 و S2 على التوالي. دع T1 هو النوع الذي ينتج عن تطبيق تحويل الملاكمة على S1 ، ودع T2 هو النوع الذي ينتج عن تطبيق تحويل الملاكمة على S2. نوع التعبير الشرطي هو نتيجة تطبيق تحويل الالتقاط (§5.1.10) إلى LUB (T1 ، T2) (§15.12.2.7).
يمكننا أن نرى هذه المشكلة من رمز البايت. في السطر 3 من رمز البايت الرئيسي ، 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
, ، الملاكمة المنطقية ذات القيمة الفارغة ، invokevirtual
طريقة java.lang.Boolean.booleanValue
, ، سوف يرمي npe بالطبع.
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokestatic #2 // Method returnsNull:()Ljava/lang/Boolean;
3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
6: invokestatic #4 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
9: astore_1
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
17: return
LineNumberTable:
line 3: 0
line 4: 10
line 5: 17
Exceptions:
throws java.lang.Exception
public static java.lang.Boolean returnsNull();
descriptor: ()Ljava/lang/Boolean;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: aconst_null
1: areturn
LineNumberTable:
line 8: 0