Kihagyás

5. gyakorlat

Figyelem! A héten kikerül a biróra a 2. házi feladat!

A gyakorlat anyaga

Osztályok létrehozása

Ahogy korábban már tárgyaltuk, a konkrét életbeli objektum(csoportok) formai leírása lesz az osztály. Osztályokat kell létrehoznunk ahhoz, hogy majd létre tudjunk hozni a memóriában objektumokat. Osztályokkal olyan objektumokat formalizálunk, melyek azonos tulajdonságokkal és operációkkal rendelkeznek, mint például az emberek. Sok különböző ember létezik, de mégis rendelkeznek közös tulajdonságokkal: van név és kor tulajdonságuk, valamint mindenki tudja magáról, hogy férfi-e. Készítsünk egy ilyen osztályt, melynek kulcsszava a class. Általában osztályokat külön fájlokba készítünk, melynek neve megegyezik a létrehozni kívánt osztállyal, kiegészítve a .java kiterjesztéssel. Tehát készítsük el az Ember.java fájlt:

1
2
3
public class Ember {

}

Az embereknek van néhány közös tulajdonságuk, amelyet UML-ben tulajdonságnak, attribútumnak hívtunk. Most a név, kor , valamint a férfi-e tulajdonságot szeretnénk a programunkban használni. Ezek legyenek String, int és boolean típusú változók.

1
2
3
4
5
public class Ember {
    String nev;
    int kor;
    boolean ferfi;
}

Elkészült az osztályunk, már csak a viselkedést kellene valahogy a forráskódban is reprezentálni. Erre metódusok lesznek a segítségünkre, melyeket az osztályba írunk bele, és ezek azt jelentik, hogy az adott osztályból létrejövő objektumok milyen viselkedéssel rendelkezhetnek. Például, az emberek tudnak köszönni, ez operációjuk. Készítsük el a koszon metódust.

1
2
3
4
5
6
7
8
9
public class Ember {
    String nev;
    int kor;
    boolean ferfi;

    public void koszon(){
        System.out.println("Szia! " + nev + " vagyok és " + kor + " éves, mellesleg " + (ferfi ? "férfi." : "nő."));
    }
}

Az így elkészült osztályból objektumokat készítünk, ezt példányosításnak hívjuk. Minden egyes objektum pontosan egy osztály példánya, azonban egy osztályból számtalan objektumpéldány is létrehozható. Hozzuk létre Józsit a main függvényben a new operátor segítségével.

1
2
3
4
public static void main(String[] args){
    Ember jozsi = new Ember();
    jozsi.koszon();
}

Fordítsuk, majd futtassuk le a programot. Ennek kimenete: Szia! null vagyok és 0 éves, mellesleg nő. Nem éppen lett Józsi, még férfi sem igazán. Ennek az az oka, hogy az osztályban lévő adattagok nincsenek rendesen inicializálva, a típusokhoz tartozó default értéket vették fel (előző gyakorlat). Ahhoz, hogy ezt megfelelő módon megtehessük, konstruktort kell létrehoznunk.

Konstruktor

Ez a speciális függvény fogja létrehozni a példányokat (objektumokat) az osztályokból, beállítani az osztályban deklarált változókat a konkrét, adott objektumra jellemző értékekre. Ilyen alapból is létezik (hiszen az előbb mi sem írtunk ilyet), de mi paramétereseket is megadhatunk, ami segítségével gyorsan és könnyen tudjuk az adott objektumot inicializálni. Megjegyzendő, hogy ha mi készítünk bármilyen konstruktort, akkor a fordító nem készít egy default, paraméter nélküli konstruktort (ahogy azt tette korábban).

  • A konstruktor nevének meg kell egyeznie az osztály nevével
  • Visszatérési értéke/típusa nincs (nem is lehet!)
  • Új objektum létrejöttekor fut le
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
/*
  default konstruktor

  Ez alapból létezik, nem muszáj kiírni, hacsak nem szánunk neki speciális szerepet.
*/
public Ember() {}

/*
  paraméteres konstruktor

  minden esetben létre kell hoznunk (ha szeretnénk paraméteres konstruktort).
*/
public Ember(String nev, int kor, boolean ferfi) {
      this.nev = nev;
      this.kor = kor;
      this.ferfi = ferfi;
}

this kulcsszó - az objektum saját magára tud hivatkozni vele, ennek akkor van gyakorlati haszna például, amikor egy metódusban szereplő paraméter neve megegyezik az osztályban deklarált adattag nevével, akkor valahogy meg kell tudnunk különböztetni, hogy melyik az objektum tulajdonsága, és melyik a paraméter.

A paraméteres konstruktorral könnyen, egy sorban létrehozhatjuk és értéket is adhatunk a példányoknak, ami energia- és helytakarékos módszer a paraméter nélkülihez képest.

Az így kiegészített osztály után hozzuk létre most már valóban Józsit.

1
2
3
4
public static void main(String[] args){
    Ember jozsi = new Ember("Jozsi", 20, true);
    jozsi.koszon();
}

A program kimenete: Szia! Jozsi vagyok és 20 éves, mellesleg férfi.

Láthatóságok

A láthatóságok segítségével tudjuk szabályozni adattagok, metódusok elérését, ugyanis ezeket az objektumorientált paradigma értelmében korlátozni kell, kívülről csak és kizárólag ellenőrzött módon lehessen ezeket elérni, használni. Része az implementáció elrejtésének, azaz akár a programot, akár az osztályt anélkül használják kívülről a felhasználók, hogy pontosan ismernék annak működését.

Eddig ismert láthatóságok:

public - mindenhonnan látható

private - csak maga az osztály látja

nem írtunk ki semmit - "friendly" láthatóság, csomagon belül public, csomagon kívül private

A későbbiekben majd megismerkedünk a 3. láthatósági módosítószóval is.

protected - a csomag, az osztály és az azokból származtatott gyermekosztályok látják

Getter/Setter

Az adattagok értékeinek lekérésére (getter), valamint inicializálás utáni módosítására (setter) használjuk. Ezek összefüggenek azzal, hogy egy objektum adatait csak ellenőrzött módon lehet lekérni, illetve módosítani, ezeken a függvényeken keresztül. Általában csak simán lekérésre, és beállításra használjuk, de ide tehetünk mindenféle ellenőrzéseket is például.

Ezekkel tulajdonképpen elrejtjük a változókat és akár még módosíthatunk is az értékeken lekéréskor vagy megadáskor (mondjuk setternek megadhatjuk, hogy ne hagyja 0-ra állítani a változó értékét (például egy osztás nevezőjét))

Hagyományos elnevezése: get + adattag neve illetve set + adattag neve.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
//getter
public String getNev() {
    return this.nev;
}

//setter
public void setNev(String nev) {
    this.nev = nev;
}

//setter
public void setKor(int kor) {
    if (kor > 0) {
        this.kor = kor;
    } else {
        System.err.println("A kor csak pozitív szám lehet!");
    }
}

Boolean értékek esetében a getter függvényt általában is + adattag neve formában szoktuk elnevezni.

1
2
3
4
5
6
7
/*
Kiegészítés: boolean érték gettere
A boolean értékű gettert get kulcsszó helyett is-zel szokás jelölni.
*/
public boolean isFerfi(){
    return this.ferfi;
}

Az Eclipse kedvesen segít ezek előállításában is (ez a ZH-n is használható), a munkaterületen jobb klikk és Source > Generate Getters and Setters... menüpontban találjuk meg az ide kapcsolódó varázslót.

toString metódus

Írassuk ki az elkészült jozsi nevű, ember típusú objektumunkat:

1
System.out.println(jozsi);

A program kimenete valami hasonló: Ember@677327b6. Ez nem túl szerencsés, pláne nem olvasható. Ezt megoldhatjuk úgy, hogy az Ember osztály összes adattagját egyesével kiírogatjuk, ám ez macerás lehet, ha több helyen is szeretnénk ezt a kiíratást használni.

Ha minimális munkával szeretnénk emberi nyelvű leírószövegeket adni az objektumról minden esetben, amikor például átadjuk a kész objektumot a System.out.println metódusnak, akkor felül kell definiálnunk a toString() metódust. Ez egy public láthatóságú, String-gel visszatérő metódus.

1
2
3
4
5
public String toString() {
    return "Az ember neve: " + this.nev +
            ", aki " + this.kor + " eves" +
            ", ferfi=" + this.ferfi;
}

Ami ugyanolyan mintha egy sorba írtuk volna a return-t, csak így nem fut ki a sor a végtelenbe. Ezek után nincs más dolgunk, nézzük meg mi történik a következő sor hatására:

1
System.out.println(jozsi);

Ennek eredménye: Az ember neve: Jozsi, aki 20 eves, ferfi=true

Ha egy kicsit szépíteni szeretnénk még ezen is, akkor a ferfi tulajdonságtól függően egy egy adott szöveget is kiírathatunk a háromoperandusú if-else (ternary operátor) használatával.

1
2
3
4
5
public String toString() {
    return "Az ember neve: " + this.nev +
            ", aki " + this.kor + " eves" +
            (this.ferfi ? "ferfi" : "no");
}

Ebben az esetben zárójelet is kell használnunk, különben a fordító a már létező szöveghez akarná hozzáfűzni a this.ferfi tulajdonságot, és az így létrejövő Stringgel szeretné meghívni a ternary operátort, aminek nem lenne értelme, hiszen a ternary operátorhoz egy logikai értékre van szükség a kérdőjel bal oldalán.

1
System.out.println(jozsi);

Ennek eredménye: Az ember neve: Jozsi, aki 20 eves ferfi

Az IntelliJ Idea és az Eclipse is szívesen segít a toString elkészítésében, hogy ezt a robotikus munkát ne kelljen kézzel

  • IntelliJ Idea esetén: Code > Generate menüpont, majd pedig a felugró menüben toString(). A menüre kattintásokat kiküszöbölhetjük, ha a gyorsbillentyűjén keresztül hívjuk meg: Alt + Insert, majd toString().
  • Eclipse esetén: A munkaterületen jobb klikk és Source > Generate toString()... menüpontban találjuk meg az ide kapcsolódó varázslót.

Ez pedig valami ilyesmit fog elkészíteni helyettünk

1
2
3
4
5
6
7
8
@Override
public String toString() {
    return "Ember{" +
            "nev='" + nev + '\'' +
            ", kor=" + kor +
            ", ferfi=" + ferfi +
            '}';
}

A @Override egyelőre számunkra nem érdekes. Az elkészült toString ugyan nem a legszebb, de minden adatot könnyedén ki tudunk belőle olvasni, így bármilyen célra hasznos lehet.

Static

Adattag

Ugyanazon a helyen tárolódik a memóriában, az összes példány esetében ugyanaz lesz az értékük, gyakorlatilag az osztály összes példánya osztozik rajtuk, példányosítás nélkül is hivatkozhatunk rájuk. Tulajdonképpen ezek az adattagok nem az objektumokhoz, hanem az osztályhoz tartoznak.

Gyakorlati jelentősége lehet például akkor, ha egy változóban szeretnénk letárolni egy olyan értéket - például a létrehozott objektumok darabszámát - amelyeknek azonosnak kell lenniük minden objektumpéldány esetén, sőt, akár példányosítás nélkül is értelmesek.

Hivatkozni rájuk Osztály.adattag módon lehetséges, amennyiben látható az adattag az adott helyről.

Metódus

Ezek a metódusok gyakorlatilag nem az objektumokhoz, hanem az osztályhoz tartoznak. Objektum példányosítása nélkül is meghívhatóak.

Ezekre példát már sokat láttunk, ilyenek többek között a public static void main(), Integer.parseInt() metódusok.

Statikus metódusban csak statikus adattagok és a kapott paraméterek használhatóak, hiszen ezek nem kapcsolódnak egyetlen objektumhoz sem, azok létezése nélkül is meghívhatunk egy statikus metódust.

Ezek a metódusokat nem lehet felüldefiniálni, de erről a későbbiekben fogunk tanulni.

Final

Adattag

A final adattag kezdeti értékét nem változtathatjuk meg a futás során. Ehhez persze adni kell kezdőértéket, vagy a létrehozás helyén (pl.: final int TOMEG = 10) vagy pedig a konstruktorban. Ha a final értéken változtatni próbálnánk meg, akkor fordítási hibát kapunk. Próbáljuk ki a következőt.

1
2
private final int tomeg = 10;
tomeg=20; // Fordítási hiba!!

Metódus

Ezeket a metódusokat a gyerekosztályban nem lehet felüldefiniálni. Erről a későbbiekben fogunk tanulni, de itt érdemes visszaemlékezni az UML-nél tanultakra.

Osztály

Nem lehet gyerekosztálya.

Konstans

Nincs rá külön kulcsszó, az eddigiekből merítve azonban könnyen kitalálható: valódi konstans létrehozása a static és final kulcsszavak együttes használatával, hiszen az így létrejövő változó: statikus lesz, bármely objektumpéldány esetén ugyanazt az egy konkrét adatot látjuk/módosítjuk; valamint a final miatt a kezdőérték nem változtatható meg.

Konstans változók nevét általában csupa nagybetűvel szoktuk írni, szóhatárnál aláhúzást teszünk.

1
2
3
public class Alma {
 public static final int ALMA_TOMEG = 10;
}

Ennek elérése kívülről:

1
int almaTomege = Alma.ALMA_TOMEG;

Garbage collection - Szemétgyűjtés

Objektumok élettartama Java-ban: (élettartam = objektum létrejöttétől a memória felszabadításáig) Amikor létrehozunk egy objektumot a new kulcsszóval, az objektum a heapen jön létre (ellentétben a primitív típusokkal). A memória felszabadítása automatikus Javaban, a garbage collector (Szemétgyűjtő) végzi el a feladatokat. A Java szemétgyűjtőről bővebben ezen, ezen és ezen az oldalon olvashatunk. Ha egy objektumot nem használunk tovább, beállíthatjuk null-ra, ez a kulcsszó jelöli, hogy az adott referencia nem hivatkozik egyetlen objektumra sem (pl. jozsi = null;) Garbage collector hívása manuálisan (nem biztos hogy lefut):

1
System.gc();

Lehetőségünk van az osztályainknak egy finalize()-nak nevezett metódust készíteni. Ez a metódus akkor fog lefutni, amikor a szemétgyűjtő felszabadítja a feleslegesnek ítélt memóriát, és egy adott objektum, amit már nem használunk, törlődni fog. Azt természetesen nem tudni, hogy mikor fog lefutni, vagy hogy le fog-e egyáltalán. A metódus célja az objektum által használt valamilyen erőforrás felszabadítása (erre később látunk példát).

Videók

Feladatok

  1. Írj egy osztályt, amely téglalapot reprezentál, annak oldalhosszait tárolja. Készíts neki konstruktort, amely az oldalakat inicializálja. Írj az osztálynak még egy konstruktort, amely csak egy paramétert vár és amellyel négyzetet lehet létrehozni. Készíts metódusokat a kerület és terület kiszámítására. Írj egy másik osztályt, amely futtatható (van benne main függvény), és a parancssori paramétereknek megfelelően létrehoz téglalap objektumokat a Téglalap osztályból, és kiszámolja a Téglalapok területének és kerületének átlagát. Példa a main függvényre: számhármasok, az első szám jelöli, hogy 1 vagy 2 paraméterből inicializálódik a téglalap, azaz négyzetet vagy téglalapot szeretnénk létrehozni, majd az ezt követő 1 vagy 2 szám tartalmazza a téglalap oldalhosszait. java TeglalapMain 1 5 2 10 22 2 9 8 1 100. Ennek jelentése: Először létrehozunk egy négyzetet, 5-ös oldalhosszal, majd téglalapot 10, 22 oldalhosszakkal, majd megint téglalapot 9 és 8 oldalhosszakkal, majd egy négyzet, melynek 100 az oldalhossza.

Kocsmaszimulátor part 1:

Bővítsük ki a már létező Ember osztályt egy privát pénz, és részegség int, és egy kocsmában boolean változókkal. Legyen egy új konstruktor, ez fogadjon a már meglévő paramétereken kívül egy pénz paramétert is, amit állítson be az Ember pénzének. A részegség 0, a kocsmában false legyen alapértelmezetten. Legyen az Embernek egy iszik(Kocsmáros kocsmaros) metódusa, ami egy Kocsmárost vár majd. Ha ezt meghívják, akkor ha az illető a kocsmában van, fogyjon 1 a pénzéből, nőjön 1-gyel a részegsége, generáljon 1 koszos poharat, és adjon 1 pénzt a kocsmárosnak, akit paraméterül kapott. Majd látjuk, hogy a poharat hova kell eltárolni, és mi a Kocsmáros. Ha nincs a kocsmában, akkor írjon ki egy üzenetet erről. Legyen egy alszik() metódusa is, ami nullázza a részegséget és kiírja, hogy elaludt, egy hazamegy() metódusa, ami false-ra állítja a kocsmában változót, és egy jön() metódusa, ami true-ra. Ezekről is történjen kiírás.

Legyen egy Kocsmáros osztály is. Neki is legyen privát pénze, amit konstruktorban is meg lehet adni. Az összes kocsmáros ugyanazokon a koszos poharakon osztozzon (static), és legyen egy elmos() metódusa, ami csökkenti eggyel a koszos poharak számát, és kiírja, hogy elmosott egy poharat. Ha nincs koszos pohár, akkor azt írja ki.

Legyen egy Ital osztály is, aminek a következő privát tulajdonságai lesznek: ár, alkoholtartalom.

Az Embernek legyen egy olyan iszik metódusa is, aminek fejléce iszik(Kocsmáros kocsmáros, Ital ital), azaz italt is tud fogadni. Ekkor az ital árát adja át az Ember a Kocsmárosnak 1 helyett. Az Ember részegsége az ital alkoholtartalmával nőjön.

Ha a részegség eléri a 40-et, akkor az Ember mindkét iszik() függvényénél automatikusan aludjon el.

Az összes osztály privát változóihoz legyenek getter, setter metódusok, és az osztályokhoz értelmes toString metódus.

Legyen egy main függvény, mondjuk Main nevű osztályban, itt írjatok egy rövidke futtatást, amiben eljátszogattok egy kicsit az emberekkel, bemutattok pár esetet, igyanak, aludjanak, stb...

Kapcsolódó linkek

Tömbök, Class Arrays, Tömbök tutorial

Tömbméret a bájtkódban

Enum Types

Objects, More on Classes

Classes

Kódolási stílus

Oracle Code Conventions for the Java Programming Language

Java Programming Style Guide

Java Programming Style Guidelines

Google Java Style Guide

Twitter Java Style Guide


Utolsó frissítés: 2021-05-04 07:53:32