Java kiegészítő lecke – Foreach ciklus

A ciklusok közül nagyon sokra azért van szükség, hogy tömböket, listákat járjunk be velük. Mivel az elemeken rendszerint sorrendben és egyesével kell végiglépkednünk, nagyon sok ciklusunk feje teljesen ugyanúgy néz ki.  Lényegi különbség csak a ciklusmagban van, attól függően, hogy épp mit akarunk tenni az elemekkel.

Milyen jó is lenne, ha a ciklus fejét lerövidíthetnénk, pláne, ha mag is tömörebb, mégis átláthatóbb lenne. A programozók lusták. Én legalábbis. Igyekszem rövidíteni, egyszerűsíteni ott, ahol lehet. De mindig szem előtt tartom a legfontosabbat, ami a jó programozás egyik alapelve: olvashatóság és átláthatóság. Az egyszerűsítés ezeket soha nem rombolhatja!

A for ciklust ismerve úgy tűnik, hogy nem igazán lehet rövidíteni. Hiszen kell a ciklusváltozó, amellyel egy adott indexű elemre hivatkozunk. Biztosítanunk kell, hogy ne szaladjunk túl a ciklusváltozóval a tömb méretén, valamint meg kell oldanunk, hogy az indexeken lépkedhessünk. Akkor mégis mit lehet ezen rövidíteni?

foreach

A for ciklusnak létezik egy egyszerűbb változata, amely pontosan ezt az egyszerűsítést oldja meg, ráadásul nagyon letisztult módon. Hogy néz ki a foreach?

for( tipus valtozo : tomb )
{
  // amit az elemmel csinalni akarsz
  System.out.print(valtozo);
}
  • Az első kiemelt sorban láthatod a foreach ciklus fejét. Csak két részből áll. Először meg kell adni egy változótípust és változónevet, majd kettősponttal elválasztva annak a tömbnek vagy listának a nevét, melyet be szeretnél járni.
  • A második kiemelésben pedig a fejben megadott változót használom a feladattól függően.

Működése

A foreach ciklus bejárja az adott tömböt vagy listát, egyesével végiglépked az elemein úgy, hogy a megadott nevű változóba mindig azt az elemet helyezi el, ahol a bejárás során éppen tart. Fontos azonban, hogy mivel nincs index, fogalmunk sincs arról, hogy éppen hányadik elemnél tartunk. A tömb elemeit nem indexeléssel éri el, hanem magukat az elemeket helyezi el a kívánt változóban.

A for ciklushoz képest különbség, hogy csak és kizárólag olyan változót használhatsz, amit ennek a ciklusnak a fejében deklarálsz. Vagyis nem adhatsz meg olyan változót, melynek a típusát a ciklus előtt adtad meg valahol, esetleg már használtad is, majd itt csak a nevére hivatkozol. Csak itt adhatod meg a típusát és csak erre a ciklusra korlátozod a hatókörét. A következő szerkezet tehát nem használható:

int szam = 0;
for( szam : szamtomb )
{
  System.out.println(szam);
}
System.out.println("A vizslak eletkoranak osszege: "+osszeg);

Természetesen egymástól független foreach ciklusokban használhatsz azonos nevű változókat, azok hatókörei nem fedik egymást.

Előnyök:

  • Rövidebb, de átlátható kód
  • Nem kell a léptetéssel foglalkozni
  • Nem kell figyelni, mikor érünk a tömb vagy lista végére
  • Kevesebb hibalehetőség
  • A break és continue utasítások ugyanúgy működnek

Hátrányok:

  • Csak egyesével lehet lépkedni az elemeken
  • Tömb vagy lista feltöltésére nem használható
  • Primitív értékeket tartalmazó tömb vagy lista elemeinek módosítására nem használható
  • Nem tudjuk, éppen hol járunk a tömbben vagy listában

A hátrányokból esetleg már látható, hogy bizonyos típusú feladatok megoldására nem használható a foreach ciklus. Ezeket, ha tetszik, ha nem, a hagyományos for ciklussal lehet csak megoldani.

Nem használható:

  • Tömbök, listák feltöltésére
  • Primitív elemek módosítására
  • Több elem együttes vizsgálatára (pl szomszédok)
  • Minimum és maximumkeresésre (ha a helyét keressük)
  • Rendezésre
  • És egyéb fel nem sorolt esetekben, amikor tudnunk kellene, épp hol járunk az adott tömbben vagy listában.

