3. gyakorlat
A gyakorlat anyaga
Parancssori paraméterek
Parancssori paraméterek használata javac
-vel való fordításnál:
java ProgramNeve param1 param2 param3...
Eclipseben a parancssori argumentumok megadása Run Configurations/Arguments alatt történhet.
Tömb bejárása a tömb length
(hossz, vagyis elemszám) tulajdonságát felhasználva:
(Természetesen nem csak a parancssori paraméterek tömbjének, hanem bármely tömbnek lekérhetjük az elemszámát a length
tulajdonsággal.)
//parameterek bejarasa for (int i=0; i<args.length; i++) { System.out.println("" + (i + 1) + ". parameter: " + args[i]); //szam szovegge alakitasa String str = "" + i; }
Paraméterek összege
A main függvény egy sztring tömböt kap, ezt nem lehet egyszerűen felülírni.
Ha számokként akarjuk kezelni a kapott paramétereket, akkor az Integer.parseInt()
függvényt kell használnunk.
//parameterek osszege int osszeg = 0; for (int i = 0; i < args.length; i++) { osszeg += Integer.parseInt(args[i]); } System.out.println("A parameterek osszege: " + osszeg);
Adattípusok
Egyszerű (primitív) adattípusok: boolean, char, byte, short, int, long, float, double, ezek nagy része ismerős lehet C-ből.
A C-vel ellentétben Java-ban létezik egy beépített primitív típus logikai érték tárolására, amelyet boolean
típusnak nevezünk, és értéke csak true
vagy false
lehet. További eltérés a C-hez képest, hogy nincs előjeltelen típus, tehát nem használhatjuk az unsigned
kulcsszót, csak és kizárólag előjeles típusokat hozhatunk létre. Bővebben ezen a linken olvashatsz a primitív adattípusokról. Egy érdekes cikk a lebegőpontos számokról, számábrázolásról.
Megjegyzés: Ha valami miatt azonban mégis szükség lenne egy előjeltelen egészre, akkor Java 8 (vagy afeletti verzió) esetén használhatjuk az
Integer
osztály néhány erre a célra létrehozott metódusát, mint például a compareUnsigned, divideUnsigned.
Később látni fogjuk ennek hasznát, de alapvetően minden Java-beli primitív típusnak létezik egy csomagoló osztálya, amellyel egy primitív típusú adatból objektumot készíthetünk, "becsomagolhatjuk" azt. A csomagoló (wrapper) osztályok a következők (sorrendjük megegyezik a primitív típusoknál történt felsorolás sorrendjével): Boolean, Character, Integer, Long, Float, Double. Egy összefoglaló táblázat a beépített típusokról:
Típus neve | Érték | Default érték | Méret | Értéktartomány |
---|---|---|---|---|
boolean | 1 bitet reprezentál | false | nincs precíz definíció | true/false |
char | 16 bites unicode karakter | \u0000 | 2 byte | 0 - 65535 |
byte | 8 bites egész | 0 | 1 byte | -128 - 127 |
short | 16 bites egész | 0 | 2 byte | -32768 - 32767 |
int | 32 bites egész | 0 | 4 byte | -2147483648 - 2147483647 |
long | 64 bites egész | 0L | 8 byte | -9223372036854775808 -,9223372036854775807 |
float | 32 bites lebegőpontos (IEEE 754) | 0.0f | 4 byte | 1.40129846432481707e-45 - 3.40282346638528860e38 (pozitív vagy negatív), +/- végtelen, +/- 0, NaN |
double | 64 bites lebegőpontos (IEEE 754) | 0.0d | 8 byte | 4.94065645841246544e-324d - 1.79769313486231570e+308d (pozitív vagy negatív), +/- végtelen, +/- 0, NaN |
A beépített típusokat bemutató példakód elérhető a /pub/Programozas-I/nappali/gyakorlat/03/PrimitivTipusok.java
útvonalon.
public class PrimitivTipusok { public static void main(String[] args) { boolean bo = true; // logikai tipus char c1 = 'a'; // karakter tipus char c2 = '\u0054'; // ket bajtos, unicode karaktereket tarol! byte b1 = 127; // 8 bites egesz tipus byte b2 = -128; // minden egesz tipus elojeles! short s = 1024; // 16 bites egesz int i = 0x7fffffff; // 32 bites egesz long l = 0x7fffffffffffffffL; // 64 bites egesz float f = 123.123f; // 32 bites lebegopontos tipus double d = 5.0; // 64 bites lebegopontos // kiiras konzolra System.out.println(bo); System.out.println(c1); System.out.println(c2); System.out.println(b1); System.out.println(b2); System.out.println(s); System.out.println(i); System.out.println(l); System.out.println(f); System.out.println(d); } }
A lebegőpontos számokkal azonban óvatosan kell bánni. Erre egy tökéletes példa a Süti program. A történet a következő: ellátogatunk az Egyesült Államokba, de sajnos hamarosan indulunk is tovább, így csak a reptéri cukrászdában vásárolhatunk sütit. A sietségünket azonban kihasználja a reptéri cukrász: elcsábít minket a konkurencia elől a 0.1 dolláros sütivel, azonban minden egyes következő sütiért 0.1 dollárral többet kér, mint amennyibe az előző került.
Vajon hány sütit ehetünk a maradék 1 dollárunkból? Írjunk rá egy programot, menstük el Suti.java
néven.
public class Suti { public static void main(String[] args) { double penzunk = 1.00; int megvettSutik = 0; for (double ar = 0.1; penzunk >= ar; ar += 0.1) { penzunk -= ar; ++megvettSutik; } System.out.println("Megvett sütik: " + megvettSutik); System.out.println("Megmaradt pénz: " + penzunk); } }
Hm.. valami nem stimmel. Számoljuk ki kézzel is, egy egyszerű táblázat segítségével:
Pénzünk | Következő süti ára | Megevett sütik száma |
---|---|---|
1.0 $ | 0.1 $ | 0 |
0.9 $ | 0.2 $ | 1 |
0.7 $ | 0.3 $ | 2 |
0.4 $ | 0.4 $ | 3 |
0.0 $ | lényegtelen | 4 |
A fenti példa által láthatjuk, hogy pl.: valuta tárolására sosem érdemes float
vagy double
típust választani. Bővebben erről a problémáról ezen és ezen linken.
String (szöveges) típus
String típus. A sztringeket az eddigiekkel szemben itt már szeretjük, kezelésük könnyű. Ezt a típust mindig nagy kezdőbetűvel írjuk.
Operátorok
+, -, *, /, %
operátorok és használatuk ugyanaz, mint C-ben.
A + jel sztringekre konkatenációt (összefűzést) jelöl. Használata nagyon intuitív. Például "kutya" + "mutya" -> "kutyamutya"
A C-beli pointereknél használt *
(dereferencia) és &
(címképzés) operátor itt nem létezik.
>>
- aritmetikai eltolás: megőrzi az előjelbitet.
>>>
- logikai eltolás: az előjelbitet is tolja. (Tehát nem őrzi meg a szám negativitását)
Az operátorokról bővebben olvashatsz az alábbi linkeken: Summary of Operators, Java Operators Tutorial.
Vezérlési szerkezetek
Majdnem minden működik, ami C-ben is működött, szóval elvileg aki idáig eljutott, ezeket már ismeri. A reláció operátorok is ugyanúgy működnek, mint C-ben, illetve a ++
és --
operátorok is ugyanúgy használhatóak.
Létezik tehát a már megszokott módon az if()
és a ?:
operátor is. Az if komplexebb feltételek megadására is alkalmas. Például ha azt szeretnénk, hogy egy x
változó 3 és 7 között legyen, vagy 13, vagy negatív és nem -9, akkor ezt a következő kóddal adhatjuk meg:
if ((x >= 3 && x <= 7) || x == 13 || (x < 0 && x != 9)) { System.out.println("A feltétel teljesül."); }
Logikai kifejezéseket tehát összefűzhetünk az &&
(és) és a ||
(vagy) operátorok használatával. A zárójelezés ilyenkor kritikus fontosságú lehet.
A boolean típus kezelhető logikai kifejezésként, mivel pontosan az is, azaz egy igaz vagy hamis értéket tárol. Ilyenkor operátorok nélkül is igazságértéket jelképeznek.
boolean pozitiv = true; if (pozitiv) System.out.println("A szám pozitív."); if (!pozitív) System.out.println("A szám nem pozitív.");
A ?:
operátor használata például a kiíráson belül lehet indokolt. Az előző kódot például jelentősen egyszerűsíti:
boolean pozitiv = true; System.out.println("A szám " + ((pozitív) ? "" : "nem ") + "pozitív.");
Viszont, C-vel ellentétben itt nincs implicit konverzió int
és boolean
között, a Java megköveteli az összehasonlító operátorok használatát ebben az esetben:
int x = 1; if (x) { //Javaban ez így nem működik! System.out.println("Minden rendben!"); } //A helyes feltétel az alábbi: if (x > 0) { System.out.println("Minden rendben!"); }
A while
, do-while
és switch
utasítás megegyezik a C-ben tanultakkal. A for utasításnál viszont itt kényelmesebben deklarálható ciklusváltozó, mivel itt az utasításon belül is megtehetjük ezt (viszont ezesetben a cikluson kívül már nem látjuk a ciklusváltozót!). Tehát a C-vel ellentétben itt már működne a következő példa:
for (int i = 0; i < 5; i++) { //ciklus tartalma }
Az egyetlen dolog, amely nem működik Java-ban, de a C-ben igen, az a goto
utasítás. A program egyes részeit ugyanúgy címkézhetjük, azonban a goto
parancsot nem használhatjuk. Ez Javaban ugyan ez foglalt kulcsszó, de implementáció nem tartozik hozzá. Ennek oka az, hogy a nyelv készítői kulcsszóvá tették a goto
-t, azonban eddig még nem jött tömeges nyomás, nem volt olyan probléma, amelyet ezen kulcsszó nélkül ne tudtak volna megoldani, így egyelőre ez egy fenntartott kulcsszó (azaz például ilyen nevű változót nem hozhatunk létre), de semmiféle működése nincs.
Memóriaterületek
A következőkben röviden bemutatjuk a stacket, illetve a heapet, azonban ezek a témakörök az előadáson ennél jóval részletesebben be lettek mutatva. Bővebben olvashatunk a témáról a következő linkeken: JVM specifikáció ide vonatkozó részei
Stack
A stack-en tároljuk a primitív típusú adatokat. Ez a memóriaterület nagyon gyors elérésű, általában lokális változókat, rövid életű adatokat tárolunk benne. A stacken létrehozott változók élettartalma a létrehozó blokkra korlátozódik, azaz pl.: ha van egy lokális változónk egy függvényben, akkor az a változó addig él, és addig lesz a memóriában tárolva, ameddig a függvény futása be nem fejeződik. Ezt követően a foglalt memóriaterület automatikusan felszabadul. Hátránya a heap memóriával szemben az, hogy a stack jóval kisebb. Javaban csak és kizárólag primitív típusú adatokat tárolhatunk a stacken (szám, bájt, karakter vagy logikai érték).
A gyakorlatban, ha egy változó értékét stacken tároljuk (pl.: int x = 42;
), akkor akármikor hivatkozunk a változóra, a változó neve gyakorlatilag egyet jelent az értékkel, érték szerint tárolódik.
Heap
A heap memórián tároljuk az objektumokat, tömböket (ezeket referencia típusúaknak hívjuk). Ez a stack-kel ellentétben egy lassabb, dinamikus futás-közbeni memóriaterület, cserébe viszont jóval nagyobb terület áll a programunk rendelkezésére. Az itt létrehozott adatokat hosszabb távon tároljuk, globálisan léteznek, nem csak egy-egy függvény belsejében, lokálisan.
Amikor létrehozunk egy objektumot a heapen, akkor az adott változó az gyakorlatilag csak egy referencia, mutató lesz a heap-beli memóriaterületre, ahol ténylegesen az objektum tárolódik, a változó maga csak ezt a referenciát tárolja. Ez ismerős lehet, a pointer fogalma hasonló. Tehát a referencia típusú változó nem közvetlenül tárolja az értéket, csak egy referenciát tárol, és a heapen tárolt értéket közvetetten, ezen a referencián keresztül érjük el.
Gyakorlati példa: Egy nagyon szemléletes példa, Bruce Eckel Thinking in Java című könyvéből: Képzeljük el, hogy van egy
Televízió
típusú objektumunk a heapen, melyhez tartozik egy referencia, amelyet letárolunk, és ennek a neve legyentávirányító
. Ahogy a való életben is, ülünk a kényelmes fotelünkben, és nézzük a televíziót, de amikor szeretnénk aTelevízió
objektumunkon bármely módosítást eszközölni (halkítani, hangosítani, csatornát váltani, be-, kikapcsolni), akkor atávirányító
segítségével tesszük meg ezt. És ha a szobában sétálva, lefekve bárhol szeretnénk aTelevízió
objektumon machinálni, akkor a távirányítót visszük magunkkal, nem pedig magát aTelevíziót
.
Alapértelmezett kimenetek
Ahogy már beszéltünk róla, a Java nyelvben szöveget írhatunk ki a képernyőre a System.out.println()
paranccsal. Ez viszont nem feltétlenül mindig a terminált jelenti.
A System.out
az alapértelmezett kimenet, ami esetünkben a terminálra volt irányítva. Ezt át is lehet állítani, hogy máshova írjunk vele, például egy fájlba.
Az eddig tanult kiíratáshoz hasonló az alapértelmezett (vagy default) hibakimenet, melynek fogalma szintén ismerős lehet. Erre a System.err.println()
paranccsal tudunk írni a már látott módon. Ez hibaüzenetek közlésére szolgál. Egyetlen szemmel látható különbsége az alapértelmezett kimenettől, hogy az Eclipse konzolján a szöveg pirossal jelenik meg.
A két kimenet viszont lehet két különböző helyre is irányítva.
A kimenetek működése aszinkron. Ez azt jelenti, hogy nem feltétlenül pont akkor írja ki a dolgokat, amikor mi utasítjuk rá: ha a kimenet éppen nem tudja ellátni a feladatát, akkor a kiírandó adat várakozik. Tehát a következő kódnak két lehetséges kimenetele lehet:
System.out.println("Most hibaüzenet következik:"); System.err.println("Ez a hibaüzenet.");
Az egyik kimenetel:
Most hibaüzenet következik: Ez a hibaüzenet.
A másik pedig:
Ez a hibaüzenet. Most hibaüzenet következik:
Ebből következik, hogy a hibakimenet NEM használható a szöveg egyszerű színezésére, mert összekavarodhat a mondanivalónk. Attól persze nem kell félni, hogy a System.out
-ra küldött két üzenet összekeveredik, ezek sorrendben várakoznak.
A gyorsabb munka érdekében az Eclipse megkönnyíti a kimenetre írást. A default kimenet eléréséhez egyszerűen beírhatjuk a sysout
szót, majd Ctrl+Space hatására kiegészíthetjük System.out.println()
-né, a syserr
szót pedig System.err.println()
-né.
Java és C-beli különbségek, hasonlóságok
Az egyes szekciókban tárgyalt és azokon túlmutató hasonlóságokról és különbségekről bővebben olvashatsz itt és itt.
Feladatok
- Írd ki a parancssori paramétereket a konzolra
- Írd ki a parancssori paramétereket a konzolra, fordított sorrendben
-
Írd ki a parancssori paramétereket úgy, hogy az n. sorban az első n darab parancssori paramétert írja ki: első sorba csak az elsőt, a másodikba az első kettőt szóközzel elválasztva, a harmadikba az első hármat, stb..
java Main egy ketto kutya cica fagyi
egy egy ketto egy ketto kutya egy ketto kutya cica egy ketto kutya cica fagyi
-
Írd ki a parancssori paraméterek közül a legnagyobbat, legkisebbet, valamint az értékek átlagát.
-
A parancssori paraméterek alapján döntsd el, hogy egy a bemenő számok számtani, mértani sorozatot alkotnak-e, vagy esetleg egyiket sem. Feltehetjük, hogy mindegyik egész szám, és legalább 3 db paraméterünk van. Az összegképletek:
- számtani: an = a1 + (n – 1) * d
- mértani: an = a1 * q n – 1
-
Számítsd ki a parancssoron kapott két időpont (óra perc óra perc) között eltelt időt, és írasd ki a konzolra (óra perc formában). A program elkészítése során ügyelj az adatok helyességére
- bemenő paraméterek száma
- az órák 0-23 intervallumba kell, hogy essenek
- a percek 0-59 intervallumba kell, hogy essenek
Kapcsolódó linkek
A gyakorlat anyaga
Tömbök
Korábban már volt szó konkrét tömbökről (pl.: parancssori paraméterek tömbje). Az akkor tárgyaltak természetesen általánosságban is igazak a tömbökre. Egy rövid összefoglaló: A tömbök azonos típusú, de egynél több érték tárolására vannak kitalálva. Az elemek száma azonban rögzített, tehát adott számú, azonos típusú elemet tartalmazó adattípusról van szó. Az indexelés 0-val kezdődik, ahogy az ismerős lehet Programozás alapjai tárgyból.
Megjegyzés: Habár Javaban, C-ben 0-tól indexelünk, nem feltétlenül van ez így minden nyelvben. Vannak olyan nyelvek, melyek egytől kezdik az indexelést. Ezen nyelvekről itt találsz egy összefoglaló listát.
Fontos lehet, hogy a C-vel ellentétben itt nagyon szeretjük a tömböket, hiszen a Java (mivel menedzselt nyelvről van szó, azaz a kód nem közvetlenül a hardveren fut, hanem a JVM-en, ahogy ezt korábban tárgyaltuk) teljes körű indexhatár-ellenőrzést végez, kivétellel jelzi, ha alul- vagy túlindexelés történik.
A Java-beli tömböket többféleképpen is létre lehet hozni. Statikusan, ha előre tudjuk a tömb elemeit, vagy pedig dinamikusan, amikor nem feltétlen tudjuk a tömb elemeinek értékét. Mind primitív típusból, mind pedig osztályokból létrejövő objektumok esetén használhatunk tömböket. Tömböket dinamikusan, a new
operátorral lehet létrehozni, mérete a már ismert length
tulajdonsággal kérdezhető le.
Egydimenziós tömbök
Az egyetlen kiterjedéssel (dimenzióval) rendelkező tömböket szokás vektornak is nevezni, ahogy ez a kifejezés matematikából ismerős lehet. A tömböt deklarálni a következőképp lehet:
típus tömbnév[]; típus[] tömbnév;
A két deklaráció között semmilyen különbség sincs. Futás közben a már említett new
operátorral tudjuk definiálni:
tömbnév = new típus[méret];
Egy konkrét példa:
int[] x = new int[5];
Ennek a tömbnek az elemei: x[0], x[1], x[2], x[3], x[4]. A tömböt legegyszerűbben egy for
ciklussal tölthetjük fel.
A Java lehetővé teszi, hogy a tömböket a definiálás során konstans értékekkel töltsük fel. Ilyenkor a tömböt a fordító hozza létre.
típus[] tömbnév = { konstans1, konstans2 } // egy konkrét példa: int[] arr = { 1, 2, 3, 4, 5 };
Adott tömb mérete minden esetben egy nemnegatív egész szám, melynek értéke nem lehet nagyobb, mint a lehetséges legnagyobb index. Ez a JVM-től függ, és mivel az indexelés int
értékekkel történik, ezért az elméleti legnagyobb elemszám Integer.MAX_VALUE
(ez egy definiált konstans), azonban ez a gyakorlatban egy ettől 5-8 értékkel kisebb szám. Derítsük ki, hogy a saját JVM-ünk esetében mekkora ez a szám: ehhez hozzunk létre egy tömböt, és próbáljuk meg lefordítani, és lefuttatni az elkészült fájlt:
String[] tomb = new String[Integer.MAX_VALUE - 4];
Többdimenziós tömbök
Analóg módon az egydimenziós tömbökhöz, illetve a korábban, Programozás alapjain tanultakhoz:
típus[][]...[] tömbnév; típus tömbnév[][]...[];
Két dimenziós tömb létrehozására egy példa:
típus[][] tömb; tömb = new típus[méret1][méret2];
Egy konkrét példa kétdimenziós tömb létrehozására és feltöltésére. A tömböt legegyszerűbben egy for
ciklussal tölthetjük fel.
int[][] tomb; tomb = new int[10][9]; for (int i=0; i < tomb.length; i++){ for (int j = 0; j < tomb[i].length; j++) { tomb[i][j] = (i+1)*(j+1)*9; } }
A Java lehetővé teszi, hogy a tömböket a definiálás során konstans értékekkel töltsük fel. Ilyenkor a tömböt a fordító hozza létre
int[][] matrix = { { 1, 2 }, { 3, 4 } };
Tömbök másolása
Mivel a tömbök referencia típusúak (előző gyakorlat), ezért egy egyszerű értékadás esetén csak a referenciát (azaz a tömb címét) másoljuk át egy másik változóba, a konkrét, memóriában létező tömböt nem. Tényleges tömbmásolást kézzel is megvalósíthatunk, egyszerű for ciklus segítségével, de létezik egy, a JDK-ban már előre elkészített metódus épp erre a célra: a tényleges másolás a System.arraycopy()
metódussal lehetséges. Ennek fejléce (javadoc):
public static void arraycopy(Object forrás, int forrásKezdetiElem, Object cél, int kezdetiPozíció, int hossz)
Egy konkrét példa tömbök másolására:
int tomb1[] = { 30, 31, 1, 2, 3, 4, 5, 6, 7 }; int tomb2[] = { 29, 0, 0, 32, 33 }; System.arraycopy(tomb1, 0, tomb2, 1, 2); for (int i=0; i<tomb2.length; i++){ System.out.print(tomb2[i] + " "); } System.out.println();
Ennek kimenete: 29 30 31 32 33
.
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:
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.
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.
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 sázmtalan objektumpéldány is létrehozható. Hozzuk létre Józsit a main függvényben a new
operátor segítségével.
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 konstrukotrt (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
/* 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.
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
.
//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.
/* 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 (de a ZH-n ki kell írni őket), 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:
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.
public String toString() { return "Ez egy ember, neve " + this.nev + ", szuletett " + this.szuldatum + ", 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:
System.out.println(jozsi);
Ennek eredménye: Ez egy ember, neve Jozsi, kora 20, ferfi=true
Az Eclipse kedvesen segít ezek előállításában is (de a ZH-n ki kell írni őket), a munkaterületen jobb klikk és Source > Generate toString()... menüpontban találjuk meg az ide kapcsolódó varázslót.
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.
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.
public class Alma { public static final int ALMA_TOMEG = 10; }
Ennek elérése kívülről:
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):
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).
Feladatok
- Hozzunk létre egy 7*10-es
int
-eket tartalmazó tömböt, töltsük fel őket, az alábbi séma szerint:tömb[x][y] = x*y;
(pl.: tömb[5][8] = 40;) - Hozzunk létre egy karakter tömböt
't' 'e' 'l' 'e' 'f' 'o' 'n'
karakterekkel. Másoljuk egy új tömbbe a'l' 'e'
karaktereket! - Í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
Kódolási stílus
Oracle Code Conventions for the Java Programming Language