انتقل إلى المحتوى
أمين أبوزيد
منشورBEG-011-L04

تنسيق المخرجات

  • الدرس 4 من 6
  • 11 دقيقة قراءة
  • مبتدئ
في هذا الدرس 11 موضوعات

هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.

Lesson Overview

You can now read numbers. The next problem is showing them. Try printing a price with System.out.println(1.5 * 2) and you get 3.0, not 3.00. Print three products one under the other and the prices wobble left and right, because the names have different lengths. Print 4.25 * 3 and you may get a long tail of digits. Your program is correct, but its output looks careless.

Java has a tool for exactly this: format strings. You write a template that says "a word padded to 12 characters, then a decimal with two digits after the point", and give the values separately. The method printf fills the template. The result is aligned columns, fixed decimals, and tidy numbers.

Back at the shop counter: the receipt is the product. A customer reads it, and the columns should line up.

Why this matters: formatted output is how backend tools print reports, logs, and invoices. The same ideas return in logging libraries, which use templates with placeholders. Learning one small template language well pays off for years.

This lesson builds on BEG-008 (printing), BEG-009 (types), and Lessons 1 to 3 of this module.

Learning Objectives

By the end of this lesson you will be able to:

  • Print with System.out.printf using the conversions %d, %s, %f, and %n.
  • Control decimals with %.2f, and width with %8.2f.
  • Left-align text with %-12s and right-align with %12s.
  • Build the same text as a String with String.format.
  • Print a literal percent sign with %%.
  • Predict what happens when the value does not fit the width, or has the wrong type for the conversion.

Written Explanation

The shape of printf

java
System.out.printf("%s costs %.2f%n", "Pen", 1.5);

The first argument is the format string. Everything in it is printed as written, except format specifiers, which start with %. Each specifier is a hole that takes the next value from the argument list, in order. Output:

text
Pen costs 1.50

printf does not add a line break by itself, so we end with %n.

The four specifiers you need

SpecifierPrintsExample valueOutput
%da whole number (int, long, ...)33
%fa decimal (double)3.141593.141590
%sany value as text"Pen"Pen
%na line break (takes no value)(new line)

%n is the right way to end a line in a format string. It produces the line separator of the current platform. The Javadoc for Formatter (the class behind printf) says it gives the value of System.lineSeparator().

By default %f prints six digits after the point, which is rarely what you want for money. That is where precision comes in.

Precision: %.2f

Write a dot and a number between % and f, and that is how many digits come after the decimal point:

java
System.out.printf("%.2f%n", 3.14159);   // 3.14
System.out.printf("%.2f%n", 3.0);       // 3.00

Two decimals always print, even 3.00. The number is rounded, not cut off. The Javadoc says rounding uses the HALF_UP mode: a value halfway between two candidates goes up. In compiled tests, %.2f of 0.125 prints 0.13, %.1f of 2.25 prints 2.3, and %.0f of 2.5 prints 3.

One warning from the same tests: %.2f of 1.005 printed 1.01 and 2.675 printed 2.68. That looks right, but do not read it as "decimal arithmetic is exact". The Javadoc explains that the rounding is applied to the decimal text of the value as Double.toString would show it, not to the full binary value. The underlying double can still be slightly off, which is why money needs better tools later. Formatting is a way to display a value, not a way to fix an inexact one.

Width: %8.2f

A number before the dot is the minimum width: how many characters the field takes. Shorter values are padded with spaces on the left, so numbers line up on the right:

java
System.out.printf("[%8.2f]%n", 3.14159);   // [    3.14]
System.out.printf("[%5d]%n", 42);          // [   42]

The brackets show the padding. %8.2f reads as: width 8, two decimals. The decimal point and the digits all count towards the width.

If the value is longer than the width, the field simply grows. The width is a minimum, never a limit, and that can break alignment, so choose a width a little bigger than your longest value.

Alignment of text: %-12s and %12s

For text, the default is to pad on the left, so the text sits at the right of its field. A minus sign flag - reverses that:

java
System.out.printf("[%-8s]%n", "Pen");   // [Pen     ]
System.out.printf("[%8s]%n", "Pen");    // [     Pen]

The convention for tables is text left, numbers right: names with %-12s, numbers with %8.2f. It is how a column of prices lines up on the decimal point. The - flag requires a width, and Java reports an error if you use it without one.

Other handy flags

Two more flags, from the Javadoc, that you will use occasionally:

java
System.out.printf("%,.2f%n", 1234567.891);   // 1,234,567.89
System.out.printf("%05d%n", 42);             // 00042

The comma flag groups thousands, using the separators of the current locale; 0 pads with zeros instead of spaces. To print an actual percent sign, double it: %%.

java
System.out.printf("%.1f%%%n", 15.0);          // 15.0%

String.format: the same, but as a String

String.format takes the same arguments as printf, but returns the text instead of printing it:

java
String row = String.format("%-6s%6.2f", "Tea", 2.0);   // "Tea      2.00"