Azt, hogy mikor nem használhatjuk a foreach ciklust, úgyis mindig a feladat dönti el, de ezek az előző példák jó támpontot adhatnak, hogy ne is gondolkodj rajta. Egyébként ha szükséged van az adott elem helyére, akkor biztosan nem használható.

Komplex példa tömbökre:

Adott egy 10 elemű egészeket tartalmazó tömb. Oldd meg a következő feladatokat:

  1. Töltsd fel egy 10 elemű tömböt véletlen számokkal a [-5;10] intervallumból.
  2. Írd ki a tömb elemeit.
  3. Írd ki a legkisebb elemet.
  4. Mi a legnagyobb elem sorszáma?
  5. Írd ki az elemek összegét.
  6. Írd ki a páratlan számok darabszámát.
  7. Írd ki a negatív számok átlagát.
  8. Írj ki minden második elemet.
  9. Rendezd a tömböt csökkenő sorrendbe és írd ki az elemeit.
  10. Szorozz meg minden elemet kettővel.
int[] tomb = new int[10];

// 1. feladat Töltsd fel egy 10 elemű tömböt véletlen
// számokkal a [-5;10] intervallumból.
for( int i = 0; i < tomb.length; i++ )
{
  tomb[i] = (int)(Math.random() * 16) - 5;
}

// 2. feladat Írd ki a tömb elemeit.
System.out.println("\n2. feladat");
System.out.println("A tombben levo szamok:");
for ( int sz : tomb)
{
  System.out.print(sz+" ");
}
System.out.println();

// 3. feladat Írd ki a legkisebb elemet.
// lehet foreach, nem érdekel a helye
System.out.println("\n3. feladat");
int min = tomb[0];
for( int szam : tomb )
{
  if( szam < min )
  {
    min = szam;
  }
}
System.out.println("A tomb legkisebb eleme: " + min);

// 4. feladat Mi a legnagyobb elem sorszáma?
// a helye kell, nem jó a foreach
System.out.println("\n4. feladat");
int max = 0;
for( int i = 1; i < tomb.length; i++ )
{
  if( tomb[i] > tomb[max] )
  {
    max = i;
  }
}
System.out.println("A legnagyobb elem sorszama: " + (max + 1));
 
// 5. feladat Írd ki az elemek összegét.
System.out.println("\n5. feladat");
int osszeg = 0;
for( int sz : tomb )
{
  osszeg += sz;
}
System.out.println("A tomb elemeinek osszege: "+osszeg);
 
// 6. feladat Írd ki a páratlan számok darabszámát.
System.out.println("\n6. feladat");
int db = 0;
for( int sz : tomb )
{
  if( sz % 2 != 0 )
  {
    db++;
  }
}
System.out.println("A paratlan szamok darabszama: " + db);
 
// 7. feladat Írd ki a negatív számok átlagát.
System.out.println("\n7. feladat");
int negosszeg = 0;
int negdb = 0;
for( int sz : tomb )
{
  if( sz < 0 )
  {
    negosszeg += sz;
    negdb++;
  }
}
System.out.println("A negativ szamok atlaga: " +
                   (double)negosszeg / negdb);

// 8. feladat Írj ki minden második elemet.
System.out.println("\n7. feladat");
System.out.print("A tömb második elemei: ");
for( int i = 1; i < tomb.length; i += 2 )
{
  System.out.print(tomb[i] + " ");
}
System.out.println();
 
// 9. feladat Rendezd a tömböt csökkenő sorrendbe
// és írd ki az elemeit.
System.out.println("\n9. feladat");
int csere;
for( int i = 0; i < tomb.length - 1; i++ )
{
  for( int j = i + 1; j < tomb.length; j++ )
  {
    if( tomb[i] < tomb[j] )
    {
      csere = tomb[i];
      tomb[i] = tomb[j];
      tomb[j] = csere;
    }
  }
}
for( int sz : tomb )
{
  System.out.print(sz + " ");
}

// 10. feladat Szorozz meg minden elemet kettővel.
System.out.println("\n10. feladat");
for( int i = 0; i < tomb.length; i++ )
{
  tomb[i] *= 2;
}

