Java programozás 23. – Saját objektumok

Saját objektumok, avagy kezdjük el tényleg használni a Java nyelvet

Mint már említettem, ez az OO szemlélet abból indul ki, hogy a fejlesztés során modellezett objektumok állandóak, csak a hozzájuk kapcsolódó teendők változnak. Az objektum egyfajta önálló entitás, ami tulajdonságokkal és viselkedésekkel rendelkezik.

Objektumok, mint modellek

Objektum lehet egy egyszerű kávéfőző, ami a következő tulajdonságokkal rendelkezik:

  • vízmennyiség
  • kávémennyiség

Az objektum azonban nem csak adatokat tárol saját magáról, hanem azokat a viselkedéseket is tartalmazza, amelyekkel ezeket az adatokat kezeli. Például van egy “feltölt” utasítása, amellyel kávét vagy vizet lehet tölteni bele. Van egy “főz” utasítása, amellyel kávét lehet főzetni vele.

Az objektumok önállóan léteznek, és önmagukat kezelni tudják, de nem automatikusan, hanem kívülről kell vezérelni őket. Kell egy vezérlőprogram, amely ezt az objektumot használja és utasítja a megfelelő viselkedésre. Például én töltöm fel a kávéfőzőt, de engem nem érdekel, hogy azt ő hogyan csinálja, vagy én indítom a főzést, de továbbra sem érdekel, hogy azt hogyan oldja meg, én csak utasítok.

kavefozoTermészetesen a feltölt metódus nem csak annyit csinál, hogy megnöveli a kávé és vízmennyiséget a gépben, hanem hibaellenőrzés is kapcsolódik hozzá, hiszen nem tölthetem túl a gépet, mert kifolyik. A főzést kezelő metódusban is kell hibakezelés, hiszen nem főzhetek akkor kávét, ha nincs benne víz vagy kávé. De nekem a vezérlőprogramban ezzel sem kell foglalkozni, ott csak kiadom az utasítást: főzzél kávét. Erre maga a gép jelez majd vissza, hogy nem fog menni, mert üres.

Objektumok, mint adattárolók

Az objektumokat nem csak arra használjuk, hogy modellezzünk velük valamit. Akkor is hasznosak, amikor logikailag összetartozó adatokat egy önálló egységként szeretnénk tárolni. A fájlkezeléses feladatok során a forrásban egy sor több adatot is tartalmaz. Azonban minden sor egy önálló egységet jelent, a benne lévő adatok ugyanahhoz a dologhoz tartoznak. Olyan ez, mint amikor egy adatbázis-kezelés feladatban a forrásban egy sor egy egyed tulajdonságait tartalmazza, csak ott a sort rekordnak hívtuk. Mondjuk egy .csv kiterjesztésű fájlban a sorokban lévő pontosvesszők valójában oszlopokat választanak el egymástól, és ezeket beolvasva nagyjából egy adatbázis tábláját kapjuk. Akkor most itt álljunk meg egy pillanatra. Vegyünk egy sort, mely egy kutya adatait tartalmazza. Nevét, fajtáját, színét, tömegét, életkorát és a nemét. A nemét egy logikai változóban tároljuk majd. Ha igaz, akkor kan kutya, ha nem, akkor szuka. Ezeket az adatokat egy sorban soroljuk fel, pontosvesszővel elválasztva a következőképp:

Buksi;tacsko;fekete;11.6;5;1

Ezek az adatok mind ugyanarra a kutyára vonatkoznak. De a fájlban lehet több kutya adata is, hasonló szerkezetben. Ilyenkor minden egyes sor egy új kutyát jelent. Ezért azt tesszük, hogy írunk egy kutya osztályt, amelyben különböző kutyák adatait tartjuk nyilván, de minden kutyáét egy önálló objektumban. Így a különböző adatok nem keverednek össze, de bármelyik kutya összes adatát egyben tudjuk kezelni.

Lássunk akkor egy példakutyát. Emlékszel, minden objektum a következő részekből áll:

  1. Változók
  2. Metódusok
  3. Konstrukciós műveletek
public class Kutya
{
// Változók
  private String nev;
  private String fajta;
  private String szin;
  private double suly;
  private int kor;
  private boolean kan;

// Metódusok
  public String getNev()
  {
    return nev;
  }

  public String getFajta()
  {
    return fajta;
  }

  public String getSzin()
  {
    return szin;
  }

  public double getSuly()
  {
    return suly;
  }

  public int getKor()
  {
    return kor;
  }

  public boolean isKan()
  {
    return kan;
  }

// Konstruktor
  public Kutya(String nev, String fajta, String szin,
             double suly, int kor, int kan)
  {
    this.nev = nev;
    this.fajta = fajta;
    this.szin = szin;
    this.suly = suly;
    this.kor = kor;
    if( kan == 1 )
    {
      this.kan = true;
    }
    else
    {
      this.kan = false;
    }
  }
}

