Chapter 1. Java a národní prostředí

Table of Contents

1.1. Kódování
1.1.1. Podporovaná kódování
1.2. Čeština v programu
1.2.1. Zdrojový kód není v implicitním kódování
1.3. České výpisy na konzoli
1.3.1. Vstup češtiny z konzole
1.4. Čeština v souborech
1.5. Převody mezi různými kódováními uvnitř programu
1.6. Třída Locale
1.7. Tisk dle národních zvyklostí
1.7.1. Základní principy format()
1.7.2. Formátování dle lokality
1.7.3. Formátování datumu a času
1.7.4. Řazení řetězců
1.7.5. Označování začátků a konců slov
1.8. Internationalization & properties soubory
1.8.1. Na text nejlépe s ResourceBundle
1.8.2. Zprávy s proměnými daty
1.8.3. ListResourceBundle -- na jiná data než text

1.1. Kódování

Note

Místo termínu “text s akcentovanými znaky” bude dále používán termín “čeština”.

  • problém zobrazení češtiny vnitřně v Javě v podstatě neexistuje

    • vnitřně používá Unicode (dvoubajtový char)

  • Java je ale provozována téměř výhradně na operačních systémech, které mají znaky téměř výhradně osmibitové

    • fonty, pomocí nichž se texty zobrazují

    • textové soubory, ve kterých jsou texty uloženy

  • problém – přizpůsobit šestnáctibitové znaky Javy osmibitovým znakům jejího okolí

1.1.1. Podporovaná kódování

  • podrobný rozbor viz přednáška z PPA1

  • Java přímo podporuje množství kódování, která mají v Core API zavedené zkratky

    • podle pokusů se někdy rozlišují a někdy nerozlišují velká a malá písmena zkratek

    • použijete-li nevyhovující zkratku kódování, bude vyhozena výjimka UnsupportedEncodingException

  • kanonická jména čtyř nejběžnějších kódování:

    • ISO-8859-2 – kódování dle mezinárodní normy (často v prostředí Unixů)

      • známé též pod názvem ISO LATIN2

      • v Javě se občas vyskytne jako ISO8859_2

    • windows-1250 – proprietární kódování firmy Microsoft

      • od ISO-8859-2 se liší jen v několika málo znacích (z akcentovaných znaků češtiny jsou to jen š, Š, ť, Ť, ž, Ž)

      • implicitní pro JDK pod Windows !

      • známé též pod názvem CP1250

      • v Javě se občas vyskytne jako Cp1250

    • IBM852 – kódování používané v konzolovém okénku Windows!

      • občas je označováno jako DOS Latin2 nebo PC Latin 2

      • nezaměňovat s ISO LATIN2, které je zcela odlišné

      • v Javě se občas vyskytne jako Cp852

    • UTF-8 – jedno z nejrozšířenějších kódování pro Unicode

      • v Javě se občas vyskytne jako UTF8

1.2. Čeština v programu

  • jména identifikátorů – nejjednodušší problém – pouze zajistit, aby byl program správně přeložen

    • JDK pod Windows má implicitní kódování windows-1250 – překlad bez jakýchkoli problémů

  • texty výpisů – složitější

    • překlad bez problémů

    • výpis na konzoli s problémy – špatné akcenty – konzole je v IBM852 – řešení viz dále

    public class CestinaIdentifikatory {
      public static void main(String[] args) {
        int pěknýČeskýČítač = 1;
        System.out.println("pěknýČeskýČítač = " 
                                     + pěknýČeskýČítač);
      }
    }

    přeložení a spuštění:

  • v .class souboru je použito UTF-8

    • .class soubory jsou plně přenositelné na jiné platformy

1.2.1. Zdrojový kód není v implicitním kódování

  • je-li zdrojový soubor v jiném než implicitním kódování, nastává již problém s překladem

    • tento soubor můžeme získat přenosem z jiné platformy (typicky z Linuxu) nebo (nevhodným) nastavením editoru

    • pro soubor UTF8.java, který má jinak zcela stejný obsah jako předchozí soubor:

      public class UTF8 {
        public static void main(String[] args) {
          int pěknýČeskýČítač = 1;
          System.out.println("pěknýČeskýČítač = " 
                                       + pěknýČeskýČítač);
        }
      }

      po pokusu o překlad dostaneme:

  • možným – ale neelegantním – řešením je promocí externího programu pro změnu kódování převést soubor do implicitního kódování

1.2.1.1. Řešení prostředky JDK – encoding

  • předchozí případ lze elegantně řešit přepínačem encoding programu java.exe

    • rozpoznává všechna dříve uvedená kódování

  • překlad a spuštění souboru UTF8.java (řešení špatného kódování konzole viz dále)

  • tento způsob je výhodný v případě, že zdrojový soubor ladíme, tj. editujeme

    • všechny nápisy v editoru zdrojového kódu vidíme v češtině

Caution

Záludnou chybou je uložení zdrojového souboru v UTF-8 s BOM (Byte Order Mark)

  • to často provede “editor o své vlastní vůli

  • s BOM javac.exe nepočítá a překlad skončí chybou

    • ta je o to horší, že chybu při neznalosti principu BOM nelze odhalit, protože v editoru se BOM nezobrazuje

  • BOM je nutné se zbavit, což lze např pomocí editoru SciTe

