Tanulj Java-t!

2. Lecke - Vezérlési elemek

Kezdjünk neki a lényegi programozásnak. Tulajdonképpen mit is nevezünk programnak? Általában szűkszavúan csak annyiról van szó, hogy adatokat tárolunk, tartunk nyilván, illetve azokkal dolgozunk, műveleteket hajtunk végre (rajtuk), majd a legvégén a programot működtető felhasználó tudtára adjuk ténykedése (ténykedésünk) végeredményét. (Itt máris felvetődik az interaktivitás kérdése is, de egyelőre ezzel még nem foglalkozunk)

Elsőnek tekintsük meg hogyan hozhatunk döntéseket. Minden döntést a rendszer egy boolean típusú változóra fog leképezni (igaz / hamis, true / false) egyszerű relációk segítségével:
(boolean) (kifejezés (reláció vagy feltétel) kifejezés)
A kifejezés bármi lehet egy változó vagy akár egy beírt érték is, persze a két kifejezésnek típusban kompatibilisnek kell lennie. Nem lehet például azt mondani, hogy (13 > true) de ez elég nyilvánvaló is.
Valamint, hogy ezeket közelebbről tanulmányozhassuk, meg kell ismernünk a legegyszerűbb feltételes elágazással.

Az if feltételes elágazás:
if (feltétel1){
  System.out.println("Ha feltétel1 teljesül ez látható");
}
[else if (feltétel2){
  System.out.println("Ha feltétel1 nem teljesül, de "+
  "feltétel2 teljesül, akkor ez látható");
}]
[else if (feltétel){ //... bármennyi else if ág megadható
}]
[else{
  System.out.println("Ha egyik feltétel sem teljesül, akkor ez");
}]

Természetesen mivel az egyes ágak külön {blokkot} tartalmaznak ezért azokban ugyan azt megtehetjük, mint amit a feltételes utasítás előtt is. Létrehozhatunk benne változókat, igaz így az csak az adott blokkon belül lesz elérhető, vagy akár elhelyezhetünk benne további feltételes elágazásokat is.

De most inkább vegyük át, hogy milyen feltételeket adhatunk meg, illetve vegyük át az alapműveleteket is, mert ezeket is belekombinálhatjuk egy feltétel kiértékelésébe:
//Alapvető műveletek:
int i=5, x=2, y=10, w;

w=i+x; // összeadás, w értéke 7 lesz
w=i-x; // kivonás, w értéke 3 lesz
w=i/x; // osztás, w értéke 2 lesz, mert int egész szám
w=i*x; // szorzás, w értéke 10 lesz
w=i%x; // osztás utáni maradékképzés, w értéke 1 lesz

//Összevont műveletek
w=100;
w=w+y; // egyenértékű ezzel: w+=y;
//ugyan így:
w-=i; // egyenlő ezzel: w=w-i;
w/=i; // egyenlő ezzel: w=w/i;
w*=i; // egyenlő ezzel: w=w*i;
w%=i; // egyenlő ezzel: w=w%i;

w=w+1; // egyenlő ezekkel:
w++; //kiértékelésnél elsőnek használja w értékét,
     //majd növeli egyel az értékét.
++w; //kiértékelésnél elsőnek növeli w értékét egyel,
     //majd csak utána használja.

w=w-1; // egyenlő ezekkel:
w--; //kiértékelésnél elsőnek használja w értékét,
     //majd csökkenti egyel az értékét.
--w; //kiértékelésnél elsőnek csökkenti w értékét egyel,
     //majd csak utána használja.

//Logikai műveletek, Relációk:
//ezeknek a végeredménye logikai boolean típus, így
//felhasználhatjuk a feltételes elágazásoknál őket

boolean logikai;

logikai=x> y; //értéke hamis mert x==2 és nem nagyobb y==10-nél
logikai=x< y; //igaz
logikai=x>=y; //igaz ha x nagyobb vagy egyenlő y-nál
logikai=x<=y; //igaz ha y nagyobb vagy egyenlő x-nél
logikai=x==y; //igaz ha x egyenlő y-al
logikai=x!=y; //igaz ha x nem egyenlő y-al

//Az if feltételes elágazás:

int[] tomb={0,1,2};
//tomb=new int[]{0,1}; // a második dupla és && példához

