Table of Contents
problematika je značně složitější než jen vstup a výstup akcentovaných znaků:
vstup a výstup akcentovaných znaků ale i dalších speciálních znaků
použití akcentovaných znaků ve zdrojových kódech (I/O, dokumentační komentáře, ...)
převod velkých písmen na malá a naopak
řazení řetězců podle pravidel abecedního řazení národního jazyka
formátování čísel
práce s datumem a časem, měnou apod.
používané zkratky:
i18n – internationalizatio
n
psaní programu tak, aby se dal snadno přizpůsobit libovolnému národnímu prostředí
l10n – localizatio
n
převedení programu do dané jazykové mutace
národní zváštnosti nemají být řešeny “natvrdo” ve zdrojovém kódu
popsat v konfiguračních souborech (viz poslední část přednášky)
využívat služeb operačního systému
řešení problémů typu řazení, formátování čísel, datumů apod.
využívat specializované třídy/metody z Java Core API respektující národní zvyklosti
podrobný rozbor viz Java Tutorial
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í
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
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
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í
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ě
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
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)
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
)
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(); } }
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(); } }
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
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é
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
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);
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
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
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
výpis jako běžné reálné číslo %f
, např. pro
double d = 1234.567;
String.format("d = %f%n", d); // d =
1234,567000
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
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
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 |
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)
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