String
je samozřejmě typ neměnný
(immutable). Jakmile je jednou vytvořen a naplněn hodnotou, hodnota
tam stále zůstává. Jsou pro to dobré důvody, např. lze tyto objekty
pohodlně a bez starostí sdílet přes více metod či vláken.
Operace, která by obsah změnila - např.
toUpperCase
, vrátí odkaz na nový objekt
řetězce.
Modifikovatelnými řetězci jsou
StringBuilder
a
StringBuffer
.
Níže uvedený úryvek kódu se snaží zabránit tomu, aby výjimka pronikla ("utekla") z metody main. Způsob, jakým je to uděláno, ale nic neřeší. jen se vypíše chybová hláška z výjimky - a navíc je otázka, zda se má v takovém případě vypsat dosud načtená část seznamu (záleží na požadavcích zadání):
public static void main(String[] args) {
...
try{
BufferedReader input = new BufferedReader(new FileReader(f));
String line = new String(input.readLine());
while (line!=null){
...
}
...
} catch (IOException e) {
System.out.println(e);
}
p = employee.first();
while (p!=null){
System.out.println(p);
p = employee.higher(p);
}
}
Pokud zadání explicitně nestanoví, že se mají vypsat aspoň správně načtené záznamy, je lepší prostě výjimku propustit z main:
public static void main(String[] args) throws IOException {
...
BufferedReader input = new BufferedReader(new FileReader(f));
String line = new String(input.readLine());
while (line!=null){
...
}
p = employee.first();
while (p!=null){
System.out.println(p);
p = employee.higher(p);
}
}
Když už se zachytí, musí se na ni reagovat - SMYSLUPLNĚ reagovat.
Výjimka je obecně technikou, jak zachytit a reagovat na mimořádnou událost v programu. Zachycení výjimky by nemělo nahrazovat jednoduchý test. Občas je to ale vidět (sic!):
public void writeInfo(Person p) {
try {
System.out.print("Info about a Person: ");
System.out.println(p.getInfo);
} catch(NullPointerException npe) {
System.out.println("Error: No person given");
}
}
public void writeInfo(Person p) {
if(p == null) {
System.out.println("Error: No person given");
} else {
System.out.print("Info about a Person: ");
System.out.println(p.getInfo);
}
}
Kód je dokonce kratší a hlavně čistší. Všimněte si také podmínky - je psána tzv. v aserci (bez negace). Je to čitelnější než:
public void writeInfo(Person p) {
if(p != null) {
System.out.print("Info about a Person: ");
System.out.println(p.getInfo);
} else {
System.out.println("Error: No person given");
}
}
Velmi častou chybou začátečníka, bohužel navíc podpořenou špatnými
vzory z jiných jazyků či prostředí, je použití operátoru
==
k porovnání řetězců na shodu po znacích. Chceme-li
zjistit, zda dva řetězce jsou obsahově stejné, tj. stejně dlouhé a mají
na stejných pozicích stejné znaky, používáme volání metody equals, která
je pro třídu String přetížená tak, aby se dosáhlo požadovaného chování.
Equals není však ve všech případech ideální. Z níže uvedených variant není ideální žádná. Proč?
je trochu zbytečný "kanón na vrabce". Navíc selže s
výjimkou, je-li name == null
.
je asi většinou špatně, testuje totiž, zda
name
ukazuje na identický řetězec
"John"
. Bohužel to často i "funguje", např.
když program vypadá takto:
String name = "John";
...
if (name == "John") {
// opravdu sem dojde, protože Java si oba výskyty literálu (tj. konstanty) "John" internalizuje,
// tj. nahradí jedinou instancí
System.out.println ("It is really John");
}
Ale když jeden z řetězců zkonstruujeme (substring, zřetězení), fungovat to nebude:
String origNameFirst = "John ";
String origName = origNameFirst + "Paul Willard";
String name = origName.substring(0, 4);
if (name == "John") {
// nedojde sem
System.out.println ("It is John");
}
už vypadá dobře, ale selže s výjimkou, je-li name
== null
. A nač to testovat zvlášť, když lze napsat
rovnou if ("John".equals(name))
, které tento
neduh nemá.
rozumné řešení, robustní i proti name ==
null
-- pak prostě vrátí
false
.
Začátečníka láká použít pro spojování řetězců operátor +, a to i v případě, že se za existující řetězec připojují další znaky či řetězce nebo se zřetězení dokonce opakuje v cyklu.
Co však zřetězení s = s + " novy";
udělá?
Má s
jako odkaz na objekt s původním
řetězcem, " novy"
jako literál / hotový řetězec v
paměti.
Vytvoří nový řetězec jako prázdný
StringBuilder
,
ty původní (s
a " novy"
)
do něj přikopíruje.
Převede StringBuilder
na
String
.
Původní objekt s
se "zapomene".
A toto provádět v cyklu? To je trochu moc operací, ne!?
První příklad na Java Anti-patterns také ukazuje tuto extrémně neefektivní konstrukci.
Něco jako s += " novy"
sice vypadá hezky, ale v
tomto ohledu vůbec nepomůže. Jediným rozumným řešením je v cyklu použití
StringBuilderu (příp. StringBufferu):
StringBuilder sb = new StringBuilder(persons.size() * 16); // well estimated buffer
for (Person p : persons) {
sb.append(p.getName);
}
Obě třídy představují modifikovatelné řetězce.
Normální String
je immutable,
nemodifikovatelný a každá změna - např. pripojení nebo odebrání
znaku/ů - znamená konstrukci řetězce nového.
Historicky starší je StringBuffer
, který je
synchronizovaný, tzn. může se nad jedním pracovat z více vláken,
aniž by se porušila atomicita změnových operací (nedostane se do
inkonzistentního stavu).
Pokud toto nepotřebujeme, resp. pohlídáme si přístup jinak, je
lepší použít rychlejší StringBuilder
.
Rozumíme tím převod znakové reprezentace určité hodnoty na hodnotu typu číslo, příp. jiného:
String myNumberInString = "-123.456";
double myNumber = Double.parseDouble(myNumberInString); // převede správně na double
Obdobně postupujeme u cílových typů float, byte, short, int, long a boolean.
Variantně lze použít i konstruktory objektových "reprezentací":
String myNumberInString = "-123.456";
double myNumber = new Double(myNumberInString); // převede správně na double přes objekt a auto-unboxing
System.out.println ("test4 -- " + myNumber);