Use it when you need to store, combine, or print the text later. System.out.printf(...) is effectively the same as printing the result of String.format(...). In the receipt (Lesson 6) you will format each line as you read the item and print everything at the end.

Errors to expect

A format string is checked when the program runs, not when it compiles. The compiler will not warn you about a mismatch:

  • %d with a decimal value, such as printf("%d", 3.5), throws IllegalFormatConversionException (real message: d != java.lang.Double).
  • A specifier with no matching value throws MissingFormatArgumentException.
  • A precision with %d (such as %.2d) throws IllegalFormatPrecisionException.

These stop the program, so format strings need testing with real values. The fix is almost always to match the specifier to the type: %d for whole numbers, %f for decimals, and %s for anything else.

Locale

Formatting uses the computer's locale by default. On a machine set up for French, %.2f may print a comma, 3,14, and the thousands separator of %,.2f changes too. For a receipt that must look the same everywhere, you can pass a locale explicitly (String.format(Locale.US, ...), which needs import java.util.Locale;). For learning, the default is fine; if your decimals look different from this lesson's, that is the reason.

Examples

Example 1: a few specifiers (simple)

java
public class Fmt {
    public static void main(String[] args) {
        System.out.printf("%d items%n", 3);
        System.out.printf("%s costs %.2f%n", "Pen", 1.5);
        System.out.printf("[%8.2f]%n", 3.14159);
        System.out.printf("[%-8s]%n", "Pen");
        System.out.printf("[%8s]%n", "Pen");
        System.out.printf("[%f]%n", Math.PI);
    }
}

Real output:

text
3 items
Pen costs 1.50
[    3.14]
[Pen     ]
[     Pen]
[3.141593]

The last line shows the default of six decimals.

Example 2: aligned columns (practical)

java
public class Table {
    public static void main(String[] args) {
        System.out.printf("%-12s %8s%n", "Product", "Price");
        System.out.printf("%-12s %8.2f%n", "Pen", 1.5);
        System.out.printf("%-12s %8.2f%n", "Notebook", 4.25);
        System.out.printf("%-12s %8.2f%n", "Backpack", 29.9);
    }
}
text
Product         Price
Pen              1.50
Notebook         4.25
Backpack        29.90

Note that the header uses the same widths, with %8s for the word "Price" (text, right-aligned over the numbers). Every row uses the same format string, which is the secret of alignment: one format, many rows. A cleaner version keeps the format in a variable so it cannot drift:

java
String row = "%-12s %8.2f%n";
System.out.printf(row, "Pen", 1.5);
System.out.printf(row, "Notebook", 4.25);

Example 3: a failure and a width that is too small (realistic)

First, the mismatch. This compiles, then stops while running:

java
System.out.printf("%d%n", 3.5);
text
Exception in thread "main" java.util.IllegalFormatConversionException: d != java.lang.Double

Second, the width problem. %6s with a long name:

java
System.out.printf("[%6s]%n", "Notebook-A4");
text
[Notebook-A4]

The text is longer than 6, so the field grew and the rest of the row would shift right. In a real table you either choose a wider column, or limit the text. For %s the Javadoc says a precision limits the number of characters, so %-8.8s cuts text longer than 8. Choose between widening and cutting based on whether losing characters is acceptable: for item names on a receipt, it often is not.

Backend Insight

Backend programs print structured text constantly: log lines, CSV exports, reports, and error messages. A log line such as User 42 paid 19.99 is built with a template and values, not by gluing strings with +. Logging libraries use the same idea with placeholders, which you will meet later.

Two cautions carry over:

  • Formatting is presentation. Keep the real value in a variable and format only when you print or store text. Rounding the number itself loses information.
  • Locale matters in services. A server in a country that uses a comma decimal separator can print 3,14 where another program expects 3.14. Machine-readable output, such as data that another program will parse, should use a fixed format, not the default locale.

Common Mistakes

Mistake 1: Forgetting %n

printf("Total: %.2f", total) leaves the cursor at the end of the line, so the next output joins it. End each line with %n.

Mistake 2: Wrong specifier for the type

  • Incorrect: printf("%d", price) where price is a double.
  • Correct: printf("%.2f", price).

The error appears only when the line runs, not when you compile.

Mistake 3: Left-aligning numbers

Numbers are easier to compare when right-aligned. Use %8.2f, not %-8.2f, for money columns.

Mistake 4: A different width in the header and in the rows

If the header says %-10s and the rows say %-12s, the columns drift. Use identical widths.

Mistake 5: Using %f with no precision for money

%f prints six decimals (1.500000). Use %.2f.

Exercises

Try each exercise yourself before opening the solutions.

Level A: Foundation

A1. What does System.out.printf("%.1f%n", 2.0); print?

أظهر الحل A1

A1. 2.0, followed by a line break.

A2. What is the difference between %8s and %-8s?

أظهر الحل A2

A2. %8s pads on the left, so the text sits at the right of an 8-character field. %-8s pads on the right, so the text sits at the left.

A3. What is wrong with System.out.printf("%d%n", 1.5);? Is the error found by the compiler or when the program runs?

