StringBuilder مقابل دمج السلاسل في toString(): كيف يعمل تحت الغطاء
\n\nهل تساءلت يومًا لماذا لا تزال المناقشات محتدمة على Stack Overflow حول الطريقة الصحيحة لكتابة toString() في Java؟ بعض المطورين يؤمنون إيمانًا راسخًا بأن StringBuilder هو الطريق الصحيح الوحيد. بينما يستخدم آخرون ببساطة دمج السلاسل عبر علامة الجمع دون أي قلق. من هو على صواب؟
دعونا نلقي نظرة تحت غطاء محرك مترجم Java ومحسّن JIT (Just-In-Time). ستكتشف ما يحدث حقًا عندما تكتب str1 + str2 ولماذا قد تفاجئك الإجابة على هذا السؤال في عام 2024.
لماذا هذا الموضوع شائع جدًا؟
\n\nسؤال "StringBuilder vs String concatenation in toString()" حصل على آلاف المشاهدات على Stack Overflow ليس من قبيل الصدفة. كل مطور Java كتب مرة واحدة على الأقل طريقة toString(). وكل واحد واجه الاختيار: الكتابة بشكل أنيق باستخدام علامة الجمع أو "بشكل صحيح" باستخدام StringBuilder.
الخرافة القائلة بأن دمج السلاسل في Java بطيء دائمًا تعيش منذ أكثر من 20 عامًا. وقد كانت صحيحة... في عصر Java 1.4 و Java 5. لكن المترجمات لا تقف مكتوفة الأيدي.
\n\nماذا يقول مواصفات Java؟
\n\nوفقًا لمواصفات لغة Java (JLS §15.18.1)، فإن عامل + للسلاسل هو سكر نحوي (syntactic sugar). المترجم ملزم بتحويل دمج السلاسل إلى كود فعال. بدءًا من Java 5 (J2SE 5.0)، يقوم مترجم javac تلقائيًا باستبدال دمج السلاسل باستخدام StringBuilder.
دعونا نتحقق من ذلك عمليًا.
\n\nمثال كود: toString() بسيط
\n\nلنأخذ مثالًا كلاسيكيًا — طريقة toString() للفئة Person:
public class Person {\n private String name;\n private int age;\n private String email;\n\n @Override\n public String toString() {\n return "Person{" +\n "name='" + name + '\\'' +\n ", age=" + age +\n ", email='" + email + '\\'' +\n '}';\n }\n}\n\n\nيبدو الكود نظيفًا وسهل القراءة. ولكن كيف يبدو في البايت كود (bytecode)؟
\n\nنلقي نظرة على البايت كود
\n\nلنقم بتجميع هذه الفئة ونلقي نظرة على البايت كود باستخدام javap -c Person.class:
public java.lang.String toString();\n Code:\n 0: new #7 // class java/lang/StringBuilder\n 3: dup\n 4: invokespecial #8 // Method StringBuilder."<init>":()V\n 7: ldc #9 // String "Person{name='"\n 9: invokevirtual #10 // StringBuilder.append(String)\n 12: aload_0\n 13: getfield #3 // Field name:Ljava/lang/String;\n 16: invokevirtual #10 // StringBuilder.append(String)\n 19: ldc #11 // String "', age="\n 21: invokevirtual #10 // StringBuilder.append(String)\n 24: aload_0\n 25: getfield #4 // Field age:I\n 28: invokevirtual #12 // StringBuilder.append(int)\n 31: ldc #13 // String ", email='"\n 33: invokevirtual #10 // StringBuilder.append(String)\n 36: aload_0\n 37: getfield #5 // Field email:Ljava/lang/String;\n 40: invokevirtual #10 // StringBuilder.append(String)\n 43: ldc #14 // String "'}"\n 45: invokevirtual #10 // StringBuilder.append(String)\n 48: invokevirtual #15 // StringBuilder.toString():String\n 51: areturn\n\n\nهل ترى؟ المترجم أنشأ StringBuilder بنفسه! يقوم بإنشاء كائن جديد StringBuilder، ويستدعي append() لكل جزء من السلسلة، ثم toString(). نفس الشيء الذي كنت ستكتبه يدويًا، ولكن مع كود مصدري أكثر قابلية للقراءة.
ماذا عن استخدام StringBuilder بشكل صريح؟
\n\nالآن دعونا نكتب النسخة باستخدام StringBuilder بشكل صريح:
@Override\npublic String toString() {\n Strin