Az előző példában láthatod, hogy a kutya 6 tulajdonsággal rendelkezik. Ezek mindegyike annak megfelelő típusú, amilyen adatot tárolni szeretnénk benne. Minden változót bezártunk, vagyis privát változóvá tettünk. Ezzel azt érjük el, hogy az adott osztály változóját nem lehet közvetlenül elérni, csakis egy metóduson keresztül kaphatjuk meg az értékét. Mint az Osztályok és objektumok témakörben írtam, ennek biztonsági okai vannak.

Az adott osztálynak azon metódusait, melyeknek csak és kizárólag az a szerepe, hogy a változói felől érdeklődőknek választ adjanak, get metódusoknak nevezzük, rövidebben getter-eknek. Get metódust minden olyan változónak biztosítani kell, amelyet kívülről szeretnénk elérhetővé tenni. Ez nem jelenti azt, hogy módosítani is lehet majd, ez csak egy lekérdezés. A get metódusok elnevezése szokásjog szerint a get szóval kezdődik, és utána nagy kezdőbetűvel a változó neve szerepel. Egyetlen kivétel a boolean típusú változót kezelő getter, ahol nem “get” hanem “is” szóval kezdjük a nevet. Ezek a metódusok mindig visszatérési értékkel rendelkeznek, mely nyilván meg kell hogy egyezzen a változó típusával. Ebből a példából kigyűjtve:

public String getNev()
public String getFajta()
public String getSzin()
public double getSuly()
public int getKor()
public boolean isKan()

Az osztályunk konstruktora csak egyfajta, mert a beolvasáskor egy kutya összes adatát megtaláljuk az adott sorban, és ezeket beolvasva, szétdarabolva hívjuk meg a konstruktort, hogy új kutyát hozzunk létre:

new Kutya("Buksi","tacsko","fekete",11.6,5,1)

Ugye emlékszel, hogy ilyet így soha nem csinálunk! Így nincs eltárolva a létrehozott objektum hivatkozása, vagyis úgy hoztuk létre, hogy a kupacról azonnal el is takarítják, amit körbenéznek szemét (vagyis hivatkozás nélküli) objektumok után.

Használjuk úgy, hogy az objektum hivatkozását eltároljuk valahol:

Kutya k = new Kutya("Buksi","tacsko","fekete",11.6,5,1)

Láthatod, hogy a konstruktornak a kutya nemét nem logikai változóként adjuk oda. A fájlból 0 vagy 1-es értéket olvastunk be, majd a konstruktorban beállítjuk, hogy melyik jelenti a true-t, és melyik a false-t. Bár az ilyen szerkezetű beállítás, amit a Kutya osztályban látsz jóval egyszerűbb is lehet. Elegáns, és a legegyszerűbb megoldás:

this.kan = kan == 1;

A konstruktor paraméterei

A konstruktorban nagyon sok mindent megcsinálhatunk, hiszen a kapott értékeket fel kell dolgozni, hogy tárolhatóak legyenek a nekik megfelelő változókban. Lehet, hogy eleve nem olyan formában kapom meg a változókat, hogy azt közvetlenül használni tudjam. Fájl beolvasásakor soronként haladunk, melyeket Stringekként tudunk beolvasni.  Ezeket utána szét kell darabolnunk, hogy aztán azt csináljunk, amit akarunk. Vegyük ismét a beolvasandó példasort:

Buksi;tacsko;fekete;11.6;5;1

Tudjuk, hogy ; karakterrel vannak az egyes “oszlopok” elválasztva egymástól. A beolvasást végző programnak fogalma sincs arról, hogy amit beolvas, az mit jelent. Ő csak beolvas, és odaadja az eredményt annak, aki azt értelmezni tudja. Annyit azért segíthet, hogy a beolvasott sor darabjait adja tovább, valahogy így:

String sor = raf.readLine();
Kutya k = new Kutya( sor.split(";") );

Láthatod, hogy egy új kutyát hozok létre, de a konstruktorának a beolvasott sor darabjait adom oda, melyeket a ; karakternél török szét. Ennek a kódnak más dolga nincs, a kutya megkapta az adatait, építse fel magát.

Hogy néz ki akkor a kutya konstruktora, ha egy halom Stringet kap? A kutyának tudnia kell, hogy a tömb darabjai közül melyik melyik adatát jelenti majd, vagyis úgy kell megírni a kutya konstruktorát, hogy tisztában legyünk a fájl szerkezetével, ami a forrásadatokat biztosítja. Akkor jöjjön a konstruktor:

/*
 * sor:     Buksi;tacsko;fekete;11.6;5;1
 * tömb:  { "Buksi","tacsko","fekete","11.6","5","1" }
 * index:    0       1        2        3      4   5
 */
public Kutya( String[] tomb )
{
  this.nev = tomb[0];
  this.fajta = tomb[1];
  this.szin = tomb[2];
  this.suly= Double.parseDouble(tomb[3]);
  this.kor = Integer.parseInt(tomb[4]);
  this.kan = tomb[5].equals("1");
}

