Osztály, objektum, konstruktor, statikus tag, equals
Tematika
- Miért OOP? Osztály vs. objektum
- Mezők és metódusok
- Példányosítás és konstruktor
- Láthatóság:
private,public - Getter, setter
- Statikus tag és metódus
equals()
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.
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.
private: csak az adott osztályon belülről érhető el. Ez a mezők alapértelmezett, ajánlott láthatósága — más osztályok ne tudják közvetlenül módosítani egyStudentbelső adatait.public: bárhonnan elérhető. Ez a metódusok jellemző láthatósága, amelyeket kívülről is meg akarunk hívni.
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.
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:
@Override— jelzi, hogy szándékosan írjuk felül azObjectosztályequals()metódusát.instanceof— ellenőrzi, hogy a paraméter valóbanStudenttípusú-e, mielőttStudent-té alakítanánk (cast).- A mezőkre
.equals()-t hívunk (pl.name.equals(other.name)), nem==-t, mert aStringis objektum — ugyanaz a csapda vonatkozik rá is.
Gyakorlat
- Bővítsétek a
Studentosztályt egy új mezővel (pl.major— szak), és generáljatok hozzá getter/settert IntelliJ-vel. - Hozzatok létre legalább 3
Studentobjektumot, és írjátok ki aStudent.getInstanceCount()értékét minden létrehozás után — figyeljétek meg, hogy nő. - Hozzatok létre két, teljesen azonos adatokkal rendelkező
Studentobjektumot, és ellenőrizzétek==-vel ésequals()-szal is — figyeljétek meg a különbséget. - 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:
privatemezőkkel rendelkezik:title,author,isbn(mindháromString),- van egy konstruktora, amely mindhárom mezőt beállítja,
- van gettere minden mezőhöz,
- felülírja az
equals()-t úgy, hogy kétBookakkor egyenlő, ha azisbn-jük megegyezik.
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.
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