2. gyakorlat
Fejlesztői környezetek¶
Az IDE (Integrated Development Environment, integrált fejlesztői környezet) célja, hogy megkönnyítse a programozást. Rengeteget segít a munkánkban, többek között tudja a korábbról ismert szintaxis-színezést, támogatja a hibakeresést, vagy például egy gombnyomással elindíthatjuk a fordítást, futtatást is.
Számos fejlesztői környezet létezik, mint például az IntelliJ IDEA, Eclipse vagy a Visual Studio Code. Ebben az anyagban az IntelliJ IDEA-ról, valamint az Eclipseről adunk részletesebb bemutatást, de más fejlesztőkörnyezetek is rendelkeznek az ismertetett dolgok nagy részével, esetleg másképp lehet őket elérni.
Az IntelliJ IDEA használata¶
Az IDEA indításakor a Welcome ablak jelenik meg. Itt kezdetben néhány beállítást testre szabhatunk (pl. világos / sötét téma), illetve a későbbiekben itt láthatjuk a korábban megnyitott projektjeinket.
Amennyiben korábban már használtuk a környezetet, akkor a legutóbbi projektet fogja alapértelmezetten megnyitni, amelyet a File→Close project menüponttal bezárhatunk.
Projekt létrehozásakor választhatunk sok sablon közül, de létrehozhatunk egyszerű Java projektet is. Ha nem választunk ki semmit, akkor ez utóbbit fogja alapból létrehozni.
Ezután a Next gombra kell párszor kattintanunk, majd megadhatjuk a projekt nevét, illetve a lokációt, hogy hova mentse a projektet. Fontos, hogy az elérési útvonalunk tartalmazza a projekt mappáját is, például C:\suli\prog1\elso-projekt
, vagy pedig /home/valaki/suli/prog1/elso-projekt
.
A projekt létrehozása után a bal oldalon láthatjuk a projekt struktúráját. Innen az src mappa a legfontosabb, ide kerülnek a Java fájlok.
Új fájl létrehozásához kattintsunk jobb klikkel az src mappára, majd new —> Java class. Ide írjuk be az osztály nevét. A jobb oldali ablakba már írhatjuk is a forráskódot.
Ha szeretnénk futtatni a programunkat, akkor először meg kell keresnünk a futtatható osztályunkat (amiben van main függvény), majd a bal oldali menüben jobb klikk az adott osztályra, és ott válasszuk ki a run opciót. Ekkor az alsó sávban megjelenik egy Run menüpont is, ahol a futtatás eredményét láthatjuk. Ha a felhasználótól kérünk be adatot, akkor azt is itt tudjuk megadni, ezeket zöld színnel jelzi a környezet.
Ha már egyszer lefuttattuk a programunkat, akkor a rendszer létrehozott hozzá egy konfigurációt, ezt a jobb felső sarokban látjuk. Ha rákattintunk, akkor tudjuk szerkeszteni is az "Edit configuration" opcióval. Itt argumentumokat is megadhatunk a programnak (a program arguments mezőbe beírva). Futtatni a legutóbbi konfigurációt a jobb felső sarokban található zöld nyíllal, vagy a Shift + F10-zel tudunk.
A rendszer a program írása során folyamatosan ellenőrzi a programkódunkat, és jelzi, ha hiba található benne. 3 féle hibát szokott jelezni:
- Fordítási hiba. Ha ilyen van, akkor a programunkat nem lehet lefordítani. Ezt a környezet pirossal jelzi.
- Warning. Ha ilyen van, akkor a programunk fordítható és futtatható, de nagy valószínűséggel rosszul működik, a javításuk mindenképp megfontolandó. Létezik "weak warning" is, amely általában kódminőséggel kapcsolatos problémákra figyelmeztet. Ilyen lehet, hogy egy adott kódblokk több helyen meg van ismételve, ahelyett, hogy kiszerveztük volna függvénybe; vagy hogy egy if szerkezetben az if és az else ág ugyanazt csinálja.
- Typo: Ha valamit elgépelünk, akkor erre szintén figyelmeztet. Az angol nyelvet ismeri.
A hibákat jelzi a kód színezésével is, illetve a jobb oldali görgetősávban is. Ezekre javításokat is tud javasolni az esetek nagy részében. Ehhez a kurzorral menjünk az adott hibás kódrészletre, majd nyomjuk meg az Alt+Enter billentyű kombinációt. Itt ki is választhatjuk a számunkra szimpatikus javítást (ha van ilyen).
A képernyő jobb felső sarkában jelzi az összesítést is, hogy melyik hibából hány darab van az aktuális osztályunkban.
Debuggolás¶
Vegyük azt a nagyon egyszerű kis Java programot, amiben kiírjuk a parancssori argumentumait a programnak. Illetve egy kicsit rontsuk is ezt el, így kezdődhet a hibakeresés!
public class Macska {
public static void main(String[] args) {
int i=0;
while (i<=args.length) {
System.out.println(i + ". argumentum: " + args[i]);
i++;
}
}
}
Futtassuk a programot a következő parancssori argumentumokkal: elso masodik harmadik
A program futásának kimenete a következő lesz:
Breakpoint¶
A legegyszerűbb lehetőségünk arra, hogy megfigyeljük a programunk működését futás közben, ha egyes sorokra breakpointot teszünk, majd lépdelve a végrehajtás menetén megpróbálunk rájönni, hogy mi is lehet a hiba, ami miatt a programunk nem jól működik.
Ehhez egyszerűen a sor elején, a sorszám melletti üres részre kattintva helyezzük azt el, majd futtassuk a debuggert a felső menüsorban a bogár ikonra kattintva (Shift + F9).
Alul a Debug fülön láthatjuk a program futásának aktuális állapotát. A bal oldalon látjuk, hogy éppen hol jár a programunk futása: most Macska osztály main függvényében vagyunk, a fájl 4. sorában. Ha egy adott függvényben vagyunk, amit más függvényből hívtunk, akkor itt a hívási hely is látszik. A jobb oldalon láthatjuk a változóink aktuális értékét, illetve ezeket módosítani is tudjuk (jobb kilkk). Vagy akár plusz kifejezéseket is hozzáadhatunk a + ikonra kattintva. Ha ide beírjuk pl. azt, hogy "2*i", akkor az folyamatosan az i változó értékének kétszeresét fogja jelezni.
A programban a fenti ikonokkal lépkedhetünk, melyek jelentése rendre:
- Step over (F8): lefuttatja az aktuális sort, majd ugrik a következő sorra. Amennyiben van függvényhívás az aktuális sorban, akkor azt is lefuttatja és ha van benne breakpoint, ott megáll.
- Step into (F7): ha az aktuális sor egy függvényhívás, akkor annak első sorára ugrik
- Force step info (Alt+Shift+F7): akkor is beugrunk az adott függvénybe, ha egyébként kihagynánk azt (általában beépített függvényekről van szó)
- Step out (Shift+F8): végigfuttatja az aktuális függvényt, a hívó helyre ugrik
- Drop frame: Kiugrik az aktuális függvényből, a hívó helyre, de a következő lépésben a függvény újra meg lesz hívva
- Step to cursor (Alt + F9): addig futtatja a programot, míg az el nem éri azt a sort, ahol a kurzor jelenleg áll.
Conditional breakpoint¶
A breakpointokhoz feltétel is megadható, melynek hatására csak akkor fog megállni a futtatás az adott ponton, ha a feltétel igaz. Ilyen feltétel lehet pl. az, hogy "a > 10". Ahhoz, hogy egy breakpointhoz feltételt írjunk, kattintsunk rá jobb klikkel, majd oda írjuk be a feltételt.
Exception breakpoint¶
Előfordulhat, hogy akkor szeretnénk megállítani a programunkat, ha abban valahol kivétel keletkezett. Ehhez kattintsunk a breakpointra jobb klikkel, majd válasszuk a "more" opciót. Itt a + ikonra kattintva válasszuk ki a "Java exception breakpoints" opciót, majd kiválaszthatjuk milyen kivételre szeretnénk megállni. Ezután ha futtatjuk a programunkat és ilyen típusú kivételt kapunk, a programunk futása az adott ponton megáll. Ellenőrizhetjük a változók értékeit, és könnyedén kitalálhatjuk mi volt a hiba oka.
Az Eclipse használata¶
Az Eclipse indításakor mindig a workspace választó ablak nyílik meg, ebben kiválaszthatjuk, hogy melyik könyvtárban szeretnénk dolgozni. Az adott workspace-en belül könnyen és gyorsan el tudjuk érni projektjeinket és ezeken belüli fájljainkat. Ettől persze még lehetséges a workspace-en kívüli fájlok megnyitása is, illetve később másik wokspace kiválasztása.
Indítás után egy részekre tagolt felületet kapunk. Ezen részek számunkra legfontosabbjait tekintjük most át.
A Package Explorer jeleníti meg a kiválasztott workspace tartalmát.
Ez annyit jelent, hogy itt láthatjuk sorban a projektjeinket, illetve az azokban felhasznált fájlokat.
Minden projektben lesz egy src
mappánk, amely a projekt létrehozásakor generálódik.
Ebbe írjuk a tényleges programkódot. Itt csomagokban tárolhatunk fájlokat.
A gyakorlatban a csomagok is egyszerű könyvtárak, ne ijedjünk meg tőlük.
Az src
mappa mellett láthatunk egy JRE System Library
nevű fájlt.
Ez a futtató környezetet tartalmazza, ettől sem kell megijedni.
Alul található egy sáv, melyen több funkciót is elérhetünk. Ebből jelenleg nekünk legfontosabb a konzol, amely a default kimenetet jelenti. Tehát ha kiírunk egy szöveget a már látott módon, akkor azt itt láthatjuk majd.
A középső nagy terület a munkaterület, ahol a tényleges programozást végezhetjük. Ehhez természetesen meg kell nyitnunk egy fájlt, amiben dolgozhatunk.
Új programot általában új projektben szoktunk készíteni. Két ugyanolyan nevű projekt nem lehet egy workspace-en belül.
Ha van egy új projektünk, akkor a tényleges programozáshoz fájlokat kell készítenünk, amiben dolgozhatunk.
Ezt jelen esetben úgy tehetjük, ha osztályokat hozunk létre az src
mappán belül.
Ha nem adunk meg csomagot, akkor az osztály a default package csomagba kerül, amely úgy tekinthető, hogy egyszerűen az src
mappában van.
Egy csomagban csak egy ugyanolyan nevű osztály létezhet.
Ha bepipáljuk a main függvény generálását, akkor rögtön futtatható osztályt generálunk. Ebből csupán egy sor hozzáadásával készíthető Hello World program. Az Eclipse további segítséget is nyújt, Ctrl + Space billentyűkombinációval automatikusan ki is egészíti megkezdett parancsainkat vagy neveinket.
Futtatni a felső eszköztáron található kis zöld körrel, illetve Ctrl + F11 billentyűkombinációval tudunk.
Amennyiben valami hibát vétünk, a futtatás nem lehetséges. Ezt már korábban is láthatjuk, mivel az Eclipse piros aláhúzással jelzi a hibásan írt neveket, illetve az egyéb hibákat: pl. hiányzó pontosvessző. Ha hosszabb kódunk van, segítséget nyújthatnak a hibák megtalálásában az oldalt elhelyezett piros jelek, amelyekre kattintva a hibás kódrészre ugorhatunk.
Ha nem szintaktikai hibát vétünk, viszont valami egyéb olyan műveletet végzünk, amely az Eclipse szerint nagy valószínűséggel helytelen vagy értelmetlen, akkor ezt sárga aláhúzással jelzi. Ettől még tudjuk futtatni a programot, de az ilyen eseteket érdemes átvizsgálni. Ilyenek lehetnek: felesleges változók (amiket létrehozunk, de nem használjuk semmire), halott kódrészletek (olyan kódrészletek, amelyek nyilvánvalóan nem fognak sosem lefutni, lásd a példákat).
Példa halott kódra.
public static int add(int a, int b) {
return a+b;
System.out.println("Ez egy halott kodreszlet.");
}
A képen azért panaszkodik az Eclipse, mert a deklarált változóval nem csináltunk semmit, tehát feleslegesnek ítéli.
Debuggolás¶
Vegyük azt a nagyon egyszerű kis JAVA programot, amiben kiírjuk a parancssori argumentumait a programnak. Illetve egy kicsit rontsuk is ezt el, így kezdődhet a hibakeresés!
public class HelloWorld {
public static void main(String[] args) {
int i=0;
while (i<=args.length) {
System.out.println(i + ". argumentum: " + args[i]);
i++;
}
}
}
Futtassuk a programot a következő parancssori argumentumokkal: elso masodik harmadik
A program futásának kimenete a következő lesz:
Breakpoint¶
A legegyszerűbb lehetőségünk arra, hogy megfigyeljük a programunk működését futás közben, ha egyes sorokra breakpointot teszünk, majd lépdelve a végrehajtás menetén megpróbálunk rájönni, hogy mi is lehet a hiba, ami miatt a programunk nem jól működik.
Ehhez egyszerűen a sor elejére kattintva, ahová a breakpointot tennénk, helyezzük azt el.
Majd futassuk a debuggerünket!
A program utasításain vagy a billentyűk, vagy a megfelelő ikonok segítségével haladhatunk végig: F5, F6, F7, F8
Billentyű | Akció |
---|---|
F5 | Jelenlegi sor futtatása, majd ugrás a következő utasításra |
F6 | Metódus átugrása, azaz a metódus végrehajtása a debugger nélkül |
F7 | Jelenlegi metódus végrehajtásának befejezése és visszalépés a hívó metódusba |
F8 | A program végrehajtása a következő breakpointig vagy watchpointig |
Lépkedjünk a programunk sorain, és figyeljük közben a programban használt változók értékeit!
Ha akarjuk, akár meg is változtathatjuk egyes változók értékét itt, ezzel is közelebb kerülve a hibás működéshez!
Conditional breakpoint¶
A változók helyenkénti megváltoztatása persze nem minden esetben biztonságos megoldás, viszont például egy sokszor ismétlődő ciklus esetében a ciklusmag állandó végrehajtásától a hajunkat téphetjük. Ilyenkor segít, ha a breakpoint tulajdonságait módosítjuk, és csak bizonyos feltételek mellett áll meg adott helynél a debugger. A breakpoint megadásakor eleve állítsuk be azt úgy, hogy az a ciklusunk utolsó végrehajtására álljon.
Újrafuttatva a debuggert az már csak akkor áll meg, amikor a ciklus feltétele a feltételünknek megfelelő.
Exception breakpoint¶
Mivel jelen példánkban láttuk, hogy a hiba egy kivétel dobásához kötött, azt is megtehetjük, hogy a kivétel típusának megfelelő breakpointot adunk a programunkhoz. Jelen esetben ez a legjobb megoldás, mert így jutunk a legközelebb a probléma gyökeréhez.
A listából válasszuk ki a hibaüzenetünknek megfelelő kivételt:
Futassuk újra a debugerünket, de csak ezzel az újonnan megadott breakpointtal!
A debugger a hiba keletkezésének pillanatában megáll. Ha figyelmesek vagyunk, a változók egyértelműen mutatják nekünk, hogyan indexeltük túl a tömbünket, és mit kell javítanunk.
Javadoc kommentek¶
Segítségével a forráskódba írt kommentekből HTML alapú dokumentáció generálható.
Egy példa a generált HTML oldalra.
Sokféle generátor létezik, a legismertebb a Java JDK-ban is meglévő javadoc nevű alkalmazás, melyről bővebben itt olvashatsz, de ezen kívül számtalan alternatíva létezik: Doc-O-Matic, Doc++, Doxygen
Használata egyszerű, a hagyományos kommentezés helyett a /**
és */
közé írjuk a kommenteket, majd ezekben használhatunk különféle speciális hivatkozásokat, melyek az elkészült dokumentációban speciálisan jelennek meg.
Néhány példa:
@author
- szerző@version
- verzió@param
- paraméter@throws
- kivételek, amiket dobhat az adott függvény/osztály@return
- visszatérési érték@see
- hivatkozás más osztályokra
Az összes speciális hivatkozás és leírásuk elérhető ezen a linken.
/**
* Hello World program.
* @author Elso Hallgato
* @author Masodik Hallgato
* @version 1.0
*/
public class HelloWorld {
/**
* A main fuggveny
*
* @param args a parancssori parameterek.
* @author Elso Hallgato
* @version 1.0
*/
public static void main(String[] args) {
System.out.println("Hello World!");
}
/**
* Osszeadas
*
* @param a az elso szam.
* @param b a masodik szam.
* @author Masodik Hallgato
* @version 0.8
* @return a szamok osszege.
*/
public static int add(int a, int b) {
return a+b;
}
}
Az elkészült forráskódból elkészíthetjük a dokumentációt a beépített javadoc
paranccsal, a jelenlegi példában javadoc HelloWorld.java
, majd az elkészült fájlok közül nyissuk meg az index.html
fájlt.
Saját függvények¶
A main függvényen kívül saját függvényeket is írhatunk, hogy átláthatóbb kódot készítsünk.
Most egyszerűsítésként minden függvényünk elé írjuk oda, hogy public static
.
Ezt követi a függvény visszatérési típusa (ha nem tér vissza, akkor void
), a függvény neve, majd paraméterei, típusokkal.
public class DiakProgram {
public static void main(String[] args) {
String nev = "Kiss Pista";
int pontszam = 85;
kiirJegy(pontszam);
boolean atment = megfelelt(pontszam);
if (atment) {
udvozles(nev);
}
}
public static void kiirJegy(int pontszam) {
if (pontszam >= 85) System.out.println("Jeles");
else if (pontszam >= 70) System.out.println("Jó");
else if (pontszam >= 55) System.out.println("Közepes");
else if (pontszam >= 40) System.out.println("Elégséges");
else System.out.println("Elégtelen");
}
public static boolean megfelelt(int pontszam) {
return pontszam >= 40;
}
public static void udvozles(String nev) {
System.out.println("Gratulálok " + nev + ", átmentél!");
}
}
Parancssori paraméterek¶
Parancssori paraméterek átadása parancssorból, java
-val való futtatásnál:
java ProgramNeve param1 param2 param3...
Parancssori paraméterek beállítása különböző fejlesztőkörnyezetekben:
- IntelliJ IDEA: Először is létre kell hozni egy konfigurációt, amit a legegyszerűbben úgy tudunk megtenni, hogy a main függvény sorában jobb klikk, és Run 'Fájlnév.main()'. Ezt követően valahol jobb felül lesz a zöld háromszög (Play) ikon, ami mellett található a konfiguráció (ez egy lenyitható választó). Erre kattintva Edit configurations, majd pedig a Program arguments részen írhatjuk be a parancssori paramétereket.
- Eclipseben: A fordítás és futtatást jelző zöld háromszög (Play) ikon melletti lenyíló menüben Run Configurations, majd pedig itt az Arguments fül alatt adhatjuk meg a parancssori paramétereket.
Tömb bejárása a tömb length
(hossz, vagyis elemszám) tulajdonsága használható.
(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.)
Az alábbi kódrészlet kiírja a parancssori paraméterek számát (természetesen a main függvényen belülre kell helyezni):
Ha szeretnénk az egyes elemeket kiíratni, egy jól ismert for
ciklust használhatunk.
A tömbök indexelése a korábbiakhoz hasonló módon történik.
// parameterek bejarasa
for (int i=0; i < args.length; i++) {
System.out.println("" + (i + 1) + ". parameter: " + args[i]);
}
Paraméterek összege¶
Ahogy láttuk, a main függvény egy String
tömböt kap, ezt semmilyen módon sem lehet felülírni/megváltoztatni.
Ha számokként akarjuk kezelni a kapott paramétereket, akkor a tömb egyes elemeit először át kell konvertálnunk számmá, amihez az Integer.parseInt()
függvényt kell használnunk.
Az Integer.parseInt()
nagyon könnyedén használható függvény, amely átalakít egy szövegesen tárolt számot ténylegesen számmá.
Természetesen csodákra ez a függvény sem képes, csak olyan esetben használjuk, ha tudjuk, hogy az érkező szöveg valóban csak számot tartalmaz.
A parancssori paraméterek összege ezek fényében már gyorsan implementálható.
// 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 (ismétlés)¶
Egyszerű (primitív) adattípusok: boolean, char, byte, short, int, long, float, double, ezekről már korábban esett szó.. 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 újabb 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 metódusokat.
Az alábbiak hasznát csak később fogjuk megérteni. 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 |
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, mentsü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 alapján beláthatjuk, hogy például 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.
Összehasonlítás nem primitívek esetén¶
Az alábbi példában a String típus példáját hozzuk, azonban ez minden nem primitív típusra is igaz lesz. A String típust mindig nagy kezdőbetűvel írjuk, mivel ez igaziból nem primitív típus, ám sok esetben tudjuk úgy használni, mintha az lenne.
Értékek összehasonlítására technikailag az ==
és !=
operátorokat használhatjuk.
Azonban ez igaziból csak referencia összehasonlítást végez, nem a szövegek tartalmát hasonlítja össze.
Nézzünk egy egyszerű példát.
public class Main {
public static void main(String[] args) {
String kedvencGyumolcs = args[0];
if (kedvencGyumolcs == "korte") {
System.out.println("A kedvencem a körte.");
}
}
}
Hiába futtatjuk a programunkat a java Main korte
paranccsal, nem lesz jó az összehasonlítás, erre a fejlesztőkörnyezet is figyelmeztet (sárgán jelöli az egyenlőségjelet).
Valódi összehasonlításra a szoveg.equals()
metódus használható, amely ténylegesen a szöveg tartalmát vizsgálja meg.
public class Main {
public static void main(String[] args) {
String kedvencGyumolcs = args[0];
if (kedvencGyumolcs.equals("korte")) {
System.out.println("A kedvencem a körte.");
}
}
}
Természetesen a "korte".equals(szoveg)
is működőképes, sőt, hosszabb távon érdemes ezt a fajta összehasonlítást előnyben részesíteni.
Operátorok¶
+, -, *, /, %
operátorok és használatuk hasonló, mint Pythonban.
Egyedüli kivétel az osztás, amely Pythonnal ellentétben itt nem lebegőpontos osztás, hanem egészosztás (ez megfelel a Python //
˙operátorának).
A + jel sztringekre konkatenációt (összefűzést) jelöl. Használata nagyon intuitív. Például "kutya" + "mutya" -> "kutyamutya"
>>
- aritmetikai eltolás: megőrzi az előjelbitet.
>>>
- logikai eltolás: az előjelbitet is tolja. (Magyarán 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.
Gyors kiértékelés¶
A logikai &&
(és) és a ||
(vagy) operátorok gyors kiértékeléssel értékelődnek ki (short circuit evaluation).
Ez annyit jelent, hogy a logikai kifejezés pontosan addig értékelődik ki, amíg egyértelműen el nem dől az értéke.
Az 1. sorban mivel az &&
(és) bal oldalán hamis érték áll, ezért a jobb oldali értéket felesleges megnézni, hiszen egy és értéke csak akkor igaz, ha a bal oldala és a jobb oldala is igaz.
A 2. sorban lévő ||
(vagy) bal oldalán hamis érték áll, vagyis itt meg kell nézni a jobb oldalt is, hiszen egy vagy akkor igaz, ha vagy a bal, vagy a jobb oldala igaz.
Ezzel szemben a 3. sorban lévő &&
esetén az és jobb oldalát is meg kell nézni, hiszen egy és értéke csak akkor igaz, ha mindkét oldala igaz.
A 4. sorban viszont csak a ||
bal oldala alapján egyértelműen tudjuk, hogy az adott vagy kifejezés értéke igaz lesz, így felesleges a jobb oldalát is megnézni a vagy operátornak.
Ezek természetesen ugyanígy igazak több részkifejezés esetén is. Talán elsőre nem tűnik fontos dolognak, azonban a való életben sokszor optimalizálhatunk a kódunkon, ha jó sorrendben írjuk meg a logikai kifejezéseinket (például, ha egy és kifejezésünk van, akkor célszerű a bal oldalra tenni a gyorsabban kiértékelhető dolgokat, míg a jobb oldalra tenni az olyan kódot, ami kiértékelése időigényesebb lehet; konkrét példa: az és bal oldalán célszerűbb egy felhasználói inputot ellenőrizni, a kifejezés jobb oldalára pedig kerülhet mondjuk az a kód, ami interneten néz valaminek utána, vagy mondjuk egy adatbázisból olvas be adatot). Arról nem is beszélve, hogy futásidejű hibákat is megelőzhetünk, ha az if-en belüli logikai kifejezésünket jól írjuk meg.
Azonban figyelni kell, hiszen a &&
és &
operátorok nem ugyanazok!
Az első a logikai és, a másik pedig a bitenkénti.
Természetesen boolean
értékek esetén a két operátor ugyanúgy működik, kivéve a gyors kiértékelést!
A bináris operátorok esetében mindig kiértékelődik mindkét oldala a bináris operátornak!
public static boolean hosszabMint(String input, int hossz) {
return input != null && input.length() > hossz;
}
public static boolean hosszabMint2(String input, int hossz) {
return input != null & input.length() > hossz;
}
A hosszabMint
függvényben logikai és hajtódik végre, vagyis ha az input null
, az és bal oldala kiértékelődik hamisra, és a jobb oldalt lefutásra sem kerül.
Ezzel szemben a hosszabbMint2
függvény bináris műveletet hajt végre, azaz mindkét oldalt megvizsgálja, tehát ha az input null
, akkor az és bal oldala hamisra értékelődik ki, majd megpróbálja a jobb oldali kifejezést is kiértékelni, de mivel az input
változó null, ezért futásidejű hibát fogunk kapni.
If-else szerkezet¶
Java esetében összehasonlításnál nem használhatjuk a 4 < x < 9
szerkezetet, a kívánt működést két külön feltétel és-elése által érhetjük el: 4 < x && x < 9
.
A hagyományos if
szerkezet mellett létezik a rövidebb verziója, a háromoperandusú if-else szerkezet is (ternary).
Háromoperandusú operátor¶
Az operátor egy rövidített formája az if-else szerkezetnek.
Szintaxisa: feltétel ? érték_ha_igaz : érték_ha_hamis
.
Ez az egyetlen operátor Java-ban, amely három operandust használ.
Gyakran használjuk egyszerű értékadásoknál, ahol az értéket egy feltételtől függően szeretnénk meghatározni.
Például a int max = (a > b) ? a : b;
kód ugyanazt jelenti, mint:
Egyéb¶
Logikai kifejezéseket összefűzhetünk az &&
(és) és a ||
(vagy) operátorok használatával. A zárójelezés ilyenkor kritikus fontosságú lehet.
if ((x >= 3 && x <= 7) || x == 13 || (x < 0 && x != -9)) {
System.out.println("A feltétel teljesül.");
}
Javaban minden logikai kifejezés egy boolean
értékké értékelődik ki (vagyis vagy igaz, vagy hamis).
Ha csak egy boolean
értékünk/változónk van, az természetesen önmagában is igazságértéket jelképez, szükségtelen kiírni az == true
részt.
boolean pozitiv = true;
if (pozitiv) {
System.out.println("A szám pozitív.");
}
if (!pozitiv) {
System.out.println("A szám nem pozitív.");
}
if (pozitiv == true) {
// Szuksegtelen az == true resz, ugyanazt jelenti, mint a 2. sorban levo kod
}
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:
Viszont, Pythonnal 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!");
}
Megjegyzés néhány foglalt kulcsszóról
A program egyes részeit címkézhetjük ugyan, azonban a goto
parancsot nem használhatjuk.
Ez Javaban ugyan foglalt kulcsszó, de implementáció nem tartozik hozzá.
Ennek oka az, hogy az eredeti, első JVM-ben létezett a goto
utasítás, és működött is (ezt James Gosling nyilatkozta, aki a JVM-et készítette).
Azonban ezt nem sokkal utána ki is vették, mert nem igazán volt használva Javaban, és a nyelv készítői úgy látták, hogy C-ben is a legtöbben rosszul használják ezt az utasítást.
Így a funkcionalitását kivették a goto
utasításnak, de meghagyták foglalt kulcsszónak (reserved keyword), hiszen létezhetett olyan kód, amiben ez benne volt, illetve nem zárták ki annak sem a lehetőségét, hogy a jövőben szükség lehet rá, így azóta is foglalt kulcsszó a goto
, de semmilyen mögöttes működése nincs.
Hasonlóan foglalt kulcszó a C-beli const
, aminek ugyanúgy nincs semmilyen működése.
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 stacken 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 stackkel ellentétben egy lassabb, dinamikus memóriaterület, cserébe viszont jóval nagyobb terület áll rendelkezésre. 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 (a stacken). 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
.
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. Python esetében ehhez a lista adatszerkezetet használhattuk, ami egy dinamikus, tetszőleges típusú és darabszámú elem tárolására kitalált adatszerkezet. Java esetében az elemek száma azonban rögzített, tehát adott számú, és kötelezően 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, Pythonban 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.
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 tömbelemek elérésekor.
A Java-beli tömböket többféleképpen is létre lehet hozni.
Statikusan, ha fordítási időben ismerjük a tömb méretét, vagy pedig dinamikusan, amikor nem ismerjük előre a tömb méreté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:
A két deklaráció között semmilyen különbség sincs, azonban Javaban az alsó változat az ajánlott (a fentit az IDE-k be is sárgíthatják). Futás közben a már említett new
operátorral tudjuk definiálni:
Egy konkrét példa:
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.
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:
Többdimenziós tömbök¶
Analóg módon az egydimenziós tömbökhöz:
Két dimenziós tömb létrehozására egy példa:
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
Egy kicsit nagyobb példa az osztályzatokat bemutató példa.
public class OsztalyzatokPelda {
public static void main(String[] args) {
// Három tanuló jegyei négy tantárgyból (matek, magyar, angol, történelem)
int[][] osztalyzatok = {
{5, 4, 3, 5}, // Kis Péter jegyei
{4, 5, 5, 4}, // Nagy Emma jegyei
{3, 4, 5, 4} // Szabó Balázs jegyei
};
String[] tanulok = {"Kis Péter", "Nagy Emma", "Szabó Balázs"};
String[] tantargyak = {"Matematika", "Magyar", "Angol", "Történelem"};
// Tanulónként kiírjuk a jegyeket
for (int i = 0; i < osztalyzatok.length; i++) {
System.out.println("\n" + tanulok[i] + " jegyei:");
for (int j = 0; j < osztalyzatok[i].length; j++) {
System.out.println(tantargyak[j] + ": " + osztalyzatok[i][j]);
}
// Átlag számítás
double atlag = 0;
for (int jegy : osztalyzatok[i]) {
atlag += jegy;
}
atlag /= osztalyzatok[i].length;
System.out.printf("Átlag: %.2f%n", atlag);
}
}
}
Az iskolai osztályzatok egy kétdimenziós tömböt bemutató példa, ahol az egyik dimenzió a tanulókat, a másik pedig a tantárgyakat reprezentálja.
Minden osztályzat egyértelműen elérhető a megfelelő tanuló és tantárgy indexének használatával, például az osztalyzatok[1][2]
a második tanuló harmadik tantárgyból szerzett jegyét jelenti, ami jelen esetben Nagy Emma angol jegye.
Tömbök másolása¶
Mivel a tömbök referencia típusúak, 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)
Ez a hagyományos, kézi másolással szemben jóval gyorsabb, mert nem elemenként másolja a tömb egyes elemeit, az implementáció Java Native Interface-t (JNI) használ.
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
.
Ezen kívül használhatjuk az Arrays.copyOf
és Arrays.copyOfRange
függvényeket is, melyekkel talán picit kényelmesebb a tömbök másolása, azonban ezek használatához be kell importálnunk az Arrays
osztályt a kódunkba.
Az importálásról később lesz majd szó.
Teljes példa tömbök másolására:
import java.util.Arrays;
public class TombMasolas {
public static void main(String[] args) {
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();
// Tömbmásolás az Arrays.copyOf segítségével (teljes tömb)
int[] tomb3 = Arrays.copyOf(tomb1, 3);
for (int elem : tomb3) {
System.out.print(elem + " ");
}
System.out.println();
// Tömbmásolás az Arrays.copyOfRange segítségével (az eredeti tömb egy része)
int[] tomb4 = Arrays.copyOfRange(tomb1, 2, 6);
for (int elem : tomb4) {
System.out.print(elem + " ");
}
System.out.println();
}
}
Videók¶
-
Primitív típusok, változók és hibakeresés: https://youtu.be/Y9xXVu-rLsU
-
Parancssori argumentumok és sztringek, sztring-szám konverzió, wrapper (csomagoló) osztályok: https://youtu.be/lPNA_Y-mifc
-
Parancssori argumentumok és sztringek, sztring-szám konverzió 2.: https://youtu.be/dr1cMLhEpdY
-
Parancssori argumentumok és sztringek, sztring-szám konverzió 3.: https://youtu.be/amLMwbpJoDM
-
Input adatok beolvasása és tömbök: https://youtu.be/KiP5cTWt0_g
-
Tömbkezelés, sztring műveletek: https://youtu.be/TjOoWV83HRQ
-
Két dimenziós tömbök: https://youtu.be/dZt10lHIMks
-
Equals metódus használata: https://youtu.be/QnbD8ii2-ck
Feladatok¶
- Írd ki a parancssori paramétereket a konzolra, valamint a paraméterek számát.
- Írd ki a parancssori paramétereket a konzolra, fordított sorrendben.
- Írj egy olyan futtatható Java osztályt, amely a parancssori argumentumokat fordított sorrendben írja ki, ráadásul mindet visszafelé!
-
Í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, és így tovább.
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. Feltételezhetjük, hogy csak számokat kapunk parancssori paraméterként.
-
Tombola sorsoló. A parancssorban megadott nevek közül húzz ki egyet véletlenszerűen. Ha nincs megadva név, jelezd a helyes használatot. 0-1 közötti véletlen számot a
Math.random()
beépített függvény segítségével kérhetsz. -
A parancssori paraméterek alapján döntsd el, hogy 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
-
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! -
Egy mozi ülésrendjét szimuláljuk 8x10-es teremmel. A program kapja meg a foglalni kívánt helyeket, majd tegye meg a foglalást, írja ki az összeget. Ha már foglalt helyre szeretnénk foglalni, azt ne engedje a program.
-
Egyszerű torpedó játék, ahol a program elrejt 3 hajót egy 5x5-ös pályán (akár véletlenszerűen, akár beégetve), és a játékos koordinátákat ad meg. Kezelje le a program a nyerést, vesztést.
-
Kommentezd a kódot javadoc kommentekkel, és generáld ki a dokumentációt! És nézd is meg a generált állományokat!
Feladatok (automatikus értékeléssel)¶
Az itt lévő feladatok szövegei, váza elérhető itt: HaziFeladatUres.java. A függvények törzsét kell elkészíteni! A megoldás tesztelése automatikus, a fájl lefordításával és lefuttatásával bármilyen számítógépen elvégezhető, internet nélkül is!
- Készíts egy statikus,
lustalkodas
nevű függvényt. A függvény két paramétert kap: azt, hogy hétköznap van-e, és azt, hogy vakáció van-e. Akkor lustálkodhatunk reggel, ha hétvége van, vagy ha vakáción vagyunk. - Készíts egy statikus,
utolsoSzamjegyEgyenloseg
nevű függvényt. A függvény két számot vár. Térjen vissza igazzal, ha a számok utolsó számjegyei megegyeznek. - Készíts egy statikus,
utolsoSzamjegyEgyenlosegN
nevű függvényt. A függvény három számot vár (a, b, n). Térjen vissza igazzal, ha az első két szám utolsó n számjegyei megegyeznek (az utolsó 3. paraméter). - Készíts egy statikus,
honap
nevű függvényt, ami egy számot vár. A függvény térjen vissza az adott hónappal szövegesen (ékezet nélkül), ha a megadott szám 1-12 között van. Minden más esetben pedig a "nem ismert" szöveggel! - Készíts egy statikus,
primek
nevű függvényt, ami egy számot vár. A függvény térjen vissza azzal, hogy az adott számig (a paraméterként kapott szám is benne van) hány darab prímszám van!