1.2.1.2. Řešení prostředky JDK – native2ascii

  • zdrojové soubory v Javě nemají možnost (narozdíl od souborů XML) defifovat použité kódování

  • předáváme-li zdrojový soubor v nějakém kódování, záleží na zkušenostech příjemce, aby použité kódování správně rozpoznal a pak použil v přepínači encoding

    • správné rozpoznání není triviální záležitost

  • elegantním řešením problému je skutečnost, že každý akcentovaný znak může být ve zdrojovém souboru zapsán pomocí sekvence \uXXXX

    • XXXX je kódový bod znaku v Unicode, např. znak 'ě''\u011b'

  • nahradíme-li takto ve zdrojovém souboru všechny akcentované znaky, dostaneme ASCII soubor

    • odstraníme trvale závislost na různém kódování

    • soubor je plně přenositelný a bez problémů přeložitelný

    • ovšem

  • převod znaků se velmi obtížně se provádí "ručně"

    • na převod použijeme program native2ascii.exe, který je součástí JDK

      • jeho parametr je encoding má zcela stejný význam jako u javac.exe

      • rozdíl od předchozího způsobu je v tom, že native2ascii.exe používá autor zdrojového kódu, který by měl znát použité kódování

      • native2ascii.exe vypisuje svůj výstup implicitně na konzoli

      • zapisujeme-li výstup do souboru, je vhodné (bezpečnější) použít pomocný soubor (zde tmp.java) a ten pak přejmenovat na původní soubor

  • tento způsob provádíme na samém konci práce, když je zdrojový soubor odladěný a my jej archivujeme, či předáváme jinam

  • takto upravený zdrojový soubor se mimořádně špatně edituje

    • potřebujeme-li (rozsáhlejší editaci), použijeme native2ascii.exe s přepínačem reverse

      • provede převod sekvencí '\uXXXX' na jejich akcentovanou podobu ve zvoleném kódování

      • v příkladu vznikající soubor IBM852.java přepíše původní IBM852.java (nepoužil se pomocný soubor)

1.3. České výpisy na konzoli

Note

Spouštíme-li programy ze SciTe nebo Eclipse, s tímto problémem se nesetkáme, protože ty již mají výstup v GUI Windows, tj. v iplicitním kódování windows-1250.

  • konzolový výstup mají většinou jen zkušební nebo cvičné programy

    • stejný princip ale použijeme i při práci se soubory

  • základem veškerých změn kódování jsou třídy InputStreamReader a OutputStreamWriter

    • představují spojení mezi 8bitovými znaky operačního systému a 16bitovými znaky Javy

    • při čtení a při zápisu probíhá překódování znaků podle přednastaveného dekódování

      • ve Windows pro vstup i výstup přednastaveno kódování windows-1250

    • pro změnu kódování použijeme pro obě třídy konstruktor

      • první parametr je instance InputStream nebo OutputStream (tedy binární zpracování souboru!)

      • druhý parametr je řetězec určující nové kódování

  • nově nastavený OutputStreamWriter se pak použije např. jako parametr PrintWriter

import java.io.*;

public class NaKonzoli {
  public static void main(String[] args) throws Exception {
    OutputStreamWriter oswDef = 
                  new OutputStreamWriter(System.out);
    System.out.println("Implicitni kodovani konzole: " 
                  + oswDef.getEncoding());

    /* IBM852 je výstupní kódování češtiny v DOSovém okénku */
    OutputStreamWriter osw = 
                  new OutputStreamWriter(System.out, "IBM852");
    System.out.println("Nastavene kodovani konzole:  " 
                  + osw.getEncoding());
    
    PrintWriter p = new PrintWriter(osw);
    p.print("Příšerně žluťoučký kůň úpěl ďábelské ódy.\n");
    p.print("áčďéěíňóřšťúůýž\n");
    p.print("PŘÍŠERNĚ ŽLUŤOUČKÝ KŮŇ ÚPĚL ĎÁBELSKÉ ÓDY.\n");
    p.print("ÁČĎÉĚÍŇÓŘŠŤÚŮÝŽ\n");
    p.flush();
  }
}

překlad a spuštění

  • používají se zde “stará” označení kódování (Cp1250, Cp852)

1.3.1. Vstup češtiny z konzole

  • princip je velmi podobný výstupu na konzoli

  • zde jsou ukázány dvě možnosti vstupu

    • pomocí InputStreamReader – tento způsob se pak použije pro vstup ze souborů

    • pomocí Scanner – běžný způsob načítání z klávesnice

import java.io.*;
import java.util.*;

public class IOKonzole {
  public static void main(String[] args) throws Exception {
    OutputStreamWriter osw = 
              new OutputStreamWriter(System.out, "IBM852");
    PrintWriter p = new PrintWriter(osw);
    InputStreamReader isr = 
              new InputStreamReader(System.in, "IBM852");
    BufferedReader ib = new BufferedReader(isr);
    Scanner sc = new Scanner(System.in, "IBM852");
    
    p.print("Zadej akcentované znaky: ");
    p.flush();
    String s = sc.nextLine();
    p.print("Zadal jsi: " + s + "\n");
    p.flush();

    p.print("Zadej další akcentované znaky: ");
    p.flush();
    s = ib.readLine();
    p.print("Zadal jsi: " + s + "\n");
    p.flush();
  }
}

1.4. Čeština v souborech

  • situace prakticky stejná, jako s češtinou z konzole

  • třída Reader čte bajty, které konvertuje na znaky podle implicitního kódování

    • totéž platí pro třídu Writer – zapisuje 16bitové znaky, ale v souboru se objeví jejich 8bitová náhrada

  • znamená to, že Reader a Writer lze správně použít jen pro implicitní kódování

    • to lze zjistit ze systémové vlastnosti file.encoding příkazem System.getProperty("file.encoding")

  • zpracování pomocí jiného kódování je stejné jako u konzole – třídy InputStreamReader a OutputStreamWriter

    • nikdy se nesnažíme o změnu file.encoding !!!

