Java programozás 20. – ArrayList

ArrayList, avagy a felmentő sereg a rugalmatlan tömbök helyett

A tömbök korlátjai

Szerintem mindenki emlékszik arra a pillanatra, amikor megismerte a tömböket. Vagy szerelem volt első látásra, vagy ekkor esett először komolyan kétségbe. De ha túltette magát az első sokkon, akkor rájött, hogy nem is olyan bonyolultak. A tömböket nagyon szeretjük. Nagyon sok és sokfajta adatot képesek tárolni. Ezek lehetnek primitív, vagy referencia típusok is. Mi több, az elemeknek sorrendje van. A nagyon sok adatból bármikor kivehetünk egyet. Akár megvizsgálhatjuk az összeset, szigorúan sorban haladva. Akkor mi a gond vele?

  1. A mérete
  2. A bővítése
  3. Keresés az elemei között

Sorolhatnám még, de bevezetésnek ennyi pont elég.

Az első problémával már biztosan találkoztál. A tömbnek elsőre jó méretet kell választani. Miért? Mert a mérete fix. Ez egy nagyon komoly döntés. Ez nem egy hajvágás. Az kinő újra. De egy tömb méretét megváltoztatni… Aztán rájössz, hogy nagyobb kell, akkor készíthetsz másikat, és abba átpakolhatod az eredeti értékeket, meg azokat, amik nem fértek el. És ha az is kicsi lesz? Vagy elsőre kiszámolhatod, hogy mekkorára van szükség, és létrehozod amekkora kell. És ha valami nem várt esemény miatt mégiscsak kicsi? Vagy épp túl nagy?

A második gond elsőre hasonlíthat az elsőhöz, valójában teljesen más. Itt bővítés alatt nem feltétlenül arra gondolok, hogy a tömb kicsi. Tételezzük fel, hogy van egy 100 elemű tömböd. Okosan ekkorát hoztál létre, mert tudtad, hogy ennél több elemet soha nem kell tárolnod. De csak 65-öt tettél bele. Akkor is felkészültél mindenre, mert az új elemeket bármikor odarakhatod a tömb végére. Az már csak apróság, hogy ha nincs tele a tömb, akkor a méretét megadó tömb.length értelmét vesztette. Neked kell külön nyilvántartanod és folyamatosan frissítened, hogy mennyi valódi elem van benne. Ráadásul olyan ügyes vagy, hogy így még ki is vehetsz elemet a tömb végéről (pontosítok, nullázod az ottani elemet), és ekkor csökkentheted a valódi elemszámot tároló változót. Profi. Hozzáadhatsz és el is vehetsz belőle. Az is apró szépséghiba, hogy a tömb végén a nem valódi elemek ugyanakkora memóriát foglalnak, mint az elején lévő valódiak. Képzeljük el, hogy gyerekek neveit tárolod annak megfelelően, hogy a tornasorban hol állnak. Érkezett egy új gyerek. Hova állítod? A sor végére? Elég ritka eset. De a tömbbe nem lehet csak úgy akárhova beszúrni egy elemet. A többit odébb kell pakolni. Neked. És ha távozik egy gyerek? Az sem feltétlenül a sor végéről fog eltűnni. És a többi üresen hagyja a helyét? Vagy pakoljunk mindenkit eggyel előrébb, aki utána állt?

A harmadik probléma akkor jött elő, amikor meg akartuk tudni, hogy egy tömbben benne van-e egy elem, akkor meg kellett keresni. Ha ügyesek voltunk, és lehetőségünk volt rá, akkor valamilyen rendezett tömbbel dolgoztunk. Abban lehet, hogy nem lineárisan, minden elemet megvizsgálva kell keresni. De milyen jó lenne, ha a kereséssel nem nekünk kell foglalkozni, hanem azonnal választ kaphatnánk arra, hogy benne van-e a keresett elem, vagy nem.

A tömbök buták. Szeretjük őket, de buták. A tanulmányaink elején muszáj megismernünk őket. Rajtuk keresztül tanulunk meg programozni. És minél jobban megtanulunk, annál jobban megismerjük a korlátait. Felismerjük azt, hogy amit mi eddig félistenként tiszteltünk, mert mindent meg tudtunk oldani vele (igaz, néha körülményesen), valójában inkább spanyolcsizma. Szűk, rugalmatlan, és ha sokat akarunk ugrálni, akkor nagyon szúr.

Ismerjük meg azt, ami minden gondunkat megoldja.

