Formator


1. Formatowanie liczb i dat



Klasa java.utill.Formatter zapewnia możliwości formatowania danych.

Tworząc formator (za pomocą wywołania konstruktora) możemy określić:

 
Uwaga: formatory dla plików powinny być po użyciu zamykane lub wymiatane (close(), flush()), co powoduje zamknięcie lub wymiecenie buforów tych destynacji.

Formatowanie polega na wywołaniu jednej z dwóch wersji metody format (na rzecz fornatora):
        
 Formatter format(String format, Object... arg)
 Formatter format(Locale l, String format, Object... arg) 

Druga z tych metod pozwala na podanie lokalizacji, m.in. wpływającej na sposób formatowania liczb.

Łańcuch formatu (zmienna format) zawiera dowolne ciągi znaków oraz specjalne elementy formatujące. Dalej następują dane do "wstawienia" w łańcuch formatu w miejsce elementów formatu i do sformatowania podług zasad określanych przez te elementy (zmienna liczba argumentów dowolnego typu - formalnie Object). Dzięki autoboxingowi nie ma problemu z formatowaniem danych typów prostych.

Dla uproszczenia dostępne są:
Elementy formatu mają następującą ogólną postać:

%[arg_ind$][flagi][szerokość_pola][.precyzja]konwersja

gdzie:
Uwaga: nawiasy kwadratowe oznaczają opcjonalność.

Symboli konwersji jest b. dużo, dla różnych symboli mogą być stosowane też dodatkowe flagi.
Wszystko to jest opisane w sposób systematyczny w dokumentacji (proszę sięgać).
Tutaj przedstawione zostaną wybrane konwersje i flagi.

Wybrane konwersje - skrót
KonwersjaMoże być stosowana wobecWynik
s lub Sdowolnych danychJeżeli argument jest null - napis "null":
w przeciwym razie
   jeżeli klasa arg na to zezwala
     - wynik wywołania arg.formatTo(...)
   w przeciwnym razie wynik wywołania arg.toString()
Uwaga: użycie jako symbolu konwersji dużego S spowoduje zamianę liter napisu na duże.
c lub Ctypów reprezentujących znaki Unicodeznak Unicode
dtypów reprezentujących liczby całkowiteliczba całkowita (dziesiętna)
ffloat,  double, Float, Double, BigDecimalliczba rzeczywista z separatorem miejsc dzisiętnych
tHdanych reprezentujących czas, czyli:
long, Long, Calendar, Date
godzina na zegarze 24-godzinnym-2 cyfry (00-23)
tMminuty - 2 cyfry (00 - 59)
tSsekundy  - 2 cyfry (00-60)
tYrok - 4 cyfry (np. 2008)
tmmiesiąc - 2 cyfry (01-12)
tddzień miesiąca - 2 cyfry (01 -31)
tRczas na zegarze 24 godzinnym sformatowany jako  "%tH:%tM"
tTczas na zegarze 24 godzinnym sformatowany jako  "%tH:%tM:%tS"
tFdata sformatowana jako "%tY-%tm-%td"

Wśród flag na szczególną uwagę zasługują:
'-' - wynik wyrównany w polu do lewej (domyslnie jest wyrównany do prawej),
'+' - wynik zawiera zawsze znak (dla typów liczbowych),
' ' - wynik zawiera wiodąca spację dla argumentów nieujmenych (tylko dla typów liczbowych).

Zatem, aby uzyskać sformatowane wyniki w poprzednim przykładowym programie (liczbę z dwoma miejscami dziesiętnymi, datę w postaci rok-miesiąc-dzień) możemy napisać:
import java.util.*;

public class Format1 {

  public static void main(String[] args) {
    double cena = 1.52;
    double ilosc = 3;
    double koszt = cena * ilosc;
    System.out.printf("Koszt wynosi %.2f zł", koszt);
    System.out.printf("\nData: %tF", Calendar.getInstance());
  }

}
Wynik:
Koszt wynosi 4,56 zł
Data: 2008-08-14

Warto tu zwrócić uwagę na to, że dla lokalizacji polskiej liczba pokazywana jest z przecinkiem jako separatorem miejsc dziesiętnych.
Aby uzyskac kropkę można napisać:
System.out.printf(Locale.ROOT, "Koszt wynosi %.2f zł", koszt);
W tym przypadku stała statyczna Locale.ROOT oznacza neutralną lokalizację (bez wybranego kraju i języka).