Ukázka, jak načíst soubor v jiném kódování než windows-1250. Zkratka požadovaného kódování se zadá z příkazové řádky a soubor se kontrolně opíše na obrazovku způsobem známým z předchozí části. Zadáme-li z příkazové řádky UTF-8, vypíše se správný text. Zadáme-li např. windows-1250, dostaneme částečně nesmyslný výpis.

import java.io.*;

public class SouborCteni {
  public static void main(String[] args) throws Exception {
    OutputStreamWriter osw = 
             new OutputStreamWriter(System.out, "IBM852");
    PrintWriter p = new PrintWriter(osw);
    String kodovani = args[0];

    FileInputStream fis = 
             new FileInputStream("vstup.utf8");
    InputStreamReader isr = 
             new InputStreamReader(fis, kodovani);
    BufferedReader br = new BufferedReader(isr);
    
    String radka;
    while ((radka = br.readLine()) != null) {
      p.println(radka);
      p.flush();
    }
    fis.close();
  }
}

Totéž lze použít i pro výstupní soubor. Zde máme možnost vytvořit více souborů se stejným obsahem, ale jiným kódováním. Typ kódování se zadá z příkazové řádky a je to současně i přípona souboru se jménem vystup. Metoda toUpperCase() třídy String převádí zcela správně akcentované znaky, bez ohledu na jejich kódování. Je to tím, že řetězec kun (nebo akcenty) je v Javě 16bitový Unicode – nemá s výstupním kódováním nic společného.

import java.io.*;

public class SouborZapis {
  public static void main(String[] args) throws Exception {
    String kodovani = args[0];
    String jmenoSouboru = "vystup." + kodovani;

    FileOutputStream fos = new FileOutputStream(jmenoSouboru);
    OutputStreamWriter osw = 
             new OutputStreamWriter(fos, kodovani);
    PrintWriter p = new PrintWriter(osw);

    String kun = "Příšerně žluťoučký kůň úpěl ďábelské ódy";
    String akcenty = "áčďéěíňóřšťúůýž";
    p.println(kun);
    p.println(akcenty);
    p.println(kun.toUpperCase());
    p.println(akcenty.toUpperCase());

    p.close();
  }
}

1.5. Převody mezi různými kódováními uvnitř programu

  • v rámci programu potřebujeme získat z řetězce znaky v daném kódování

    • metoda byte[] getBytes(String kodovani) třídy String

    • pro řetězec vrátí pole bajtů ve zvoleném kódování

  • opačný postup je, máme-li pole bajtů v určitém kódování a potřebujeme z něj získat řetězec

    • konstruktor String(byte[] bytes, String kodovani)

Jak lze snadno zjistit kódy jednotlivých znaků pro různá kódování.

public class PrevodStringu {
  public static String byteNaHexa(byte[] b) {
    String s = " ";
    for (int i = 0;  i < b.length;  i++) {
      int j = (b[i] < 0) ? 256 + b[i] : b[i];
      s = s + s.format("%02x ", j);
    }
    return s;
  }

  public static void main(String[] args) throws Exception {
    String test = "áčďéěíňóřšťúůýž";
    String[] kodovani = {"windows-1250", "ISO-8859-2  ",
                         "IBM852      ", "UTF-8       ",
                         "UTF-16BE    ", "UTF-16LE    ",
                         "UTF-16      "};
    for (int i = 0;  i < kodovani.length;  i++) {
      byte[] b = test.getBytes(kodovani[i].trim());
      System.out.println(kodovani[i] + ":" + byteNaHexa(b));
    }
  }
}
windows-1250: e1 e8 ef e9 ec ed f2 f3 f8 9a 9d fa f9 fd 9e 
ISO-8859-2  : e1 e8 ef e9 ec ed f2 f3 f8 b9 bb fa f9 fd be 
IBM852      : a0 9f d4 82 d8 a1 e5 a2 fd e7 9c a3 85 ec a7 
UTF-8       : c3 a1 c4 8d c4 8f c3 a9 c4 9b c3 ad c5 88 c3 b3 c5 99 c5 a1 c5 a5 c3 ba c5 af c3 bd c5 be 
UTF-16BE    : 00 e1 01 0d 01 0f 00 e9 01 1b 00 ed 01 48 00 f3 01 59 01 61 01 65 00 fa 01 6f 00 fd 01 7e 
UTF-16LE    : e1 00 0d 01 0f 01 e9 00 1b 01 ed 00 48 01 f3 00 59 01 61 01 65 01 fa 00 6f 01 fd 00 7e 01 
UTF-16      : fe ff 00 e1 01 0d 01 0f 00 e9 01 1b 00 ed 01 48 00 f3 01 59 01 61 01 65 00 fa 01 6f 00 fd 01 7e

1.6. Třída Locale

  • nejdůležitější třída pro práci s národním prostředím

    • je z balíku java.util

  • ovlivňuje činnost mnoha dalších tříd

    • lze vytvořit její instanci, která je pak dalšími třídami používána

      • problém změny národního prostředí se tak redukuje na změnu pomocí jedné řádky kódu

  • uschovává informace o jazyku a o oblasti (zemi, státu)

  • statická metoda Locale.getDefault() vrací objekt, který popisuje aktuální nastavení pro právě platnou instanci JVM

    • nastavení se shoduje s nastavením, které lze změnit v operačním systému

    • pro Windows XP je to ve Start/Control Panel/Regional Settings

  • dále bude používán pojem lokalita = kombinace jazyka a země

