اللقب الشهير لهذا الخطأ في عالم جافا: "خطأ المليار دولار" —
وصفه مخترع مفهوم null نفسه. كل مطوّر جافا سيقابل
java.lang.NullPointerException (اختصارًا NPE) مئات المرات، لذا فهمه
مرة واحدة بعمق يوفّر عليك سنوات من التخمين.
ماذا يعني null أصلًا؟
المتغيّر من نوع كائن (مثل String أو ArrayList) هو إشارة إلى مكان في
الذاكرة. null يعني: "هذه الإشارة لا تشير إلى أي شيء".
String name = null; // صندوق فارغ — لا يوجد نص هنا
int len = name.length(); // 💥 NullPointerException
لا يمكنك أن تسأل "لا شيء" عن طوله — هذا كل ما يقوله الخطأ.
كيف تقرأ الـ Stack Trace
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "name" is null
at com.shop.UserService.validate(UserService.java:42)
at com.shop.Main.main(Main.java:15)
- السطر الأول: منذ جافا 14 الرسالة تحدد الفاعل بالاسم —
nameهو الـ null. UserService.java:42: أول سطر يخص كودك أنت — ابدأ منه دائمًا (تجاهل أسطر مكتبات جافا الداخلية).- الأسطر التالية: سلسلة الاستدعاءات التي أوصلت إلى هناك.
الأسباب الخمسة الأكثر شيوعًا
| السبب | مثال |
|---|---|
| حقل لم يُهيَّأ | private List<String> items; ثم items.add(...) |
| دالة أعادت null | map.get("مفتاح غير موجود") |
| ترتيب المقارنة | input.equals("نعم") وinput قد يكون null |
| سلسلة استدعاءات طويلة | user.getAddress().getCity().trim() |
| فكّ تغليف تلقائي | Integer count = null; ثم int c = count; |
الحلول العملية
1) أعطِ الحقول قيمة ابتدائية — امنع null من الولادة
// ❌ قنبلة موقوتة
private List<String> items;
// ✅ آمن منذ اللحظة الأولى
private List<String> items = new ArrayList<>();
القاعدة نفسها للدوال: أعِد قائمة فارغة بدل null —
return Collections.emptyList(); تجعل كل حلقات for عند المستدعي آمنة.
2) اقلب ترتيب المقارنة
// ❌ ينفجر إذا كان input = null
if (input.equals("نعم")) { ... }
// ✅ الثابت أولًا — لا يمكن أن يكون null
if ("نعم".equals(input)) { ... }
3) فكّك السلاسل الطويلة عند التشخيص
// ❌ أي حلقة في السلسلة قد تكون null — أيها المذنب؟
String city = user.getAddress().getCity().trim();
قسّمها لأسطر منفصلة وسيقودك رقم السطر في الـ stack trace إلى المذنب مباشرة. (منذ جافا 14 الرسالة نفسها تحدده حتى في السلسلة الواحدة.)
4) استخدم Optional للقيم التي قد تغيب
عندما يكون الغياب حالة طبيعية (مستخدم غير موجود مثلًا)، أعلنها في نوع الدالة بدل مفاجأة المستدعي بـ null:
Optional<User> findUser(String email) { ... }
String name = findUser(email)
.map(User::getName)
.orElse("زائر");
5) افشل مبكرًا في مداخل الدوال
public void process(Order order) {
Objects.requireNonNull(order, "order يجب ألا يكون null");
// من هنا فصاعدًا order مضمون
}
الفشل الفوري برسالة واضحة أفضل ألف مرة من NPE غامض بعد عشرين سطرًا.
قائمة تحقّق سريعة
- ما أول سطر في الـ stack trace يخص كودك؟ ابدأ منه.
- من هو الـ null؟ (جافا 14+ تسمّيه في الرسالة حرفيًا)
- هل للحقول قيم ابتدائية؟ هل تعيد دوالك قوائم فارغة بدل null؟
- هل الثابت على يسار
equals؟ - هل الغياب حالة متوقعة؟ → استخدم
Optional.
الخلاصة
NPE لا يقول "برنامجك سيّئ" بل يقول: "استدعيت شيئًا على لا شيء — في هذا
السطر بالضبط". اقرأ الـ stack trace من أول سطر يخصك، سمِّ الـ null، ثم
عالج المصدر لا العرَض: قيم ابتدائية، قوائم فارغة، وOptional للغياب
المشروع.
أكمل رحلتك في مسار جافا بالعربي، وقارن مع الفرق بين جافا وبايثون و الفرق بين Kotlin وجافا — حيث حلّت Kotlin مشكلة null من جذورها في نظام الأنواع نفسه.