if(i*x==y){
  //ha a két oldalon lévő kifejezések értéke megegyezik
}
if(i!=23){
  //ha a két oldalon lévő kifejezések értéke nem egyezik
}
if(i==23 & y==10){
  //hamis  igaz
  //és kapcsolat & mind a két oldalt kiértékeli és úgy hoz döntést
}
if(tomb.length>=3 && tomb[2]==2){
  /*         igaz    igaz
  (másodiknál hamis & hiba, nincs 2 indexű elem)
  Dupla és kapcsolat && ha az első feltétel hamis, akkor nem
  értékel tovább és hamisnak veszi az egész feltételt.
  Ha tomb nagysága nagyobb vagy egyenlő mint 3 és a tomb 2 indexű
  eleme egyenlő 2-vel. Vegyük ki az egysoros megjegyzést a "tomb"
  második példájához. Azzal ez a feltétel nem fog teljesülni, mert
  a tomb nagysága már csak kettő lesz, és a vezérlés kilép a feltétel
  kiértékeléséből. Ha most kipróbáljuk ezt a sima és & el akkor azt
  tapasztaljuk, hogy futási hibával leáll a program, mert a feltétel
  első fele bár hamis az & miatt kiértékeli a második részt is,
  amiben olyan indexre hivatkozunk (2) ami már nem létezik.*/
}
if(i<=10 | false){
  /*igaz   hamis
  Sima vagy kapcsolat, mint a sima és kapcsolatnál itt is
  mind a két oldal kiértékelődik. Az egész akkor ad igaz értéket,
  ha valamelyik oldala igaz. Itt látható az is, hogy meg lehet
  adni direkt fix boolean értéket is.*/
}
if(logikai=(x!=2) || y==10){
  /*        hamis    igaz
  Dupla vagy kapcsolat, hasonló a dupla és kapcsolathoz, de itt
  ha az első feltétel igaz, akkor nem értékel tovább, és
  igaznak veszi az egész feltételt.
  Itt azt is láthatjuk, hogyan raktározzunk el egy kiértékelendő
  kifejezés értékét, későbbi feldolgozás céljából.*/
}
if(logikai ^ tomb[0]==0){
  /* hamis   igaz
  Kizáró vagy kapcsolat, vagyis csak akkor ad igaz értéket ha az
  egyik oldala igaz és a másik hamis, a sorrend nem számít!
  Ha mind a két oldal azonos: igaz, hamis esetén hamist ad vissza.*/
}
/**És természetesen mindezeket kombinálhatjuk is egymással!
   Az elején jobb, ha mindig (zárójelezünk) ,hogy lássuk mi tartozik össze*/

if((logikai | tomb.length==2) || ((tomb.length>=3 && tomb[2]==2) ^ x==2)){
  /*Ha nem látjuk melyik zárójelpár tartozik össze, akkor álljunk a
  kurzorral a kívánt zárójelre, majd erre a hozzátartozó zárójelpárt
  körberajzolja egy négyzet.*/
}

A switch-case feltételes végrehajtás:

Ezt csak azért ismertetem, mert része a nyelv szintaktikájának, de Én konkrétan nem szeretem, és nem is ajánlom senkinek se a használatát. Persze kivételes esetekben lehetséges, hogy ezt kell majd használni, de az elég ritka lesz, ugyanis a switch-ben szereplő kiértékelhető kifejezés típusa csak int (Integer, 32 bites előjeles egész) lehet illetve a vele kompatíbilis típusok (byte short char).
switch(kifejezés){
  case érték : {[utasitások]} [break;]
  [case érték : {[utasitások]} [break;]]// bármennyi case megadható
  [default : {[utasitások]}] // ha nincs végrehajtható ág és ez definiálva
  //van, akkor ez fog lefutni.
}
Szemléltetve egy egyszerű példán keresztül:
int i=4;

switch(i){ // az érték amit vizsgálunk
  case 5 : { System.out.print('5'); } break; // itt megáll a kiírás
  case 4 : { System.out.print('4'); }
  case 3 : { System.out.print('3'); } break; // és itt is
  case 2 : { System.out.print('2'); }
  case 1 : { System.out.print('1'); }
  case 0 : { System.out.print('0'); }
  default : { System.out.println("Alapértelmezés"); }
}
//Ebben a példában eltértem a megszokott {blokk} strukturálásától, ezt
//csupán a jobb olvashatóság/értelmezhetőség miatt tettem.

ciklusok:

A programokban előfordulnak olyan esetek, amikor valamit többször akarunk végrehajtani. Azért, hogy ezeket az utasításokat ne keljen többször egymás után leírnunk, feltalálták a ciklusokat.
Elsőnek ismerkedjünk meg a for ciklussal:
for([kezdőérték megadása];[belépési feltétel];[kifejezés]){
  // utasítások
}
Egy egyszerűbb példa, hogy érthetőbb legyen:
for(int i=0;i<=10;i++){
  System.out.println("i értéke: "+i);
}

//Természetesen ezt a végtelenségig bonyolíthatjuk:
int i=87;
for(;i>25;i-=5){
  i=i+2; // a ciklus változót bárhol módosíthatjuk
  if(i<40){
    System.out.print("i kisebb mint 40 mert ");
    //a print nem rakja új sorba a szöveges kurzort
  }
  System.out.println("i értéke: "+i);
}
Most pedig nézzük meg a while ciklust:
Ez egyébként hasonlít a for-ra, csak attól "szabadabb" a kezelése.
while(belépési feltétel){ // 1.változat
  // utasítások
}

do{ // 2.változat
  // utasítások, ezek egyszer mindenféleképpen végrehajtódnak
}while(belépési feltétel);
Nagy általánosságban elmondható, hogy programok írásakor az 1. változatot használjuk a leggyakrabban, a 2. tulajdonképpen csak nagy ritkán jöhet elő, persze ez lehet megszokás kérdése is, Én mindenesetre mindig az 1. szoktam alkalmazni, és szintén ezt javaslom mindenkinek!
Na jó ajánlom :-)

Használható példának most azt fogjuk megszámolni, hogy egy bizonyos karakter hányszor szerepel egy String-ben. Ehhez a String charAt(int index); metódusát fogjuk használni ami visszaadja a paraméterül megadott indexedik karaktert a Stringből. Ha ilyen nincs akkor futási hiba keletkezik, konkrétabban egy kivétel jön létre, ami alul a program konzolján jelenik meg. Egy ilyen kivételdobás tartalmazza azt a helyet ahol a hiba konkrétan keletkezett -végigvezetve- addig a pontig amíg a kivétel(vagyis a hiba) le nem lett kezelve -vagyis el nem lett kapva- ha ilyen nincs, akkor a hiba végigvezetése a belépési pontig tart. Azaz a main metódusig.
Az egyes hiba sorokon az egérrel kattintva a Free Java betölti (ha lehetséges) azt az osztályt amire a hiba vonatkozik, megjelölve a hibát kiváltó sort. Vagy azt a részt (általában metódushívást) amin keresztülment. De most egy kicsit elkalandoztunk, a kivételkezelést majd később vesszük. Most inkább ássuk bele magunkat ebbe a példába:
String s="Ennek a Stringnek a karaktereit fogjuk vizsgálni";
char c='i';  //az 'i' karaktert számoljuk meg a Stringben
int index=0; //a kezdőindex ahonnan indulunk
int db=0;    //ebben tároljuk,hogy hányszor van benne 'i'

while(index<s.length()){ //ha index kisebb a String hosszánál
  if(s.charAt(index++)==c){ //ha index-edik karakter c
    db++; //megnöveljük egyel a darabszámot
  }
}
System.out.println("Az s Stringben: "+s);
System.out.println("A c char karakter: "+c);
System.out.println(db+" * található meg.");

Annyit azért még hozzá kell tenni, hogy a Stringek indexelése mint a tömbnél nullától kezdődik, a hossza pedig (length()) mindig számszerűen a String karaktereinek számát adja vissza. Vagyis egy 1 karaktert tartalmazó Stringben csak a charAt(0) ad vissza értéket és a length() 1-et ad vissza.
Tehát egy üres String (String s="";) nem tartalmaz semmit és a length() 0-át ad vissza.
Talán jobb is lesz, ha megismerjük egy kicsit a String illetve a StringBuffer osztályokat.

Válasszuk ki a Keresés menü Java osztály böngésző menüpontját, majd a megjelenő dialógusablakban a fastruktúrából jelöljünk ki egy osztályt, jelen esetben a java.lang.String.java -t ez után két lehetőségünk van:
Megnyitjuk a forrásfájlt a szövegszerkesztőbe vagy Elemezzük a Java osztály elemzővel. Az előbbi esetben megnézhetjük az eredeti osztályt felkommentezve persze angolul, míg az utóbbi esetben egy külön ablakban megjelenik a String osztály tulajdonságainak kivonata, úgymint konstruktorok metódusok. A könnyebb átláthatóság kedvéért az osztály saját publikus mezői, vagyis amiket példányosítás után meg lehet hívni vastagabban vannak kiemelve.
Itt még érdemes megjelölni az Összes publikus metódusa jelölőnégyzetet, aminek hatására láthatóak lesznek az osztály őseinek is a publikus metódusai.