Kilka innych przykładów pokazuje program na wydruku.
import javax.swing.*;

public class Format2 {

  public static void main(String[] args) {
    System.out.println("Wyrównany wydruk tablicy (po 2 elementy w wierszu)");
    int[] arr = { 1, 100, 200, 4000 };
    int k = 1;
    for (int i : arr) {
      System.out.printf("%5d", i);
      if (k%2 ==0) System.out.println();
      k++;
    }
    // Zastosowanie znaku < (element formatu stosowany wobec argumentu z poprzedniego formatowania)
    System.out.println("Zaokraglenia");
    System.out.printf("%.3f %<.2f %<.1f", 1.256 );
    
    // Znak < szczególnie przydatny w datach/czasie
    Calendar c = Calendar.getInstance();
    c.set(Calendar.MONTH, 1);
    System.out.printf("\nW roku %tY i miesiącu %<tm mamy %d dni", c, c.getActualMaximum(Calendar.DATE) );
    
    // Oczywiście możemy formatować do Stringów
    String dateNow = String.format("%td-%<tm-%<tY", System.currentTimeMillis());
    JOptionPane.showMessageDialog(null, dateNow);
  }

}
Wynik działania programu (konsolę i okno dialogowe) pokazuje rysunek.
r



2. Interfejs Formattable

Dla symbolu konwersji s lub S jeżeli klasa drugiego argumentu metody format na to pozwala, to do formatowania zostanie użyta metoda formatTo.
Jest to możliwe tylko wtedy gdy klasa argumentu "do sformatowania" implementuje interfejs Formattable i definiuje jego jedyną metodę:

public void formatTo(Formatter formatter, int flags, int width, int precision)

Jeśli teraz za pomocą jakiegoś formatora następuje formatowanie obiektu z użyciem symbolu konwersji s lub S, to:
  1. wywoływana jest metoda formatTo z klasy obiektu,
  2. metodzie formatTo jest przekazywany formator (jako parametr formatter), flagi formatowania (parametr flags), szerokość pola (parametr width) i precyzja (parametr precision),
  3. w metodzie formatTo możemy sprawdzić wartości przekazanych informacji (np. locale formatora, flagi, szerokość precyzje) i na tej podstawie podjąć odpowiednie działania przetwarzające obiekt do wynikowego napisu,
  4. przed zwróceniem sterowania z metody formatTo wywołujemy metodę format przekazanego formatora,
  5. sformatowany napis będzie przez formator zapisany do odpowiedniej destynacji (np. wyprowadzony na standardowe wyjście).
Sprawdzić jakich użyto flag formatowania możemy za pomocą porównywania ze stałymi statycznymi klasy java.util.FormattableFlags. Dostępne są stałe o następujących nazwach:

Przykładowy program pokazuje zastosowanie metody formatTo do łatwej zmiany sposobu wyprowadzania informacji o obiektach klasy Person.  W trybie normalnym wyprowadzane jest nazwisko, w trybie alternatywnym (zastosowana flaga #) - nazwisko i imię. W metodzie formatTo, na podstawie przekazanych informacji wybieramy formę formatowania,i budujemy napis formatu na podstawie przekazanej informacji, po czym za jego pomocą formatujemmy obiekt, używając przekazanego formatora.

import java.util.*;
import static java.util.FormattableFlags.*;

public class Person implements Formattable{
  
  private String fname;
  private String lname;

  public Person(String fname, String lname) {
    this.fname = fname;
    this.lname = lname;
  }

  @Override
  public void formatTo(Formatter formatter, int flags, int width, int precision) {
    String txt = lname;
    if ((flags & ALTERNATE) == ALTERNATE) txt += ' ' + fname;
    String fs = "%";
    if ((flags & LEFT_JUSTIFY) == LEFT_JUSTIFY) fs += '-';
    if (width >= 0) fs += width;
    if (precision >= 0) fs += "."+precision;
    fs += ((flags & UPPERCASE) == UPPERCASE) ?  "S" : "s";
    formatter.format(fs, txt);
  }


  public static void main(String[] args) {
    Person e =  new Person("Jan", "Kowalski");
    System.out.printf("%#s\n", e);
    System.out.printf("%20s\n", e);
    System.out.printf("%#30S\n", e);
    System.out.printf("%#.10S\n", e);
  }

}
Wynik działania programu:
Kowalski Jan
            Kowalski
                  KOWALSKI JAN
KOWALSKI J