سؤال

لماذا هذا الرمي 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-unboxing null يتم إرجاع.


يحتاج هذا السؤال إلى تحليل نوع مماثل:

مشغل Java الشرطي ؟: نوع النتيجة

نصائح أخرى

الخط:

    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
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top