Výpis hodnot pro přednastavenou lokalitu.

import java.util.*;
import java.io.*;

public class MojeLocale {
  public static void main(String[] args) throws Exception {
    Locale d = Locale.getDefault();
    System.out.println("Země : " + d.getCountry());
    System.out.println("Jazyk: " + d.getLanguage());
    System.out.println("Země : " + d.getDisplayCountry());
    System.out.println("Jazyk: " + d.getDisplayLanguage());
    System.out.println("ISO země : " + d.getISO3Country());
    System.out.println("ISO jazyk: " + d.getISO3Language());
  }
}

vypíše pro Control Panel/Regional Setting/Czech:

Země : CZ
Jazyk: cs
Země : Česká republika
Jazyk: čeština
ISO země : CZE
ISO jazyk: ces
  • metoda void setDefault(Locale newLocale)

    • dokáže pro konkrétní instanci JVM (ne pro operační systém!) změnit lokalitu

  • instanci třídy Locale dostaneme pomocí Locale(String jazyk, String zeme)

    • je možné pracovat s více lokalitami najednou

  • příklady použití konstruktoru a zkratek jazyků a zemí

    czLocale = new Locale("cs", "CZ");
    skLocale = new Locale("sk", "SK");
    usLocale = new Locale("en", "US");
    gbLocale = new Locale("en", "GB");
  • objekty Locale jsou pouze identifikátory, které samy nejsou schopny žádné činnosti

    • poskytují se jiným třídám jako parametry jejich "výkonných" metod

      • tyto metody ale nemusejí podporovat všechny možné kombinace jazyků a zemí

      • podporované lokality lze u každé třídy, která je s nimi schopna pracovat, zjistit voláním statické metody getAvailableLocales()

        • vrátí pole všech podporovaných Locale, např.:

          Locale[] l = DateFormat.getAvailableLocales();
      • podle pokusů patří lokality "cs_CZ" a "sk_SK" vždy mezi podporované

1.7. Tisk dle národních zvyklostí

  • před JDK 1.5 málo přehledné použití několika různých tříd

    • NumberFormat pro čísla, měny

    • DecimalFormat pro speciální formát celých a reálných čísel

    • DateFormat a SimpleDateFormat pro data a časy

  • od JDK 1.5 lze použít metodu format() ze tříd PrintStream a String

    • nedokáže ale předchozí třídy plně nahradit

1.7.1. Základní principy format()

Note

Používáme jen tehdy, když nám nestačí možnosti System.out.println().

  • jasná inspirace možnostmi jazyka C

  • možnost volit šířku výpisu, zarovnávání doprava či doleva, nevýznamové mezery a množství dalších věcí

    • vyčerpávající informace i s příklady viz java.util.Formatter

  • základní statická metoda String.format(parametr)

    • poskytne řetězec, který se dá dále zpracovat (poslat na výstup – stream, soubor, GUI, ...)

  • stejná metoda je ve třídě PrintStream, tj. System.out.format()

  • vyřešeno přenositelně odřádkování pomocí %n, např.:

    String.format("nova radka%n");

  • základní princip je formátovací řetězec jako první parametr a pak jednotlivé položky jako další parametry

  • základní pravidla

    • ve formátovacím řetězci jsou za znakem % formátovací znaky

    • kolik je znaků %, tolik musí být dalších parametrů (s výjimkou %n)

    • String.format("i = %d, j = %d%n", i, j);

1.7.1.1. Výpis celého čísla v desítkové soustavě

  • používá se %d, např. pro int i = -1234;

    String.format("i = %d%n", i);        // i = -1234
  • počet míst lze stanovit, pak se doplňují mezery zleva, tj. zarovnání doprava, např.:

    String.format("i = %7d%n", i);       // i =   -1234
  • počet míst lze stanovit a zarovnat doleva (zbylé místo se doplní mezerami), např.:

    String.format("i = %-7dahoj%n", i);  // i = -1234   ahoj
  • lze vynutit výpis i + znaménka, např. pro int i = 1234;

    String.format("i = %+7d%n", i);      // i =   +1234
  • vynutí se výpis nevýznamových nul, např.:

    String.format("i = %07d%n", i);      // i = -001234

1.7.1.2. Výpis celého čísla v jiných soustavách

  • osmičková soustava, např. pro int j = 30;

    String.format("j = %o%n", j); // j = 36

  • šestnáctková soustava, např. pro int j = 30;

    String.format("j = %X%n", j); // j = 1E

  • počet míst lze určit, např.:

    String.format("j = %3X%n", j); // j =  1E
  • lze vynutit nevýznamové nuly, např. pro: int j = 10;

    String.format("j = %02X%n", j); // j = 0A

1.7.1.3. Výpis znaku

  • používá se %c, např. pro char c = 'a';

    String.format("c = %c%n", c); // c = a

  • lze použít přetypování a lze vypsat více proměnných najednou

    String.format("Znak %c ma ASCII hodnotu: %d%n", c, (int) c);

    // Znak a ma ASCII hodnotu: 97

1.7.1.4. Výpis reálného čísla

  • výpis jako běžné reálné číslo %f, např. pro double d = 1234.567;

    String.format("d = %f%n", d); // d = 1234,567000

    Note

    Desetinný oddělovač je závislý na lokalitě – pro ČR je to čárka, nikoliv tečka.

  • výpis ve vědeckotechnické notaci %g, např. pro double d = 1234.567;

    String.format("d = %g%n", d); // d = 1.234567e+03

    Note

    Desetinný oddělovač je tečka.

  • lze nastavit počet míst celkem (10) a počet míst za desetinným oddělovačem (1), číslo bude zaokrouhleno

    String.format("d = %10.1f%n", d); // d = 1234,6

  • lze použít zarovnání doleva, výpis nevýznamových nul, oddělovač řádů apod. stejně, jako u celého čísla