Láthatod, hogy ahol lehetett, foreach ciklust használtam. A feltöltésnél, minimumkeresésnél, rendezésnél és a második elemek kiíratásánál a fent említettek miatt nem használhattam, így ott for ciklusra volt szükség. Az tűnhet még fel, hogy a foreach ciklusoknál nem i-t használok számok esetén. Ennek csak logikailag van jelentősége. A kezdő programozóknál i jelentése gyakran összekapcsolódik az index fogalommal, így adott esetben ezt tévesen használná fel foreach ciklus esetén.

Még egyszer hangsúlyozom: Foreach esetén a megadott változó nem index, hanem maga az aktuális elem.

For és foreach különbségek objektumok esetén

Ha a tömbünk nem primitív értékeket tartalmaz, hanem objektumokat, a foreach ciklus használata akkor is egyszerű. Tömböt vagy listát feltölteni továbbra sem tudsz, de az elemeket (objektumokat) módosíthatod. Csak arra kell ügyelni, hogy a ciklusban használt változó típusa egyezzen meg a tömb vagy lista típusával.

Na de ne szaladjunk ennyire előre. A feladatban kutyák objektumaival dolgozunk. A kutyáknak van fajtája, életkora és színe. Először a vizslák életkorának összegét kell kiszámítani, majd minden kutya életkorát meg kell növelni eggyel. Ehhez a kutya osztály tartalmaz egy setKor() nevű metódust, mellyel a kutya életkora beállítható. Mivel a leckékben az ArrayList is tananyag volt, és a kutyákat tömbben és listában is tárolhatom, mindkettőre mutatok példát. Lássuk akkor a for ciklusos megoldást tömbbel és listával.

For ciklus tömbbel:
Kutya[] kutyak =
{
  new Kutya("vizsla", 6, "arany"),
  new Kutya("vizsla", 3, "fekete"),
  new Kutya("labrador", 7, "zsemle"),
  new Kutya("labrador", 7, "fekete"),
  new Kutya("vizsla", 3, "barna"),
  new Kutya("labrador", 7, "barna"),
};

int osszeg = 0;
for( int i = 0; i < kutyak.length; i++ )
{
  if( kutyak[i].getFajta().equalsIgnoreCase("vizsla") )
  {
    osszeg += kutyak[i].getKor();
  }
}
System.out.println("A vizslak eletkoranak osszege: "+osszeg);

for( int i = 0; i < kutyak.length; i++ )
{
  kutyak[i].setKor( kutyak[i].getKor()+1 );
}
For ciklus listával:
ArrayList<Kutya> kutyak = new ArrayList<Kutya>();

kutyak.add( new Kutya("vizsla", 6, "arany") );
kutyak.add( new Kutya("vizsla", 3, "fekete") );
kutyak.add( new Kutya("labrador", 7, "zsemle") );
kutyak.add( new Kutya("labrador", 7, "fekete") );
kutyak.add( new Kutya("vizsla", 3, "barna") );
kutyak.add( new Kutya("labrador", 7, "barna") );

int osszeg = 0;
for( int i = 0; i < kutyak.size(); i++ )
{
  if( kutyak.get(i).getFajta().equalsIgnoreCase("vizsla") )
  {
    osszeg += kutyak.get(i).getKor();
  }
}
System.out.println("A vizslak eletkoranak osszege: "+osszeg);

for( int i = 0; i < kutyak.size(); i++ )
{
  kutyak.get(i).setKor( kutyak.get(i).getKor()+1 );
}

Ha megnézed, a for ciklus esetén különbség van a két megoldás között. Tömb esetén a ciklus futási feltétele kutyak.length, lista esetén pedig kutyak.size(). Másrészt amikor egy konkrét kutyát vizsgálunk, akkor tömb esetén az adott elemet az indexe alapján a kutyak[i]-vel érhetjük el, míg listánál a kutyak.get(i) metódussal. Másrészt lista esetén a kiemelt sor kezdőket azért zavarba is tud hozni, hogy akkor most melyik rész pontosan micsoda. Végül is a lényeg látszik: más adatszerkezet, más ciklusmag. A foreach ennél egyszerűbb lenne?

