Osztály, objektum, konstruktor, statikus tag, equals

Magas szintű programozási nyelvek 2 (INBPM0315-21) · 3. labor gyakorlat · Frissítve: 2026-09-19

Tematika

Kontextus

Ma lesz az első alkalom, hogy osztály, objektum, példányosítás, konstruktor fogalmakat egy laboron, gyakorlatban, közösen felépítve is használjuk. Ma egyetlen osztályt építünk fel lépésről lépésre — a Student-et —, és minden új fogalmat ehhez az egy példához kötünk.

Hirdetés

Miért OOP? Osztály vs. objektum

Tegyük fel, hogy nyilván kell tartanunk néhány hallgató nevét, Neptun-kódját és jegyeit — pusztán változókkal és tömbökkel:

String name1 = "Kovács Anna";
String neptun1 = "ABC123";
int[] grades1 = {5, 4, 5};

String name2 = "Nagy Béla";
String neptun2 = "XYZ789";
int[] grades2 = {3, 4, 2};

Mi történik, ha 30 hallgatónk van? Mi történik, ha szükségünk van egy függvényre, amely kiszámolja egy hallgató átlagát — írjunk getAverage1()-et és getAverage2()-t is? Az egy hallgatóhoz tartozó adat (név, Neptun-kód, jegyek) és a rajta végzett műveletek (átlagszámítás) szét vannak szórva, és semmi nem köti össze őket explicit módon.

OOP (objektumorientált programozás): Az OOP egy olyan programozási paradigma, amelyben egy dolog adatait és a rajta végezhető műveleteket egyetlen egységbe — egy ún. objektumba — csomagoljuk össze.

Osztály (class): Az osztály egy sablon (blueprint): leírja, hogy minden Student-nek milyen adatai lesznek (mezők), és milyen műveleteket lehet rajta végrehajtani (metódusok) — de önmagában nem egy konkrét hallgató.

public class Student {
    // mezők (adatok) ide kerülnek
    // metódusok (műveletek) ide kerülnek
}

Objektum (object, példány): Az objektum a sablonból ténylegesen létrehozott, konkrét dolog — egy adott hallgató, valós adatokkal. A Student osztály leírja a formát; a new Student(...) egy valódi, memóriában létező objektumot hoz létre.

Mezők és metódusok

Mező (attribútum): A mezők azok az adatok, amelyeket egy Student objektum magával visz.

public class Student {
    String name;
    String neptunCode;
    int[] grades;
}

Metódus: A metódusok azok a műveletek/viselkedések, amelyeket egy Student képes végrehajtani — és amelyek a saját mezőin dolgoznak.

    double getAverage() {
        int sum = 0;
        for (int grade : grades) {
            sum += grade;
        }
        return (double) sum / grades.length;
    }

A legfontosabb szemléletváltás itt: a getAverage metódusnak nem kell paraméterben megkapnia, hogy "melyik hallgatóról" van szó — automatikusan azon az objektumon dolgozik, amelyiken meghívjuk. Ez az implicit "melyik objektum vagyok én" (amit Java-ban a this kulcsszó fejez ki explicit módon, ha valaha meg kellene különböztetni egy mezőt egy azonos nevű paramétertől) a lényegi különbség a korábban megszokott imperatív stílushoz képest, ahol egy getAverage(grades1)-szerű függvénynek explicit meg kellett mondani, melyik adaton dolgozzon.

Példányosítás és konstruktor

Konstruktor: A konstruktor egy speciális metódus, amely akkor fut le, amikor egy objektum létrejön — ilyenkor állítjuk be a kezdeti állapotát. Neve megegyezik az osztály nevével, és nincs visszatérési típusa.

public class Student {
    String name;
    String neptunCode;
    int[] grades;

    Student(String name, String neptunCode, int[] grades) {
        this.name = name;
        this.neptunCode = neptunCode;
        this.grades = grades;
    }
}

Itt látjuk a this egy konkrét, hasznos szerepét: a this.name a mezőre utal, a bal oldali name pedig a paraméterre — enélkül nem tudnánk megkülönböztetni a kettőt, mivel szándékosan ugyanaz a nevük.

Példányosítás: A példányosítás az a lépés, amikor a new kulcsszóval ténylegesen létrehozunk egy objektumot az osztályból.

Student anna = new Student("Kovács Anna", "ABC123", new int[]{5, 4, 5});
Student bela = new Student("Nagy Béla", "XYZ789", new int[]{3, 4, 2});

