String processing #3

Format strings using the formatting parameters: %b, %c, %d, %f, and %s in format strings.

Podstawy narzędzia jakim jest formatowanie poznaliśmy już w pierwszym artykule z tego działu. Była to jednak namiastka możliwości tego mechanizmu. Czas zająć się bardziej zaawansowanymi przykładami.


Na skróty:

String [API]
Formatter [API]
[Elementy składowe formatu]
[Znaki konwersji]
[Flagi]
Repozytorium z przykładami [GitHub]


Szybkie przypomnienie

Formatowania używamy głównie, gdy dysponując zbiorem kilku stałych i/lub zmiennych musimy z nich skleić jeden obiekt String.

Przykładowo posiłkując się zmiennymi zawierającymi imię i numer telefonu możemy stworzyć taki ciąg znaków:

String name = "Roman";
String phone = "500 900 909";
String formattedText = String.format("Imie: %s, Telefon: %s", name,phone);
    
System.out.println(formattedText);

„Imie: Roman, Telefon: 500 900 909”

Składnia

Wiemy już, że znacznik formatowania zaczyna się znakiem „%”, wiemy też, że po tym znaku musi się znajdować literka odpowiadająca typowi danych, jakie będziemy chcieli wstawić w miejsce znacznika. Ta literka to jedna z:

  • b – boolean, jeśli otrzyma w argumencie obiekt typu boolean, lub prymityw boolean, wstawi wartość true/false zgodnie zawartością obiektu/prymitywu. Może też przyjąć warunek logiczny, lub dowolny obiekt/prymityw, który zawsze będzie formatowany do wartości true (nawet jeśli podamy liczbę ujemną lub zero). Wyjatkiem jest podanie referencji do null, zostanie ona sformatowana jako false.
  • c – char.
  • d – liczba całkowita, nie przyjmie w argumencie liczby zmienno przecinkowej.
  • f – liczba zmienno przecinkowa, nie przyjmie w argumencie liczy całkowitej.
  • s – string, podobnie jak boolean, przyjmie obiekt każdego typu i sformatuje go zgodnie z tym co zwróci metoda toSting() podanego obiektu.

To, czego do tej pory mogliśmy nie wiedzieć, to fakt, że „ta literka” jest jednym z pięciu elementów składowych znacznika formatowania, ale jest jedynym wymaganym. Nie bierzemy tutaj pod uwagę znaku procenta (%), to tylko sygnał, że w tym miejscu zaczyna się znacznik.