ArrayList

Ha nagyon sarkosan szeretnénk fogalmazni, mondhatnánk, hogy az ArrayList egy változtatható méretű tömb. Sőt, a méretével egyáltalán nem kell foglalkoznunk, ha nem akarunk. Megkérdezni azért szabad.

Az ArrayList valójában egy osztály, ami a motorháztető alatt szintén egy tömbbel dolgozik. De nem most mondtam, hogy a tömb mérete fix? És ha változtatni kell a tömb méretén? Akkor létrehoz egy újat és azzal dolgozik. Az ArrayList osztály tele van pakolva olyan hasznos metódusokkal, amelyek az összes előzőleg felsorolt problémát nemcsak hogy megoldják, hanem még többre is képesek. Oké, így picit becsapva érezheted magad, hiszen mégis csak tömböt használsz. Csak nem Te. És ez sok gondtól megkímél.

Lássuk akkor, hogyan használhatjuk az ArrayList-et, és mi mindenre jó. Tételezzük fel, szükségünk van egy olyan listára, mely egész számokat tárol.

Ahhoz, hogy létrehozhassunk egyet, importálni kell azt a kódot, ahol ő található, az ArrayList osztályt:

import java.util.ArrayList;
public class Lista
{
  public static void main(String[] args )
  {
    ArrayList<Integer> szamok = new ArrayList<>();
  }
}

Az első kiemelt sor mutatja az osztály importálását, ilyet már láthattál a Scanner esetén. A második egy változó deklarálás és egy példányosítás. Akkor most dekódoljuk, hogy mit is látunk:

  1. Megadunk egy ArrayList osztályú változót.
  2. Rögzítjük, hogy ebben Integer osztályú objektumokat szeretnénk tárolni.
  3. A változó neve: szamok
  4. Egy új listát hozunk létre, ahol a típust már nem kell újra megadni.
  5. És nem adunk meg semmit sem a konstruktorának.

Miért nem <int> szerepel a típusmegadásnál, ahogy a tömböknél láttuk? Mi ez az Integer? Nagy betűvel kezdődik, akkor ez egy osztály?

Igen. Ez egy burkoló vagy csomagoló osztály. Arra való, hogy becsomagolja magába a primitív értéket, így olyan helyen is használhatjuk azokat, ahol csak Objektummal állnak szóba.

Azért van erre szükség, mert a lista csak és kizárólag referencia típusú adatokat képes tárolni, primitív típusokat nem rakhatunk bele. Ha mégis azokat szeretnénk tárolni, akkor a primitív típusok megfelelő csomagoló osztályát kell használnunk típusként:

  • int helyett Integer
  • double helyett Double
  • char helyett Character
  • boolean helyett Boolean
  • (valamint a többi egész és valós típus, azonos névvel)

Vegyünk egy tömb témakörrel kapcsolatos komplex feladatot, de most új barátunkat használjuk. Sorsoljunk ki 20 egész számot a [-10;40] intervallumból és tároljuk el őket. A kiemelt sorokat a példa után megmagyarázom, ezek tartalmazzák a gyakran használt ArrayList metódusokat és a lényegi részeket, melyek a lista általános használatához szükségesek.

