Elméleti alapozás
Adat, művelet, metódus, osztály, csomag, objektum (és egyéb szörnyetegek)
A fenti néhány programozási alapfogalom voltaképpen egy átlagos programozási nap unalmas, agyonrágott kelléktárát alkotják. A programozó ezekkel dolgozik nap mint nap, sőt azt hiszem nem többel, legfeljebb más projekteken. Ebben a fejezetben megpróbálom -hangsúlyozom: alapszinten és főként a laikusok számára-, tehát lényegében "laikus-nyelven" elmagyarázni ezeket a nehezen érthető fogalmakat. Fontos mozzanat ez, másként fennáll a veszélye, hogy a laikus soha, de soha nem érti meg a programozás alapjait.
Amint az már említésre került, a számítástechnika, tulajdonképpen a programozás egyik célja a való világ modellezése.
Ehhez egzakt, pontos (szabványos), a számítógép által tökéletesen értelmezhető bemeneti adatokra van szükség. Az is bizonyos, hogy a megírt programnak lesznek végeredményei is, ezek a kimeneti adatok. A számítások során keletkezhetnek átmeneti, tárolóadatok is, ezek azonban legtöbbször csak rövid élettartamúak, mert nem kerülnek mentésre.
Az adatok különböző típusúak lehetnek, a 2 típusfajta az egyszerű (primitive) és az összetett. Az utóbbit majd később értelmezzük, most az egyszerű típussal foglalkozunk tovább. Például egy autó tömege szám típusú (numerikus, mert számot, mennyiséget tárol), míg egy betű karakter típusú:
int autoTomeg = 1200;
//kilogramm
char karakter = ’J’; //azaz J betű
A bemeneti adatokon a megírt program szerint műveleteket kell elvégeznünk ahhoz, hogy megfelelő kimeneti adatokat kapjunk. Voltaképpen a műveletek változtatják át a bemeneti adatokat kimeneti adatokká.
Mindehhez persze megfelelő bemeneti és kimeneti perifériák, tárolóegységek és ezek tökéletes összhangja szükséges, amely karmesteri szerepet túlnyomórészt a processzor tölti be.
A számítógép agya, a processzor tehát rengetegféle alacsony szintű műveletet hajt végre (memóriacím-képzés, buszcsatolás, vezérlés, ellenőrzés, stb.), ám ezek legtöbbjét az operációs rendszer -amely szintén egy előre megírt programcsomag-, teljesen megoldja, de úgy is fogalmazhatunk: előlünk ezeket a műveleteket teljességgel elrejti (szaknyelven szólva: transzparens a fejlesztő számára). Nekünk, az általános, azaz az alkalmazásszintű programozás szintjén szintúgy rengeteg dolgot kell ismernünk, ám ez a processzor gépi kódjától különböző, jóval magasabb szint.
A helyes kimeneti adat, azaz a végeredmény tehát alacsony-, és magas szintű műveletek összehangolt futásának eredője.
A következő futtatható Java-kódban egyszerűen 2 db bemeneti adatot (1 és 2) összeadunk, hogy kapjunk 1 db kimeneti adatot (3). Ennek során bemeneti perifériaként billentyűzetet, kimeneti perifériaként monitort (és esetleg videókártyát) használtunk fel. A perifériák alacsony szintű műveleti illesztéséhez nem sok közünk volt, ám az bizonyos, hogy a mozzanatot sikeresnek kell értékelnünk, ha képesek voltunk a kód futtatására.
public class Main {
public static void main(String[] args) {
int a = 1;
int b = 2;
int c = a + b;
System.out.println(c);
}
}
Végeredmény:
3
A metódus (method) bemeneti adato(ka)t és legtöbbször több műveletet foglal magába, általában 1, de a műveletnél magasabb szintű probléma megoldására hivatott. Ilyen lehet például a százalékszámítás, amely során 2, esetleg 3 bemeneti adat és több művelet felhasználásával osztanunk és szoroznunk is kell (lásd az alábbi kódot).
A metódus további fontos tulajdonsága, hogy lehet 1 visszatérési értéke (return), amely voltaképpen a metódus egyetlen kimeneti adata. (A metódusnak lehet több kimeneti adata is, ha összetett típusba, például tömbbe van csomagolva.) Ezzel valójában nem mondtunk újat, mert a cél mindig is a helyes kimeneti adat produkálása volt, de ha megtesszük, hogy a metódust egyetlen, kívülről nem, vagy csak nagyon szabályozott feltételekkel módosítható egységbe zárjuk, akkor programozás-technikailag nagyot léptünk egy új, még fejlettebb irányba, ez pedig az objektumorientált programozás.
Az újfajta metódusszemlélet azonban még tovább fokozható: számos metódust előre megírhatunk és később könnyen felhasználhatjuk őket, ha pedig rendszerezve lesznek, akkor egy elképesztően praktikus „programozási közkönyvtárrá” fognak válni.
Az alábbi futtatható Java-kódokban egy százalékszámító metódus 2 db bemeneti paramétert vár, amelyből 1 db kimeneti adatot állít elő:
public class Main {
public static double szazalekSzamitas(double szazSzazalek, int szazalek){
double eredmeny = 0;
double egySzazalek = szazSzazalek / 100;
eredmeny = egySzazalek * szazalek;
return eredmeny;
}
public static void main(String[] args) {
double szazSzazalek = 50;
int szazalek = 20;
double eredmeny = szazalekSzamitas(szazSzazalek, szazalek);
System.out.println(eredmeny);
}
}
Végeredmény:
10.0
A fenti algoritmus kissé tömörebben:
public class Main {
public static double szazalekSzamitas(double szazSzazalek, int szazalek){
return (szazSzazalek / 100) * szazalek;
}
public static void main(String[] args) {
double szazSzazalek = 50;
int szazalek = 20;
System.out.println(szazalekSzamitas(szazSzazalek, szazalek));
}
}
Végeredmény:
10.0
Néhány régebbi programozási szakkönyvben a metódus, mint fogalom kétféle formában kerül definiálásra:
-
függvény – olyan metódus, amelynek van önálló típusa, vele kimeneti értéke (return) - Külön függvény című fejezet,
-
eljárás – olyan metódus, amelynek nincs kimeneti értéke (void) - Külön (nem rendőrségi) eljárás című fejezet.
Újabb szakkönyvekben már nem találkoztam ilyen definícióval, noha a metódus szinonimájaként ezekben is használatosak az említett szavak, főként a függvény.
Az osztály (class) több, valamilyen logikai kapcsolat alapján összefüggő adatot, valamint a rajtuk elvégezhető műveleteket foglal magába (Az absztrakció mélységei: az osztály fogalma című fejezet). A rendszerezéskor bármilyen szempont felmerülhet, de ha a cél a logikus, ezért hamar átlátható rendszerezettség, akkor érdemes az alábbi szempontok alapján csoportosítani őket:
-
funkció szerint - például hálózati működéssel kapcsolatos adatok és metódusok osztálya (class Network{}),
-
bemeneti adattípusok szerint - hiszen másfajta metódusok szükségesek egy numerikus és egy karakter típusú bemeneti adat feldolgozásához (például class Integer{} - ez egy valós, létező osztály, a Java szabványos osztályhierarchiájában helye: java.lang.Integer). Például nem lehet 2 betűt összeszoroznunk egymással. (Igazából elméletileg lehet, mert a betűket is bináris számsorok reprezentálják, ám ennek nincs sok értelme.),
-
bármilyen logikus szempont szerint - például meghatározhatunk egy Munkavállaló osztályt, amely néven, címen, fizetésen felül bármilyen, a munkavállalói státuszhoz köthető metódust is tartalmazhat, mondjuk fizetésemelés(), adólevonás(), stb.
További példaként az alábbi nem futtatható Java-kódban egy Háromszög osztályt és egyszerű metódusait láthatjuk (ilyenek például a kötelező getter-setter, a kerület-, területszámoló függvény, stb.):
public class Haromszog {
private double oldal = 1;
public Haromszog(double oldal) { //Konstruktor
setOldal(oldal);
}
public double getOldal() {
return oldal;
}
public void setOldal(double oldal) {
if (oldal <= 0) {
oldal = 1;
}
this.oldal = oldal;
}
public double keruletHaromszog (double oldal){
return 3 * oldal;
}
public double teruletHaromszog (double oldal){
double eredmeny = oldal * oldal *
Math.sqrt(3) / 4;
return eredmeny;
}
public void equals(Haromszog haromszog) {
if (haromszog.getOldal() ==
this.getOldal()) {
System.out.println("A háromszögek megegyeznek.");
} else
System.out.println("A háromszögek NEM
egyeznek.");
}
}
Az osztályokba rendszerezett kódtömeget megfoghatjuk és alaposan be is csomagolhatjuk ünnepi (☺), úgynevezett csomagokba (package). A Java-nyelvben ez a rendszerezés legmagasabb hierarchikus szintje. A csomagok rengeteg kódot tartalmazhatnak, sokszor akár azonos osztálynévvel, ezért a kódok felé egyértelmű elérési utakat kell kialakítanunk. Csak gondoljunk a fájlrendszerek mappaszerkezetére, amelyek tervezésekor ugyanez volt a fő szempont. A szabványos elérési utat nem GPS határozza meg nekünk, hanem a csomagok elnevezésében, valójában az elérési út megadásában trükközünk. A Java-szabvány a következőket definiálja:
országkód.cégnév.további osztályozási szempontok
com.oracle.java.docs
A Java-program futtatása -mint minden más programé-, a processzor kisebb, átmeneti tárolóként szolgáló memóriaterületeinek (regiszterek) segítségével a műveleti memóriában (RAM) történik. Ehhez –főként az adatok és az utasítások futtatására-, előzetesen RAM-memóriaterületeket kell lefoglalni. Ez a Javában nem megy automatikusan a Java-program indításával. Az osztályba szervezett adatok és metódusok tehát csak „élettelen” tervrajzok, szövegformátumban előre megírt programkódok, sokszor még akkor is, amikor a program már esetleg elindult (run – fut).
A tervrajz NEM egyenlő a felépített házzal!
Ahhoz, hogy belőlük valós időben futtatott, működő programkódok legyenek, amelyek megkapták a megfelelő memóriaterületet és processzoridőt, létre kell hozni őket a new paranccsal, úgy is mondják, hogy példányosítani kell őket. Csak innentől kezdve lehet őket objektumoknak hívni. Tehát pontosan eddig a pillanatig nevezzük őket „klasszikus” definíció szerint osztálynak.
(Itt jegyzem meg, hogy a static módosítószó használatakor azonnal megtörténik a memóriafoglalás.)
Nézzük például az alábbi futtatható Java-kódot, amely 10 körben generál véletlenszámokat 1 és 100 között! Láthatóan importálunk, tehát beemelünk a projektbe egy előre megírt véletlenszám-generátort (import java.util.Random), de a kód egészen addig csak tervrajz marad, amíg a tervrajzot nem példányosítjuk a new paranccsal (Random rnd = new Random()). Csakis ezután használhatjuk fel céljaink szerint (rnd.nextInt(100) + 1).
import java.util.Random;
public class Main {
public static void main(String[] args) {
Random rnd = new Random();
int eredmeny = 0;
for(int i = 1; i <= 10; i++){
eredmeny = rnd.nextInt(100) + 1;
System.out.println(eredmeny);
}
}
}
Végeredmény (például):
79
37
95
49
43
4
50
88
90
85