Elméleti alapozás

Típuskonverzió avagy casting nemcsak Hollywoodban

 

Bevezetés

Implicit típuskonverzió

Explicit típuskonverzió

Bővítő típuskonverzió

Szűkítő típuskonverzió

 

Bevezetés

 

A legtöbb programozási szakkönyv már az első oldalakon megemlíti, hogy a Java erősen típusos nyelv. Természetesen ettől az elhamarkodott kijelentéstől a kezdő csak halálra rémülni tud, hiszen...

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

...ez a számítógépes fogalom eléggé bonyolult, ezért nehezen érthető. Ebben a fejezetben főként a kezdők számára azért lesz alapszinten ismertetve, mert néhány jellegzetes típuskonverziós problémába hamar belefuthatunk. Egyúttal hangsúlyozottan alapszinten értelmezzük is a fogalmat.

 

A számítógép különböző típusú, összetettségű és bonyolultságú bemeneti adatot vár számításaihoz, ezek fogadását a Java programozási nyelv alapértelmezésben képes biztosítani.

 

Például egy magas szinten kommunikáló felhasználói program nemcsak numerikus bemeneti adatokat (számokat) kérhet be, hanem betűket, szavakat, egyéb vezérlőkaraktereket is. Vagy gondolhatunk bármelyik numerikus közigazgatási azonosítónkra (adószám, személyi szám, TB-szám, stb.), amelyet nem kérhetünk be  számként, hiszen valójában nem vagy csak részben azok (éppen ezért legtöbbször String típusként vannak deklarálva).

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Sokszor van szükség a különböző adattípusok közti átváltásokra, ezt típuskonverziónak nevezzük.

 

Implicit típuskonverzió

 

A Java-rendszer a maga módján igyekszik a típuskonverziót zökkenőmentesen és automatikusan megoldani, ezt implicit típuskonverziónak nevezzük. A magas szintű automatika ellenére azonban konverziós hiba a program összetettsége miatt hamar jelentkezhet rossz vagy csonkolódott végeredmény, értelmezhetetlen karaktersor, visítozó hibaüzenet és egyéb tünet formájában.

 

Elméletileg a fenti meghatározás után már azt is megérthettük, hogy mit értünk a fejezet kezdőmondatának állítása alatt: a Java erősen típusos nyelv.

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Ahol lehetséges vagy műveletileg indokolt, a fordító figyeli a típusegyezéseket és megpróbálja azt zökkenőmentesen, implicit típuskonverzióval megoldani.

 

Explicit típuskonverzió

 

Más esetekben a típusok egyeztetése nem egyértelmű a rendszer számára vagy a típuskonverziót éppen  magunknak kell ráerőltetnünk a rendszerre, ezt explicit típuskonverziónak, másnéven kasztolásnak (casting), típuskényszerítésnek nevezzük, hiszen a művelet során mintegy „kényszerítjük” a fordítóprogramot a típuskonverzióra.

 

"Casting is the forceful conversion of one data type to another data type."

 

"Casting is the process of forcefully making a variable behave as a variable of another type."

 

Ennek ő, mármint a Java-rendszer (a JVM), illetve a fordító néha engedelmeskedik, néha nem. Az ok sajnos eléggé szerteágazó, de legtöbbször az a probléma, hogy az egymásra erőltetett típusok valójában nem kompatibilisek egymással, amelynek eredménye legtöbbször valamilyen szintű adatcsonkolódás, adatvesztés.

 

Bővítő típuskonverzió

 

Egyszerű adattípusok esetében a Java típuskonverziós automatika felfelé, azaz amikor hasonló típusokat nagyobb értéktartományba konvertálunk, problémamentesen, azaz adatvesztés nélkül működik. Az explicit típuskonverzió (kasztolás) demonstrálására először nézzük meg az alábbi futtatható, de hibás Java-kódot:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    int a = 2147483647;
    int b = 2147483647;
    System.out.println(a + b);
    }
}

 

Hibás végeredmény:

-2
 

A fenti Java-kódban a végeredmény szavatoltan túl fogja lépni az int adattípus tárolókapacitását, ezért hibás végeredményt fogunk látni: -2. A megoldás: kasztolni kell long típusra, hiszen ezen adattípusok tárolókapacitásai:

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    int a = 2147483647;
    int b = 2147483647;
    System.out.println((long)a + b);
    }
}
 

Végeredmény:

4294967294

 

Számítástól függően könnyen futhatunk ilyen "túlfutásos" hibába, hiszen kevesen jegyzik meg a különböző adattípusok pontos paramétereit.

 

Gondoljunk bele: nyilvánvalóan vannak ésszerű és ésszerűtlen kasztolási kísérletek, ezek részben a Java-nyelv belső felépítéséből, részben pedig a programozás általános törvényszerűségeiből következnek. Ennek demonstrálására vegyünk egy másik példát.

 

Amint az a Java-nyelv (egyszerű) adattípusai - Karakter című fejezetben említésre került, a Java-nyelvben a karakterek előjelnélküli integer számok formájában kerülnek tárolásra, az a betű kódja például 67. Ezért karaktert számformátumban is megadhatunk, a kiírás (System.out.println(a)) karaktert fog eredményezni:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    char a = 67;
    System.out.println(a);
    }
}
 

Végeredmény:

a

 

A kasztolás-típuskényszerítés segítségével azonban a kiírás számot fog visszaadni:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    char a = 67;
    System.out.println((int)a);
    }
}
 

Végeredmény:

67

 

Vegyünk egy harmadik példát, egy átlagos magyar autórendszámot:

 

String autorendszam = ”MAK-344”;

 

Ebben az esetben a rendszám valójában különböző adatait (3 nagybetű – kötőjel – 3 szám) egységesen String adattípusként kezeljük és nincs is értelme numerikus értékké (számmá) kasztolnunk az utolsó 3 számértéket, hiszen maga a nyilvántartás sem úgy kezeli őket, azaz nem végez el rajtuk semmilyen matematikai műveletet. (Lehet, hogy elvégez, mert a rendszámkiadás mögött van valamilyen rejtett algoritmus, ám nekem erről nincs tudomásom.) Hiszen bár igaz, ám közigazgatásügyileg mégsem értelmezhető a következő művelet:

 

MAK-344 + 10 = MAK-354.

 

(Zárójelben jegyzem meg, hogy az esetleges numerikus kasztolásnak egyedül talán akkor lehet értelme, ha a bevitt String adatot ellenőrizni akarjuk, azaz valódi rendszám került-e a rendszerbe. Itt lényeges lehet az az algoritmus, amely a szabványos rendszámformátum /3 nagybetű – kötőjel – 3 szám/ karakterenkénti leellenőrzését végzi el. Ezt most nem boncolom tovább, de az is lehetséges, hogy az ellenőrzés megoldható numerikus kasztolás nélkül is.)

 

www.informatika-programozas.hu - Ezt most meg kell tanulni!

 

Ehhez hasonló, de már implementált (konkrétan megvalósított, leprogramozott) problémát tanulmányozhatunk a Nyilvános adatok a szupertitkos személyi számból című fejezetben.

 

Szűkítő típuskonverzió

 

Szűkítő típuskonverzió esetén a nagyobb tárolókapacitású típusból lépünk vissza egy kisebbe. Ez programozás-technikailag "életveszélyes" kategória, hacsak programozó zseniként nem tudjuk fejből az összes adattípus adattárolási határait és nem tartjuk fejben programunk minden egyes változójának összes állapotát...

 

Szóval -mivel manapság már csak néhány igen speciális esetben kell figyelni a számítógép hardveres terhelhetőségét-, a szűkítő típuskonverziót inkább ne alkalmazzuk és a változók deklaráláskor alapértelmezésben mindig a nagyobb tárolókapacitású típust válasszuk.

 

Az adatvesztés demonstrálására nézzük meg az alábbi futtatható Java-kódot, amelyben a double típusú PI értékét (3.141592653589793) kasztoljuk int típusba:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    double PI = 3.141592653589793;
    System.out.println((int)PI);
    }
}
 

Végeredmény:

3

 

Ez persze nagyon durva hibának számít, hiszen lebegőpontos számot nem szabad egész típussá konvertálnunk. Azonban mivel maga a PI értéke is végtelen...

 

3.14159265358979323846264338327950288419716939937510...

 

...annak megadásakor átléphetjük a double típus adattárolási tartományát is:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    double PI = 3.14159265358979323846264338327950288419716939937510;
    System.out.println(PI);
    }
}
 

Végeredmény:

3.141592653589793

 

A hibaanalízist tovább finomíthatjuk: adatvesztés léphet fel például double adattípusból a másik lebegőpontos adattípusba (float) való konverzió esetén is:

 

www.informatika-programozas.hu - Futtatható Java-kód!

 

 

 

 

 

 

 

 

public class Main {
    public static void main(String[] args) {
    double PI = 3.141592653589793;
    System.out.println((float)PI);
    }
}
 

Végeredmény:

3.1415927

 

A programokon belüli adatáramlásnak tehát kasztolási szempontból sokféle átmenete és kimenete lehet, sokuk nem is magából a Java-nyelv lehetőségeiből, hanem a modellezett valóság speciális körülményeiből következik. Az bizonyos, hogy még a legtapasztaltabbak is belefutnak olykor 1-2 speciális kasztolási problémába, ám az Eclipse-platform magas szinten képes minket támogatni ilyen jellegű hibák detektálásában, sőt sok esetben a hibamegoldásban is.