أظهر الحل A3

A3. %d expects a whole number, but 1.5 is a double, so the program throws IllegalFormatConversionException. It is found when the program runs, not by the compiler.

Level B: Application

B1. Print the three items Tea 2.0, Coffee 3.5, and Juice 12.75 in two columns: names left-aligned in 10 characters, prices right-aligned in 7 with two decimals.

أظهر الحل B1

B1.

java
System.out.printf("%-10s %7.2f%n", "Tea", 2.0);
System.out.printf("%-10s %7.2f%n", "Coffee", 3.5);
System.out.printf("%-10s %7.2f%n", "Juice", 12.75);

B2. Print the number 0.075 as a percentage with one decimal. (Hint: multiply by 100 and use %%.)

أظهر الحل B2

B2. System.out.printf("%.1f%%%n", 0.075 * 100); prints 7.5%. The 0.075 * 100 may not be exactly 7.5 in binary arithmetic, but one decimal place hides that. (Not run separately; the pattern matches the compiled %.1f%% example.)

B3. Use String.format to build the text Total: 8.34 (the word, then the number right-aligned in a field so the whole line is 14 characters wide) and print it with println.

أظهر الحل B3

B3. For example String s = String.format("%-6s%8.2f", "Total:", 8.34); System.out.println(s); gives a line of 14 characters: Total: followed by 8.34. Check the length with s.length().

Level C: Challenge

C1. A column is formatted with %-8s. The item names are Pen, Notebook, and Calculator. What happens to the alignment of the row for Calculator, and what are two ways to fix it?

أظهر الحل C1

C1. Calculator has 10 characters, more than the width 8, so the field grows and everything after it shifts right in that row. Fixes: widen the column (%-10s or more), or limit the text with a precision (%-8.8s), which cuts it to Calculat.

C2. Print a three-row receipt snippet (name, quantity, line total) where the quantity column is 3 wide, the totals are 9 wide with two decimals, and a totals row below lines up under the totals column. Use one format string for the three item rows.

أظهر الحل C2

C2.

java
String row = "%-8s %3d %9.2f%n";
System.out.printf(row, "Pen", 2, 3.0);
System.out.printf(row, "Notebook", 1, 4.25);
System.out.printf(row, "Tea", 10, 20.0);
System.out.printf("%-12s %9.2f%n", "Total", 27.25);

The totals column starts at the same position in each row: 8 + 1 + 3 + 1 = 13 characters in, where it is 9 wide. In the last row, "Total" in %-12s plus one space takes 13 characters as well, so the number lines up. (Not run separately; widths computed by hand from the specifiers.)

Mini Challenge

Write Price list. Print a heading and five products of your choice with prices, aligned in three columns: a code (%-6s), a name (%-14s), and a price (%8.2f). Make one of the prices a whole number such as 5, as a double (5.0), to see that two decimals still print. Make one name longer than 14 characters and watch the alignment break, then fix it one of the two ways from C1.

Check your work: every row uses the same format string, prices line up on the decimal point, and the fixed version aligns for the long name.

Summary & Next Lesson

Key takeaways

  • printf(format, values...) prints, String.format(...) returns the same text as a String.
  • %d whole number, %f decimal, %s text, %n line break, %% a percent sign.
  • %.2f gives two decimals (rounded half up). %8.2f is at least 8 wide, right-aligned. %-12s left-aligns text in 12.
  • Width is a minimum: a longer value widens the field and breaks alignment.
  • Mismatched specifier and value type fail at run time, not compile time.

Next lesson

Next is "Validating input: never trust the user". You have the tools to read, convert, and print. Now you will make the program ask again when the user types something unusable.

Self-check

  1. What does %.2f do?
  2. What does %-10s do?
  3. What does %n do, and why use it instead of \n here?
  4. What happens at run time with printf("%d", 2.5)?
  5. How does String.format differ from printf?
أظهر الإجابات
  1. It prints a decimal with two digits after the point, rounding as needed.
  2. It prints text left-aligned in a field at least 10 characters wide.
  3. It ends the line with the platform's line separator. It gives the right line ending for whichever operating system runs the program.
  4. It throws IllegalFormatConversionException, because %d expects a whole number.
  5. It returns the formatted text as a String instead of printing it.

References

  • Oracle, Java SE 21 API, Formatter. Supports the specifier syntax, %d, %s, %f (default precision 6, HALF_UP rounding), %n (the platform line separator), width and the - flag, the , flag, precision on %s, locale effects, and the exceptions named above.
  • All code and output shown in this lesson were compiled and run with JDK 21 (two solutions marked "not run separately" were checked by hand). The claim that System.out.printf behaves like printing String.format output comes from the PrintStream.printf documentation, which I did not open; it is hedged in the text with "effectively". The statement that logging libraries use placeholders is general knowledge, not checked here.

التطبيق

طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.

المختبر والتمارين الاختبار

تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.

اكتب للبحث في المستويات والمراحل والوحدات والدروس.