الدوال والمصفوفات والنصوص

StringBuilder وتنسيق النصوص

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

StringBuilder وتنسيق النصوص

في الدرس السابق استكشفت كلاس String وأساليبه المفيدة. ربما لاحظت خاصية مميزة: في كل مرة تعدّل فيها String، تُنشئ Java خلف الكواليس كائنًا جديدًا في الذاكرة. بالنسبة للتعديلات المتفرقة هذا مقبول، لكن داخل حلقة تكرار — أو عند بناء نص طويل قطعة قطعة — يصبح الأمر مكلفًا. هنا يأتي دور StringBuilder. ستتعلم أيضًا String.format وprintf، وهما أداتان تتيحان لك إنتاج مخرجات منظمة وأنيقة دون تسلسل نصوص معقّد.

لماذا النصوص غير قابلة للتغيير؟

كائنات String في Java غير قابلة للتغيير — بمجرد إنشائها لا يمكن تغيير محتواها. عندما تكتب:

String result = ""; for (int i = 0; i < 5; i++) { result = result + i; // ينشئ كائن String جديد في كل تكرار } System.out.println(result); // 01234

تُنشئ Java ستة كائنات String هنا — كائن فارغ واحد، ثم كائن لكل عملية إضافة. مع خمس تكرارات لن تلاحظ شيئًا، لكن مع آلاف التكرارات يُهدَر قدر كبير من الذاكرة والوقت.

عدم قابلية التغيير ميزة في الغالب. يجعل النصوص آمنة للمشاركة بين الخيوط ويسمح للـ JVM بتخزينها مؤقتًا. التكلفة تظهر فقط عند التسلسل داخل حلقات مكثفة.

StringBuilder: مخزن مؤقت للأحرف قابل للتعديل

يحتفظ StringBuilder بمخزن مؤقت واحد قابل للتمدد. تُعدّله في مكانه — لا تُنشأ كائنات جديدة لكل تغيير. النمط الأساسي هو: أنشئ، عدّل مرارًا، ثم استدعِ toString() مرة واحدة في النهاية.

StringBuilder sb = new StringBuilder(); for (int i = 0; i < 5; i++) { sb.append(i); // يعدّل المخزن المؤقت نفسه } String result = sb.toString(); System.out.println(result); // 01234

كائن StringBuilder واحد بدلًا من ستة كائنات String — المخرج نفسه، هدر أقل بكثير.

أهم أساليب StringBuilder

جميع أساليب التعديل تُعيد this، ما يتيح سلسلتها:

StringBuilder sb = new StringBuilder("Hello"); sb.append(", World"); // Hello, World sb.insert(5, " Beautiful"); // Hello Beautiful, World sb.replace(6, 15, "Java"); // Hello Java, World sb.delete(11, 17); // Hello Java sb.reverse(); // avaJ olleH System.out.println(sb.toString()); // avaJ olleH System.out.println(sb.length()); // 10 System.out.println(sb.charAt(0)); // a
  • append(value) — يُضيف أي قيمة (String، int، double، char، boolean، ...) إلى النهاية.
  • insert(index, value) — يُدرج في موضع محدد.
  • replace(start, end, str) — يستبدل الأحرف من start (شامل) إلى end (غير شامل).
  • delete(start, end) — يحذف الأحرف في ذلك النطاق.
  • reverse() — يعكس المخزن المؤقت بالكامل.
  • length() — عدد الأحرف الحالية.
  • toString() — يحوّل المخزن إلى String عادي غير قابل للتغيير.
سلسلة الأساليب تُبقي الكود مختصرًا. بما أن كل أسلوب تعديلي يُعيد StringBuilder نفسه، يمكنك الكتابة: new StringBuilder().append("A").append("B").append("C").toString() في سطر واحد.

بناء صف CSV — مثال عملي

String[] names = {"Alice", "Bob", "Carol"}; StringBuilder csv = new StringBuilder(); for (int i = 0; i < names.length; i++) { csv.append(names[i]); if (i < names.length - 1) { csv.append(","); } } System.out.println(csv.toString()); // Alice,Bob,Carol

هذا النمط — التراكم بـ StringBuilder، إضافة الفواصل بشرط، ثم استدعاء toString() — يظهر في كل مكان في كود Java الحقيقي.

String.format — نصوص بقوالب أنيقة

يعمل String.format كمحرك قوالب. تكتب نص التنسيق مع عناصر نائبة (محددات التنسيق)، فيملأها الأسلوب بالقيم:

String name = "Alice"; int age = 30; double score = 92.5; String line = String.format("Name: %s | Age: %d | Score: %.1f%%", name, age, score); System.out.println(line); // Name: Alice | Age: 30 | Score: 92.5%

أشيع محددات التنسيق:

  • %s — أي كائن، يُحوَّل عبر toString().
  • %d — أعداد صحيحة (int، long).
  • %f — أعداد عشرية؛ %.2f تعني منزلتين عشريتين.
  • %n — سطر جديد متوافق مع المنصة (يُفضَّل على \n داخل نصوص التنسيق).
  • %% — علامة النسبة المئوية الحرفية.
العرض والمحاذاة: %10s يضع النص في حقل عرضه 10 أحرف محاذيًا لليمين؛ %-10s يحاذيه لليسار. مفيد لطباعة الجداول.

printf — التنسيق والطباعة في خطوة واحدة

يطبّق System.out.printf نص التنسيق نفسه لكنه يطبع مباشرة — دون حاجة لمتغير String وسيط:

String[] products = {"Apple", "Banana", "Cherry"}; double[] prices = {1.20, 0.50, 3.75}; System.out.printf("%-10s %8s%n", "Product", "Price"); System.out.printf("%-10s %8s%n", "-------", "-----"); for (int i = 0; i < products.length; i++) { System.out.printf("%-10s %8.2f%n", products[i], prices[i]); }

المخرج:

Product Price ------- ----- Apple 1.20 Banana 0.50 Cherry 3.75
printf لا يُضيف سطرًا جديدًا تلقائيًا. أنهِ دائمًا نص التنسيق بـ %n (أو \n) حين تريد كل استدعاء في سطره الخاص، خلافًا لـ println التي تفعل ذلك تلقائيًا.

اختيار الأداة المناسبة

  • تسلسل بسيط ومرة واحدة — عامل + كافٍ؛ يُحسّنه المترجم.
  • بناء نص عبر خطوات متعددة أو داخل حلقة — استخدم StringBuilder.
  • إنتاج مخرجات منسقة (جداول، تقارير، تسميات) — استخدم String.format أو printf.
StringBuffer مقابل StringBuilder: قد تصادف StringBuffer في الكود القديم. وهو آمن للخيوط لكنه أبطأ. ما لم تكن تُشارك المخزن بين الخيوط، دائمًا فضّل StringBuilder.

الخلاصة

عدم قابلية String للتغيير ميزة لا عيب، لكنها تعني أن التسلسل المكثف داخل الحلقات يُهدر الذاكرة. يمنحك StringBuilder مخزنًا مؤقتًا واحدًا قابلًا للتعديل يمكنك فيه append وinsert وdelete وreverse قبل التحويل إلى String مرة واحدة عبر toString(). يتيح لك String.format وprintf تضمين القيم في قوالب مقروءة باستخدام محددات التنسيق مثل %s و%d و%.2f، ما يجعل المخرجات المنظمة نظيفة وسهلة الصيانة. بهذه الأدوات تستطيع التعامل بكفاءة مع كل مهمة بناء نصوص في Java.