الدالة printf للطباعة المنسقة
كيف نطبع النصوص والأرقام بالشكل الذي نريده تمامًا؟
إعداد: م. بدر الدين محمد الحليمي · م. محمد زهير كحيل
في نهاية هذه المحاضرة ستكون قادرًا على
- تشرح أجزاء سطر
printf: الأمر والقالب والقيم - تختار رمز التنسيق المناسب لكل قيمة
- تفرّق بين خطأ الترجمة وخطأ وقت التشغيل
- تتحكم في عدد الخانات العشرية والعرض والمحاذاة
- تطبع جدولًا منسقًا، وتبني نصًا منسقًا باستخدام
String.format
محتويات المحاضرة
- تعمل
printfمثل نموذج فيه فراغات - كل سطر
printfيتكوّن من ثلاثة أجزاء - خمسة رموز تنسيق تغطي الأساسيات
- الرمز
%tيحتاج إلى حرف بعده وإلى قيمة تاريخ - كل فراغ يأخذ قيمته بالترتيب
- الرمز الخاطئ لا يوقف المترجم، بل يفشل أثناء التشغيل
- خطأ الترجمة يمنع التشغيل، وخطأ وقت التشغيل يظهر أثناءه
- القيمة الناقصة والرمز غير المناسب يفشلان عند التشغيل أيضًا
- الصيغة
%.2fتحدد عدد الخانات العشرية - العرض يحجز خانات:
%5dيحاذي لليمين و%-10sيحاذي لليسار %05dو%cو%bو%%تكمل صندوق الأدوات- العرض الثابت يطبع جدولًا مستقيم الأعمدة
String.formatتبني النص المنسق ولا تطبعه- الخلاصة:
printf= قالب + رموز تنسيق + قيم - أسئلة المراجعة
تعمل printf مثل نموذج فيه فراغات
الجملة التي فيها فراغات. الجملة ثابتة، وكل %s فراغ ينتظر قيمة.
"Hello %s!"ما يملأ الفراغ.
"World"ما تراه على الشاشة.
Hello World!املأ الفراغ
توقّع الناتج ثم شغّل. غيّر World إلى اسمك وشغّل مرة أخرى.
public class Form {
public static void main(String[] args) {
System.out.printf("Hello %s!%n", "World");
}
}Hello World!
كل سطر printf يتكوّن من ثلاثة أجزاء
| الجزء | في المثال | معناه |
|---|---|---|
| 1. الأمر | System.out.printf | اطبع بالتنسيق الذي أحدده لك. |
| 2. القالب | "Hello %s!%n" | نص ثابت فيه فراغات تُملأ: %s و%n. |
| 3. القيم | "World" | ما يوضع في الفراغات، وتفصل بينها فواصل. |
نسينا %n في السطر الأول. ماذا يطبع البرنامج؟
public class NoNewLine {
public static void main(String[] args) {
System.out.printf("Hello %s!", "Sara");
System.out.printf("Hello %s!%n", "Ali");
}
}أظهر الناتج
Hello Sara!Hello Ali!
لماذا؟ printf تطبع القالب فقط، فإذا لم يكن فيه %n يبقى المؤشر في السطر نفسه. اضغط «تتبّع سطرًا سطرًا» وراقب مكان الناتج بعد كل أمر.
خمسة رموز تنسيق تغطي الأساسيات
| الرمز | القيمة | مثال | الناتج | للتذكّر |
|---|---|---|---|---|
%s | نص (String) | ("%s", "Ali") | Ali | s = string: النص كما هو |
%d | عدد صحيح | ("%d", 25) | 25 | d = decimal: بدون فاصلة عشرية |
%f | عدد عشري | ("%f", 3.14) | 3.140000 | f = float: 6 خانات عشرية |
%t | التاريخ والوقت | ("%tY", d) | 2026 | t = time: وY تعني السنة |
%n | سطر جديد | ("A%nB") | A, B | n = new line: مثل الضغط على Enter |
جرّب الرموز
توقّع كل سطر. ثم غيّر القيم، وأضف %n داخل أحد النصوص لتقسيمه إلى سطرين.
public class Specifiers {
public static void main(String[] args) {
System.out.printf("%s%n", "Ali");
System.out.printf("%d%n", 25);
System.out.printf("%f%n", 3.14);
System.out.printf("A%nB%n");
}
}Ali 25 3.140000 A B
ماذا يعني الحرف d في %d؟
%f، والتاريخ له %t.الرمز %t يحتاج إلى حرف بعده وإلى قيمة تاريخ
%t وحده لا يكفي: الحرف بعده يحدد الجزء المطلوب من التاريخ، وY تعني السنة. والقيمة نفسها يجب أن تكون تاريخًا، وليست رقمًا عاديًا.
اطبع السنة
نستخدم تاريخًا ثابتًا ليبقى الناتج ثابتًا. في GregorianCalendar يبدأ عدّ الشهور من 0، فالرقم 0 يعني يناير. غيّر السنة إلى 2030 وشغّل.
public class YearOnly {
public static void main(String[] args) {
System.out.printf("%tY%n", new java.util.GregorianCalendar(2026, 0, 1));
}
}2026
كل فراغ يأخذ قيمته بالترتيب
فراغان وقيمتان
الترتيب مهم: الفراغ الأول يأخذ القيمة الأولى، والثاني يأخذ الثانية. النوع مهم: فراغ الرقم يحتاج رقمًا، وفراغ النص يحتاج نصًا.
public class NameAge {
public static void main(String[] args) {
System.out.printf("Name: %s, Age: %d%n", "Ali", 19);
}
}Name: Ali, Age: 19
تحقّق سريع: ماذا سيُطبع؟
public class Blanks {
public static void main(String[] args) {
System.out.printf("%d + %d = %d", 2, 3, 5);
}
}أظهر الناتج
2 + 3 = 5
لماذا؟ كل فراغ يأخذ القيمة التالية بالترتيب: الأول يأخذ 2، والثاني 3، والثالث 5.
تحقّق سريع: أي رمز تنسيق يناسب الفراغ لطباعة السعر؟
System.out.printf("Price: ___", 4.5);
%f، والناتج Price: 4.500000.الرمز الخاطئ لا يوقف المترجم، بل يفشل أثناء التشغيل
تحقّق سريع: اكتشف الخطأ. ماذا يحدث عند تنفيذ هذا السطر؟
System.out.printf("Age: %d", "19");
"19" بين علامتي تنصيص نص (String)، و%d يحتاج عددًا صحيحًا. والمترجم لا يقرأ ما داخل القالب، فلا يكتشف ذلك إلا JVM عند الوصول إلى الفراغ.شاهد الفشل بنفسك
شغّل البرنامج: يظهر Age: أولًا، ثم رسالة java.util.IllegalFormatConversionException: d != java.lang.String. ثم اضغط «تتبّع سطرًا سطرًا»: تنجح الترجمة بلا مشكلة، ثم يحدث الفشل عند هذا السطر أثناء التنفيذ.
public class AgeError {
public static void main(String[] args) {
System.out.printf("Age: %d", "19");
System.out.println("This line never runs");
}
}Age:
| في الرسالة | معناها |
|---|---|
IllegalFormatConversionException | استثناء: القيمة لا تناسب رمز التنسيق. |
d | رمز التنسيق: %d. |
!= | لا يناسب. |
java.lang.String | نوع القيمة التي أعطيناها: نص. |
خطأ الترجمة يمنع التشغيل، وخطأ وقت التشغيل يظهر أثناءه
| وجه المقارنة | خطأ الترجمة | خطأ وقت التشغيل |
|---|---|---|
| من يكتشفه؟ | المترجم javac | JVM أثناء التنفيذ |
| هل يعمل البرنامج؟ | لا، لا يُنتج ملف .class | نعم، حتى يصل إلى السطر الخاطئ |
| هل يظهر ناتج؟ | لا شيء | ما طُبع قبل الخطأ |
| مثال | int age = "19"; | printf("Age: %d", "19"); |
الخطأ نفسه، ومصيران مختلفان. في int age = "19"; يعرف المترجم نوع المتغير، فيرفض السطر. أما قالب printf فهو عند المترجم مجرد نص، وأي قيمة مقبولة بعده.
هذا خطأ ترجمة: البرنامج لا يعمل إطلاقًا
لاحظ أن Age: لا تظهر هنا إطلاقًا. احذف علامتي التنصيص.
public class AgeCompile {
public static void main(String[] args) {
int age = "19";
System.out.printf("Age: %d%n", age);
}
}incompatible types: String cannot be converted to intأظهر التصحيح
public class AgeCompile {
public static void main(String[] args) {
int age = 19;
System.out.printf("Age: %d%n", age);
}
}Age: 19
لماذا؟ المترجم يعرف أن age من نوع int، فيتوقف قبل التشغيل، ولذلك لا يُطبع شيء.
وهذا خطأ ترجمة أيضًا: الفاصلة الناقصة
المترجم لا يقرأ ما داخل القالب، لكنه يفحص شكل السطر نفسه. أضف الفاصلة بين القالب والقيمة.
public class AgeComma {
public static void main(String[] args) {
System.out.printf("Age: %d%n" 19);
}
}')' expectedأظهر التصحيح
public class AgeComma {
public static void main(String[] args) {
System.out.printf("Age: %d%n", 19);
}
}Age: 19
لماذا؟ تبدو الرسالة بعيدة عن السبب: يطلب المترجم ) لأنه بعد القالب ينتظر فاصلة أو قوس إغلاق، فوجد الرقم 19. والسبب الحقيقي هو الفاصلة الناقصة.
أصلح خطأ وقت التشغيل
هذا البرنامج يُترجم بنجاح لكنه يفشل عند التشغيل. أصلحه ليطبع Age: 19. للتصحيح طريقتان: احذف علامتي التنصيص حول القيمة، أو استخدم %s.
public class AgeFix {
public static void main(String[] args) {
System.out.printf("Age: %d%n", "19");
}
}Age: 19
تلميح 1
"19" نص، و19 عدد.
الحل
public class AgeFix {
public static void main(String[] args) {
System.out.printf("Age: %d%n", 19);
}
}القيمة الناقصة والرمز غير المناسب يفشلان عند التشغيل أيضًا
| السطر | الرسالة بعد Exception in thread "main" | السبب |
|---|---|---|
printf("%d", 4.5); | IllegalFormatConversionException: d != java.lang.Double | %d لا يقبل عددًا عشريًا |
printf("%f", 5); | IllegalFormatConversionException: f != java.lang.Integer | %f لا يقبل 5؛ اكتب 5.0 |
printf("%s %d%n", "Ali"); | MissingFormatArgumentException: Format specifier '%d' | فراغان وقيمة واحدة |
printf("%t", date); | UnknownFormatConversionException: Conversion = 't' | %t يحتاج إلى حرف بعده، مثل %tY |
بدّلنا القيمتين. ماذا يظهر على الناتج قبل الخطأ؟
public class Swapped {
public static void main(String[] args) {
System.out.printf("Name: %s, Age: %d%n", 19, "Ali");
}
}أظهر الناتج
Name: 19, Age:
لماذا؟ %s يقبل أي قيمة فيطبع 19، ثم يصل %d إلى النص "Ali" فيتوقف البرنامج. لهذا قد يبدو أول الناتج صحيحًا والخطأ في مكان آخر.
قيمة ناقصة
شغّل البرنامج واقرأ الرسالة، ثم أضف القيمة 19 بعد "Ali".
public class Missing {
public static void main(String[] args) {
System.out.printf("%s %d%n", "Ali");
}
}Ali
الصيغة %.2f تحدد عدد الخانات العشرية
%f يطبع 6 خانات عشرية دائمًا، وهذا نادرًا ما نريده. الرقم بعد النقطة يحدد عدد الخانات، وتُقرَّب القيمة في الطباعة فقط؛ أما المتغير نفسه فلا يتغير.
غيّر عدد الخانات
غيّر %.2f إلى %.3f ثم إلى %.0f، وتوقّع الناتج في كل مرة.
public class Precision {
public static void main(String[] args) {
double pi = 3.14159;
System.out.printf("%f%n", pi);
System.out.printf("%.2f%n", pi);
System.out.println(pi);
}
}3.141590 3.14 3.14159
هل تقطع %.1f الخانات أم تقرّبها؟
public class Rounding {
public static void main(String[] args) {
System.out.printf("%.1f%n", 2.75);
}
}أظهر الناتج
2.8
لماذا؟ تقرّب %.1f إلى خانة واحدة ولا تقطع، فتصبح 2.75 هي 2.8.
إجمالي الفاتورة
السعر 12.5 والكمية 3. احسب الإجمالي في متغير من نوع double، واطبعه بخانتين عشريتين كما في الناتج المتوقع.
public class Receipt {
public static void main(String[] args) {
double price = 12.5;
int quantity = 3;
// Calculate the total and print it with printf
}
}Total: 37.50
تلميح 1
القالب هو "Total: %.2f%n".
الحل
public class Receipt {
public static void main(String[] args) {
double price = 12.5;
int quantity = 3;
double total = price * quantity;
System.out.printf("Total: %.2f%n", total);
}
}العرض يحجز خانات: %5d يحاذي لليمين و%-10s يحاذي لليسار
| الصيغة | معناها | القيمة | الناتج بين | |
|---|---|---|---|
%5d | 5 خانات، ومحاذاة لليمين | 42 | | 42| |
%-5d | 5 خانات، ومحاذاة لليسار | 42 | |42 | |
%-10s | 10 خانات، ومحاذاة لليسار | "Ali" | |Ali | |
%8.2f | 8 خانات، منها خانتان عشريتان | 3.14159 | | 3.14| |
الخطوط العمودية تكشف المسافات
وضعنا | حول كل قيمة لترى المسافات. احذف علامة - من %-10s: إلى أين انتقل الاسم؟
public class Width {
public static void main(String[] args) {
System.out.printf("|%5d|%n", 42);
System.out.printf("|%-10s|%n", "Ali");
System.out.printf("|%8.2f|%n", 3.14159);
}
}| 42| |Ali | | 3.14|
الاسم أطول من العرض المحدد. ماذا يحدث؟
public class TooLong {
public static void main(String[] args) {
System.out.printf("|%3s|%n", "Mohammed");
}
}أظهر الناتج
|Mohammed|
لماذا؟ العرض حد أدنى وليس حدًا أقصى: لا تقطع Java القيمة أبدًا، بل تأخذ ما تحتاج إليه من خانات.
%05d و%c و%b و%% تكمل صندوق الأدوات
| الصيغة | القيمة | الناتج | الاستخدام |
|---|---|---|---|
%05d | 42 | 00042 | يملأ الخانات الفارغة بأصفار، مثل رقم الطالب |
%c | 'A' | A | حرف واحد من نوع char |
%b | true | true | قيمة من نوع boolean |
%% | لا تحتاج إلى قيمة | % | علامة النسبة نفسها |
الرموز الأربعة معًا
غيّر %05d إلى %07d، وغيّر passed إلى false، ثم شغّل. لماذا لا يوجد مقابل لـ %% بين القيم؟
public class Toolbox {
public static void main(String[] args) {
int id = 42;
char grade = 'A';
boolean passed = true;
System.out.printf("ID: %05d%n", id);
System.out.printf("Grade: %c%n", grade);
System.out.printf("Passed: %b%n", passed);
System.out.printf("Score: %d%%%n", 95);
}
}ID: 00042 Grade: A Passed: true Score: 95%
كم علامة % تظهر في الناتج؟
public class Percent {
public static void main(String[] args) {
System.out.printf("Discount: 50%%%n");
}
}أظهر الناتج
Discount: 50%
لماذا؟ %% تطبع علامة واحدة، و%n سطر جديد. أما % وحدها في نهاية القالب فتفشل عند التشغيل برسالة UnknownFormatConversionException.
العرض الثابت يطبع جدولًا مستقيم الأعمدة
جدول الطلاب
السطر الأول رأس الجدول، وعرضه يساوي عرض الأسطر تحته. اضغط «تتبّع سطرًا سطرًا» وراقب الجدول يُبنى سطرًا بعد سطر. ثم أضف طالبًا رابعًا.
public class StudentTable {
public static void main(String[] args) {
System.out.printf("%-10s%5s%8s%n", "Name", "Age", "GPA");
System.out.printf("%-10s%5d%8.2f%n", "Sara", 19, 3.75);
System.out.printf("%-10s%5d%8.2f%n", "Ahmed", 20, 3.2);
System.out.printf("%-10s%5d%8.2f%n", "Mohammed", 21, 2.5);
}
}Name Age GPA Sara 19 3.75 Ahmed 20 3.20 Mohammed 21 2.50
جدول الأسعار
اطبع الجدول المتوقع: اسم المادة بعرض 8 إلى اليسار، والسعر بعرض 6 وخانتين عشريتين. الكتاب Book بسعر 12.5، والقلم Pen بسعر 1.75.
public class PriceTable {
public static void main(String[] args) {
// Print the header, then one line for each item
}
}Item Price Book 12.50 Pen 1.75
تلميح 1
قالب الرأس "%-8s%6s%n".
تلميح 2
قالب الصفوف "%-8s%6.2f%n".
الحل
public class PriceTable {
public static void main(String[] args) {
System.out.printf("%-8s%6s%n", "Item", "Price");
System.out.printf("%-8s%6.2f%n", "Book", 12.5);
System.out.printf("%-8s%6.2f%n", "Pen", 1.75);
}
}String.format تبني النص المنسق ولا تطبعه
تعمل String.format بالقالب والقيم نفسها، لكنها تُرجع النص بدل طباعته. فنحفظه في متغير من نوع String ونستخدمه لاحقًا.
احفظ النص ثم اطبعه
اضغط «تتبّع سطرًا سطرًا». لا يظهر شيء بعد السطر الأول، بل يظهر صندوق line وفيه النص الجاهز، فنطبعه مرتين دون إعادة التنسيق.
public class FormatText {
public static void main(String[] args) {
String line = String.format("%s: %.2f", "GPA", 3.756);
System.out.println(line);
System.out.println(line);
}
}GPA: 3.76 GPA: 3.76
لاحظ أن نتيجة String.format لا تُحفظ ولا تُطبع. ماذا يطبع البرنامج؟
public class FormatOnly {
public static void main(String[] args) {
String.format("%05d", 42);
System.out.println("Done");
}
}أظهر الناتج
Done
لماذا؟ String.format تُرجع النص فقط. فإذا لم نستخدم ما أرجعته، ضاع. الطباعة عمل printf وprintln.
رمز الطالب
استخدم String.format لبناء الرمز من الرقم 7 بعرض 4 خانات وأصفار بادئة، واحفظه في المتغير code. ثم اطبع Code: متبوعًا بقيمته باستخدام println.
public class StudentCode {
public static void main(String[] args) {
// Build the code with String.format, then print it
}
}Code: 0007
تلميح 1
%04d تعني 4 خانات مع أصفار بادئة.
الحل
public class StudentCode {
public static void main(String[] args) {
String code = String.format("%04d", 7);
System.out.println("Code: " + code);
}
}الخلاصة: printf = قالب + رموز تنسيق + قيم
"Hi %s%n"%s %d %f %t %n"Sara"Hi Saraدورك الآن
اطبع الاسم Sara والعمر 19 في سطر واحد كما في الناتج المتوقع، ثم انتقل إلى سطر جديد. جرّب أولًا، ثم استخدم التلميح. وبعد نجاحك، جرّبه باسمك وعمرك.
public class AboutMe {
public static void main(String[] args) {
// Print the sentence with printf
}
}My name is Sara and I am 19.
تلميح 1
القالب هو "My name is %s and I am %d.%n".
الحل
public class AboutMe {
public static void main(String[] args) {
System.out.printf("My name is %s and I am %d.%n", "Sara", 19);
}
}أسئلة المراجعة
ماذا يحدث عند تنفيذ System.out.printf("%f", 5);؟
5 عدد صحيح، و%f يحتاج عددًا عشريًا. ولا تحوّل printf القيمة وحدها، والمترجم لا يقرأ القالب. والحل كتابة 5.0.أي صيغة تطبع الاسم إلى اليسار في عرض 12 خانة؟
- تعني المحاذاة لليسار، وبدونها تكون المحاذاة لليمين. والصفر البادئ للأعداد فقط.اجمع ما تعلمته. ماذا يطبع البرنامج؟
public class ReviewMix {
public static void main(String[] args) {
System.out.printf("%-6s|%03d|%.1f%%%n", "Sara", 7, 91.25);
}
}أظهر الناتج
Sara |007|91.3%
لماذا؟ %-6s تحجز 6 خانات لليسار، و%03d تكمل بالأصفار، و%.1f تقرّب إلى خانة واحدة، و%% تطبع %.
ما الفرق بين printf وString.format؟
String.format تعطيك النص لتحفظه أو تستخدمه لاحقًا.