English 🇬🇧
CPCS203  ·  الوحدة 05  ·  الجزء 3 من 3

🎁 فئات التغليف والأعداد الكبيرة

Wrapper Classes و BigInteger | البرمجة كائنية التوجه — الوحدة الخامسة، الجزء الثالث

🎁 فئات التغليف (Wrapper Classes) 🔄 التغليف التلقائي (Auto-Boxing) 🔢 BigInteger 📐 BigDecimal
📋 جدول المحتويات
  1. فئات التغليف (Wrapper Classes) — تغليف الأنواع الأساسية ككائنات
  2. BigInteger و BigDecimal — الحسابات خارج الحدود
1
فئات التغليف (Wrapper Classes) — تغليف الأنواع الأساسية ككائنات
🔌 قصة من الواقع: مليون جهاز!

كان أحمد رجل أعمال. سافر إلى الولايات المتحدة الأمريكية، ووجد صفقة رائعة في أحد المصانع، فاشترى مليون جهاز إلكتروني — مستثمرًا كل ما يملك. وصلت الشحنة إلى ميناء جدة. فتح الصندوق الأول، وصل الجهاز بالكهرباء... لا شيء. رائحة احتراق. 💨

الأجهزة مصممة لـ 110 فولت، أما المملكة العربية السعودية فتعمل على 220 فولت. مليون جهاز — غير متوافق تمامًا. لا يستطيع بيع جهاز واحد. انتهى مشروعه قبل أن يبدأ.

ثم اتصل به صديقه خالد. "يا أحمد — لا تلمس أي جهاز. ابنِ صندوقًا صغيرًا: يأخذ 220 فولتًا من الحائط، يحوّلها إلى 110 فولت في الداخل، ويعطي جهازك ما يحتاجه بالضبط. ضعه في المنتصف." وضع أحمد صندوق التغليف (Wrapper) في المنتصف — دون أن يتغيّر شيء على أي من الجانبين — ونجح مشروعه. 🎉

لدى Java نفس المشكلة. الأنواع الأساسية (Primitives) (int, double, char...) تعمل بشكل ممتاز وحدها، لكن عالم Java الكائني — ArrayList، والأنواع العامة (Generics)، والمكتبات — يقبل الكائنات (Objects) فقط. حل Java هو بالضبط صندوق خالد: فئة التغليف (Wrapper Class) (Integer, Double, Character...) توضع في المنتصف. لا يتغير شيء على أي من الجانبين.

في Java، توجد 8 أنواع أساسية (Primitive Types): byte, short, int, long, float, double, char, boolean. بعض واجهات برمجة Java وهياكل البيانات (مثل ArrayList) تعمل مع الكائنات (Objects) فقط، وليس مع الأنواع الأساسية. الحل؟ توفر Java فئة تغليف (Wrapper Class) لكل نوع أساسي تقوم بتغليفه داخل كائن!

