أساسيات جافا

فئات التغليف والتعبئة التلقائية

15 دقيقة الدرس 9 من 14

فئات التغليف والتعبئة التلقائية

أنواع Java البدائية — int وdouble وboolean وغيرها — سريعة وكفؤة في استهلاك الذاكرة، لكنها ليست كائنات. يصبح هذا مشكلة حين تحتاج إلى تخزينها في مجموعة، أو تمريرها لدالة تتوقع Object، أو استدعاء دالة مساعدة على رقم. تحل فئات التغليف هذه المشكلة بلف كل نوع بدائي داخل كائن حقيقي.

فئات التغليف الثماني

لكل نوع بدائي فئة مقابلة في حزمة java.lang:

  • byteByte
  • shortShort
  • intInteger
  • longLong
  • floatFloat
  • doubleDouble
  • charCharacter
  • booleanBoolean

ستتعامل مع Integer وDouble وBoolean أكثر من غيرها، فهي الأكثر شيوعًا في كود Java اليومي.

التعبئة التلقائية وفك التعبئة

قبل الإصدار Java 5 كان عليك إجراء التحويل يدويًا. اليوم تقوم Java بذلك تلقائيًا:

  • التعبئة التلقائية (Autoboxing) — يحوّل المُترجِم النوع البدائي إلى كائن تغليف تلقائيًا.
  • فك التعبئة (Unboxing) — يحوّل المُترجِم كائن التغليف إلى النوع البدائي تلقائيًا.
import java.util.ArrayList; import java.util.List; public class AutoboxDemo { public static void main(String[] args) { // تعبئة تلقائية: int -> Integer Integer boxed = 42; // المُترجِم يكتب: Integer.valueOf(42) // فك تعبئة: Integer -> int int primitive = boxed; // المُترجِم يكتب: boxed.intValue() // تعمل بسلاسة مع المجموعات List<Integer> numbers = new ArrayList<>(); numbers.add(10); // تعبئة تلقائية numbers.add(20); // تعبئة تلقائية int sum = numbers.get(0) + numbers.get(1); // فك تعبئة للاثنين System.out.println(sum); // 30 } }

يبدو التحويل غير مرئي على مستوى الكود المصدري، لكن المُترجِم يُدرج الاستدعاءات. فهم هذا مهم للأداء وللخطأ الخفي الذي ستراه لاحقًا.

دوال مفيدة في فئات التغليف

فئات التغليف ليست مجرد حاويات — فهي تحمل دوالًا ثابتة مساعدة لا تستطيع الأنواع البدائية امتلاكها.

public class WrapperMethods { public static void main(String[] args) { // --- Integer --- System.out.println(Integer.MAX_VALUE); // 2147483647 System.out.println(Integer.MIN_VALUE); // -2147483648 System.out.println(Integer.toBinaryString(10)); // 1010 System.out.println(Integer.toHexString(255)); // ff System.out.println(Integer.bitCount(7)); // 3 (ثلاثة بتات بقيمة 1) // --- Double --- System.out.println(Double.isNaN(0.0 / 0.0)); // true System.out.println(Double.isInfinite(1.0 / 0.0)); // true // --- Boolean --- System.out.println(Boolean.logicalAnd(true, false)); // false System.out.println(Boolean.toString(true)); // "true" } }

تحليل النصوص إلى أنواع بدائية

من أكثر استخدامات فئات التغليف شيوعًا تحليل مدخلات المستخدم أو قيم الضبط المُخزَّنة كنص إلى أرقام أو قيم منطقية صالحة للاستخدام.

public class ParseDemo { public static void main(String[] args) { int age = Integer.parseInt("25"); double price = Double.parseDouble("19.99"); boolean flag = Boolean.parseBoolean("true"); // غير حساسة لحالة الأحرف System.out.println(age + 1); // 26 System.out.println(price * 2); // 39.98 System.out.println(!flag); // false // التحويل في الاتجاه الآخر: بدائي أو تغليف -> نص String s1 = Integer.toString(100); // "100" String s2 = String.valueOf(3.14); // "3.14" System.out.println(s1 + " / " + s2); // "100 / 3.14" } }
parseInt مقابل valueOf: تُعيد Integer.parseInt("42") نوعًا بدائيًا int. تُعيد Integer.valueOf("42") كائن Integer (مع الاستفادة من الذاكرة المؤقتة المذكورة لاحقًا). فضّل parseInt حين تحتاج نوعًا بدائيًا، وفضّل valueOf حين تحتاج كائنًا.

فخ ذاكرة Integer التخزينية

إليك أحد أشهر مزالق المبتدئين في Java. تُخزّن JVM كائنات Integer للقيم الواقعة بين -128 و127 شاملةً. حين تُعبَّأ قيمة في هذا النطاق تلقائيًا، تستعيد الكائن المُخزَّن ذاته في كل مرة. أما خارج هذا النطاق فيُنشأ كائن جديد في كل مرة.

public class CachePitfall { public static void main(String[] args) { Integer a = 100; Integer b = 100; System.out.println(a == b); // true -- نفس الكائن المُخزَّن Integer x = 200; Integer y = 200; System.out.println(x == y); // false -- كائنان مختلفان! System.out.println(x.equals(y)); // true -- المقارنة الصحيحة } }
لا تستخدم == أبدًا لمقارنة كائنات التغليف. يفحص المعامل == تطابق المرجع (هل هذان الكائنان نفس الكائن في الذاكرة؟)، لا تساوي القيمة. كائنَا Integer اللذان يحملان 200 كائنان مختلفان و== يُعيد false. استخدم دائمًا .equals() لمقارنة قيم التغليف.

القيمة null وخطأ NullPointerException عند فك التعبئة

نظرًا لأن كائنات التغليف كائنات حقيقية، يمكن أن تكون null. يُطلق فك تعبئة قيمة null استثناء NullPointerException — مصدر شائع للأعطال عند العمل مع المجموعات أو قيم الإرجاع من الدوال.

public class NullUnbox { public static void main(String[] args) { Integer value = null; // هذا السطر يُطلق NullPointerException عند التشغيل: // int n = value; // فك تعبئة null // النمط الآمن: تحقق أولًا if (value != null) { int n = value; System.out.println(n); } else { System.out.println("القيمة فارغة"); } } }
لماذا يحدث هذا؟ فك التعبئة هو اختصار لاستدعاء value.intValue(). استدعاء أي دالة على null يُطلق NullPointerException. لا يستطيع المُترجِم تحذيرك لأن الإسناد يبدو قانونيًا — احرص دائمًا على حماية مراجع التغليف التي قد تكون null.

الخلاصة

تُجسّر فئات التغليف الهوّة بين الأنواع البدائية وعالم الكائنات. تتيح لك التعبئة التلقائية وفك التعبئة كتابة كود طبيعي بينما يتولى المُترجِم التحويلات. احتفظ بثلاث قواعد في ذهنك: استخدم parseInt وparseDouble لتحويل النصوص إلى قيم؛ قارن التغليفات دائمًا بـ.equals() لا بـ==؛ واحرص على الحماية من null قبل فك التعبئة. بهذه العادات ستتجنب أكثر الأخطاء شيوعًا المرتبطة بالتغليف.