1.7.1.5. Výpis řetězce

  • používá se %s, např. pro String s = "Ahoj lidi";

    String.format("s = %s%n", s); // s = Ahoj lidi

  • řetězec lze vypsat velkými písmeny

    String.format("s = %S%n", s); // s = AHOJ LIDI

  • lze stanovit šířku výpisu, výpis bude zarovnán doprava

    String.format("s = |%11s|%n", s);  // s = |  Ahoj lidi|
  • výpis lze zarovnat i doleva

    String.format("s = |%-11s|%n", s); // s = |Ahoj lidi  |

1.7.2. Formátování dle lokality

  • format() využívá přednastavenou lokalitu

  • vhodnější je však danou lokalitu zadat pro každé použití

    • String.format(lokalita, formátovací_řetězec, tisknuté_parametry)

1.7.2.1. Tisk a načtení čísel

  • u čísel se v národních zvyklostech mění desetinný oddělovač a oddělovač řádů

    • jejich používání se ve format() vynutí pomocí znaku “,” (čárka) bezprostředně za znakem “%

Ukázka používaných oddělovačů v různých lokalitách. Průzkum typu oddělovače řádů v české lokalitě. Formátovací řetězec "%,.2f \t%s%n" znamená:

  • , – tiskni oddělovač řádů

  • .2 – tiskni na dvě desetinná místa se zaokrouhlením

  • f – tiskni reálné číslo

  •  \t – tiskni mezeru a tabulátor

  • %s – tiskni řetězec (zde název lokality)

  • %n – tiskni odřádkovávací znaky dle konvencí platformy

Výpis znaku “ě” (kódový bod \u011b) v dalším tisku je proto, aby byl zřejmý rozsah a uložení bajtů.

import java.util.*;

public class CislaLocale {
  public static void main(String[] args) throws Exception {
    double d = 1234567.894;
    Locale[] lo = { new Locale("cs", "CZ"), 
                    new Locale("sk", "SK"),
                    new Locale("en", "US"), 
                    new Locale("de", "DE") };
    for (int i = 0;  i < lo.length;  i++) {
      System.out.format(lo[i], "%,.2f \t%s%n", d,
                        lo[i].getDisplayName());
    }
    
    double d1 = 1234.5;
    String s = String.format(lo[0], "ě %,.2f", d1);
    byte[] b = s.getBytes("UTF-16BE");
    
    for (int i = 0;  i < b.length;  i++) {
      int j = (b[i] < 0) ? 256 + b[i] : b[i];
      System.out.format("%02x ", j);
    }
  }
}

vypíše:

1 234 567,89 čeština (Česká republika)
1 234 567,89 Slovak (Slovakia)
1,234,567.89 English (United States)
1.234.567,89 German (Germany)
01 1b 00 20 00 31 00 a0 00 32 00 33 00 34 00 2c 00 35 00 30
  • v české typografii je oddělovač řádů pevná mezera šířky čtvrt čtverčíku (“čtverčík” je typografický termín)

    • Java tento znak nepoužívá (měl by kódový bod \u202F [narrow no-break space])

    • místo něj používá pevnou mezeru (kódový bod \u00A0 [no-break space])

      • není-li tento znak k dispozici v používaném fontu, zobrazí se nesmyslný znak, např. při výpisu na konzoli: 1á234á567,89

    • budou-li ale takto formátovaná čísla na vstupu, bude jako oddělovač řádů velmi pravděpodobně použita normální mezera (kódový bod \u0020)

      • to je důvod, proč se prakticky téměř nikdy nepodaří takto formátovaná čísla načíst

Ukázka načtení reálného čísla. Použijeme známý Scanner s nastavenou lokalitou. V české lokalitě zadáme číslo bez oddělovače řádů, protože znak \u00A0 se zadává z klávesnice špatně a znak mezery by byl interpretován jako konec čísla.

import java.util.*;

public class CislaLocaleCteni {
  public static void main(String[] args) throws Exception {
    Locale[] lo = { new Locale("cs", "CZ"), 
                    new Locale("en", "US") };
    Scanner sc = new Scanner(System.in).useLocale(lo[1]);
    System.out.print("Zadej realne cislo (1,234,567.89): " );
    double d = sc.nextDouble();
    System.out.println(d);
                    
    sc = new Scanner(System.in).useLocale(lo[0]);
    System.out.print("Zadej realne cislo (1234567,89): " );
    d = sc.nextDouble();
    System.out.println(d);
  }
}

vypíše:

Zadej realne cislo (1,234,567.89): 98,765.432
98765.432
Zadej realne cislo (1234567,89): 98765,432
98765.432

Number Format Pattern Syntax na: http://java.sun.com/docs/books/tutorial/i18n/format/decimalFormat.html

1.7.2.2. Tisk označení měny

  • zde je vhodné použít třídu java.text.NumberFormat

    • její instanci vhodnou pro výpis měny získáme tovární metodou getCurrencyInstance()

    • u instance NumberFormat pak využíváme metodu format(), která je zcela jiná než dříve popisovaný String.format()

    • při výpisu se bere ohled na to, kolik desetinných míst se při výpisu měny používá

      • např. koruna se skládá ze sta haléřů – výpis je na dvě desetinná místa

