Gyakorlati alapok II.
Külön függvény
A függvény olyan metódus, amelynek van adott típusú kimeneti értéke (return), ellentétben az eljárással, amelynek nincs (void), de az utóbbi kijelentést pontosítjuk a következő, Eljárás + return című fejezetben.
A függvény voltaképpen egy, a külvilág felé zárt feldolgozó egység, amely általunk beadott bemeneti adatokból belső programja szerint gyárt 1 db kimeneti adatot (összetett típus esetén lehet ez sok darab is, amelyek egyetlen komplex adatszerkezetbe, például tömbbe vannak zárva).
Az alábbi NEM futtatható Java-kódban egy százalékszámító metódus 3 bemeneti paramétert vár (double szazSzazalek, int szazalek, String irany), amelyből 1 db kimeneti adatot állít elő (return eredmeny). Ez az előre gyártott kód bármelyik projektbe megfelelően beillesztve azonnal képes százalékszámításra (ezt majd alább tapasztalni fogjuk):
public double szazalekSzamitas(double szazSzazalek, int szazalek,
String irany){
double eredmeny = 0;
double egySzazalek = szazSzazalek / 100;
switch(irany){
case "+": eredmeny = szazSzazalek + (egySzazalek
* szazalek); break;
case "-": eredmeny = szazSzazalek - (egySzazalek
* szazalek); break;
default: System.out.println("Rossz
műveleti jel!");
}
return eredmeny;
}
Először ismétlésképpen nézzünk meg egy eljárást:
public class Main {
public static void hibaUzenet(){
System.out.println("Ez egy
hibaüzenet!");
}
public static void main(String[] args) {
hibaUzenet();
}
}
Végeredmény:
Ez egy hibaüzenet!
Jól láthatjuk, hogy nincs visszatérési értéke (return), azaz típusa void, egyetlen célja valamilyen feladat elvégzése, itt egy hibaüzenet kiírása.
Most pedig nézzünk meg egy egyszerű függvényt. Az alábbi futtatható Java-kód egy számot (2 - ez a bemeneti adat) kétszerez meg külön függvényben (szamKetszerezes(int bemenetiAdat)):
public class
Main {
public static int szamKetszerezes(int bemenetiAdat){
return bemenetiAdat * 2;
}
public static void main(String[] args) {
System.out.println(szamKetszerezes(2));
}
}
Végeredmény:
4
Nagyon fontos tanulságaink vannak, ezek részben egyeznek az eljárásnál tapasztaltakkal:
-
a metódus működtető kódjai a Main osztályon belül, de a fejléc után a {} jelek közé kerülnek (itt: return bemenetiAdat * 2;),
-
a metódus fejlécében (public static int szamKetszerezes(int bemenetiAdat)) fontos szabványos deklarációk vannak:
-
public - ez egy hozzáférés-módosító (access modifier), azt jelenti, hogy a metódus kívülről, azaz más metódusok felöl látható, elérhető, meghívható,
-
int - mivel a Java erősen típusos nyelv, mindig figyelni kell a típusok egyezésére (bár a Java ezt bizonyos szintig képes lekezelni, de konkrétan a függvény bemeneti-kimeneti típuskülönbségeit nem tűri). A függvény fejlécében mindig előre kell jelezni, hogy milyen típusú kimeneti adatot fog szolgáltatni (itt int). Ha ez nem történik meg, vagy az adattípusok valamilyen módon nem egyeznek, hibaüzenetet kapunk és a kód nem fut le!
-
static - az osztály példányosításakor (itt: Main) ezzel a jelzővel illetett metódus vagy adattag szintén bekerül a műveleti memóriába (úgy mondják, hogy osztályszintű metódus, adattag), azaz azonnal elérhető, futtatható (Alapszinten a static módosítóról című fejezet),
-
szamKetszerezes(int bemenetiAdat) - a szokásos névkonvenciók figyelembevételével (Adatok, metódusok elnevezésének problémái című fejezetben foglaltak szerint) a metódusok és az adatok neveit mindig kisbetűvel kezdjük (camel case)! A zárójelek közé tett int bemenetiAdat egyértelműen a függvény egyetlen bemeneti adata, másképpen fogalmazva a bemeneti paraméterlistában deklarált bemeneti paramétere. A zárójelek közé több bemeneti paramétert is deklarálhatunk, például a százalékszámító kódban: (double szazSzazalek, int szazalek, String irany).
-
Egy kicsit időzzünk még a függvények típusegyezésének problémájánál! Fontossága miatt ismételjük meg az egyik megállapítást:
A függvény fejlécében mindig előre kell jelezni, hogy milyen típusú kimeneti adatot fog szolgáltatni (itt int).
public static int szamKetszerezes(int bemenetiAdat)
Ez azt mutatja meg, hogy milyen típusú lesz a függvény kimeneti értéke (return). Ha ez nem történik meg, vagy a típusok valamilyen módon nem egyeznek, akkor hibaüzenetet kapunk, vagy a függvény hibás eredményt szolgáltat!
A probléma szemléltetésére gyártsunk egy, a típusegyezésben garantáltan hibás függvényt: most kétszerezés helyett harmadolja a bemeneti adatot (itt: 2). Tehát a következő matematikai művelet kerül végrehajtásra:
2 / 3 = 0.666...
Az osztás eredménye nyilvánvalóan nem lesz egész szám:
public class
Main {
public static int szamHarmadolas(int bemenetiAdat){
return bemenetiAdat / 3;
}
public static void main(String[] args) {
System.out.println(szamHarmadolas(2));
}
}
Végeredmény:
0 (rossz!)
A fordító ugyan nem visítozik, ám a végeredmény mégis rossz, mert int típus nem tud nem egész, azaz lebegőpontos számot tárolni (például 0.66, ezért mutat 0-t). A végeredmény valójában csonkolódik. A megoldás a bemeneti és kimeneti adatok típusainak összehangolása. Nézzük meg, hogy hol van típusdeklaráció, 2 helyen a külső függvény fejlécében:
public static int szamHarmadolas(int bemenetiAdat)
A korrekt eredményhez a bemeneti adat típusát (bemenetiAdat) és a kimeneti adat típusát (a függvény deklarált típusa, vele a return értékét) azonosra kell állítanunk. Mivel osztás végeredménye legtöbbször nem egész szám, válasszuk a double típust, amely köztudottan a lebegőpontos számok tárolására szolgál (Numerikus lebegőpontos című fejezet):
public class
Main {
public static double szamHarmadolas(double bemenetiAdat){
return bemenetiAdat / 3;
}
public static void main(String[] args) {
System.out.println(szamHarmadolas(2));
}
}
Végeredmény:
0.6666666666666666
Természetesen a típusegyezéseken felül a konkrét bemeneti adatnak is típuskorrektnek kell lennie, hiszen nem sokat érünk például az alábbi értékadással (a):
public class
Main {
public static double szamHarmadolas(double bemenetiAdat){
return bemenetiAdat / 3;
}
public static void main(String[] args) {
System.out.println(szamHarmadolas(a));
}
}
Ilyen esetekben szavatoltan hibaüzeneteket kapunk:
Exception in thread "main" java.lang.Error:
Unresolved compilation problem:
a cannot be resolved at Main.main(Main.java:7)
A megszerzett tudás birtokában most már az eredetileg önmagában nem működő százalékszámításos függvényünk is működőképes lesz:
public class Main {
public static double szazalekSzamitas(double szazSzazalek,
int szazalek, String irany){
double eredmeny = 0;
double egySzazalek = szazSzazalek /
100;
switch(irany){
case "+":
eredmeny = szazSzazalek + (egySzazalek * szazalek); break;
case "-":
eredmeny = szazSzazalek - (egySzazalek * szazalek); break;
default:
System.out.println("Rossz műveleti jel!");
}
return eredmeny;
}
public static void main(String[] args) {
System.out.println(szazalekSzamitas(100, 25, "+"));
}
}
Végeredmény:
125.0
Láthatjuk, hogy hol adtuk meg a bemeneti adatokat: a System.out.println() függvényben, hiszen a százalékszámító függvény hívása ezen függvény paraméterlistájában történik (System.out.println(szazalekSzamitas(100, 25, "+");). A 100, 25, "+" bemeneti paraméterek azt jelentik, hogy a függvény számolja ki 100-nak 25%-át, majd adja hozzá és ez az összeg lesz a függvény egyetlen kimeneti adata (125.0). Mindezt azért sikerült ilyen közvetlenül elérnünk, mert a System.out.println() úgy van programozva, hogy képes bemeneti paraméterként fogadni teljes függvényeket, illetve vegyük észre, hogy képes azonnal megjeleníteni a kimeneti adatot is.
Ám a bemeneti adatok megadását "közvetetten" is meg tudjuk tenni: ekkor nem bízunk semmit a System.out.println() függvényre és egyéb programautomatikára, hanem "külsőleg", kézzel oldjuk meg a deklarációkat:
public class Main {
public static double szazalekSzamitas(double szazSzazalek,
int szazalek, String irany){
double eredmeny = 0;
double egySzazalek = szazSzazalek /
100;
switch(irany){
case "+":
eredmeny = szazSzazalek + (egySzazalek * szazalek); break;
case "-":
eredmeny = szazSzazalek - (egySzazalek * szazalek); break;
default:
System.out.println("Rossz műveleti jel!");
}
return eredmeny;
}
public static void main(String[] args) {
double szazSzazalek = 50;
int szazalek = 10;
String irany = "-";
double eredmeny =
szazalekSzamitas(szazSzazalek, szazalek, irany);
System.out.println(eredmeny);
}
}
Végeredmény:
45.0
A kód legfontosabb tanulsága, ahogy a százalékszámító függvény kimeneti adatának tartalmát átveszi egy ugyanolyan típusú változó (double eredmeny):
double eredmeny =
szazalekSzamitas(szazSzazalek, szazalek, irany);
Mivel a 2 függvény viszonylag független egymástól, alapjában véve használhatunk azonos elnevezéseket is, bár nagyobb projekt esetén ebből hamar zavar keletkezhet. Gondoljunk csak arra, hogyha a százalékszámítás konkrét, mondjuk egy pénzügyi művelet implementációja, ráadásul a bemeneti adatokat felhasználók gépelik be mondjuk néhány 100 kilométerrel távolabb, akkor az általános programozási protokollok szerint a 2 függvény fizikai távolságban és programozás-technikailag is igen távol eshet egymástól.
Egy programozási módszertanban azonban -ahol mindennél sokkal fontosabb a kódok egymásra ható működésének megértése-, az azonos elnevezés módszertanilag hasznos, ezért járható út. A továbbiakban -ameddig lehetséges-, ezt az elvet követjük.
A százalékszámító és a System.out.println() függvény közt észrevehetünk egy további fontos különbséget: az előbbi kizárólag 3 adott típusú bemeneti adatot képes fogadni, minden más bemeneti adat hibaüzenetet fog generálni. Ugyanakkor a System.out.println() függvény paraméterlistája rendkívül sokféle kód és kódszerkezet befogadására és futtatására képes; ez voltaképpen csak a függvény leprogramozásának kérdése. Erről meggyőződhetünk, ha átolvassuk az Írjunk ki a konzolra mindenfélét a System.out.println() függvénnyel! című fejezetet.