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