Výpis měnových hodnot. Znak $ je uveden (dle amerických zvyklostí) automaticky před číslem. Symbol měny je možné též získat ze třídy Currency a pak použít známý String.format().

import java.util.*;
import java.text.*;

public class MenaLocale {
  public static void main(String[] args) throws Exception {
    NumberFormat nf;
    double d = 1234567.894;
    Locale[] lo = { new Locale("cs", "CZ"), 
                    new Locale("sk", "SK"),
                    new Locale("en", "US"), 
                    new Locale("de", "DE") };
    for (int i = 0;  i < lo.length;  i++) {
      nf = NumberFormat.getCurrencyInstance(lo[i]);
      String s = nf.format(d);
      System.out.println(s + "\t" + lo[i].getDisplayName());
    }

    Currency cur = Currency.getInstance(lo[0]);
    String symbol = cur.getSymbol();
    System.out.format(lo[0], "%,.2f %s %n", d, symbol);
  }
}

vypíše:

1 234 567,89 Kč  čeština (Česká republika)
1 234 567,89 Sk  Slovak (Slovakia)
$1,234,567.89    English (United States)
1.234.567,89 €   German (Germany)
1 234 567,89 Kč

1.7.3. Formátování datumu a času

  • konvence v zobrazování datumu a času jsou v různých lokalitách velmi odlišné

    • třída java.text.DateFormat spolehlivě funguje pro různé lokality

Ukázka různých výpisů datumu.

import java.util.*;
import java.text.*;

public class DatumLocale {
  public static void main(String[] args) throws Exception {
    DateFormat df;
    Date d = new Date();
    Locale[] lo = { new Locale("cs", "CZ"), 
                    new Locale("sv", "SE"),
                    new Locale("en", "US"), 
                    new Locale("de", "DE") };
    for (int i = 0;  i < lo.length;  i++) {
      df = DateFormat.getDateInstance(DateFormat.DEFAULT, lo[i]);
      String s = df.format(d);
      System.out.println(s + "\t" + lo[i].getDisplayName());
    }
  }
}

vypíše:

4.3.2007     čeština (Česká republika)
2007-mar-04  Swedish (Sweden)
Mar 4, 2007  English (United States)
04.03.2007   German (Germany)

1.7.3.1. Změna množství vypisované informace

  • metoda getDateInstance() má dva parametry – první určuje množství vypisované informace (mod výpisu)

Table 1.1. Konstanty DateFormatDEFAULT, SHORT, MEDIUM, LONG, FULL

Česká republikaUnited States 
DEFAULT4.3.2007Mar 4, 2007
SHORT4.3.073/4/07
MEDIUM4.3.2007Mar 4, 2007
LONG4. březen 2007March 4, 2007
FULLNeděle, 4. březen 2007Sunday, March 4, 2007

1.7.3.2. Výpis času

  • platí stejná pravidla, pouze se instance třídy DateFormat získá metodou getTimeInstance()

    • opět má jako první parametr konstanty DateFormatDEFAULT, SHORT, MEDIUM, LONG, FULL

    • prakticky použitelné jsou:

      • DEFAULT – 19:53:20

      • SHORT – 19:53

1.7.3.3. Pomocí format()

  • dává možnost vytvořit si vlastní formát datumu a času

    • nastavení lokality však tento výpis ovlivňuje velmi málo

      • prakticky jen výpis názvů měsíců a názvů dnů slovem

  • formátovací řetězec je vždy uvozen %t za nímž následuje další písmeno

    • to může určovat buď jeden údaj, např. %tB plné jméno měsíce, %tA plné jméno dne, %tH hodina ve 24hodinovém cyklu atd.

      • může být závislý na lokalitě

    • nebo určuje skupinu údajů, např. %tT výpis hodina:minuta:sekunda, %tF výpis rok-měsíc-den

      • není závislý na lokalitě

  • všechny možnosti jsou přehledně popsány v java.util.Formatter

  • velmi zajímavé je, že skutečným parametrem může být buď objekt třídy Calendar (nebo potomků – GregorianCalendar) nebo proměnná typu long, ve které je počet milisekund od 1.1.1970

Výpis časových údajů pomocí format().

import java.util.*;
import java.text.*;

public class DatumLocaleFormat {
  public static void main(String[] args) throws Exception {
    Calendar c = Calendar.getInstance();
    Date d = c.getTime();
    long l = d.getTime();
    Locale[] lo = { new Locale("cs", "CZ"), 
                    new Locale("sv", "SE"),
                    new Locale("en", "US"), 
                    new Locale("de", "DE") };
    for (int i = 0;  i < lo.length;  i++) {
      System.out.format(lo[i], "%tB %tA %tF \t %s %n", 
                                l,  c,  l,     lo[i].getDisplayName());
    }
  }
}

vypíše:

březen Neděle 2007-03-04 čeština (Česká republika)
mars söndag 2007-03-04   Swedish (Sweden)
March Sunday 2007-03-04  English (United States)
März Sonntag 2007-03-04  German (Germany)

1.7.4. Řazení řetězců

1.7.4.1. Porovnávání řetězců

  • při porovnávání jen na rovnost pomocí String.equals() žádné problémy nenastanou

  • chceme-li např. řadit řetězce podle abecedy a k tomu využít metodu String.compareTo(), pak české znaky v řetězci způsobí potíže

    • tato metoda vrací číslo menší nebo větší než nula v případě nerovnosti porovnávaných řetězců

    • jakmile je v řetězci akcentovaný znak, pak svým kódováním zcela “vybočuje z řady” neakcentovaných znaků

      • např. v české abecedě je posloupnost B, C, Č, D – kódové body těchto znaků jsou \u0042, \u0043, \u010C, \u0044

  • pro tento případ máme třídu java.text.Collator spolupracující s Locale

    • v Java Core API v dokumentaci k Collator je čeština se svým třífázovým řazením uváděna jako příklad

      For example, in Czech, "e" and "f" are considered primary differences, while "e" and "ě" are secondary differences, "e" and "E" are tertiary differences...

    • prakticky to znamená, že pro porovnávání řetězců v češtině je již vše připraveno