Wrapper Class: Integer
الخارج (عالم الكائنات)
كائن Integer
42
private int value = 42;
الدوال: intValue()، compareTo()، parseInt()...
النوع الأساسي (Primitive Type) فئة التغليف (Wrapper Class) مثال
byteByteByte b = new Byte((byte)5);
shortShortShort s = new Short((short)100);
intIntegerInteger i = new Integer(42);
longLongLong l = new Long(100L);
floatFloatFloat f = new Float(3.14f);
doubleDoubleDouble d = new Double(2.718);
charCharacterCharacter c = new Character('A');
booleanBooleanBoolean b = new Boolean(true);
📄 WrapperDemo.java
public class WrapperDemo { public static void main(String[] args) { // التغليف التلقائي (Autoboxing): النوع الأساسي → كائن التغليف (Java تفعل هذا تلقائيًا) Integer intObj = 42; // مثل: new Integer(42) Double dblObj = 3.14; // مثل: new Double(3.14) // فك التغليف التلقائي (Auto-unboxing): كائن التغليف → النوع الأساسي (Java تفعل هذا تلقائيًا) int x = intObj; // مثل: intObj.intValue() double y = dblObj; // مثل: dblObj.doubleValue() // دوال ثابتة مفيدة int parsed = Integer.parseInt("123"); // String → int String asStr = Integer.toString(456); // int → String int maxVal = Integer.MAX_VALUE; // 2,147,483,647 int minVal = Integer.MIN_VALUE; // -2,147,483,648 System.out.println(parsed); // 123 System.out.println(maxVal); // 2147483647 } }

فئات التغليف في Java مدمجة في اللغة — لكن يمكنك أيضًا بناء فئة التغليف الخاصة بك. هذه فكرة قوية: خذ أي نوع أساسي، ولفّه داخل صنف، وأضف أي دوال تحتاجها. على سبيل المثال، يمكننا تعريف صنف يسمى MyInt يلفّ نوع int ويوفر وظائف إضافية.

📄 MyInt.java — فئة التغليف المخصصة الخاصة بك
public class MyInt { private int value; // النوع الأساسي الملفوف public MyInt(int value) { this.value = value; } public int getValue() { return value; } public boolean isEven() { return value % 2 == 0; } public boolean isPositive() { return value > 0; } public String toBinary() { return Integer.toBinaryString(value); } @Override public String toString() { return "MyInt(" + value + ")"; } } // الاستخدام MyInt n = new MyInt(42); System.out.println(n.getValue()); // 42 System.out.println(n.isEven()); // true System.out.println(n.isPositive()); // true System.out.println(n.toBinary()); // 101010 System.out.println(n); // MyInt(42)
💡 لماذا توجد فئات التغليف (Wrapper Classes)

بعض واجهات برمجة Java (مثل ArrayList<Integer> و HashMap<String, Double>) تقبل الكائنات (Objects) فقط (لأنها تستخدم الأنواع العامة Generic). لا يمكنك كتابة ArrayList<int>. لذا تتيح فئات التغليف تخزين قيم الأنواع الأساسية داخل هذه الهياكل.

ميزتا التغليف التلقائي (Autoboxing) وفك التغليف التلقائي (Unboxing) في Java (منذ الإصدار الخامس) تتولّيان معظم التحويلات تلقائيًا، لذلك نادرًا ما تحتاج إلى استدعاء .intValue() يدويًا.

✓ مرجع سريع: التغليف وفك التغليف (Boxing & Unboxing)
  • التغليف التلقائي (Auto-boxing): تحوّل Java تلقائيًا النوع الأساسي إلى نوع التغليف المقابل عند الحاجة. Integer x = 5; → تكتب Java new Integer(5) بدلًا عنك.
  • فك التغليف التلقائي (Auto-unboxing): تحوّل Java تلقائيًا الكائن المغلّف إلى نوعه الأساسي. int y = x; → تستدعي Java x.intValue() بدلًا عنك.
  • ثوابت مفيدة: Integer.MAX_VALUE، Integer.MIN_VALUE، Double.MAX_VALUE.
  • التحليل (Parsing): Integer.parseInt("42") تحوّل String إلى int — شائع جدًا في البرامج الفعلية!
2
BigInteger و BigDecimal — الحسابات خارج الحدود

ماذا لو كان العدد الذي تحتاج لحسابه كبيرًا جدًا لدرجة أنه لا يسع في نوع long (الذي يبلغ حده الأقصى حوالي 9.2 × 1018)؟ أو تحتاج إلى حساب عشري بدقة بالغة — كما في الحسابات المالية أو التشفير؟

توفر Java صنفين خاصين: BigInteger و BigDecimal في الحزمة java.math.

🔢
BigInteger
يمثّل أعدادًا صحيحة كبيرة بشكل تعسفي — لا فيض (Overflow) أبدًا. يدعم جميع العمليات الحسابية: add()، subtract()، multiply()، divide()، pow()، mod().
📐
BigDecimal
يمثّل أعدادًا عشرية بدقة تعسفية — لا أخطاء تقريب. ضروري للحسابات المالية والعلمية التي تستلزم الدقة.
⚠️ ملاحظة مهمة

لا يمكنك استخدام عوامل +، -، *، / مع BigInteger أو BigDecimal. يجب استخدام دوالهما: .add()، .subtract()، .multiply()، .divide().

📄 BigNumberDemo.java
import java.math.BigInteger; import java.math.BigDecimal; public class BigNumberDemo { public static void main(String[] args) { // ── BigInteger ────────────────────────────────────────────────── BigInteger n1 = new BigInteger("99999999999999999999999999999"); BigInteger n2 = new BigInteger("11111111111111111111111111111"); System.out.println("n1 + n2 = " + n1.add(n2)); System.out.println("n1 * n2 = " + n1.multiply(n2)); System.out.println("n1 ^ 2 = " + n1.pow(2)); // ── BigDecimal ────────────────────────────────────────────────── BigDecimal price = new BigDecimal("19.99"); BigDecimal tax = new BigDecimal("0.15"); BigDecimal total = price.multiply(tax.add(BigDecimal.ONE)); System.out.println("Total with tax = " + total); // لماذا لا تستخدم double للأموال: System.out.println(1.0 - 0.9); // يطبع 0.09999999999999998 ❌ System.out.println( new BigDecimal("1.0").subtract(new BigDecimal("0.9")) ); // يطبع 0.1 ✅ } }
n1 + n2 = 111111111111111111111111111110
n1 * n2 = 1111111111111111111111111111088888888888888888889
n1 ^ 2 = 9999999999999999999999999999800000000000000000000000000001
Total with tax = 22.9885
double subtraction: 0.09999999999999998 ❌
BigDecimal subtraction: 0.1 ✓
🔐 استخدام من الواقع: التشفير!

سأل أحد الطلاب: "لماذا نحتاج في أي وقت لأعداد بهذا الحجم؟"

في علم التشفير (Cryptography)، تتطلب خوارزميات مثل RSA التعامل مع أعداد صحيحة تمتد لمئات الأرقام (مثل أعداد من 2048 بت أو 4096 بت). إن تحليل مثل هذه الأعداد إلى عواملها الأولية هو ما يجعل التشفير آمنًا. وBigInteger هو الأداة القياسية لتطبيق هذه الخوارزميات في Java.

📌 ملاحظة للطلاب

BigInteger وBigDecimal ليسا بالمفهوم الدقيق مفاهيمَ كائنية التوجه (OOP) — بل هما ميزتان من مكتبة Java. ندرسهما هنا لأنهما يوضحان كيف تُغلّف Java سلوكًا عدديًا معقدًا داخل أصناف بدوال، وهو مثال رائع على مبدأي التغليف (Encapsulation) والتجريد (Abstraction) في العمل.

الوحدة الخامسة مكتملة — الملخص الشامل
🎉 كل ما غطّته الوحدة الخامسة (الأجزاء الثلاثة)
🔒
قابل للتغيير وغير قابل للتغيير — Mutable vs Immutable (الجزء الأول)
ثلاثة قواعد للصنف غير القابل للتغيير (Immutable Class): جميع الحقول private، بلا دوال ضبط (Setters)، ولا دالة getter تعيد مرجعًا لكائن قابل للتغيير (بل أعد نسخة دفاعية).
🔧
الاعتماد (Dependency) — "يستخدم" (الجزء الثاني)
استخدام مؤقت لصنف آخر كمعامل في دالة. ليس جزءًا من هوية الكائن. رمز UML: سهم متقطع.
📦
التجميع (Aggregation) — "يحتوي على" بشكل ضعيف (الجزء الثاني)
يمكن للكائن المحتوى أن يعيش باستقلالية وقد يُشارَك. يُستقبل من الخارج عبر معامل المُنشئ (Constructor). رمز UML: معين مفتوح ◇.
💎
التركيب (Composition) — "يمتلك" بشكل قوي (الجزء الثاني)
الكائن المحتوى يُنشأ داخل المالك ولا يمكنه البقاء بعد تدميره. يُنشأ داخل المُنشئ باستخدام new. رمز UML: معين مملوء ◆.
🔄
التجميع الذاتي (Self-Aggregation) (الجزء الثاني)
صنف يحتوي على حقل من نفس نوعه (مثل Student supervisor داخل Student). أساس القوائم المترابطة (Linked Lists) في هياكل البيانات.
🔢
التعددية (Multiplicity) (الجزء الثاني)
يحدد عدد الكائنات المشاركة في علاقة: 1، 0..1، *، 1..*، ومدى مثل 5..60.
🎁
فئات التغليف (Wrapper Classes) (الجزء الثالث)
تلفّ Java كل نوع من الأنواع الأساسية الثمانية في صنف (مثل int → Integer) لتمكينه من الاستخدام ككائن. يتولى التغليف التلقائي (Autoboxing) وفك التغليف التلقائي (Unboxing) التحويلات تلقائيًا.
🔢
BigInteger و BigDecimal (الجزء الثالث)
أصناف مكتبة Java للأعداد الصحيحة الكبيرة بشكل تعسفي والأعداد العشرية ذات الدقة العالية. استخدم الدوال (add()، multiply()) — لا عوامل الحساب. ضرورية للتشفير والحسابات المالية.