أساسيات البرمجة الكائنيّة

التابع toString

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

التابع toString

كل كائن في Java يمتلك قدرة مدمجة على تمثيل نفسه كنص. تأتي هذه القدرة من التابع toString() المُعرَّف في java.lang.Object — الجذر الذي ترثه جميع الفئات في Java. ولأن كل فئة تكتبها ترث تلقائيًا من Object، فإن كل كائن يمتلك بالفعل تابع toString() قبل أن تكتب سطرًا واحدًا.

ما الذي يُنتجه toString الافتراضي؟

التنفيذ الافتراضي الموروث من Object يُنتج سلسلة نصية تجمع اسم الفئة الكامل مع رمز تعريف الكائن (identity hash code) بالنظام السادس عشر:

// الصيغة الافتراضية: ClassName@hexHashCode // مثال: BankAccount@7ef88735

لنرَ ذلك عمليًا. افترض أن لديك فئة بسيطة BankAccount وحاولت طباعتها:

public class BankAccount { private String owner; private double balance; public BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount("Alice", 1500.00); System.out.println(account); // BankAccount@6d06d69c } }

الناتج لا معنى له أثناء التصحيح. لا يمكنك معرفة المالك أو الرصيد من رمز سادس عشري. لهذا السبب بالضبط يُعدّ تجاوز toString() من أول الأشياء التي يفعلها مطور Java المحترف عند إنشاء فئة جديدة.

متى يُستدعى toString تلقائيًا؟ تستدعي Java التابع toString() تلقائيًا على الكائن في كل مرة تحتاج فيها إلى تمثيله كنص: داخل System.out.println(obj)، وأثناء دمج السلاسل النصية ("Account: " + obj)، وداخل أدوات التصحيح والسجلات ومعظم بيئات التطوير. نادرًا ما تستدعيه بالاسم؛ Java تفعل ذلك عنك.

تجاوز toString

التجاوز يعني تقديم نسختك الخاصة من التابع لتحلّ محل الموروثة. يجب أن يتطابق التوقيع تمامًا: يجب أن يكون public، ويُعيد String، ولا يأخذ معاملات. الحاشية @Override غير مطلوبة، لكنها موصى بها بشدة — فهي تُخبر المُصرِّف بأنك تنوي تجاوز تابع موروث، فيتمكن من اكتشاف الأخطاء المطبعية أو التواقيع الخاطئة.

public class BankAccount { private String owner; private double balance; public BankAccount(String owner, double balance) { this.owner = owner; this.balance = balance; } @Override public String toString() { return "BankAccount{owner='" + owner + "', balance=" + balance + "}"; } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount("Alice", 1500.00); System.out.println(account); // الناتج: BankAccount{owner='Alice', balance=1500.0} } }

الناتج الآن مفيد على الفور. يستطيع أي شخص يقرأ سجلًا أو يُصحّح خطأً في بيئة التطوير أن يفهم الكائن في لمحة.

تنسيق الأرقام في toString

قد تُنتج قيم double الخام ناتجًا مزعجًا مثل 1500.0000000001 بسبب دقة الفاصلة العائمة. استخدم String.format() للتحكم في العرض:

@Override public String toString() { return String.format("BankAccount{owner='%s', balance=%.2f}", owner, balance); } // الناتج: BankAccount{owner='Alice', balance=1500.00}

%.2f يُنسّق رقم الفاصلة العائمة بمنزلتين عشريتين تمامًا — مثالي للعملات.

استخدم String.format() لأي شيء يتجاوز الدمج البسيط. يكون أكثر وضوحًا عندما تملك حقولًا عديدة، ويمنحك تحكمًا كاملًا في تنسيقات الأرقام والمحاذاة وأنماط التاريخ. فكّر فيه كقالب حيث %s خانة سلسلة نصية و%d خانة عدد صحيح.

مثال واقعي بحقول متعددة

إليك فئة Product ذات حقول متعددة لتوضيح كيفية توسيع toString():

public class Product { private String name; private String category; private double price; private int stockQuantity; public Product(String name, String category, double price, int stockQuantity) { this.name = name; this.category = category; this.price = price; this.stockQuantity = stockQuantity; } @Override public String toString() { return String.format( "Product{name='%s', category='%s', price=%.2f, stock=%d}", name, category, price, stockQuantity ); } } public class Main { public static void main(String[] args) { Product p = new Product("Wireless Keyboard", "Electronics", 49.99, 200); System.out.println(p); // Product{name='Wireless Keyboard', category='Electronics', price=49.99, stock=200} } }

toString ودمج السلاسل النصية

بما أن Java تستدعي toString() تلقائيًا أثناء الدمج، يمكنك تضمين الكائنات مباشرةً داخل سلاسل نصية أكبر:

BankAccount account = new BankAccount("Alice", 1500.00); String message = "Processing payment for: " + account; System.out.println(message); // Processing payment for: BankAccount{owner='Alice', balance=1500.00}
لا تكشف بيانات حساسة في toString. التابع toString() الذي يتضمن كلمات المرور أو أرقام بطاقات الائتمان أو المفاتيح السرية يُشكّل خطرًا أمنيًا — تلك السلاسل تنتهي في السجلات وتتبّعات المكدس ورسائل الخطأ التي قد تراها أطراف غير مقصودة. أدرج فقط الحقول الآمنة للعرض العلني.

أساليب شائعة لتنسيق toString

لا يوجد تنسيق واحد مطلوب، لكن مجتمع Java يميل إلى أنماط متسقة قليلة:

  • أسلوب الأقواس المتعرجةClassName{field1=value1, field2=value2} (المستخدم في الأمثلة أعلاه، يطابق ما تُولّده بيئات التطوير تلقائيًا)
  • أسلوب الأقواس المربعةClassName[field1=value1, field2=value2]
  • الأسلوب النثري — جملة بلغة طبيعية، مفيدة عندما يُعرض الكائن للمستخدمين النهائيين لا للمطورين

اختر الأسلوب الذي يناسب جمهورك. استخدم الأقواس المتعرجة أو المربعة للمخرجات الموجهة للمطورين، واستخدم الجملة الطبيعية عندما تظهر السلسلة في واجهة مستخدم أو بريد إلكتروني.

الخلاصة

يحوّل التابع toString() أي كائن إلى سلسلة نصية مقروءة للإنسان. النسخة الافتراضية الموروثة من Object تُنتج رمزًا سادس عشريًا لا فائدة منه أثناء التصحيح. بتجاوزها باستخدام @Override public String toString() تحصل على مخرجات ذات معنى في println، ودمج السلاسل، والسجلات، وأدوات التصحيح — وكل هذا مجانًا. أبعد الحقول الحساسة عنه، واستخدم String.format() لتنسيق الأرقام بشكل نظيف، والتزم بالأسلوب الذي تختاره في جميع أنحاء قاعدة الكود.