1.7.4.2. Způsoby řazení v češtině podle normy

  • řazení v češtině je definováno normou

  • řazení probíhá ve třech fázích

    • v první fázi se nerozlišuje velikost znaků a také se nerozlišují některé akcenty

      • znaky s takzvanou “primární řadicí platností” jsou:

        a b c č d e f g h ch i j k l m n o p q r ř s š t u v w x y z ž

    • ve druhé fázi se pro stejné řetězce z první fáze berou v úvahu znaky se “sekundární řadicí platností”:

      á ď é ě í ň ó ť ú ů ý

      které se v první fázi řadily jako:

      a d e e i n o t u u y

      • jsou-li dvě slova až na akcenty stejná, pak se slovo bez akcentů dává na první místo a slovo s akcenty na druhé

    • ve třetí fázi se navíc rozlišuje i velikost znaků

      • nejprve se řadí slova s malými písmeny a pak slova s velkými písmeny

  • seřadíme-li příklady uváděné v normě programem v Javě, dostaneme výsledek přesně podle normy

    • stačí jen zajistit, aby Collator používal správnou lokalitu

    • protože se jedná o absolutní řazení, je možné řadit nejen pole řetězců, ale i řetězce v odpovídajících kolekcích

Ukázka abecedního řazení podle normy – vstupní pole řetězců je právě v opačném pořadí.

import java.util.*;
import java.text.*;
import java.io.*;

public class Razeni {
  public static void main(String[] args) throws Exception {
    String[] ret = { "nový věk", 
                     "Nový Svět", "Nový svět", 
                     "nový Svět", "nový svět", 
                     "Nový Svet", "Nový svet" ,
                     "abc traktoristy", 
                     "ABC nástrojaře", "abc nástrojaře", 
                     "ABC kováře", "ABC klempíře", 
                     "abc frézaře", "ABC",
                     "Abc", "abc", "A", "a" };

    Arrays.sort(ret, new CeskyAbecedniComparator());
 
    for (int i = 0;  i < ret.length;  i++) {
      System.out.println(ret[i]);
    }
  }
}

class CeskyAbecedniComparator implements Comparator<String> {
  private Collator ceskyCol = Collator.getInstance(
                      new Locale("cs", "CZ"));

  public int compare(String s1, String s2) {
    return ceskyCol.compare(s1, s2);
  }
}

vypíše:

a
A
abc
Abc
ABC
abc frézaře
ABC klempíře
ABC kováře
abc nástrojaře
ABC nástrojaře
abc traktoristy
Nový svet
Nový Svet
nový svět
nový Svět
Nový svět
Nový Svět
nový věk