System.out.println(anna.getAverage());

Fontos tudatosítani: anna és bela két különálló objektum, ugyanabból az osztályból — mindegyiknek saját, egymástól független példánya van a name, neptunCode és grades mezőknek. Az anna jegyeinek módosítása nem érinti bela adatait.

Láthatóság: private, public

Láthatóság (access modifier): A láthatósági szintek szabályozzák, hogy egy mező vagy metódus honnan érhető el.

public class Student {
    private String name;
    private String neptunCode;
    private int[] grades;

    public Student(String name, String neptunCode, int[] grades) {
        this.name = name;
        this.neptunCode = neptunCode;
        this.grades = grades;
    }

    public double getAverage() {
        int sum = 0;
        for (int grade : grades) {
            sum += grade;
        }
        return (double) sum / grades.length;
    }
}

Ha a mezők private-ok, kívülről (pl. a main-ből) nem lehet közvetlenül hozzájuk férni (anna.name nem fordulna le). Ez szándékos — a következő részben megnézzük, hogyan adunk mégis kontrollált hozzáférést hozzájuk.

Hirdetés

Getter, setter

Getter: Egy public metódus, amely egy private mező értékét adja vissza, kontrollált módon.

Setter: Egy public metódus, amely egy private mező értékét állítja be, kontrollált módon (pl. ellenőrzéssel).

public String getName() {
    return name;
}

public void setName(String name) {
    this.name = name;
}

Miért nem tesszük egyszerűen public-ká a mezőt magát? Mert a setterben tudunk ellenőrzést végezni, mielőtt módosítanánk az állapotot:

public void setName(String name) {
    if (name == null || name.isBlank()) {
        throw new IllegalArgumentException("A név nem lehet üres!");
    }
    this.name = name;
}

Egy public mezőnél erre nincs lehetőség — bárki bármit beleírhatna.

IntelliJ-s gyorsítás: nem kell kézzel megírni minden getter/settert. Helyezzétek a kurzort az osztály belsejébe, Alt+Insert (Windows/Linux) vagy Cmd+N (macOS) → Getter and Setter → válasszátok ki a mezőket, amikhez kell → OK. Az IntelliJ automatikusan legenerálja a fent látott formát. Egy másik megoldás a Code → Generate → Getter and Setter menüpont kiválasztása az IntelliJ programban.

Statikus tag és metódus

Eddig minden mező példányonként (objektumonként) külön létezett. A statikus tagok ezzel szemben az osztályhoz tartoznak, nem egy konkrét példányhoz — egyetlen közös példányuk van, amit minden objektum megoszt.

Statikus mező: Számoljuk meg, hány Student objektumot hoztunk létre eddig:

public class Student {
    private static int instanceCount = 0;

    private String name;
    private String neptunCode;
    private int[] grades;

    public Student(String name, String neptunCode, int[] grades) {
        this.name = name;
        this.neptunCode = neptunCode;
        this.grades = grades;
        instanceCount += 1;
    }

    public static int getInstanceCount() {
        return instanceCount;
    }
}

Statikus metódus: A getInstanceCount() static, mert nem egy konkrét hallgatóhoz tartozó adatot ad vissza, hanem egy, az egész osztályra vonatkozó információt. Ezért nem egy objektumon, hanem magán az osztályon hívjuk meg:

Student anna = new Student("Kovács Anna", "ABC123", new int[]{5, 4, 5});
Student bela = new Student("Nagy Béla", "XYZ789", new int[]{3, 4, 2});

System.out.println(Student.getInstanceCount());   // 2

Fontos megfigyelés: instanceCount-ból egyetlen közös példány létezik, amelyet anna és bela konstruktora is növel — szemben a name, neptunCode, grades mezőkkel, amelyekből minden objektumnak saját, független példánya van.

Ez az a fogalom, amivel a korábbi órák main metódusában (public static void main) már találkoztatok anélkül, hogy tudtátok volna, mit jelent a static. Azért static, mert a JVM-nek nincs szüksége egy objektum létrehozására ahhoz, hogy elindítsa a programot.

equals()

Próbáljuk meg összehasonlítani két, azonos adatokkal létrehozott Student objektumot:

Student s1 = new Student("Tóth Elemér", "QWE111", new int[]{5, 5, 5});
Student s2 = new Student("Tóth Elemér", "QWE111", new int[]{5, 5, 5});