import java.util.ArrayList;
public class Lista
{
  public static void main(String[] args )
  {
    ArrayList<Integer> szamok = new ArrayList<>();
// töltsük fel a listát
    for( int i = 0; i < 20; i++ )
    {
      szamok.add( (int)(Math.random() * 51) - 10 );
    }
    System.out.println("A lista mérete: " + szamok.size());

// írjuk ki az elemeit
    for( int i = 0; i < szamok.size(); i++ )
    {
      System.out.print( szamok.get(i)+" " );
    }
    System.out.println();

// töröljünk ki a lista legkisebb elemét
// ha több legkisebb van, akkor az elsőt
    int min = 0;
    for( int i = 0; i < szamok.size(); i++ )
    {
      if( szamok.get(i) < szamok.get(min) )
      {
        min = i;
      }
    }
    System.out.println("A legkisebb eleme: " + szamok.get(min));
    szamok.remove(min);
    System.out.println("A lista merete: " + szamok.size());
    
// a lista elemei
    for (Integer i : szamok)
    {
      System.out.print(i+" ");
    }
    System.out.println();
    
// szúrjunk be egy véletlen elemet a lista elejére
    szamok.add( 0, (int)(Math.random() * 51) - 10 );

    for (Integer i : szamok)
    {
      System.out.print(i + " ");
    }
    System.out.println();
    
// nézzük meg, benne van-e az intervallum legnagyobb
// eleme a listában, és ha igen, hol?
    int hely = szamok.indexOf(40);
    if( hely > -1 )
    {
      System.out.println("A 40-es elem helye: " + hely);
    }
    else
    {
      System.out.println("Nincs 40-es elem a listában.");
    }

// vizsgáljuk meg, van-e 0 érték a listában
    if( szamok.contains(0) )
    {
      System.out.println("A lista tartalmaz 0-at.");
    }
    else
    {
      System.out.println("A lista NEM tartalmaz 0-at.");
    }

// rendezzük a listában szereplő számokat növekvő sorrendbe
    int csere;
    for (int i = 0; i < szamok.size() - 1; i++)
    {
      for (int j = i + 1; j < szamok.size(); j++)
      {
        if( szamok.get(i) > szamok.get(j) )
        {
          csere = szamok.get(i);
          szamok.set(i, szamok.get(j));
          szamok.set(j, csere);
        }
      }
    }

// írjuk ki a rendezett számokat
    System.out.println("Rendezett sorrend:");
    for (Integer i : szamok)
    {
      System.out.print(i + " ");
    }
    
    System.out.println();

// töröljük ki a negatív elemeket a rendezett listából
    for( int i = 0; i < szamok.size(); i++ )
    {
      if( szamok.get(i) > -1 )
      {
        szamok.removeAll(szamok.subList(0, i));
        break;
      }
    }

// írjuk ki a listában maradt elemeket
    for (Integer i : szamok)
    {
      System.out.print( i +" ");
    }
  }
} 

Akkor lássuk a feladat megoldását részenként, melyen keresztül az ArrayList működését is megértjük. A felsorolás elején lévő számok a kiemelt sorokat jelentik.

  • 8 – Figyeld meg, hogy nem hivatkozok a lista méretére a feltöltésekor, mivel a mérete alaphelyzetben 0. A for ciklusban a futási feltételben számként adom meg, hogy 20x fusson le a ciklus, vagyis 20 elemet fogok eltárolni a listában. Minden elem hozzáadás után a lista mérete eggyel nő.
  • 10 – Itt láthatod, hogyan adunk hozzá egy elemet a listához, ami mindig a lista végére kerül.
  • 12 – A lista méretét a .size() metódussal kaphatod meg.
  • 15 – A .size() már szerepelt, de most már a lista bejárásához használom egy for ciklus futási feltételében.
  • 17 – A lista bármelyik eleme indexelhető, hasonlóan a tömbökhöz, csak itt a hivatkozáshoz a .get(index) metódust használjuk, és nem a tömböknél tanult tomb[index] szerkezetet.
  • 32 – Bármilyen elemet eltávolíthatok az indexe alapján a .remove(index) metódussal. Az utána elhelyezkedő elemek eggyel balra tolódnak és a lista mérete eggyel csökken.
  • 36-39 – Foreach ciklus használható az elemek eléréséhez, például kiíratás esetén. Ha csak az elemek számítanak és az indexük nem, akkor a foreach ciklus mindig használható a for helyett.
  • 43 – Az add(index, elem) metódussal a lista tetszőleges helyére beszúrhatunk egy elemet. Ha nem a lista végére szúrunk be elemet, akkor a beszúrás helyén lévő és a mögötte állók eggyel jobbra tolódnak, vagyis valódi beszúrásról beszélünk, nem cseréről!
  • 53 – Az String kezelésből már ismert .indexOf(elem) metódussal megkaphatjuk egy adott elem helyét a listában. Ha az eredmény -1, akkor nincs a listában. Az indexOf() mindig a lista elejéről indítja a keresést, és több előfordulás esetén az első találat helyét adja meg. A lastIndexOf(), hasonlóan a String témakörben tanulthoz hátulról adja meg az első előfordulás helyét, és -1-et ha nincs találat.
  • 64 – A .contains(elem) logikai választ (boolean) ad arra a kérdésre, hogy az adott elem benne van-e a listában.
  • 82-83 – A set(index, elem) metódus az index helyen lévő elemet cseréli fel az általunk megadottra. Ilyenkor a mögötte álló elemek a helyükön maradnak, vagyis nem beszúrás történik. Jellemzően az elemek felcserélésekor használjuk, hiszen az elemek eltávolítása és hozzáadása nem így történik.
  • 102 – Ez egy komplexebb példa. Egy listából ki lehet törölni egy másik lista elemeit. Jelen esetben a .subList(int start, int end) metódust használom. A for ciklusban megnézem, hogy a rendezett tömbben hol található az első nem negatív elem. Ennek a helye i lesz. A szamok.subList(0, i) azt jelenti, hogy a 0 indextől az i előtti indexig tartó elemeket kiemelem a listából, majd ezt a kapott listát odaadom a removeAll metódusnak, hogy ezeket törölje a szamok listából.