Ez ugye annyit tesz csak, hogy a beolvasott sort tömbbé darabolva a konstruktor a megfelelő darabokat a megfelelő típussá alakítja, majd eltárolja azokat. Ráadásul ez a szerkezet rendkívül rugalmas. Ha a fájlban esetleg megjelenik egy új tulajdonság a kutyánál, mondjuk testmagasság, akkor a beolvasó programon semmit nem kell módosítani. Csak a kutyába kell egy új változó, valamint a konstruktorába kell beszúrni egy új sort, ami az adott tulajdonságot az új változóban tárolja el.

private int magassag;

public Kutya( String[] tomb )
{
 this.nev = tomb[0];
 this.fajta = tomb[1];
 this.szin = tomb[2];
 this.suly= Double.parseDouble(tomb[3]);
 this.kor = Integer.parseInt(tomb[4]);
 this.kan = tomb[5].equals("1");
 this.magassag = Integer.parseInt(tomb[6]);
}

Saját metódusok

Ide most nem a gettereket sorolnám, holott azok is metódusok, csak külön kategóriát alkotnak. Sokszor előfordul, hogy nem csak lekérdezni kell adatokat, hanem az objektumhoz kapcsolódik valamilyen tevékenység is. Tegyük fel, a kutyánkat etetni szeretnénk, és ha “ránézünk”, szeretnénk pár dolgot megtudni róla.

Ezeket a teendőket mind metódusokon keresztül tudjuk megtenni. Hogy egyszerűbb legyen a példa, amikor a kutyát megetetjük, akkor nem lesz éhes. De csak akkor etethetjük, ha valóban az. Az etetéshez bevezetek egy új változót, ehes néven. Ez egy skálán elhelyezkedve a kutya pillanatnyi állapotát jelenti. 0 jelentse azt, hogy nem éhes, a 5-ös pedig a majd éhen halt. Ezen kívül bevezetek egy olyan metódust is, amivel “rá lehet nézni a kutyára”, de hogy milyennek néz ki, az a pillanatnyi állapotától is függ.

Ezek a metódusok nemcsak arra szolgálnak, hogy a két változó értékét módosítják, hanem arra is, hogy ellenőrzött körülmények között teszik azt. Nem fog enni, ha nem éhes. Nézzük meg ezeket:

private int ehes;

public void etet( int kaja )
{
  if( ehes == 0 )
  {
    System.out.println("A kutya nem ehes.");
  }
  else
  {
    System.out.println("A kutya jóllakott.");
    ehes = 0;
 }
}

Lássuk a ránézést.

 public String leiras()
{
  StringBuilder desc = new StringBuilder();
  desc.append("Ez egy "+szin+" szinu "+fajta+". Jelenleg ");
    
  String kaja;
  switch( ehes )
  {
    case 0 :  kaja = "nem"; break;
    case 1 :  kaja = "kicsit"; break;
    case 3 :  kaja = "kozepesen"; break;
    case 4 :  kaja = "nagyon"; break;
    default : kaja = "borzasztoan"; break;
  }
  desc.append(kaja+" ehes.");

  return desc.toString();
}

Az objektumok tehát rendkívül sokoldalúak. Valódi dolgok modelljeként is használhatjuk őket, valamint adattárolóként is működnek. Az emelt érettségi programozási feladatában ez utóbbira van szükségünk.

Pár lényeges dolog összeszedve:

  • A változókat mindig védd meg, tedd őket priváttá.
  • Írd meg a megfelelő get metódusokat, hogy elérd a változókat.
  • Ha a változón módosítani kell, arra is írj metódust. (setter)
  • Egy jó konstruktor már fél siker. Állíts be benne mindent, amit csak tudsz. Akár olyan változókat is, melyeket nem a fájlbeolvasáskor kaptál, hanem a meglévő változókból lehet kiszámítani. A konstruktort utólag is bővítheted.
  • Írj saját metódusokat, és használd az objektum változóit, ha szükséged van rájuk.
  • Mindig legyen egy aktualizált toString() metódusa az objektumnak, mely a változóit írja ki, így ellenőrizni tudod, megfelelő objektummal dolgozol-e.

 

4 Replies to “Java programozás 23. – Saját objektumok”

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

  2. Kedves Csaba, nagyon jó és összeszedett leírás. Itt a végén egy dolog nem teljesen világos: hol a helye a saját metódusoknak és pontosan hogyan hivatkozhatunk, jeleníthetjük meg az eredményüket?

    • A saját metódusoknak abban az osztályban van a helye, amelyiknek a változóit (állapotát) használják vagy kezelik (setter, getter). Helyileg mindegy, hogy a konstruktor előtt, után van, vagy épp mi az objektum metódusainak sorrendje. Itt is vannak esetleges konvenciók, hogy pl a publikus metódusok legyenek az objektumban hamarabb, és utánik a privát metódusok. Esetleg az is megoldható, hogy azok a metódusok legyenek egy csoportban, amelyek logikailag összetartoznak. Így pl az is kiderülhet, hogy az osztályt esetleg célszerűbb kisebb, logikailag koherensebb osztályokra szétbontani.

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 .