Elementy składowe to (kolejność jest istotna):

  1. argument_index – opcjonalny, wskazuje, który obiekt z listy argumentów ma być wykorzystany przez ten znacznik. Podajemy numer i znak dolara, np „1$” .
  2. flags – opcjonalny, do wyboru mamy pięć dodatkowych znaczników (flag), którymi możemy zmienić wygląd tekstu utworzonego przez ten znacznik (np. możemy wskazać żeby liczby ujemnie były wypisane w nawiasie bez poprzedzającego je minusa). Flagi te to kolejno: „-+0,(” . Szczegółowo omówię je poniżej.
  3. width– opcjonalny, minimalna liczna znaków na jakiej ma się zmieścić tekst utworzony przez znacznik. Podajemy po prostu liczbę np „15.
  4. precision – opcjonalny, do formatowania liczb zmienno przecinkowych, określa ilość liczb po przecinku. Podajemy jako liczbę poprzedzoną kropką np „.2” . Nie zwiększa długości narzuconej przez parametr width, jeśli ustalimy width na 10 i precision na 2 to sformatowana liczba będzie miała 8 cyfr przed i 2 cyfry po przecinku.
  5. conversion_char – wymagany, znany już nam element określający typ formatowanego obiektu. Wszystko co jest zapisane po tym znaku jest już traktowane jako stała część generowanego ciągu znaków, a wszystko co jest między tym znakiem, a znakiem procenta „%” będzie interpretowane jako element znacznika i jeśli nie będzie zgodne ze składnią, może wyrzucić wyjątek.

Brzmi strasznie, ale spokojnie. Już tłumaczę to na przykładzie.

Załóżmy, że mamy dane imię, nazwisko i pensję:

String name = "Yayami";
String sureName = "Omate";
double salary = 6521.99d;

Chcemy je sformatować do tekstu:

Pracownik: imie naziwsko, pensja: kwota

Najprościej, używając tylko obowiązkowych elementów znacznika formatowania napiszemy to tak:

String.format("Pracownik: %s %s, pensja: %f", name,sureName, salary);

Do metody format w pierwszym argumencie przekazaliśmy formatowany string zawierający trzy znaczniki formatowania i w drugim argumencie listę trzech obiektów, które mamy wykorzystać do formatowania. Ostatecznie otrzymujemy tekst:

„Pracownik: Yayami Omate, pensja: 6521,990000”

Fajnie, wstawiło się nam imię, nazwisko i… No właśnie, ta pensja jakaś dziwna.

Znak konwersji ‚f’ domyślnie formatuje liczbę do 6 miejsc po przecinku.

Nam to nie jest potrzebne. Wykorzystajmy więc jeden z opcjonalnych elementów – precision.

String.format("Pracownik: %s %s, pensja: %.2f", name,sureName, salary);

Znacznik „%f” zamienił się na „%.2f” . Czyli wpisaliśmy kropę jako oznaczenie elementu precision oraz zaraz po niej liczbę 2 mówiącą o tym, że chcemy mieć precyzję o wartości 2, czyli dwa miejsca po przecinku. Otrzymamy zatem:

Pracownik: Yayami Omate, pensja: 6521,99

Element precision możemy zastosować TYLKO jeśli znakiem konwersji jest ‚f’. Dla innych znaków konwersji w trakcie wykonywania programu rzucony zostanie wyjątek: IllegalFormatPrecisionException.

W kolejnym przypadku weźmy pod uwagę konieczność zamiany miejscami imienia z nazwiskiem. Oczywiście można to zrobić poprzez podanie metodzie format argumentów w odwrotnej kolejności:

String.format("Pracownik: %s %s, pensja: %.2f", sureName,sure, salary);

Ale można to też zrobić przy pomocy kolejnego opcjonalnego elementu: argument_index. Jest to zawsze liczba indeksowana od 1 (!!!) poprzedzająca znak „$”. Na przykład drugi argument zapiszemy jako „2$„. Zamieńmy więc imię z nazwiskiem, bez zmieniania kolejności na liście obiektów:

String.format("Pracownik: %2$s %1$s, pensja: %3$.2f", name,sureName, salary);

Co daje name: „Pracownik: Omate Yayami, pensja: 6521,99
Zauważ, że mimo, iż pensja nie zmieniła swojego miejsca, to też podałem przy niej numer argumentu „3$” . Dlaczego? Bez tego program podczas pracy zwróciłby wyjątek „IllegalFormatConversionException: f != java.lang.String” .

Metoda format ma swój licznik obiektów początkowo równy 1. Jeżeli przy danym znaczniku nie wykorzystamy elementu argument_index, metoda wybierze z listy obiektów ten, na który wskazuje jej licznik, a następnie zwiększy licznik o 1. Jeżeli podajemy element argument_index, metoda NIE zwiększa swojego licznika! Dlatego w powyższym przykładzie, gdyby nie został podany element „3$” , to metoda w miejsce pensji próbowałaby sformatować sobie pierwszy obiekt z listy, czyli String name.

Przykład następny. Chcielibyśmy, aby imię i nazwisko zawsze zajmowało odpowiednio 15 i 20 znaków. Zastosujemy więc element width.

String.format("Pracownik: %2$15s %1$20s, pensja: %3$.2f", name,sureName, salary);

Otrzymujemy:

Pracownik: Omate Yayami, pensja: 6521,99

Ponieważ, w tym przypadku nasze imię i nazwisko mają mniej niż ustalone 15 i 20 znaków, pola zostały dopełnione spacjami od lewej.

Omówiliśmy już elementy: argument_index, width, precision, conversion_char. Został więc ostatni element flags. Na poziomie omawianego egzaminu istotne są dla nas następujące flagi:

  • – (minus) – wyrównanie do lewej, obowiązkowo przy tej fladze musimy podać element width. Wtedy jeśli wygenerowany z obiektu tekst będzie krótszy niż długość podana w elemencie width, to na końcu tekstu zostanie dopisane tyle spacji, ile znaków brakuje do osiągnięcia wymaganej długości.
  • + (plus) – tą flagę możemy zastosować tylko, jeśli podany conversion_char jest równy „d” lub „f” – czyli tylko przy liczbach. Zastosowanie flagi sprawia, że przed liczbą zostanie dopisany znak plus lub minus, w zależności od tego czy liczba jest dodatnia, czy ujemna.
  • 0 (zero) – również do zastosowania tylko z liczbami, koniecznie musimy podać element width. W tym przypadku jeśli formatowana liczba będzie miała długość mniejszą niż wskazana w elemencie width , to zostanie ona dopełniona odpowiednią ilością zer od prawej.
  • , (przecinek) – kolejna flaga do zastosowania tylko z liczbami. Formatuje liczbę zgodnie z Locale maszyny, na której działa aplikacja. Przykładowo, dla niektórych Locale, tysiące są oddzielone przecinkami, np. liczba 1 milion wygląda tak: „1,000,000”.
  • ( (prawy nawias) – ostatnia flaga, też stosowana tylko z liczbami. Liczby ujemne wypisywane są w nawiasach okrągłych, bez minusa przed nimi. Czyli liczba minus dwa będzie sformatowana do „(2)” .

To tyle. Znamy już całą składnię potrzebną do zaawansowanych zabaw z formatowaniem. Dla lepszego zrozumienia, przeanalizuj jeszcze kilka przykładów.

Poniższe przykłady są również dostępne w repozytorium z przykładami: GitHub.

Przykłady

String arg1_s = "Johny";
String arg2_s = "Goldborn";
int arg3_d = -32;
double arg4_f = 128.500d;
boolean arg5_b = false;

String result;

//Podstawowe formatowanie, nie musimy wykorzystac wszyskich podanych do metody obiektów
result = String.format("Imie: %s \nNazwisko: %s \nwiek: %d",arg1_s,arg2_s,arg3_d,arg4_f,arg5_b);
System.out.println(result);// Imie: Johny 
            // Nazwisko: Goldborn 
            // wiek: -32

//Ten sam obiekt mozemy wykorzystac wielokrotnie
result = String.format("Dwa razy imie: %s , %1$s",arg1_s,arg2_s,arg3_d,arg4_f,arg5_b);
System.out.println(result);// Dwa razy imie: Johny , Johny

//Liczba calkowita ujemna zapisana w nawiasie zamiast z minusem, dopelniona zerami do 6 miejsc
result = String.format("%3$(04d",arg1_s,arg2_s,arg3_d,arg4_f,arg5_b);
System.out.println(result);// (0032) - zauwaz, ze 2 z 6 miejsc zostaly zajete przez nawiasy, dlatego sa tlyko dwa zera

//Liczba zmienno przecinkowa na 10 miejscach, z precyzja 3, dopelniona zerami, ze znakiem, z lokalym formatowaniem przecinkow
result = String.format("%4$+0,10.3f",arg1_s,arg2_s,arg3_d,arg4_f,arg5_b);
System.out.println(result); // +00128,500

//String na 30 znakach dopelnion spacjami od lewej
result = String.format("\"%30s\"",arg1_s,arg2_s,arg3_d,arg4_f,arg5_b);
System.out.println(result); // "                         Johny"

//String na 30 znakach dopelnion spacjami od prawej
result = String.format("\"%-30s\"",arg1_s,arg2_s,arg3_d,arg4_f,arg5_b);
System.out.println(result); // "Johny                         "

//Warunek logiczny
result = String.format("%b %b",arg1_s.isEmpty(), arg3_d < 100);
System.out.println(result);//false true

//Przekazanie inta do znaku konwersji = b
result = String.format("%b",0);
System.out.println(result);//true

//Przekazanie nulla do znaku konwersji = b
result = String.format("%b",null);
System.out.println(result);//false

//Przekazanie nulla do znaku konwersji = d
result = String.format("%d",null);
System.out.println(result);//null