Foreach ciklus tömbbel:
Kutya[] kutyak =
{
  new Kutya("vizsla", 6, "arany"),
  new Kutya("vizsla", 3, "fekete"),
  new Kutya("labrador", 7, "zsemle"),
  new Kutya("labrador", 7, "fekete"),
  new Kutya("vizsla", 3, "barna"),
  new Kutya("labrador", 7, "barna"),
};

int osszeg = 0;
for( Kutya k : kutyak )
{
  if( k.getFajta().equalsIgnoreCase("vizsla") )
  {
    osszeg += k.getKor();
  }
}
System.out.println("A vizslak eletkoranak osszege: "+osszeg);

for( Kutya k : kutyak )
{
  k.setKor( k.getKor()+1 );
}
Foreach ciklus listával:
ArrayList<Kutya> kutyak = new ArrayList<Kutya>();

kutyak.add( new Kutya("vizsla", 6, "arany") );
kutyak.add( new Kutya("vizsla", 3, "fekete") );
kutyak.add( new Kutya("labrador", 7, "zsemle") );
kutyak.add( new Kutya("labrador", 7, "fekete") );
kutyak.add( new Kutya("vizsla", 3, "barna") );
kutyak.add( new Kutya("labrador", 7, "barna") );

int osszeg = 0;
for( Kutya k : kutyak )
{
  if( k.getFajta().equalsIgnoreCase("vizsla") )
  {
    osszeg += k.getKor();
  }
}
System.out.println("A vizslak eletkoranak osszege: "+osszeg);

for( Kutya k : kutyak )
{
  k.setKor( k.getKor()+1 );
}

Hoppá! A két kiemelt rész ugyanaz? A CTRL-C és CTRL-V ész nélküli használata nagyon sokszor fog még boldogítani programozáskor. De nem itt! Foreach ciklus esetén már tisztáztuk, hogy az objektumhoz nem indexeléssel férünk hozzá, hanem a k változóban maga az adott kutya van, ahol éppen a bejárás során tartunk.

A kiemelt részekben azt láthatod, hogy foreach ciklus esetén teljesen mindegy, hogy listával vagy tömbbel dolgozunk, az aktuális elemet mindenképpen a k változóba rakja. Nem érdekli az adatszerkezet, amivel dolgoznia kell. Csak végigmegy az elemein és kész. Berakja azokat a megfelelő változóba, azon keresztül meg mindenhez hozzáférhetünk, amit a kódok megengednek.

A k.setKor( k.getKor()+1 ); sorokban pedig arra láthatsz példát, hogy referencia típusú változók esetén a tömb vagy lista aktuális elemét akár módosítani is tudod. Emlékszel, primitív változók (számtömb) esetén ezt nem teheted meg!

A foreach ciklust a példák alapján sokkal rugalmasabban használhatjuk. A kód rövidebb, átláthatóbb lesz, valamint a megoldás független a használt adatszerkezettől. Azokra kell csak emlékezni, hogy milyen korlátai vannak ennek a ciklusnak. A későbbiekben megismerünk majd új adatszerkezeteket is, amelynél az előzőekhez hasonlóan szintén könnyen hozzáférhetünk a benne tárolt adatokhoz, mégis egyértelmű lesz, hogy mit is csinálunk.

Ahol lehet, használj foreach ciklust!

4 Replies to “Java kiegészítő lecke – Foreach ciklus”

  1. Ha a minimum keresésben nem az indexét keressük, hanem csupán az értéket, talán lehet használni a foreach-t. Vagyis nem “ha tomb[i] < tomb[min] …", hanem "ha tomb[i] < min – akkor min = tomb[i]".

    • Igen, ezt írtam is feljebb, hogy akkor nem használható, ha a helyére van szükségünk. A komplex példába betettem 1-1 feladatot, ahol az érték, és ahol a hely kell, hogy látsszon a különbség.

      Köszönöm az észrevételt!

  2. System.out.println(“\n3. feladat Ez működik ! “);
    min = tomb[0];
    for( int szam : tomb )
    {if( szam < min ) // if( tomb[i] < min )
    min = szam; // min = tomb[i];
    }
    System.out.println("A tomb legkisebb eleme: " + min);
    // Köszönöm a példát, most értettem meg igazán ezt a ciklust!

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .