Elméleti alapozás
Java-nyelv néhány összetett adattípusa
String
Egy program működéséhez gyakran van szükségünk a felhasználótól érkező vagy felé irányuló szöveges üzenetekre, ezek általában szavak, kifejezések, mondatok, egyéb szimbólumok. A számítógép számára azonban tökéletesen mindegy, hogy ezen üzenetek értelmesek-e vagy sem, alapértelmezésben ezt nem maga a számítógép, azaz az operációs rendszer dönti el, hanem a program megírásakor -önmaga jól felfogott érdekében-, a programozó kezeli le.
(Mostanában kezdenek elterjedni a verbális szövegértelmező alkalmazások, amelyek már rendszerszinten képesek ilyen funkciót ellátni, például Win7-ben a Speech Recognition. A beszédfelismerés lehetősége egyébként már felveti a mesterséges intelligencia kérdését is.)
A kommunikációs szövegek, szakszóval stringek, karakterfüzérek értelmezése a programírás egyik fontos és fárasztó művelete, amely során a programot "bolondbiztossá" kell tennünk, hogy ne kerülhessen a rendszerbe rossz vagy értelmezhetetlen bemeneti adat.
Természetesen rengetegféle olyan program létezik, amelyik nem végez szöveges kommunikációt a felhasználóval, de a String adattípusra ilyen-olyan okok miatt legtöbbször szükségünk van. Éppen ezért az egyik legtöbbet használt, immár összetett adattípus a String, amelyet tehát leginkább karakterfüzérnek, karaktersorozatnak fordíthatunk. Valóban: a String valamilyen belső logika szerint összetartozó karakterek csoportja (amely tehát a felhasználó számára értelmes szöveggé áll össze).
A Java nyelv egyik ősében, a C nyelvben még nem volt String-kezelés, hanem a char* deklarációval került megvalósításra, amely számos hibalehetőség forrása volt: vagy kicsi lett a String számára lefoglalt memóriahely vagy egyszerűen elfelejtettük a helyfoglalást.
Mivel a String összetett adattípus, a Java már objektumként kezeli és így is hozzuk létre a new paranccsal, szigorúan kettős aposztrófok (" ") közé téve a szöveget:
String st = new String("Java");
public class Main {
public static void main(String[] args) {
String st = new String("Java");
System.out.println(st);
}
}
Végeredmény:
Java
A deklaráció -amint azt a Javában már megszokhattuk-, szigorú szabályok szerint történik, erre egyetlen kivétel a keletkezett objektum neve (referenciája), a fenti esetben st, ezt a szokásos programozási névkonvenciók figyelembevételével mi adtuk neki. De természetesen String objektumot létrehozhatunk üresen is: ekkor nem töltjük fel karakterekkel, hanem csak a memória-címfoglalás történik meg:
String st = new String();
A létrehozott String egy különálló, csakis a String osztály által használható memóriaterületre kerül (String pool). Mi történik akkor, ha itt összehasonlítás céljából létrehozunk egy másik, ugyanolyan példányt?
Az összehasonlítás memóriacím és tartalom alapján is megtörténhet.
public class Main {
public static void main(String[] args) {
String st1 = new String("Java");
String st2 = new String("Java");
System.out.println(st1 == st2);
}
}
Végeredmény:
false
Ebben az esetben a Java pusztán lokáció, azaz memóriahely-foglalás,
valójában memóriacím szerint hasonlította össze a példányokat. A
végeredmény ebből a szempontból csakis
false lehet, mert a 2
objektum memóriacíme különböző (hiszen különálló objektumok).
Ha tartalmilag szeretnénk az összehasonlítást megtenni, az
equals() függvényt kell bevetnünk:
public class Main {
public static void main(String[] args) {
String st1 = new String("Java");
String st2 = new String("Java");
System.out.println(st1.equals(st2));
}
}
Végeredmény:
true
Tartalmilag a 2 objektum valóban azonos, a végeredmény tehát true lesz.
A nemzetközi helyzet feszülődik, ugyanis String objektumot egyszerű érték-hozzárendeléssel (=) is létrehozhatunk...
String st = "Java";
...és ekkor 2 azonos tartalmú String objektumot létrehozva...
String st1 = "Java";
String st2 = "Java";
...a Java-nyelv egyszerű összehasonlítással is azonosnak fog értékelni, mert a 2 objektum (a String pool-ban) egyetlen memóriaterületre mutat (ahol éppen a "Java" String tárolódik):
public class Main {
public static void main(String[] args) {
String st1 = "Java";
String st2 = "Java";
System.out.println(st1 == st2);
}
}
Végeredmény:
true
A deklaráció még kissé tovább is bonyolítható:
String st1 = "Java";
String st2 = "Ez a " + st1 + " java";
Az st1 objektum létrehozása után értéke ("Java") néhány további karakterfüzérrel bekerül st2 objektumba és ez lesz kiírva:
public class Main {
public static void main(String[] args) {
String st1 = "Java";
String st2 = "Ez a " + st1 + " java!";
System.out.println(st2);
}
}
Végeredmény:
Ez a Java java!
String objektum akkor is létrejön, ha például a System.out.println() függvényben kerül felhasználásra:
public class Main {
public static void main(String[] args) {
System.out.println("Java");
String st = "Java";
System.out.println("Java" == st);
}
}
Végeredmény:
Java
true
Nyilvánvalóan egy String objektum csakis karaktereket tartalmaz, ám abból általában többet. Ebből következve van lehetőségünk létrehozandó String objektum bemeneti paramétereként karaktertömböt (char[]) megadnunk:
char[] java = new char[]{'J','a','v','a'};
String st2 = new String(java);
Az összehasonlítások a már megállapított eredményeket fogják adni:
public class Main {
public static void main(String[] args) {
String st1 = new String("Java");
char[] java = new char[]{'J','a','v','a'};
String st2 = new String(java);
System.out.println(st1 == st2);
System.out.println(st1.equals(st2));
}
}
Végeredmény:
false
true
Ha a Java API-dokumentációba kicsit beleássuk magunkat, akkor felfedezhetjük, hogy a Java minden keletkezett String objektumot különálló konstans karaktertömbökben tárol (private final char array[]).
A karakterek indexelése a tömbökhöz hasonlóan mindig 0-val fog kezdődni, ezt az indexszámot kapja majd a String 1. karaktere.
A keletkezett objektum a private és final hozzáférés-módosítóknak köszönhetően megváltoztathatatlan lesz (inmutable), ha ezt igényeljük, akkor az objektumról másolat fog létrejönni és az lesz megváltoztatva.
Ez a tulajdonság teszi lehetővé, hogy egy String objektumhoz további, beépített metódusokat kapcsolhassunk és ezek számunkra korrekt eredményeket szolgáltassanak. A legfontosabb String metódusok:
-
equals() - megvizsgálja, hogy a paraméterként bevitt String objektum tartalmilag egyezik-e az eredetivel,
-
length() - visszaadja a String hosszát,
-
isEmpty() - megvizsgálja, hogy a String objektum üres-e (tartalmaz-e karaktereket),
-
indexOf() - visszaadja a keresett karakter indexszámát,
-
charAt() - megmondja, hogy megadott indexnél melyik karakter található,
-
substring() - a megadott paraméterek alapján az eredetiből kiemel egy kisebbet,
-
contains() - megvizsgálja, hogy a String objektum tartalmazza-e a paraméterként megadott karakterszekvenciát,
-
replace() - új karaktereket cserél fel régiekre,
-
compareTo() - tartalmi összehasonlítás, mint az equals() esetében,
-
concat() - az eredeti objektumhoz egy paraméterként megadott karakterszekvenciát fűz hozzá.
Az összehasonlítások a már megállapított eredményeket fogják adni:
public class Main {
public static void main(String[] args) {
String st1 = new String("Cirbolya és Borbolya");
String st2 = new String("Cirbolya");
System.out.println(st1.equals(st2));
System.out.println(st1.length());
System.out.println(st1.isEmpty());
System.out.println(st1.indexOf("C"));
System.out.println(st1.charAt(0));
System.out.println(st1.substring(9, 11));
System.out.println(st1.contains("bolya"));
System.out.println(st1.replace('o', 'i'));
System.out.println(st2.compareTo("Cirbolya"));
System.out.println(st2.concat(" és barátai"));
}
}
Végeredmény:
false
20
false
0
C
és
true
Cirbilya és Birbilya
0 (azaz hasonlóak)
Cirbolya és barátai
A fenti metódusok kiválóan teljesítik feladatukat, hiszen folyamatosan követik azt a belső logikát, egyúttal elvárást, miszerint csakis azonos vagy valamilyen módon hasonló adattípusokat, illetve közös tulajdonságaikat érdemes egymással összehasonlítanunk. Így működhet együtt például egy összetett String objektum és egy karakter, mint egyszerű adattípus.
Ez a belső logika azonban gyakran szenved törést, sokszor önnön tudatlanságunk miatt; ezt már minden programozó megtapasztalta. Adott például egy String objektum:
String st = "1";
Vajon ez a deklaráció szám-e vagy String?
Természetesen az utóbbi, mert ha megpróbáljuk őt és egy számot összehasonlítani, akkor a fordító azonnal visítani fog:
Sőt, "hagyományosnak" mondható típuskényszerítés (casting - kasztolás) sem fog működni:
System.out.println(szam == (int)st);
Végeredmény:
hibaüzenet
A megoldás -hogyha ez egyáltalán lehetséges-, hogy a bevitt String-et át kell alakítani, konvertálni számmá (Integer.parseInt()):
public class Main {
public static void main(String[] args) {
String st = new String("1");
int szam = 1;
int atalakitottSzam = Integer.parseInt(st);
System.out.println(szam == atalakitottSzam);
}
}
Végeredmény:
true
Egy kissé másképpen:
public class Main {
public static void main(String[] args) {
String st = new String("1");
int szam = 1;
System.out.println(szam == Integer.parseInt(st));
}
}
Végeredmény:
true
A fenti kódokban tehát a számok összehasonlítása már működni fog és korrekt eredményeket fog szolgáltatni.
A Java-nyelven belül természetesen többféle, legtöbbször magasszintű lehetőség áll rendelkezésre a felvetődött problémák megoldására. A fenti int-String típusütközés például sok mással együtt igen kényelmesen lekezelhető a beépített függvényeknél még nem említett valueOf() függvény segítségével. Ez a metódus a következő adattípusokat képes zökkenőmentesen egy String objektumba irányítani:
-
boolean,
-
char,
-
char[],
-
int,
-
long,
-
float,
-
double,
-
Object.
public class Main {
public static void main(String[] args) {
char karakter = 'a';
int szam = 2;
boolean valasztas = true;
String st1 = String.valueOf(karakter) + String.valueOf(szam)
+ String.valueOf(valasztas);
System.out.println(st1);
}
}
Végeredmény:
a2true
További érdekes, de kissé problematikus adalék, ha String objektum létrehozásához a fenti valueOf() függvény kizárásával byte típusú tömböket használunk fel, mert ebben az esetben ügyelnünk kell az alapértelmezett karakterkódolásra, amely gépenként, operációs rendszerenként, fejlesztési projektenként különböző lehet. Az alábbi kódban egy byte típusú tömböt hozunk létre és kezdeti értékeit ({65, 66, 67, 68, 69}) átadjuk az st1 objektumnak, amelynek hatására kimeneti adatként ABCDE karaktereket fogunk kapni:
public class Main {
public static void main(String[] args) {
byte[] tomb = {65, 66, 67, 68, 69};
String st1 = new String(tomb);
System.out.println(st1);
}
}
Végeredmény:
ABCDE
Ám ha rosszul van beállítva karakterkódolás vagy olyan karaktereket adunk be, amelyek az alapértelmezett karakterkódolás alapján nem értelmezhetők, akkor vagy értelmezhetetlen kimeneti karaktereket, vagy "nem támogatott kódolási kivétel" című hibaüzenetet fogunk kapni (UnsupportedEncodingException).