Ezek a példák lefedik az ArrayList témakör nagy részét. Persze vannak még finomságok benne, de úgy gondolom indulásnak ennyi pont elég. Egy fontos dolgot viszont megemlítenék:

Az ArrayList is túlindexelhető! Nem hivatkozhatsz olyan indexű elemre, ami nem létezik!

ArrayList, de nem minden áron

A helyzet az, hogy nem minden esetben éri meg az ArrayList-et használni. Tény, hogy rengeteg mindent tud, de a tömböket nem válthatja ki teljes mértékben. Tisztázzunk akkor pár irányelvet, melyet figyelembe kell venni, hogy ha választanod kell a tömb és az ArrayList között.

Tömb:

  • Ha a tanulmányaid elején jársz.
  • Ha előre tudod, hány elemet szeretnél tárolni, és nem akarod bővíteni a számukat.
  • Ha csak primitív értékeket tárolsz.
  • Ha az alap algoritmusokat még nem alkalmazod hibátlanul.

ArrayList:

  • Ha már az alap algoritmusokat tetszőleges feladatokban hibátlanul alkalmazni tudod.
  • Ha objektumokkal dolgozol.
  • Ha a tárolt elemeid száma változhat.
  • Ha a tömbök már inkább korlátoznak, mint segítenek.

Diamond operátor

A 7-es verziójú Java-tól kezdődően bevezették az úgynevezett diamond operátort. Ez valójában nem operátor, de hivatalos Java oldalon is így nevezik, valamint rengeteg hivatkozás is ilyen névvel illeti. Arról van szó, hogy a lista deklarálása után az inicializáláskor nem kötelező a típust megadni, a szerkezetből elhagyható. A 6. sorban lévő eredetileg ismertetett deklarálást és inicializálást rövidítheted a 7. sorban látható módon. A diamond talán a típuselhagyás után ottmaradó <> jelek alakjára utal. Azért mutattam meg ezt a dolgot, mert újabb kódokban már nem találkozhatsz ilyennel, de régiekben még a megjegyzésben szerepló forma is előfordulhat. Nem hiba, csak már felesleges ismét kiírni a típust.

import java.util.ArrayList;
public class Lista
{
  public static void main(String[] args )
  {
// ArrayList<Integer> szamok = new ArrayList<Integer>();
    ArrayList<Integer> szamok = new ArrayList<>();
  }
}

Egyéb tudnivalók

Hamarosan ide kerülnek a burkoló osztályokkal kapcsolatos tudnivalók.

Következő lecke: Fájlkezelés alapjai

8 Replies to “Java programozás 20. – ArrayList”

  1. Pingback: Java programozás 13. – Osztályok és objektumok |

  2. Pingback: Java programozás 24. – asszociatív tömb avagy rugalmas párok |

  3. Pingback: Java kiegészítő lecke – Foreach ciklus

  4. Pingback: Java programozás 21. – Fájlkezelés alapjai

  5. Szia!
    Szuper az oldal,rengeteg oldalt neztem vegig,de a te oldalad az abszolut nyero!Nagyon jol magyarazol,le a kalappal elotted.Bar sajnalom hogy nincs folytatás.Tervezed hogy fogsz meg boviteni?Szivesen latnek az oldalon List,LinkedList-el kapcsolatos peldakat,mqgyarazatokat.Az Oroklodesrol is szivesen fogadnek posztot
    De rengeteg dolgot irhatnek meg ide.Egy szo,mint szaz.Szuper vagy!!!Orok halam neked!!!

  6. nagyon jók a magyarázatok, a példák viszont nehezen követhetőek, mert nincsenek strukturálva,
    nekem azt tanították, hogy ha egy függvény/metódus több mint 20 sor, akkor túl sok mindent csinál egyszerre, és bontsam szét,

Hozzászólás a(z) Fan bejegyzéshez Válasz megszakítása

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 .