Menu
flag Ar iconالعربيةdown icon

NullPointerException في جافا: الأسباب وكيفية إصلاحه

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

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

أكثر أخطاء جافا شيوعاً

يحدث استثناء NullPointerException (يسمّيه الجميع NPE) عندما تحاول استخدام مرجع لا يشير إلى أي شيء -null- وكأنه يشير إلى كائن حقيقي. متغيّرات جافا من النوع الكائني إما أن تحمل كائناً أو تحمل null، وهي القيمة التي تعني «لا يوجد كائن هنا». في اللحظة التي تطلب فيها من null أن يفعل شيئاً - أن تستدعي دالة عليه، أو تقرأ أحد حقوله، أو تصل إليه بفهرس - لا يوجد شيء يُتعامل معه، فترمي آلة جافا الافتراضية استثناءً.

وعلى عكس try-catch الذي تلجأ إليه في الصفحة السابقة لمعالجة الأعطال المتوقّعة، يكون NPE في الغالب مجرد خطأ برمجي. هدف هذه الصفحة ليس التقاطها، بل فهم سبب حدوثها وكتابة شيفرة لا تنتجها أصلاً.

لا يوجد أي String ليُنفَّذ عليه length()، لذا يتوقّف البرنامج باستثناء NullPointerException.

قراءة الرسالة

منذ جافا 14، تخبرك الرسالة بالضبط بما الذي كان null - ويُسمّى هذا Helpful NullPointerException (الاستثناء المفيد). اقرأه قبل أن تغيّر أي شيء:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
	at Main.main(Main.java:4)

يهمّ جزآن. Cannot invoke "String.length()" هي العملية التي فشلت، وbecause "name" is null تسمّي المتسبّب. أما السطر at Main.main(Main.java:4) فهو تتبّع المكدّس الذي يشير إلى السطر بالضبط. إذن فالإصلاح ليس «لفّ السطر 4 داخل try» - بل «معرفة لماذا تكون name تساوي null في السطر 4». وفي الغالب يكون الخطأ في موضع أسبق، حيث كان ينبغي إسناد القيمة ولم يحدث.

الطرق التي تُطلِقه بها

تأتي استثناءات NPE من حفنة من العمليات، كلها صور مختلفة لـ«ملامسة null»:

البحث في الخرائط مصدر كلاسيكي: يعيد get قيمة null عند غياب المفتاح، وكثيراً ما يظهر NPE بعد أسطر، عندما تستخدم تلك القيمة أخيراً. وفكّ التغليف هو الحالة الماكرة - إذ يؤدّي إسناد قيمة Integer تساوي null إلى int إلى رمي استثناء، لأنه لا يوجد عدد لنسخه.

الحماية بفحص null

أبسط دفاع هو جملة if تؤكّد أن المرجع ليس فارغاً قبل أن تستخدمه:

عندما تقارن متغيّراً بسلسلة String ثابتة، ضع الثابت أولاً - "yes".equals(answer) بدلاً من answer.equals("yes"). فإذا كانت answer تساوي null، تعيد الصيغة الأولى false بهدوء، بينما ترمي الثانية استثناءً.

الفشل السريع باستخدام Objects.requireNonNull

نثر فحوص null في كل مكان يصبح مزعجاً. عندما يجب ألّا تكون قيمة null أبداً - مثل وسيط مُنشئ (constructor) - تحقّق منها عند الحدود باستخدام Objects.requireNonNull. فهي ترمي الاستثناء فوراً، برسالة واضحة، عند النقطة التي تصل فيها القيمة الخاطئة، بدلاً من حدوث ذلك لاحقاً في أعماق شيفرتك:

هذه العادة في «الفشل السريع» تحوّل استثناء NPE غامضاً يبعد 200 سطر إلى شكوى دقيقة عند المصدر. والتقاط الاستثناء هنا غرضه إظهار الرسالة فقط - أما في الشيفرة الحقيقية فكنت ستتركه يظهر كي يُصلَح الخطأ.

تجنّب القيم الفارغة من البداية

أفضل NPE هو الذي لا يمكن أن يحدث أبداً لأنه لا يوجد null لتصطدم به. وهناك بضع عادات تُحدث فرقاً كبيراً:

  • أعِد مجموعة أو سلسلة فارغة، لا null أبداً. فـ Collections.emptyList() و"" آمنان للمرور عليهما بحلقة واستدعاء الدوال عليهما.
  • استخدم getOrDefault على الخرائط بحيث يُنتج البحث الفاشل قيمة حقيقية بدلاً من null.
  • هيّئ الحقول عند التصريح بها بدلاً من تركها null حتى «لاحقاً».

عندما تكون القيمة اختيارية فعلاً - بحث قد لا يجد شيئاً بصورة مشروعة - تقدّم جافا Optional، وهو حاوية تُجبر المستدعِي على معالجة حالة «الغياب» بدلاً من إعادة null في صمت. وهذا هو المفهوم ذو الصلة الذي ينبغي قراءته تالياً إن أردت أن تُخرِج هذه الفجوات من تصميم واجهاتك البرمجية كلياً.

الخلاصة

استثناء NullPointerException هو طريقة جافا في إخبارك بأنك استخدمت مرجعاً يحمل null وكأنه يحمل كائناً. ونادراً ما يكون الإصلاح هو التقاطه - بل قراءة الرسالة المفيدة، والتتبّع رجوعاً إلى الموضع الذي كان ينبغي تعيين القيمة فيه، ثم إما ضمان أنها ليست فارغة، أو حماية الموضع الذي تستخدمها فيه. اعتمد على Objects.requireNonNull للفشل السريع عند الحدود، وفضّل القيم الفارغة وgetOrDefault على null، والجأ إلى Optional عندما يكون الغياب نتيجة حقيقية ومتوقّعة. أتقِن هذه العقلية، وسيغدو أكثر أخطاء جافا شيوعاً واحداً من أندرها في شيفرتك أنت.

الأسئلة الشائعة

ما الذي يسبّب استثناء NullPointerException في جافا؟

يحدث عندما تستخدم مرجعاً يشير إلى null وكأنه يشير إلى كائن حقيقي - مثل استدعاء دالة (name.length())، أو قراءة حقل، أو الوصول إلى عنصر في مصفوفة، أو فكّ تغليف (unboxing) قيمة Integer تساوي null. المتغيّر لا يحمل أي كائن، فلا يوجد شيء يُتعامل معه، فترمي آلة جافا الافتراضية استثناء NullPointerException.

كيف تُصلح استثناء NullPointerException في جافا؟

اقرأ الرسالة - فمنذ جافا 14 تذكر بالضبط ما الذي كان null (مثلاً "Cannot invoke "String.length()" because "name" is null"). ثم تتبّع سبب كون ذلك المتغيّر null: تهيئة مفقودة، أو دالة أعادت null، أو بحث في خريطة لم يجد شيئاً. أصلِح المصدر بحيث لا تكون القيمة null أبداً، أو احْمِ موضع الاستخدام بفحص null أو بـ Objects.requireNonNull أو بـ Optional.

هل الأفضل التحقق من null أم التقاط استثناء NullPointerException؟

تحقّق من null. فاستثناء NullPointerException يدلّ على خلل في منطقك، لا على حالة متوقّعة، لذا ينبغي أن تمنعه بدلاً من التقاطه. التقاطه يُخفي موضع المشكلة الحقيقية. احتفظ بـ try/catch للحالات الاستثنائية فعلاً مثل أعطال الإدخال/الإخراج.

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن