Minden nyelv tartalmaz olyan részeket, amelyeket egyszerűen tudni kell - ezekre a részekre szokták mondani: száraz az anyag. De tudjuk, hogy nincs olyan száraz anyag, amit ne lehetne egy kis sörrel könnyebben emészthetővé tenni... készítsünk hát magunk mellé egy üveggel a jobbik fajtából, és feküdjünk neki az ivásnak... illetve az olvasásnak... :)
A programozási nyelvek nyelvtana általában négy fogalommal jellemezhető:
operátorok, amelyek műveleteket határoznak meg az előző két kategória elemei között, tehát ha egy vödörbe öntünk három üveg sört, akkor összegeztük a három üvegnyi sör tartalmát, amelyet beletettünk egy vödörbe.
A változókat és a típusokat is el kell neveznünk, hogy hivatkozni tudjunk rájuk, vagyis pontosan azonosítani tudjuk őket - ezért őket közös néven azonosítónaknevezzük. Mivel Java nyelven az osztályok egyben típusok is, ezért az osztályok, a típusok és a változók elnevezése jobbára azonos szabályok szerint kell történjen:
Gyakorlatilag bármilyen hosszú Unicodebetű és szám kombinációt használhatunk, de:
nem kezdődhet számmal
nem tartalmazhat írásjeleket - kivéve a $ (dollár) és az _ (aláhúzás) jelet
nem lehet benne se szóköz, se egyéb speciális karakter (’^’, ’°’, stb.)
Általános irányelv, hogy a lehetőség ellenére a dollár jelet nem teszünk a változó nevébe, és nem kezdjük aláhúzás jellel.
Helyes azonosítók:
sor Sör sÖr ser _sör sör2 egy_sör $sör |
Hibás azonosítók:
2sör egy sör kis-sör super sör sör: Sört! |
A Java nyelv kulcsszavait nem használhatjuk azonosítóként, ezek a következők:
abstract boolean break byte case catch char class const continue default do double else enum extends false final finally float for goto if implements import instanceof int interface long native new package private protected public return short static super switch synchronized this throw throws transient true try void volatile while |
A Sun elkövette azt a hibát, hogy az Java 5.0 verziójában egy új kulcsszót hozott be: ez a kulcszó az enum. Ez önmagában nem probléma, de ezt nem foglalta le kulcsszóként a nyelv megelőző verzióiban, mint a const és a goto kulcsszót, amelyeket a nyelv azóta sem használ. Így az 1.5.0 JRE alatt ugyan futottak azon régebbi programok, amelyek ezt az enum kulcsszót azonosítóként használták, de az 1.5.0 JDK alatt nem lehetett ezeket lefordítani, mert a fordító hibát jelzett.
A Java nyelv - hasonlóan a Pascal és a C/C++ nyelvhez - alapvetően bináris számítógépeken futó programok készítésére használatos: egyszerűbb a mi fogalmainkat használva megírni egy programot, amelyet aztán lefordítunk a számítógép nyelvére (gépi kód), amelyet ugyan meg lehet tanulni, de hatékonyabban tudunk felhasználói igényeket kielégíteni, ha magas szinten programozunk. A bináris számítógépek hatékony kihasználásához nem feltétlen szükséges ismernünk a számítógép magját alkotó CPU által kezelhető adattípusokat, ezt megteszik helyettünk az említett programnyelvek, mégpedig a primitív típusok által.
Egy bináris (más néven digitális) számítógép számára minden a világon két számból épül fel: 0 és 1, amelyeket bináris számnak (binary digit, vagyis bit) nevezünk. Bármi, amit a számítógépünkön találunk, az mind bináris számok különféle kombinációja és sorozata, s a kettes számrendszerírja le a kombinációk és sorozatok kódolását. A különféle hosszúságú kettes számrendszerben kódolt számoknak a szakma neveket adott, nézzük át őket (a teljesség igénye nélküli):
bit (boolean) - bit: a legkisebb egység, értéke lehet 0 vagy 1, illetve igaz (true) vagy hamis (false).
byte - bájt: 8 bináris szám által reprezentált szám, értéktartománya 28, vagyis 0 – 255 vagy -128 – 127.
char - karakter: egy bájt által reprezentált karakter, kódolása általában ASCII.
word - egy gépi szó: két bájt, 16 bit által reprezentált szám, értéktartománya 216, vagyis 0 – 65535 vagy -32768 – 32767.
integer - egész szám: egy vagy több gépi szó által reprezentált szám, értéktartománya tipikusan 232, vagyis 0 – 4 milliárd vagy -2 milliárd – 2 milliárd.
long (integer) - nagy egész szám: általában négy gépi szó által reprezentált szám, értéktartománya általában 264, vagyis 0 – 16 trillió vagy -8 trillió – 8 trillió.
real (float) - valós szám: két gépi szóból álló egyszerű (lebegőpontos) valós szám, értéktartománya előjel nélkül ~1.4-45 – ~3.438.
double - nagy valós szám: négy gépi szóból álló (lebegőpontos) valós szám, értéktartománya előjel nélkül: ~4.9-324 – ~1.8308.
A lebegőpontos számok használata során ismernünk kell a trükköt, hiszen nyilvánvaló kell legyen, hogy 32 bitbe nem fér el minden valós szám 1.4-45 és 3.438 között. A megoldás egyszerű: az egész számokat tároló megoldások a szám összes számjegyét tárolják, míg az utóbbi kettő - valós szám tárolására használt számkódolási eljárás - kerekít. A megadott tartományon belül tetszőlegesen nagy számot tudunk tárolni, de csak 7-8 számjegy pontossággal. További trükk, hogy a nulla értéket csak megközelíteni tudjuk, a legkisebb érték és a nulla között mindig lesz egy tartomány, amelyet nem tudunk használni. Az alábbi számegyenesre tekintve láthatjuk, hogy a kék pontok jelentik a kódolás határértékeit, a zöld részen tudunk számokat tárolni 7-8 számjegy pontossággal, és a piros területeket már nem tudjuk tárolni:
A Java nyelvben a felsorolás sorrendjében a boolean, a byte, a char, a short, az int, a long, a float és a double használatos, ezeket nevezzük primitív típusnak. Fontos tudni, hogy nincs előjel nélküli típus, az összes primitív típus előjeles szám, nézzünk példát ezen típusok használatára.
Vegyük elő a NetBeans környezetben a már létrehozott Kocsma projektet, és keressük meg benne a kocsma csomagot, majd azon belül a Mainosztályt, majd az alábbi programot írjuk bele:
package kocsma; public class Main { public static void main(String[] args) { // Ide fogjuk írni az alábbi programrészleteket } } |
A // kezdetű sor helyére kell írni az alábbiakban megemlített programrészleteket, majd az F6 gomb megnyomásával futtatni tudjul a (helyesen megírt) programot.
A boolean típust akkor használjuk, ha egy eldönthető kérdésekre adott válasz csak igen/igaz vagy nem/hamis lehet, a boolean ugyanis nem képes a talán vagy a nem tudom válaszokat értelmezni. A C/C++ nyelvektől eltérően egy boolean típusú változó értéke csak a true vagy a falseértéket veheti fel, a számszerűen jelzett értékeket nem képes értelmezni.
boolean igaz = true; boolean hamis = false; boolean válasz; válasz = igaz; |
Vannak olyan műveletek, amelyek természetszerűleg booleanértéket adnak eredményül:
válasz = 9 < 3; System.out.println(válasz); válasz = 3 < 10; System.out.println(válasz); |
Az első esetben false (hamis) eredményt kapunk, hiszen a kilenc nem kisebb, mint a három; a második esetben az eredmény true (igaz), hiszen a három kisebb, mint a tíz.
A példában említett true és false literál, vagyis a nyelvbe épített és értékkel bíró fogalom. Láthatunk továbbá több deklarációt és értékadásokat is - ezekről később több szót ejtek.
A byte típust akkor célszerű használnunk, ha nulla közeli egész számokkal számolunk, hiszen értéktartománya mindössze a -128 és a 127 közötti számokra érvényes:
byte szám; szám = 12; szám = -12; szám = -128; szám = 127; |
A példában láthatunk egy deklarációt és négy értékadást, ahol a számnevű változónak adunk más-más értéket, s itt már szám literálokat használunk. Fontos tudni, hogy a primitív típusok képesek a csöndes túlcsordulásra:
byte szám; szám = 127; szám++; System.out.println(szám); |
A rövidke programrészlet eredménye -128 lesz, nem pedig a matematikailag várható 128. Ennek oka, hogy a változó túlcsordult, túllépte az értékkészlete egyik határát, és ezért értékül az értékkészletének másik határértékét veszi fel. Nagyon oda kell figyelnünk erre a tulajdonságra, mivel a programunk mindezt csöndben teszi, legtöbbször igen keserves és hosszú munkával tudjuk csak megkeresni a hiba okát.
A short, az int és a long teljesen azonos módon használható, mint a byte, a különbség mindössze az értéktartományban van (illetve abban, hogy a long típus literál használatához a szám mögé egy lkaraktert kell írnunk):
Ha nem adunk meg egy egész szám literál után módosító karaktert, akkor a nyelv szabályai szerint a fordító megpróbálja konvertálni arra a típusra, ami a használt változó típusa, egyéb esetben a literál típusa intlesz.
A többi nyelvhez hasonlóan a char egy öszvér adattípus, alapvetően számokat tartalmaz, de ha az értékét kiírjuk, akkor karaktereket kapunk eredményül. A Java nyelv úttörő volt a tekintetben, hogy a char típus alapja nem a 8 bites byte, hanem az 32 bites integer, hiszen a Java nem ASCII alapú, hanem UTF-8 az alapértelmezett karakter kódolása, mivel ismert világ összes karaktere nem fér el 8 biten.
char betű = 64; System.out.print(betű); betű = '\n'; System.out.print(betű); betű = 'a'; System.out.print(betű); betű = '\t'; System.out.print(betű); betű = '\\'; System.out.print(betű); betű = '\''; System.out.print(betű); betű = '"'; System.out.print(betű); betű = '\070'; System.out.print(betű); betű = '\u003F'; System.out.print(betű); |
Nézzük meg ezt a gyakorlatban:
Eredményül az alábbi (ránézésre értelmetlen) karakter halmazt kapjuk:
@ a \'"8? |
Lássuk a dekódolást:
A @ karakter UTF-8 (és ASCII) kódja a 64 szám, ezért a @ az első kiírt karakter.
A \n karakter az új sor kódja, ez nem látható karakter, viszont a (szintén nem látható) kurzort a következő sorba teszi.
Mint láthatjuk, eléggé változatos módon tudunk karaktereket megadni, azonban a char kettős természetét az is meghatározza, hogy tudunk vele műveleteket végezni:
char betű = 64; betű++; System.out.print(betű); betű += 0; System.out.print(betű); betű += '0'; System.out.print(betű); |
Az eredmény az AAq karaktersorozat lesz, hiszen a 64 az ’@’ karaktert kódolja, amihez ha hozzáadunk egyet, akkor ’A’ betű lesz, amit a 65 kódol. Ha ehhez hozzáadunk nullát (0), akkor az eredmény továbbra is 65 marad, de ha karakteresen adunk hozzá nullát (’0’), amelynek a számszerű értéke 48, akkor az eredmény 113 lesz (mivel 65+48 az 113), amely szám a ’q’ karaktert kódolja.
A karakter literálok két szimpla idézőjel közötti karakterek. Általában egy karakter, de ha az első karakter visszaper jel (\), akkor több karakter is lehet a két idézőjel között. Érdemes megtanulni a speciális karakter literálok használatát, mivel használatuk sok esetben szükséges a szövegfeldolgozáshoz.
A valós számok használata akkor kerül előtérbe, amikor olyan számolásokat végzünk, amelyek nem végezhetők el könnyedén egész számokon. A valós számok használata lassíthatja a program működését, mivel ezek kezelése néhány processzoron nincsen kellően gyorsítva, ezért csak akkor használjunk valós számokat, amikor feltétlenül szükséges. Nézzük a float típus használatát:
float szám = 0; System.out.println(szám); szám = 1/3; System.out.println(szám); szám = 1f/3f; System.out.println(szám); szám = -1*0f; System.out.println(szám); System.out.println(szám == 0f); szám = 100000000000000000000000000000000000000f; System.out.println(szám); szám = 1.0E38f; System.out.println(szám); |
Eredményül nem pont azt kapjuk, amit várnánk:
0.0 0.0 0.33333334 -0.0 true 1.0E38 1.0E38 |
Az első 0.0 érthető, hiszen ezt adtuk értékül a szám nevű változónak. A második 0.0 azonban elgondolkodtató: az egyharmad értéke nem szabadna nulla legyen. Ennek oka az, hogy a Java nyelv két egész számot lát: elosztja az egyet hárommal: hányszor van meg egyben a három? Ugye egyszer sem, ezért kaptunk eredményül nullát. A problémát úgy tudjuk kikerülni, hogy a szám mögé írt f betűvel mondjuk meg, hogy ez a szám nem egész szám, hanem float. A 1f/3f művelet eredménye már 0.33333334, mivel a float csak nyolc számjegy pontosságú, azt követően nincs több értékes számjegy. A -0.0 eredmény a számábrázolás érdekessége, ugyanis van plusz nulla és mínusz nulla is, és ahogy a következő sorban látjuk: a kettő egyenlő egymással. A nagy számokat megadhatjuk a számjegyekkel is, de rövidebb a normálforma: 1.0·1038, amely a program forrásában 1.0E38 formán kódolódik.
A double használata annyiban tér el a float használatától, hogy az f karakter helyett d karaktert kell használnunk a literál megadásakor (amelyet akár el is hagyhatunk, hiszen a doubletípus az alapértelmezett lebegőpontos típus).
Mindegyik primitív típusnak van egy burkoló osztálya, amely már használható objektum orientált módon, s képes önmagán néhány egyszerű (nem matematikai) műveletet elvégezni.
A boolean típushoz tartozó burkoló osztály, amelynek leginkább azt a tudását használjuk fel, hogy szövegből képes önmagát legyártani:
Boolean válasz= new Boolean("tRuE"); System.out.println(válasz); |
A paraméterben átadott szöveg hatására a válasz nevű változó értéke true lesz. Akkor és csak akkor lesz az eredmény true, ha az átadott szöveg karakterhelyesen "true", azonban a kis és nagybetű nincs megkülönböztetve (case insensitive). Ugyanígy használható a valueOf metódus is, amely egy picit gyorsabb is:
Boolean válasz= Boolean.valueOf("tRuE"); System.out.println(válasz); |
Az eredmény jelen esetben is truelesz. Az 1.5 Java verziótól használhatjuk az automatikus konverziót is:
Boolean válasz = true; boolean érték = válasz; |
Az 1.5 verzió előtt ezt csak hosszabban tudtuk leírni:
Boolean válasz = Boolean.valueOf(true); boolean érték = válasz.booleanValue(); |
Ezen osztályok mind a Number osztályból származnak, amely a számokat kezelő osztályok őse. A Numberőstől örökölt kényszer szerint van hat metódusuk, amely a burkoló osztály által hordozott szám primitív típusát adja vissza:
A fenti programrészlet eredménye:
Mint látható, a valós számot hordozó Double esetén az egész számra való alakítás is megtörténik, ha egész típusú primitív típust kérek el, azonban az átalakítás során csonkolás történik (eltűnik a tizedespont mögötti számsor), nem pedig kerekítés!
Ezek az osztályok már több műveletet is lehetővé tesznek, nekünk azonban egyelőre elég a valueOfismerete, amely szöveges formából alakít számmá, akárcsak a Boolean esetén:
Ennek eredménye:
Fontos, hogy a valós típusoknak lehet negatív és pozitív végtelen értéke - ha túl nagy számot szeretnénk szöveges formából átalakítani vagy a művelet eredménye túl nagy, ezt lekérdezhetjük az isInfinite metódussal.
A karakter ismét kilóg a sorból, hiszen a célja alapvetően nem a számolás, hanem karakterek ábrázolása.
Character karakter = new Character('a'); System.out.println(karakter); System.out.println(Character.isDigit('0')); System.out.println(Character.isDigit('a')); System.out.println(Character.isWhitespace('\t')); System.out.println(Character.isWhitespace('a')); System.out.println(Character.isISOControl('\t')); System.out.println(Character.isISOControl('a')); System.out.println(Character.toLowerCase('A')); System.out.println(Character.toUpperCase('a')); |
A futás eredménye:
a true false true false true false a A |
A Character osztálynak van egy rakás statikus metódusa, amely a megadott karakterről döntést hoz. A példában rákérdeztünk párosával arra, hogy az átadott karakter szám-e, helykitöltő karakter-e, valamit ISO vezérlőkarakter-e. Az utolsó két metódus pedig konvertálta az átadott karaktert kis-, illetve nagybetűsre. Ezen túl sok egyéb metódust megtalálunk a Character osztályban (írásirány, tükörírás, stb.), ezeket nem részletezném.
A Boolean osztályt leszámítva az összes burkoló osztálynak van négy hasznos konstansa:
Lássunk rá példát:
System.out.println(Byte.TYPE); System.out.println(Byte.MAX_VALUE); System.out.println(Byte.MIN_VALUE); System.out.println(Byte.SIZE); System.out.println(Short.TYPE); System.out.println(Short.MAX_VALUE); System.out.println(Short.MIN_VALUE); System.out.println(Short.SIZE); System.out.println(Integer.TYPE); System.out.println(Integer.MAX_VALUE); System.out.println(Integer.MIN_VALUE); System.out.println(Integer.SIZE); System.out.println(Long.TYPE); System.out.println(Long.MAX_VALUE); System.out.println(Long.MIN_VALUE); System.out.println(Long.SIZE); System.out.println(Float.TYPE); System.out.println(Float.MAX_VALUE); System.out.println(Float.MIN_VALUE); System.out.println(Float.SIZE); System.out.println(Double.TYPE); System.out.println(Double.MAX_VALUE); System.out.println(Double.MIN_VALUE); System.out.println(Double.SIZE); System.out.println(Character.TYPE); System.out.println(Character.MAX_VALUE); System.out.println(Character.MIN_VALUE); System.out.println(Character.SIZE); |
Az eredményt mindenki nézze meg a saját gépén... :)
Mit sem ér egy üveg sör, ha nem tudunk vele semmit kezdeni.
A deklarált változóink nem érnek sokat, ha nem tudunk rajtuk műveleteket végezni. Műveletet csak és kizárólag primitív típusokon, illetve típusok között tudunk végezni, és a műveletet az operátor vagy az operátorok határozzák meg. Nem kell megijedni, egyszerűen arról van szó, hogy az összeadás műveletét a + jel, mint operátor határozza meg.
Eddigi programjaink során már találkoztunk az értékadás műveletével:
int szám; szám = 2; |
Ezt úgy kell olvasnunk, hogy legyen egy int típusú és szám nevű változónk, majd a szám nevű változó értéke legyen egyenlő kettővel.
A primitív típusú változóink szinte kivétel számokat tárolnak, ezért mindegyiken képesek vagyunk matematikai műveleteket végezni. Lássuk, miképp tudjuk a matematikai alapműveleteket Java nyelven elvégeztetni.
A legegyszerűbb operátor az előjelváltás:
Az előjelváltás a tipikus példája a prefix típusú operátornak, hiszen az operátort követi az operandus.
A négy alapvető matematikai művelet közül a leginkább használt művelet az összeadás, Java nyelvben is pontosan úgy működik, mintha papírra vetnénk:
A szám = 3 + 4 utasítás úgy olvasandó, hogy a szám változó értéke legyen egyenlő a három meg a négy értékével, vagyis a szám értéke hét lesz. A szám = szám + 5 utasítás már feltételez előző értéket a szám változóban (ami jelen esetben 7), s a jelentése annyi, hogy a szám változó értéke legyen egyenlő a szám változó előző értéke meg öt értékével, vagyis a szám változóban a 12 lesz eredményképpen. A következő utasítás hasonlóképpen értelmezhető, azt jelenti, hogy a számváltozó értékét megnöveljük egyel. A kiírt eredmény 13 lesz.
Azonos módon értelmezhető, mint az összeadás:
Az eredmény pedig -7 lesz.
A szorzás is úgy működik, mint az előző két művelet, csak a műveleti jel változik:
Az eredmény 60 lesz, ahogy az sejthető.
Az osztás kilóg az előző három művelet közül, egy kicsit speciális a helyzete. Az egyik buktató - amiről már volt szó, hogy ha egész számok az operandusok, akkor egész osztás fog történni:
Normál esetben az eredmény másfél lenne, de az egész osztások miatt az 30/4 eredménye 7 lesz, ezt követően a 7/5 eredménye pedig 1, aztán az 1/1 természetesen 1. Ha a valós eredmény érdekel minket, akkor valós számokkal kell számolnunk:
Az eredményül kapott 1.4 egy kissé gyanús lehet matematikában jártas egyéneknek. A probléma gyökere ott van, hogy a 30/4 még mindig egész osztás marad, attól függetlenül, hogy a szám változó típusa valós. Ennek oka, hogy a Java nyelvben - és más nyelvekben is - az egyenlőség jel jobb oldalán kezdődik a kifejezés végrehajtása, mégpedig balról jobbra. Mivel a 30 egy egész szám és a 4 is egy egész szám, ezért a két számon értelmezett osztás művelet egész osztás lesz: a szám változóba eredményül 7 kerül. A következő sorban az utasítás szerint a szám változó értékét el kell osztani öttel. Itt a számváltozó típusa okán már valós osztás lesz, és a 7/5 eredménye az 1.4. A hibát úgy tudjuk kikerülni, hogy jelezzük a 30 és a 4 valós voltát (illetve a többi szám valós voltát is):
Eredményül most már másfelet fogunk kapni. A nullával való osztásban is vannak különbségek. Ha valós számot osztunk nullával, akkor eredményül végtelent(Infinity) kapunk:
Ellenben egész szám esetén a program futása megszakad:
Egy kivétel (Exception) keletkezik, hogy nullával próbáltunk meg osztani:
A kivételekről majd később, annyit jegyezzünk meg, hogy ha egész számokkal osztunk, akkor annak súlyos következményei lehetnek.
Az egész osztás párja a maradékképzés, amikor nem arra vagyunk kíváncsiak az osztás során, hogy mennyi az eredmény, hanem arra, hogy mennyi a maradék:
Az eredményül kapott 2 nem okozhat meglepetést, ha vissza tudunk emlékezni az általános iskola második osztályában tanultakra: harmincban a négy megvan hétszer, maradék kettő. Mivel a maradékképzés is osztás, itt is kaphatunk kivételt, ha nullával szeretnénk osztani: például 30 % 0.
Néhány nyelvben a maradékképzés csak egész számokra használható, ám Java nyelvben a művelet elvégezhető valós számokon is:
Eredményül az alábbi számokat kapjuk:
A megoldás egyszerű: 30.0 / 4.1 az egy valós számot ad eredményül. Ezt csonkolva hetet kapunk, amelyet ha visszaszorzunk a 4.1 számmal, elvileg 28.7 lesz az eredmény (a példában jól látszik a valós számábrázolás pontatlansága!). Ha a 30.0 számból kivonjuk a 28.7-et, akkor kapunk 1.3-at, mint maradék. El nem tudom képzelni, hogy ezt hol lehet kihasználni... :)
Gyakori feladat, hogy egy változó értékét növeljük vagy csökkentsük egyel. Ez normál esetben így nézne ki:
Mivel ez a forma hosszú és összetett, a C nyelvből kölcsönzött ++ és -- operátort tudjuk használni, azonban ezek lehetnek prefix és postfix operátorok is:
Az eredmény:
Ami nem meglepő, hiszen az első esetben a kiírás után növekedett a változó értéke, a második esetben a kiírás előtt, aztán a harmadik esetben a kiírás után csökkentettük a változó értékét, majd pedig a kiírás előtt.
Két érték összehasonlítása relációs jelekkel történik, s eredményképpen boolean típust kapunk, amely lehet igaz vagy hamis. Szaladjunk gyorsan át ezeken a műveleteken:
A relációs jelek jelentése sorban:
Fontos, hogy csak ebben a formában tudjuk használni a relációs jeleket, a ’<=’ helyett a ’=<’ nem használható, illetve a ’!=’ helyett se tudjuk használni a ’<>’ jelet.
A relációs műveletekkel nem tudjuk kifejezni azt az egyszerű matematikai képletet, hogy 4 < szám < 10, amely akkor igaz, ha a számértéke nagyobb, mint négy, és kisebb, mint tíz:
Ha mégis megpróbáljuk, akkor fordítási hibát kapunk eredményül (a fordítási hibákról később):
A megoldáshoz a matematikai képletet szét kell választanunk két részre: 4 < szám ÉS szám < 10, vagyis a szám értéke legyen nagyobb, mint négy és legyen kisebb, mint tíz:
Mint látható, az && jel helyettesíti az ÉS szót, a futás eredménye pedig false, mivel a 10 ugyan nagyobb, mint 4, de nem kisebb, mint 10.
Ha két relációs művelet közül elég, ha az egyik teljesül, akkor össze tudjuk kapcsolni őket egy VAGYművelettel:
Az első esetben a kifejezés akkor lesz igaz, ha a szám változó értéke maradék nélkül osztható kettővel. A második esetben hárommal kell oszthatónak lennie, a harmadik esetben a kifejezés akkor lesz igaz, ha a szám változó értéke maradék nélkül osztható kettővel vagy hárommal. A VAGY műveletet a ||jellel tudjuk jelölni.
Sokszor előfordul, hogy a kiszámolt boolean eredmény ellentéte kell valamilyen okból kifolyólag, ekkor a NEM(más néven tagadás) műveletet kell használnunk:
A tagadást a logikai változó elé tett felkiáltó jellel tudjuk megejteni, a példában a tagadásból következik, hogy a false kerül kiírásra.
Mivel számítógépeink egyelőre bitek alapján működnek, ezért célszerű beépíteni egy programnyelvbe a bitműveleteket. A bitműveletek alapja a kettes számrendszer, és minden primitív típus alapvetően kettes számrendszerben tárolt szám, ezért - a valós számokat leszámítva - értelmezhető rajtuk az összes bitművelet.
Induljunk ki egy szép kerek számból, azaz nézzük meg, hogy a decimális 10 hogy néz ki kettes számrendszerben 8 biten:
Keressünk egy másik számot is, például nézzük meg a 57-es számot kettes számrendszerben, szintén 8 biten:
Ezzel a két számmal fogunk bitműveleteket végezni.
A bitléptetés egy nagyon alacsony szintű művelet, a legtöbb CPU támogatja, ritkán szükség is van rá, ezért nem lehetett kihagyni a Java nyelvből. A bitek léptetése során a bináris számban lévő bitek sorrendje azonos marad, azonban a pozíciójuk megváltozik, jobbra vagy balra tolódnak el.
Fontos tudni, hogy a bitléptetés mindig 32 bites egész számon történő művelet, ha más adattípuson végeznénk el, akkor is 32 bites eredményt kapunk!
A balra léptetés során a bitek balra mozognak el, és jobb oldalon 0 értékek jönnek be, a bal oldalon kieső bitek pedig elvesznek:
Az eredmény magáért beszél, a bitek elkezdenek balra mozogni, jobb oldalon pedig 0 értékek jönnek be:
A jobbra léptetés azonos módon működik, mint a balra léptetés:
Az eredmény itt is magáért beszél:
A jobbra léptetés esetén létezik előjeles léptetés, amikor a bináris szám bal szélén nem 0 érték jön be, hanem az előjelbit ismétlődik:
Mint látható, bal oldalon alapból egy egyes érték van, és ezzel töltődik fel a bithalmaz:
Ha a kapott bitek mindegyikét negálni szeretnénk, akkor a ~ operátort kell használnunk:
A kiírt eredmény -11, amely binárisan 11110101, ugyanis a negatív számokat kettes komplemens alapon kezelik az elterjedt számítógépek. A kettes komplemens egy bitenkénti tagadás, majd az eredményhez hozzáadunk egyet - így lesz a 10 kettes komplemense -10.
A bitenkénti és művelethez már kettő operandus kell:
Az eredmény 8 lesz, amelynek az oka, hogy a két számnak csak a negyedik pozícióban van azonosan 1 értéke:
A bitenkénti vagy művelethez is kettő operandus kell:
Az eredmény 59, ami binárisan 00111011, mivel ott tartalmaz 1 értéket, ahol a megadott két operandusnál legalább egy darab 1 érték volt:
A kizáró vagy hasonlít a vagy művelethez, viszont csak akkor lesz az eredmény is 1 érték, ha a megadott két operandusban azonos pozíción csak egy 1 érték van:
Amelynek eredménye 51, hiszen:
Rikán találkozunk a programozás során bitműveletekkel, szinte kizárólag csak az ÉS művelet fordul elő, amikor olyan adatokon kell dolgoznunk, amelyek egymás mellé zsúfolt bitekből állnak.
A fentieken túl maradt néhány művelet, amelyeket nem lehet könnyedén csoportba sorolni, ezért kerültek az egyéb műveletek közé.
Gyakori eset, hogy egy változó értéke az előző értékéhez képest változik meg:
A változó = változó műveleti jel kifejezés további részejellegű kifejezéseket rövidíteni tudjuk:
A rövidítés során a műveleti jel az egyenlőség jel elé kerül és az egyenlőség jel után eltűnik a kifejezésből a változó neve: változó műveleti jel= kifejezés további része. Ezt bármilyen műveletnél meg tudjuk tenni, amely két operandussal működik.
Ha egy eldöntendő kérdés alapján szeretnénk értéket adni egy változónak, akkor ezt a feltétles értékadással tudjuk megtenni. Nézzünk egy példát az abszolút érték képzésére:
A második sorban találjuk a lényeget, amelyet úgy tudunk kiolvasni, hogy a szám nevű változó értéke legyen egyenlő a -szám értékével, ha a szám < 0 feltétel igaz, egyébként legyen egyenlő a számértékével.
A + jel használható szövegek összefűzésére is, ekkor két kisebb szövegből egy nagyobb szöveg lesz:
Az eredmény természetesen 8.
Az előzőekben említett program-alkotórészek fabatkát se érnek vezérlő szerkezetek nélkül, amelyek alapvetően döntésképessé és ciklikussá teszik a programjainkat, ezért kétféle vezérlő szerkezettel találkozhatunk általában:
Láthatjuk, hogy mindkét fajta vezérlő szerkezet függ egy feltételtől, amely szinte kivétel nélkül egy eldöntendő állítás, amelyre egyértelműen igazat vagy hamisat kell eredményül adnunk.
Az egyik legegyszerűbb vezérlő szerkezet, amely a feltételtől függően végrehajtja az őt követő utasítást:
Mint láthatjuk, a feltételes elágazás egy if kulcsszóval kezdődik, amelyet két kerek zárójel közötti feltétel követ, s végül egy darab utasítással zárul. A programot futtatva minden esetben azt kapjuk, hogy "Vegyünk egy sört és igyuk meg!", hiszen a kiinduló feltétel szerint mindig több pénzünk van, mint amennyibe egy üveg sör kerül, de ha a pénz változó értékét kisebbre vesszük, mint a sör ára, akkor a feltétel már hamis lesz, ezért nem kerül kiírásra az említett szöveg.
Gyakori hiba, hogy az if szerkezetet több utasítás is követi, amelyről úgy gondoljuk, hogy egybe tartoznak, csak ezt a számítógéppel nem beszéltük meg:
A várt eredmény nem marad el, hiszen kikerül a képernyőre az ismert szöveg, amely abban a hitben hagy minket: a program megfelelően működik, ám egy tesztelés nem tesztelés, nézzük meg, mi történik, ha nincs elég pénzünk sörre:
Az eredmény egy árva "...és igyuk meg!", mert sörre bizony nem volt pénzünk. A megoldás egyszerű, minden if szerkezetet blokkutasítással kövessünk, és a blokkon belül írjuk meg a feltételtől függő programrészt - ha egy utasításról van szó, akkor is, inkább legyen hosszabb a program, mint hibás:
A várakozásainknak megfelelően ez a program már nem ír ki semmit, hiszen nincs pénzünk sörre, ezért bővítsük ki a programot egy olyan feltételes szerkezettel, ahol a feltétel pont a fenti ellentéte:
Ebben az esetben a program kimentén a "Sajnos nincs pénzünk sörre..."szöveg olvasható.
A hibalehetőségek csökkentése a programozók elemi érdeke, ezért találták ki régesrég az if-else szerkezetet, amely lehetővé teszi, hogy a programunk tegyen valamit a feltétel fennállása esetén (igaz ág), illetve tegyen másvalamit akkor, ha a feltétel nem teljesül (hamis ág), így nem kell két feltételt karbantartani, s a program is rövidebb lesz:
Mint láthatjuk, a módosítás mindössze annyi, hogy a második if és a feltétel helyére egy elsekerült.
Sok esetben szükség van többágú elágazásra, mivel a való életben is gyakran kell egy bekövetkezett tényre több lehetőség közül választani, gondoljunk csak arra az esetre, amikor sört akarunk venni a sarki kisboltban, de nincs mindig tele a pénztálcánk. Ekkor az éppen aktuális anyagi lehetőségeink határozzák meg, hogy milyen sört tudunk megvásárolni.
Egymásba ágyazott kétágú elágazásokból tudunk építeni többágú elágazást, ekkor a hamis ágba újabb elágazást tudunk tenni, amely sok ág esetén kellően átláthatatanná teheti a programunkat:
A program tartalmaz több - egymásba ágyazott - if-else szerkezetet, amelyeknél látnunk kell egy döntési sorozatot. Ha a fenti feltételek szerint 100 forintból akarunk sört inni, akkor a kiértékeljük a pénz >= 500 feltételt, amely nyilvánvalóan hamis lesz, ezért nem tudunk Guinness sört inni, kénytelenek vagyunk a hamis ágon továbblépni. Az első hamis ágban egy újabb feltételt kell kiértékelni: pénz >= 300, amely szintén hamis lesz, ezért Leffe sört sem tudunk inni. Továbbmegyünk a második hamis ágon, ahol meglátjuk az utolsó feltételt, amelynek már megfelel a pénzmagunk, így tudunk inni egy Sopronit. Észre kell vennünk, hogy bármelyik feltétel teljesülése esetén - a hamis ág kihagyása miatt - a program végrehajtása az első hamis ág után folytatódik (menjünk haza...).
Az egymásba ágyazott if-else szerkezetek mindig átírhatóak egy if-else-ifszerkezetre, amely sokkal áttekinthetőbb, mivel látszólag mellőzi az egymásba ágyazást:
Észre kell vennünk, hogy az else utasítások után lespóroltunk egy blokk utasítást, mivel a blokkban csak egy if utasítás volt. Ezzel az apró módosítással átláthatóbbá tettük a program működését, hiszen fentről lefelé olvashatjuk a feltételeket és a feltételekhez tartozó utasításokat, illetve az utolsó else után azt az utasítást, amely akkor hajtódik végre, ha egyik feltétel se teljesült. Ez az utolsó else ág egyébként elhagyható, ha nincs rá szükségünk.
Ha primitív típus meghatározott értéke alapján szeretnénk különféle dolgokat végezni, akkor egy hosszabb if-else-if szerkezet helyett használjuk inkább a switch utasítást:
Ahogy a példában látjuk, döntés a sörökSzáma változó értékétől függ, ha a változó értéke megegyezik valamelyik caseág értékével, akkor az az ág végrehajtásra kerül. A várt működés szerint a program az alábbit írja ki:
Ha egyik ág se hajtódna végre, akkor a default ágra kerül a vezérlés, ha nincs default ág, akkor a program végrehajtása a switch utasítás végétől folytatódik. A switch működési sajátossága, hogy az első egyezőségtől kezdve az összes utasítást végrehajtja a blokk végéig, ezért minden ágat egy break utasítás zár, amely hatására a program végrehajtása a switch utasítás után folytatódik. Gyakori programozói hiba a break elhagyása, amely okán másképp működik a programunk, próbáljuk ki a fenti programot brakeutasítások nélkül:
Mivel két sörre volt pénzünk, a két sört tartalmazó ágtól kezdve az összes utasítás végrehajtódik.
Sok esetben szükséges egy-egy programrészletet megismételni, gondoljunk csak arra, ha bemegyünk egy kocsmába egy ezressel és addig szeretnénk sörözni, amíg el nem fogy a pénzünk:
A végrehajtás során a belépési feltételünk kiértékelésre kerül, csak akkor hajtódik végre a ciklus magja, ha a feltétel igaz - vagyis van pénzünk legalább egy sörre. A ciklusmag végrehajtása után a feltétel újra és újra kiértékelődik, és a ciklus magja újra és újra végrehajtódik.
Gyakori programozási hiba, hogy a ciklusmagban nem változtatjuk a feltételben szereplő változók értékét: ekkor végtelen ciklust kapunk, amely - ha ideje engedné - a ciklusmagot végtelen esetben hajtaná végre.
A hátultesztlő ciklus működése hasonlít az előltesztelő ciklus működésére, egyedüli különbség, hogy a feltétel kiértékelése az első ciklusmag lefutása után történik meg, tehát a hátultesztelő ciklus egyszer mindenképpenvégrehajtja a ciklusmagot:
A hátultesztelő ciklus tipikus felhasználási területe a beolvasott érték alapján történő döntés, a beolvasást a ciklus addig ismétli, amíg a várt értéket sikerül beolvasni.
-.-