1.7.5. Označování začátků a konců slov

  • detekovat jednotlivá slova je při práci s textem nutnost

    • zlom řádek

    • zvýraznění (výběr) slov “dvojklikem”, ...

  • pokud slova detekujeme “ručně”, používáme zásadně konstrukci

    if (Character.isLetter(ch)) {

    • případně ještě metody isDigit(), isLetterOrDigit(), isLowerCase() a isUpperCase()

  • na automatickou detekci slov, vět a případně i možných zlomů řádek existuje java.text.BreakIterator

    • podobně jako u datumů a časů získáváme instanci BreakIterator pomocí statických metod:

      • getCharacterInstance() – detekuje hranice znaků (znak ch)

      • getWordInstance() – detekuje hranice slov

      • getSentenceInstance() – detekuje hranice vět

      • getLineInstance() – detekuje místa možného zlomu řádek

    • všechny fungují pro přednastavenou lokalitu

      • pro jinou lokalitu použijeme jejich přetížené verze s parametrem typu Locale

    • instanci je nutné nejdříve nastavit zkoumaný text metodou

      void setText(String text)

    • BreakIterator používá vlastní ukazatel na hranice slov

      • dá se posouvat doprava – next() a doleva – previous()

      • dá se nastavit na začátek – first() a na konec – last()

      • další metody vrací hraniční indexy od zadaného ofsetu –following(int offset) a preceding(int offset)

      • všechny vrací int – index do řetězce

    • BreakIterator za samostatné úseky považuje i mezery a interpunkci

      • je třeba použít metodu Character.isLetter() pro znak na dané hranici

    • procházíme-li postupně celý řetězec, použije se BreakIterator.DONE pro ukončení cyklu

Ukázka, jak lze vyseparovat jednotlivá česká slova z textu.

import java.io.*;
import java.text.*;

public class BreakIterator1 {
  public static void main(String[] args) throws Exception {
    String veta = "Příšerně 'žluťoučký' kůň úpěl ďábelské " +
                   "ódy, které se nedaly poslouchat.";

    BreakIterator wbi = BreakIterator.getWordInstance();
    wbi.setText(veta);
    int zac = wbi.first();
    int kon = wbi.next();

    while (kon != BreakIterator.DONE) {
      String slovo = veta.substring(zac, kon);
      if (Character.isLetter(slovo.charAt(0))) {
        System.out.println(slovo);
      }
      zac = kon;
      kon = wbi.next();
    }          
  }
}

vypíše:

Příšerně
žluťoučký
kůň
úpěl
ďábelské
ódy
které
se
nedaly
poslouchat

1.8. Internationalization & properties soubory

  • Na i18n se používají se .properties soubory

  • Příklad vše ozřejmí

    • Nejprve se podívejme na program, který i18n potřebuje

      public class NotI18N {
         static public void main(String[] args) {
              System.out.println("Hello.");
              System.out.println("How are you?");
              System.out.println("Goodbye.");
          }
      }

1.8.1. Na text nejlépe s ResourceBundle

  • Připravíme několik textových .properties souborů podle jasně dané jmenné konvence:

    • Soubor MessagesBundle_de_DE.properties:

      # FOR GERMAN ... tohle je komentář ... následují páry klíč = hodnota
      greetings = Hallo.
      farewell = Tschüß.
      inquiry = Wie geht's?
    • Soubor MessagesBundle_fr_FR.properties:

      greetings = Bonjour.
      farewell = Au revoir.
      inquiry = Comment allez-vous?
    • A nezapomeneme na defaultní MessagesBundle.properties

      greetings = Hello.
      farewell = Goodbye.
      inquiry = How are you?
  • Upravíme kód tak, že použijeme třídy Locale a ResourceBundle

    import java.util.*;
    public class I18NSample {
     static public void main(String[] args) {
       String language, country;
       if (args.length != 2) {
          language = new String("en"); country = new String("US");
       } else {
          language = new String(args[0]); country = new String(args[1]);
       }
       Locale currentLocale = new Locale(language, country);
       ResourceBundle messages = 
              ResourceBundle.getBundle("MessagesBundle",currentLocale);
       System.out.println(messages.getString("greetings"));
       System.out.println(messages.getString("inquiry"));
       System.out.println(messages.getString("farewell"));
     }
    }
    • Výstup programu je pak (včetne příkazu, kterým se spustí):

      % java I18NSample fr FR
      Bonjour.
      Comment allez-vous?
      Au revoir.
    • Je vidět, že program tiskne text ze spravneho .properties souboru

    • Pokud pouzijete Locale, ke keterému neexistuje .properties soubor (například kombinace fr a CA)použije se defaultní MessagesBundle.properties

    • Můžete specifikovat i platformu:

      • Locale currentLocale = new Locale("fr", "CA", "UNIX");

1.8.2. Zprávy s proměnými daty

  • http://java.sun.com/docs/books/tutorial/i18n/format/messageFormat.html

  • Někdy texty obsahují prěměnlivé ůdaje například:

    • The current balance of account #34-98-222 is $2,745.72.

    • Delete all files older than 120 days.

  • Naivní řešení ...

    ResourceBundle msgBundle;
    ...
    String message = msgBundle.getString("deleteolder" 
                                        + numDays.toString() // to je těch 120
                                        + msgBundle.getString("days"));
  • ... fungule pro angličtinu, ale už nemusí fungovat pro např. němčinu, kde po číslovce může být nejen slovíčko "Tag", ale také sloveso

  • Řešením je použití třídy MessageFormat a její metody format spolu s drobnou úpravou .properties souboru

    # .properties soubor
    deleteOrder = Delete all files older than {0} days.
    ResourceBundle messages = 
           ResourceBundle.getBundle("MessageBundle",currentLocale);
    MessageFormat formatter = new MessageFormat(""); // pattern zatím neuvažujeme
    formatter.setLocale(currentLocale);
    formatter.applyPattern(messages.getString("deleteOrder"));
    Object[] argumentyZpravy = {new Integer(120)};
    String output = formatter.format(argumentyZpravy); 
    System.out.println(output);
  • Složitejší příklad z Java Tutorialu:

    # .properties soubor
    template = At {2,time,short} on {2,date,long}, we detected \
    {1,number,integer} spaceships on the planet {0}.
    planet = Mars
    Object[] argumentyZpravy = {
        messages.getString("planet"),
        new Integer(7),
        new Date()
    };
    String output = formatter.format(argumentyZpravy);
  • Na http://java.sun.com/docs/books/tutorial/i18n/format/choiceFormat.html je ukázáno, jak si poradit s variantami:

    • Žádné soubory nenalezeny

    • Nalezen 1 soubor

    • Nalezeny 2 soubory

    • Nalezeno 5 souborů

  • Formátování může být důmyslně řízeno -- dataily na http://java.sun.com/javase/6/docs/api/java/text/MessageFormat.html

1.8.3. ListResourceBundle -- na jiná data než text

  • Uvažujme dvě Locale

    • StatsBundle_fr_FR.class

    • StatsBundle_ja_JP.class

  • Pro Japonsko pak třída vypadá takto:

    import java.util.*;
    public class StatsBundle_ja_JP extends ListResourceBundle {
      public Object[][] getContents() { // jediná metoda - vrací 2D pole
        return contents;
      }
      private Object[][] contents = {   // zde je definováno 2D pole
       { "GDP", new Integer(21300) },   // explicitní převod int na Integer
       { "Population", new Integer(125449703) },
       { "Literacy", new Double(0.99) },
      };
    }
  • Použití v programu:

    Locale currentLocale = new Locale("ja","JP"); 
                           // nebo Locale.getDefault()
    ResourceBundle stats = 
             ResourceBundle.getBundle("StatsBundle",currentLocale);
    Integer gdp = (Integer)stats.getObject("GDP");
    System.out.println("GDP = " + gdp.toString());
    Integer pop = (Integer)stats.getObject("Population");
    System.out.println("Population = " + pop.toString());
    Double lit = (Double)stats.getObject("Literacy");
    System.out.println("Literacy = " + lit.toString());