Innét szintén elérhető a Java osztály böngésző, igaz itt csak kiválasztani lehet vele. Kattintsunk a felső sorban lévő ... gombra, majd válasszuk ki a java.lang.StringBuffer.java -t vagy még egyszerűbb, ha csak beírjuk az Osztály nevéhez a hiányzó Buffer szót, majd Ok-t nyomunk. Ugyan ezt persze megtehettük volna simán a Java osztály elemző szerkeszthető legördülő menüjében is.
Ezek a metódusnevek persze nem sokat mondhatnak így elsőre, később viszont arra jó lesz, hogy ha használni akarunk egy osztály metódusát, de már nem emlékszünk rá pontosan, akkor itt szemügyre vehetjük a kívánt osztályt.
Továbbá a jelölőnégyzetekkel beállíthatjuk, hogy az osztály mely adataira vagyunk kíváncsiak és melyeket nem akarunk látni.

A String néhány metódusának használata:
String(String) A leggyakrabban használt konstruktor PL.: String s=new String("Ez egy String"); ami egyenértékű ezzel: String s="Ez egy String";
String(StringBuffer) Egy StringBuffer tartalmából hozhatunk létre egy String-et
char charAt(int) Visszaadja az int számnak megfelelő indexedik karaktert
boolean equals(Object) Összehasonlítja a Stringet a paraméterül kapott Objektummal
boolean equalsIgnoreCase(String) Összehasonlítja a Stringet a paraméterül kapott Stringel, de a kis/nagy betű különbségeket nem veszi figyelembe
int indexOf(String) Visszaadja a Stringben a paraméterül kapott String első előfordulásának indexét, ha ilyen nincs akkor -1 -et ad vissza
int indexOf(String,int) Mint az előző, de megadható, hogy melyik indextől kezdje a keresést
int lastIndexOf(String)
int lastIndexOf(String,int)
Mint az előzőek, csak a keresést a String végéről kezdik
int length() Visszaadja a String hosszát, ha ez 0 akkor üres a String
String replace(char regi,char uj) Egy új Stringet ad vissza, de kicserél benne minden régi karaktert újra
String substring(int elso,int utolso) Visszaadja a String első és utolsó index közötti részét
String toLowerCase() Kisbetűsíti a Stringet
String toUpperCase() Nagybetűsíti a Stringet
String toString() Visszaadja a Stringet reprezentáló Stringet, vagyis saját magát
static String valueOf(int) Statikus metódus! Visszaadja az int szám String alakját, vagyis
String s=String.valueOf(123); s egyenlő lesz ezzel: "123"
Ezt a metódust meghívhatjuk bármilyen primitív típussal (boolean double, stb.) valamint Objektum referenciára is.

A StringBuffer néhány metódusának használata:
StringBuffer() Egy üres StringBuffert hoz létre
StringBuffer(String) Egy String tartalmú StringBuffert hoz létre
StringBuffer append(String [...]) Hozzáadja a StringBuffer tartalmához a Stringet, String helyet megadhatunk más (primitív) típust is. A visszaadott StringBuffer az eredeti referenciája! Nem hoz létre újat!
char charAt(int) Visszaadja az int számnak megfelelő indexedik karaktert
StringBuffer delete(int,int) Kitörli a két index közötti szöveg területet
StringBuffer deleteCharAt(int) Kitörli az int indexű karaktert
StringBuffer insert(int,char) Beilleszti int indexedik pozícióba char karaktert, az esetleg ott lévő karakter(ek) jobbra tolódnak. A char helyett más típust is megadhatunk
int length() Visszaadja a StringBufferben lévő szöveg hosszát
void setCharAt(int,char) Beállítja int indexedik karaktert char-ra
void setLength(int) Beállítja a StringBuffer hosszát PL.: 0 értéknél törli a tartalmát
String toString() Visszaadja a StringBuffer String alakját

Azt azért mondanom se kell, hogy ha olyan értéket adunk meg ami nem álja meg a helyét PL.: insert-elni akarunk egy nem létező indexre akkor futásidőben kapunk egy kivételt.
Tényleg már megint a kivétel téma... áh majd (talán) a következő leckében.

Most következzen végre egy kis gyakorlás:

Az eddig átvett anyagból már képesnek kell lennünk egy kisebb konzolos program megírására, ha nem akkor is. :-)
Eddig nem taglaltam hogyan kell konzolos programból karaktereket beolvasni a billentyűzetről, vagyis interaktívabbá tenni programjainkat, de ezt nem is fogom megtenni, mert ezzel egyszerűen foglalkozni sem érdemes!
A Java alapvetően a grafikus felhasználói felületre épül.
Így az interaktivitás kérdését meghagyom arra az időre amikor elérjük a grafikus felhasználói felület készítését. Viszont addig is kihasználjuk azt a lehetőséget, hogy a Java program számára futtatáskor átadhatunk paramétereket.

Legyen ez a kiindulási pont:
[Új fájl létrehozása / fájl neve: Lecke2 / Java / osztály generálása / main pipa be / constructor pipa ki / Létrehozás / majd a lenti kódrészletet másoljuk be a main metódusba (kijelölés Ctrl + C, beillesztés Ctrl + V)]
if(args.length==0){
  System.out.println("Kérem adjon meg legalább 1 paramétert");
  System.exit(0); //ezzel megszakíthatjuk a program futását
}
System.out.println("A program ezeket kapta paraméterül:");
for(int i=0;i<args.length;i++){
  System.out.println(i+". paraméter: \""+args[i]+"\"");
}
A futási paramétereket a Beállítások / Futási/fordítási beállítások menüpontban illetve az eszközsoron jobb oldalon lévő fogaskerékre kattintva állíthatjuk be.
Ha ez megvan már csak az F8-as funkcióbillentyűt kell lenyomnunk és lefordul a program, majd ha ez sikeres volt le is fut.
Megjegyzés: csak a futtatáshoz elegendő az Alt + F8-at leütni.

A feladat leírása:

Készítsünk egy egyszerű konzolos programot a fenti példa felhasználásával, ami a következőket látja el:

  • Kiírja, hogy milyen futásidejű paramétert kapott a program, ha semmilyet akkor azt valamint ebben az esetben álljon le a program futása is
  • Kiírja a paramétereket addig, amíg nem tartalmaz az egyik egy 'w' karaktert (tehát az első 'w'-nél a kiírás leáll)
  • Kiírja a program által minden másodiknak kapott paramétert csupa nagybetűvel
  • Végül megjeleníti, hogy a program összesen hány darab karaktert kapott paraméterül
Sok sikert a megoldáshoz!


//A teljes megoldás:
public class Lecke2 {

  public static void main(String[] args){
    if(args.length==0){
      System.out.println("Kérem adjon meg legalább 1 paramétert");
      System.exit(0); 
    }
    StringBuffer osszes=new StringBuffer();

    System.out.println("A program ezeket kapta paraméterül:");
    for(int i=0;i<args.length;i++){
      System.out.println(i+". paraméter: \""+args[i]+"\"");
      //egyben az összes paramétert belerakjuk egy StringBufferbe
      osszes.append(args[i]);
      osszes.append('\n');//új sor karakter
    }

    System.out.println("---------------------------------");
    System.out.println("Kiírás amíg nincs \'w\' karakter:");
    int index=0;

    while(index<osszes.length() && osszes.charAt(index)!='w'){
      System.out.print(osszes.charAt(index++));//kiírás és léptetés egyel
    }
    if(index==osszes.length()){
      System.out.println("Nem volt a paraméterekben \'w\' karakter.");
    }
    else{
      System.out.println("A kiírás az első \'w\' karakternél megállt!");
    }

    System.out.println("---------------------------------");
    System.out.println("Minden második paraméter kiírása nagybetűvel:");
    for(int i=1;i<args.length;i+=2){
      System.out.println(i+". paraméter: \""+args[i].toUpperCase()+"\"");
    }

    System.out.println("---------------------------------");
    int db=osszes.length()-args.length;
    System.out.println("A paraméterek összesen : "+db+" karaktert tartalmaznak");
  }
}
Ha mégsem sikerülne összehozni a megoldást, akkor olvasd át újra ezt, esetleg Még az első leckét is!
Ha most se megy akkor próbáld azt, hogy minden egyes kritériumot külön-külön próbálod megírni, így lehet, hogy hosszabb lesz, de a lényeg, hogy működjön.
Ha végképp nem megy akkor jelöld ki a fenti "üres" táblázatot, mert az rejti a megoldás egy lehetséges változatát.
Soha ne olvassuk visszafelé a leckéket!