تخطَّ إلى المحتوى

النصوص في Java: String وأهم دوالها

الدرس 8 من 11 · Java

اسم المستخدم، ورسالة الخطأ، وعنوان المقال: معظم ما يعرضه أي برنامج نصوص. استخدمت String من أول درس، لكنها تخفي عشرات الدوال المفيدة. في هذا الدرس ستتعلم كيف تقيس النص وتقطعه وتبحث فيه وتقارنه بالطريقة الصحيحة، ولماذا نحتاج StringBuilder أحياناً.

النص سلسلة من الحروف، ولكل حرف رقم يبدأ من صفر، تماماً مثل المصفوفات:

String word = "Java";
System.out.println(word.length());
System.out.println(word.charAt(0));
System.out.println(word.charAt(word.length() - 1));
System.out.println(word.isEmpty());
الناتج
4
J
a
false
  • length(): عدد الحروف. لاحظ أنها دالة بأقواس هنا، بعكس length في المصفوفات.
  • charAt(i): الحرف في الموضع i، ونوعه char.
  • isEmpty(): هل النص فارغ تماماً ""؟

تخيّل النص مثل عقد من الخرز: كل خرزة حرف، ولكل خرزة مكان مرقّم في الخيط.

هذه أهم معلومة في الدرس: النص في Java غير قابل للتعديل (Immutable). كل دالة «تغيّر» النص تُرجع في الحقيقة نصاً جديداً وتترك الأصلي كما هو:

String name = "sara";
name.toUpperCase();
System.out.println(name);
String upper = name.toUpperCase();
System.out.println(upper);
الناتج
sara
SARA

السطر الثاني لم يفعل شيئاً مفيداً، لأننا لم نخزن النص الجديد. تذكّر دائماً أن تستقبل النتيجة في متغير.

String text = " Hello Java ";
String clean = text.trim();
System.out.println("[" + clean + "]");
System.out.println(clean.substring(6));
System.out.println(clean.substring(0, 5));
System.out.println(clean.toLowerCase());
System.out.println(clean.replace("Java", "World"));
الناتج
[Hello Java]
Java
Hello
hello java
Hello World
  • trim(): تحذف المسافات من البداية والنهاية. مفيدة جداً مع ما يكتبه المستخدم.
  • substring(6): من الموضع 6 إلى النهاية.
  • substring(0, 5): من الموضع 0 إلى ما قبل الموضع 5.
  • replace(a, b): تستبدل كل ظهور للنص الأول بالثاني.
String email = "[email protected]";
System.out.println(email.contains("@"));
System.out.println(email.indexOf("@"));
System.out.println(email.indexOf("#"));
System.out.println(email.startsWith("omar"));
System.out.println(email.endsWith(".org"));
الناتج
true
4
-1
true
false

indexOf تُرجع موضع أول ظهور، وتُرجع -1 إذا لم تجد شيئاً. يمكنك دمجها مع substring لاستخراج جزء من النص:

String email = "[email protected]";
int at = email.indexOf("@");
String user = email.substring(0, at);
String domain = email.substring(at + 1);
System.out.println(user);
System.out.println(domain);
الناتج
omar
example.com

في درس الشروط عرفت أن مقارنة النصوص تكون بـ equals. لنرَ السبب عملياً:

String a = "java";
String b = new String("java");
System.out.println(a == b);
System.out.println(a.equals(b));
System.out.println(a.equalsIgnoreCase("JAVA"));
System.out.println("apple".compareTo("banana") < 0);
الناتج
false
true
true
true
  • == تسأل: هل المتغيران يشيران إلى نفس الكائن في الذاكرة؟
  • equals تسأل: هل المحتوى متطابق؟ وهذا ما نريده دائماً تقريباً.
  • equalsIgnoreCase: مقارنة تتجاهل حالة الحروف.
  • compareTo: مقارنة أبجدية؛ تُرجع عدداً سالباً إذا كان النص الأول يأتي قبل الثاني، وموجباً إذا كان بعده، وصفراً عند التساوي.
String line = "تفاح,موز,عنب";
String[] fruits = line.split(",");
System.out.println(fruits.length);
System.out.println(fruits[1]);
String joined = String.join(" - ", fruits);
System.out.println(joined);
الناتج
3
موز
تفاح - موز - عنب

split تقطع النص عند فاصل معيّن وتُرجع مصفوفة، و String.join تفعل العكس. ستحتاجهما كثيراً عند قراءة بيانات من ملف أو من المستخدم.

بدلاً من سلسلة طويلة من علامات +، استخدم String.format:

String name = "ليلى";
int age = 14;
double avg = 91.456;
String msg = String.format("%s عمرها %d ومعدلها %.1f", name, age, avg);
System.out.println(msg);
الناتج
ليلى عمرها 14 ومعدلها 91.5

%s مكان لنص، و %d لعدد صحيح، و %.1f لعدد عشري برقم واحد بعد الفاصلة.

StringBuilder: بناء النصوص بكفاءة

Section titled “StringBuilder: بناء النصوص بكفاءة”

لأن النص لا يتغير، فكل عملية + داخل حلقة تنشئ نصاً جديداً كاملاً وترمي القديم. مع آلاف التكرارات يصبح ذلك بطيئاً. الحل هو StringBuilder: نص قابل للتعديل تضيف إليه ثم تحوّله إلى String في النهاية:

StringBuilder sb = new StringBuilder();
for (int i = 1; i <= 5; i++) {
sb.append(i);
sb.append(" ");
}
String result = sb.toString().trim();
System.out.println(result);
System.out.println(new StringBuilder("abc").reverse());
الناتج
1 2 3 4 5
cba

الفرق مثل الكتابة بقلم حبر على ورقة جديدة في كل مرة، مقابل الكتابة على سبورة تضيف إليها وتمسح منها كما تشاء.

  1. اكتب برنامجاً يخزن جملة في متغير، ثم يطبع عدد حروفها وعدد كلماتها (استخدم split(" ")).
  2. اطبع الجملة معكوسة باستخدام StringBuilder.
  3. خزّن اسم ملف مثل photo.png، واستخرج الامتداد باستخدام lastIndexOf(".") و substring.
  4. تحدٍّ: اكتب دالة isPalindrome تستقبل كلمة وتُرجع true إذا كانت تُقرأ من الجهتين بنفس الشكل مثل level.
  • مقارنة النصوص بـ ==: استخدم equals دائماً. هذا الخطأ لا يظهر عند الترجمة، بل يعطي نتائج خاطئة وقت التشغيل.
  • تجاهل القيمة المُرجعة: text.trim(); وحدها لا تغيّر text. اكتب text = text.trim();.
  • موضع خارج الحدود: "Java".charAt(4) يسبب الخطأ StringIndexOutOfBoundsException، لأن آخر موضع هو 3.

سؤال

ما ناتج "Hello".substring(1, 3)؟

سؤال

لماذا نستخدم equals بدلاً من == لمقارنة نصين؟

  • النص سلسلة حروف مرقمة من صفر: length() و charAt() و substring().
  • النصوص لا تتغير: كل دالة تُرجع نصاً جديداً يجب أن تخزنه.
  • قارن المحتوى بـ equals أو equalsIgnoreCase، ولا تستخدم ==.
  • split تقسم النص إلى مصفوفة، و String.join تدمج، و String.format تنسّق.
  • لبناء نص داخل حلقة استخدم StringBuilder ثم toString().