System.out.println(s1 == s2);   // false!

Miért false? A == objektumoknál nem a tartalmat, hanem a referenciát hasonlítja össze — azt, hogy a két változó ugyanarra a memóriabeli objektumra mutat-e. s1 és s2 két külön objektum, még ha ugyanazok is az adataik.

equals(): Ha azt akarjuk eldönteni, hogy két objektum tartalmilag egyezik-e, felül kell írnunk az equals() metódust:

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;
    if (!(obj instanceof Student)) return false;

    Student other = (Student) obj;
    return name.equals(other.name) && neptunCode.equals(other.neptunCode);
}
System.out.println(s1.equals(s2));   // true

Amit tisztázni kell:

Gyakorlat

  1. Bővítsétek a Student osztályt egy új mezővel (pl. major — szak), és generáljatok hozzá getter/settert IntelliJ-vel.
  2. Hozzatok létre legalább 3 Student objektumot, és írjátok ki a Student.getInstanceCount() értékét minden létrehozás után — figyeljétek meg, hogy nő.
  3. Hozzatok létre két, teljesen azonos adatokkal rendelkező Student objektumot, és ellenőrizzétek ==-vel és equals()-szal is — figyeljétek meg a különbséget.
  4. Bővítsétek az equals()-t úgy, hogy a jegyeket (grades) is vegye figyelembe (segítség: java.util.Arrays.equals(grades, other.grades)).

Puffer (~30 perc)

Kérdések, illetve további gyakorlási idő azoknak, akik lassabban haladnak — ez az első labor, ahol egy egész osztályt építünk fel több lépésben, érdemes időt hagyni arra, hogy mindenki utolérje magát.

Házi feladat

Ezek a feladatok a 3. órán tanultak (osztály/objektum, konstruktor, láthatóság, getter/setter, statikus tag, equals()) önálló, otthoni elmélyítésére szolgálnak. Az első két feladat a saját Student osztályotokra épül, a harmadik egy teljesen új, önállóan megírt osztályt kér, a negyedik pedig egy új fogalmat vezet be a Student-en.

1. feladat (Könnyű) — Email mező validációval

Vegyetek fel a Student osztályba egy új, private String email mezőt, generáljatok hozzá gettert/settert IntelliJ-jel. A setterben ellenőrizzétek, hogy az email cím tartalmaz-e "@" karaktert — ha nem, dobjatok IllegalArgumentException-t (ezt a kivételt majd a 8. órán tárgyaljuk részletesen, most elég annyit tudni, hogy throw new IllegalArgumentException("üzenet"); formában lehet dobni).

2. feladat (Közepes) — Összesített statisztika statikus taggal

Vegyetek fel a Student osztályba egy private static int totalGradesSum mezőt, amely az összes, bármely Student objektumhoz valaha hozzáadott jegy összegét tartja nyilván (frissítsétek a konstruktorban, a kezdeti jegyek alapján). Írjatok egy public static double getOverallAverageContribution() metódust, amely visszaadja, hogy Student.getInstanceCount() (3. óra) alapján átlagosan hány jegy "összeg-értéket" ad hozzá egy hallgató.

3. feladat (Közepes/Haladó) — Önálló osztály: Book

Írjatok meg önállóan, nulláról egy Book osztályt, amely:

Hozzatok létre két, azonos isbn-nel, de eltérő title/author értékkel rendelkező Book objektumot, és ellenőrizzétek equals()-szal, hogy egyenlőnek számítanak-e.

4. feladat (Haladó) — Másoló konstruktor

Írjatok a Student osztályhoz egy másoló konstruktort (copy constructor): egy olyan konstruktort, amely paraméterként egy másik Student objektumot kap, és annak adatai alapján hoz létre egy új, önálló Student példányt.

public Student(Student other) {
    // ide kerül a megoldás
}

Ellenőrizzétek, hogy a másolat és az eredeti valóban két különálló objektum (pl. az egyik jegyeinek módosítása ne érintse a másikat) — ehhez gondoljátok át, hogy a grades tömböt egyszerű értékadással vagy másolással (Arrays.copyOf) érdemes-e átvenni.

Hirdetés

Hasznosnak találtad ezt a jegyzetet?

Ezek a jegyzetek minden hallgató számára ingyenesek. Ha időt spóroltál vele, fontold meg egy borravaló hagyását.

☕ Hívj meg egy kávéra