Változók típusai
Operátorok
Aritmetikai operátorok
+-*/
Relációs operátorok
Összehasonlító operátorok
!= nem egyenlő
== egyenlő true false
a%b==0 maradék nélkül
Logikai operátorok
&& and || or
true && true = true
true && false = false
true || false =true
false && false = false
Feltételek
if (a<5)
Sytem.out.println("rossz");
else Sytem.out.println("jo");
For ciklus
for(int a=0; a< 10; a++)
while és do while ciklusok
break;
kiugrik
switch case állapot
break;
default:
bemenet kezelés
import java.util Scanner;
Scanner bemenet=new Scanner (system.in);
String nev=bemenet.nextLine();
if (Integer.parse.Int(); < 5)
numerikus
Tömbök
Bitléptető és bitenkénti logikai operátorok
Értékadó operátorok
Egyéb operátorok
Logikai operátorok
public class Elso {
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso();
e.prog( args );
}
}
Mentsd el a fenti szöveget az Elso.java nevû fájlba. Fontos, hogy a fájl neve mindenképpen ez legyen! Jóval késõbb fogjuk kifejteni, miért , de a forrásban a "public class" mögött levõ névvel megegyezõnek kell legyen a forrásfájl neve .java kiterjesztéssel. Most mondd azt:
javac Elso.java
Ha minden jól ment, a parancs némi motozás után üzenet kiírása nélkül befejezi a futását. Ha most megnézed a könyvtárat, ahol a forrásszöveg volt, találsz egy Elso.class nevû fájlt. Ez a lefordított Jáva program, ebben van benne a programod Jáva bájtkódban. Most már csak egy Jáva virtuális gépet kell találnunk, ami le is futtatja. A java parancs pont ezt tudja.
java Elso
És megjelenik a következõ szöveg:
Hello, ez az elso Java programunk!
Hihetetlen, már mûködõképes programot írtunk! Igaz ugyan, hogy az egészbõl nem értünk semmit, de elsõ lépésnek nem rossz! A sok kékséggel most ne foglalkozzunk, egy sort azért elmagyarázok. A System.out.println arra jó, hogy amit a mögötte levõ zárójelek közé írtunk, kiírja a konzolra. Az idézõjelek fontosak: ez mondja meg a fordítóprogramnak, hogy amit mögötte lát, az nem programszöveg, nincs értelme a számítógép számára, csak egy, a gép számára értelmetlen, önkényes karaktersorozat, vagyis betûk, számok és szimbólumok sora. Errõl bõvebben majd egy késõbbi fejezetben! Végül az utasítást egy pontosvesszõ zárja, ez kötelezõ. Minden Jáva utasítás végén pontosvesszõ kell legyen.
Azt tanultuk a fejezet elején, hogy a számítógépnek a program elõre mondja meg, mit kell majd csinálnia. Ugyan a mi kis programunk elindításakor már így is jó sok mindent csinál (el se tudod képzelni, mennyi mindent!), mégis, demonstráljuk ezt a képességét most szuperprogramunk egy jelentõs bõvítésével: írassunk ki vele egy újabb sort.
public class Masodik {
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
System.out.println( "Ez pedig egy masodik sor tole" );
}
public static void main( String args[] ) {
Masodik e= new Masodik();
e.prog( args );
}
}
Másoljuk ki a szövegbõl a szövegszerkesztõbe, mentsük el, fordítsuk le és futtassuk. (Most utoljára súgok: Copy-Paste Notepad-be, elmenteni a Notepad-bõl Masodik.java néven, javac Masodik.java, java Masodik). A futtatási eredmény nem meglepõ:
Hello, ez az elso Java programunk!
Ez pedig egy masodik sor tole
Figyeljük meg, hogy a két szöveg egymás alá íródott ki. Ez a System.out.println tulajdonsága, ez minden, általa kiírt karaktersorozat után egy soremelést is kiír. A soremelés egy olyan speciális karakter, ami a következõ karakter kiírása pozícióját egy új sor elejére lépteti.
Feladat:
írasd ki a saját nevedet a számítógéppel!
Ezzel a lecke végére is értünk. Jókora utat jártunk be, a számítógép fogalmától eljutottunk egy mûködõképes Jáva programig. A következõ fejezetben bemutatjuk, miféle számításokat tudunk végeztetni szilíciumagyú barátunkkal.
Ellenõrzõ kérdések
1. Mi a számítógépes program?
2. Mi a processzor szerepe a számítógépben?
3. Mi az operációs rendszer?
4. Miért szükségesek fordítóprogramok?
5. Miért elõnyös a McDonald's?
6. Mi a különbség a gépi kód és a Jáva bájtkód között?
7. Mi a Jáva virtuális gép?
8. Milyen parancs indítja el a Jáva fordítóprogramot?
9. Milyen parancs indítja el a Jáva virtuális gépet?
10. Mi az a Jáva szerkezet, amivel a konzolra lehet írni?
11. Mit jelent a karakter fogalma?
12. Mi az a soremelés karakter?
2. fejezet
Fiókos szekrény szilíciumból. A számítógép mindent képes tárolni, csak mi nem felejtsük el, mit hová tettünk: a változók. Fiókméretek és adattípusok. Két egyszerû adattípus, az egész és a lebegõpontos. Néhány alapmûvelet kezdetnek.
A számítógép olyan, mint a mamátok fiókos szekrénye. Ez egy nagyon jó hasonlat, magamtól nem is tudtam volna kitalálni, Roger Kaufman örökbecsû FORTRAN képeskönyvébõl vettem. FORTRAN-t ma már kevesen használnak, de a könyv még ma is a legjobb bevezetõ kezdõknek a számítógépes programozásba. De vissza a fiókos szekrényhez! A szekrénynek, akarom mondani a gépnek rengeteg fiókja van a legkülönbözõbb cuccok tárolására. A gép persze nem zoknikat vagy több éves villanyszámlákat tárol, hanem adatokat. Hogy a sok fiók között ne vesszünk el, megcímkézzük a fiókokat, egyszerû és találó neveket adunk nekik. Egy ilyen fióknév egy változó neve, tartalma pedig a változóban tárolt érték.
A változó egy igen fontos fogalom, ezért lássunk néhány hasonlatot. Pénztárcánkban levõ pénz változó mennyiség, ez megfelel a változó értékének. Egy konkrét pénztárca megfelel a változónak magának, holott pénztárcák általában nincsenek felcímkézve. Vagy vegyünk egy teherautót: a rajta levõ sóder mennyisége változó érték, a teherautó rendszáma pedig azonosítja, melyik értéket keressük.
A változó egy analógia, ami lehetõvé teszi nekünk, hogy a gép sok milliónyi memóriarekeszébõl kiválasszuk a minket érdeklõt. Egy változó mindig egy adag memóriarekeszre vonatkozik (hogy mennyire, azt majd az adattípusok tárgyalásánál meglátjuk) és a Jáva nyelv azt a kényelmességet adja nekünk, hogy erre az adag memóriarekeszre egy általunk választott névvel hivatkozhatunk. A névnek egyedinek kell lennie és betûvel kezdõdnie, a további karakterei lehetnek betûk, számok vagy aláhúzásjel. Néhány példa érvényes változónevekre:
• i
• j17
• PI
• nagyon_hosszu_valtozonev
Itt van három érvénytelen is:
• 32ev
• a$
• nagyon hosszu valtozonev
A Jáva nyelv különbözõnek veszi a kis- és nagybetûket, így a pi és a PI két külön változót jelöl.
Mint ahogy a fiók kiválasztásánál is fontos, hogy cetliket tárolunk-e benne vagy lábosokat, úgy a számítógépnek is tudnia kell, mennyi memóriát kell egy változóhoz rendelnie. Ezt az határozza meg, milyen típusú adatokat tárolunk a fiókban, akarom mondani a memóriarekeszben. A tárolni kívánt adat típusát a változó adattípusának megadásával kell meghatároznunk, hasonló módon, mint ahogy a változó nevét is a változónévvel. A Jáva nyelvnek van néhány beépített adattípusa továbbá a képessége, hogy új adattípusokat hozhasson létre a programozó. Most csak két adattípus vizsgálunk meg, az egész és a lebegõpontos típust.
Az egész típus a számítógépünk legalapvetõbb típusa, leginkább ezzel szeret számolni a gép, ezért mi is használjuk, ahol csak lehet. Az egész típusú szám 0-tól nagyjából 2 milliárdig nõhet és csökkenhet pozitív és negatív irányban egyaránt és nevéhez illõen törtrésze nem lehet. A típust az int kulcsszó leírásával deklaráljuk. Pl.:
int i;
int K,nagyon_hosszu_valtozonev;
A deklaráció a Jáva nyelvben kötelezõ. Minden változót elõbb deklarálni kell és csak aztán lehet használni. Deklarálás bárhol lehet a programban a változó felhasználása elõtt, tehát akár a program közepén is. A deklarálás hatására a változóhoz hozzárendelõdik a tárterület, ahol a változó értékét tárolja gépünk, de a változó értékérõl semmit sem tudunk. Sõt mi több, a Jáva fordító, ha felderíti, hogy olyan változót használtunk, aminek az értékérõl még nem rendelkeztünk, hibaüzenetet küld.
De mit beszélek én itt a változó értékének megadásáról és a változó felhasználásáról, amikor még csak a deklarálást ismerjük? Ha egy változót már deklaráltunk, következõ lépésben adjunk neki gyorsan értéket. Pl.:
i = 3;
K = -10;
Ez az értékadó utasítás. Fontos, hogy ne tekintsd az értékadó utasítást a matematikából ismert egyenlõség teljes szinonímájának. A matematikai egyenlõség azt fejezi ki, hogy két kifejezés a levezetés teljes idõtartamára bizonyos feltételek mellett megegyezik. Ha tehát y = 2x, akkor ez így van bármilyen x-re a megadott peremfeltételek mellett. Az értékadó utasítás azonban, hogy a FORTRAN képeskönyvbõl orozzak megint, egy "bemén" jel, tehát abban a pillanatban, amikor kiértékelõdik, a jobb oldalán egy szám lesz, ami beleíródik, "bemén" a változó mögött meghúzódó memóriába csak egyszer és akkor, amikor az utasítás végrehajtódik. Ha késõbb frissíteni akarjuk a változó értékét, újra végre kell hajtanunk egy értékadó utasítást. Ha tehát én azt mondom, hogy y = 2*x, akkor a gép veszi az x változó értékét, megszorozza kettõvel és beteszi az y változóba (figyelj, mostantól slamposabban fogalmazok! eddig mindig hangsúlyoztam, hogy a változónév mögött hozzárendelt memória húzódik meg, de remélhetõleg ez már jól a fejedbe vésõdött. Mostantól csak azt mondom, hogy, beleíródik a változóba). Ez csak egyszer és az x aktuális értékével történik meg, ha x késõbb megváltozik és fontos, hogy ezt y is kövesse, újra végre kell hajtani az értékadást.
Suttyomban megint elõreszaladtam egy kicsit és leírtam egy Jáva kifejezést. Végre is erre tartjuk a számítógépet, számoljon gyorsan. A Jáva program is mindent képes kiszámolni, ami leírható egy véges mûveletsorozattal, azaz algoritmussal. A Jáva program jónéhány mûveletet képes számokkal elvégezni, ezek közül nézzük most a négy alapmûveletet.
• + : összeadás
• - : kivonás
• * : szorzás
• / : osztás
A mûveletek a matematikában megszokott módon precedenciával rendelkeznek, tehát zárójelek nélkül a szorzás és az osztás "erõsebb" az összeadásnál és a kivonásnál. Ezen (szokásos módon) zárójelekkel változtathatunk. Pl. a 5+2*10 értéke 25, míg az (5+2)*10 értéke 70. Zárójeleket tetszõleges mélységben ágyazhatunk egymásba, csak párosan legyenek. A kifejezésekben a konstansokat, esetünkben egész számokat és a változókat egyenértékûen használhatjuk. Ha tehát az y változóba az x változó kétszeresét akarnád tenni, a helyes válasz: y = x*2. Lássuk ezt egy mûködõ programban is!
public class Szamit1{
public void prog( String args[] ) {
int x,y;
x = 5;
y = x*2;
System.out.println( "Eredmeny: "+y );
}
public static void main( String args[] ) {
Szamit1 e = new Szamit1();
e.prog( args );
}
}
Nem hiszem, hogy a System.out.println során kívül bármi is magyarázatot igényelne. A már megismert karaktersorozat-konstanshoz (az idézõjelek között levõ részhez) hozzáfûztük az y változót, ami egy egész. Ekkor a Jáva fordító automatikusan olyan kódot fordít, ami az y-ban levõ értéket karaktersorozattá alakítja és hozzáfûzi a karaktersorozat-konstanshoz. Tehát az eredmény:
Eredmeny: 10
Feladat
írj egy olyan programot, ami kiírja 2 elsõ nyolc hatványát. A kiírási kép legyen tehát:
2^0 = 1
2^1 = 2
...
2^7 = ....
Oldd meg a feladatot önállóan, majd a saját programodat hasonlítsd össze a megoldással .
Ugye nem ért váratlanul az elõzõ feladatban az n=n+1 típusú értékadás? Matematikailag ez egy teljes csõd, de számítógépül annál több értelme van: vedd az n értékét, adj hozzá egyet, majd tárold vissza n-be, vagyis növeld meg n-t eggyel.
Most csavarjunk egyet a dolgon es 0-tol kezdve negatív irányba haladjunk kettõ hatványaival. Vagyis a kiírási kép legyen:
2^0 = 1
2^-1 = 0.5
...
(Ugye emlékszel: valaminek a negatív hatványa megegyezik a pozitív hatvány reciprokával. Vagyis: 2-4 = 1/24 ).
Ha programunkat lelkesen megmódosítanánk úgy, hogy az összes n = n + 1-et n = n - 1-re és az összes x = x * 2-t x = x / 2-re cserélnénk, szomorú nyomtatási kép fogadna.
2^0 = 1
2^-1 = 0
2^-2 = 0
...
ami egyértelmûen helytelen. A baj okára magad is rájöhetsz: egész számok osztása során elvesznek a törtrészek és a 1/2 osztás 0.5 eredménye mint 0 tárolódik vissza x-be. Egészekkel ezt a problémát nem tudod megoldani, szükségünk lesz a Jáva egy másik alaptípusára, a lebegõpontos típusra. A lebegõpontos változót így deklarálod:
double pi;
double z0,z1;
és így adhatsz neki értéket:
pi = 3.1415927;
A lebegõpontos szám már elég nagy ahhoz, hogy nagyobb számokat írjál le vele, mint amit kényelmesen be tudsz csépelni egy sorba, ezért jól jöhet a tudományos számábrázolás. Ha emlékszel, ekkor a számot egy 0-10 közötti szám és egy 10 hatvány szorzataként írjuk le. Pl: 2500 = 2.5*1000 = 2.5*103. Jávában ezt úgy kell leírni: 2.5E3. Tehát:
z0 = 2.5e3;
Persze ha nem akarod ezt a kis számot exponenciális ábrázolásban leírni, nem kell.
z1 = 2500;
A double-ban leírható maximális szám kb. 10308 , a legkisebb 10-308, ugyanez persze negatív irányban is. Ekkora számokkal a gép persze jóval lassabban számol, mint egészekkel, így double-t csak akkor használjunk, ha szükséges.
public class kettohatvany {
public void prog( String args[] ) {
int n,x;
n = 0;
x = 1;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
}
public static void main( String args[] ) {
kettohatvany e = new kettohatvany();
e.prog( args );
}
}
Feladat
Írjuk meg most az elõzõ, balsikerû feladványunkat a kettõ negatív hatványaival úgy, hogy x-et egész helyett double-nak deklaráljuk. Próbáld meg magad megírni, majd ellenõrizd a megoldást .
public class kettonhatvany {
public void prog( String args[] ) {
int n;
double x;
n = 0;
x = 1;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
}
public static void main( String args[] ) {
kettonhatvany e = new kettonhatvany();
e.prog( args );
}
}
Ennyit elsõ közelítésben a számokról és a velük való mûveletekrõl. A Jáva persze jóval több adattípussal és mûvelettel rendelkezik, mint amit eddig láttunk, de a következõ részben inkább a program végrehajtását befolyásoló szerkezetekkel foglalkozunk.
Ellenõrzõ kérdések
1. Mi az összefüggés a változó neve és értéke között?
2. Hogy jön a fiókos szekrény a számítógéphez?
3. Mit jelent, hogy egy változót deklarálni kell?
4. Hol kell lennie egy változó deklarációjának?
5. Hogyan kell leírni egy egész típusú változó deklarációját?
6. Mi a legnagyobb és legkisebb érték, amit egy egész típusú változóban tárolhatunk?
7. Mi a különbség a Jáva értékadó utasítása és a matematikai egyenlõség között?
8. Mi az algoritmus?
9. Hogyan kell a zárójeleket elhelyezni a 5+6*3 kifejezésben, hogy a kifejezés értéke 33 legyen? És ha 23-at akarunk?
10. Mi a lebegõpontos típus?
11. Mekkora a legnagyobb és legkisebb szám, ami a lebegõpontos típussal leírható?
12. Mi az exponenciális számábrázolás?
13. Mennyi 2.001e3?
14. Írd le 0.12-t exponenciális ábrázolásban!
3. fejezet
Terelgetjük a számítógépet; a program futásának menete. Struktúrált programozás, építkezés Matrjoska babákból. Elágazások és logikai kifejezések. Megdolgoztatjuk a gépet: a ciklusok.
A számítógép, mint mondtam, akár százmillió vagy újabban akár egymilliárd utasítást is képes végrehajtani másodpercenként. A Jáva ugyan nagyban lassít rajta, azonban egy modern számítógép még a Jáva bájtkódot is jócskán másodpercenként tízmillió utasítás felett hajtja végre. Ekkora sebességgel száguldó vonatot terelgetni kell, nehogy valami falnak ütközzön. Ezt a feladatot látják el a programvezérlõ utasítások.
A programvezérlõ utasításokkal feltétellel vagy feltétel nélkül a program egy másik pontjára adhatjuk a vezérlést; a feltétel valamilyen kifejezés kiértékelésébõl adódik. Klasszikusan ezt a C vagy a BASIC goto utasításával egyszerû bemutatni, ez az utasítás egy paramétert kap, amely paraméter egy helyet határoz meg a programban (egy sorszámot vagy egy címkét pédául). Amikor a goto végrehajtódik, a program futása a paraméter által meghatározott helyen folytatódik.
Jávában nincsen goto és ez egy vallás elterjedésének köszönhetõ. A strukturált programozás papjai a 70-es évek elején kezdték terjeszteni, hogy nem szép dolog a programot teleszórni goto-val, mert a végrehajtás nagyban követhetetlen lesz. Ez így is van. Az egyedül üdvözítõ megoldás a vallás hívei szerint az egymást teljesen magába foglaló, struktúrált programblokkok rendszere. Egy ilyen programblokk egy vagy több (vagy sok) Jáva utasítást foglal magába és késõbb fogjuk meglátni, mit lehet velük csinálni. Elõzetesként: feltételtõl függõen végre lehet hajtani a blokkot (vagyis a benne levõ utasításokat), ismételni lehet a blokkot, egy feltételtõl függõen újra végre lehet hajtani, stb. Akárhogy is, struktúrált a program attól lesz, hogy a blokkok határai nem keresztezik egymást, a fentebb levõ blokk teljesen magában foglalja a benne levõ blokkot vagy blokkokat. A Jávában a programblokkot egy { jel vezeti be és egy } jel zárja. Megjegyzendõ, hogy egy utasítás önmagában blokkot képez, tehát ha a kapcsos zárójelek elmaradnak, a blokk csupán egy utasításból áll. Természetesen nem hiba egy utasítást kapcsos zárójelek közé fogni, csak nem szükséges. Legegyszerûbb ezt egy példával megvilágítani!
Az if utasításnak van egy paramétere. A paraméter kiértékelésétõl függõen a mögötte levõ blokkot végrehajtja vagy sem. Példa:
if( x < 0 ) {
System.out.println( "x kisebb volt, mint 0!" );
}
Minthogy a blokk csak egy utasításból áll, a kapcsos zárójelek elhagyhatók.
if( x < 0 )
System.out.println( "x kisebb volt, mint 0!" );
A paraméter egy új típusú, logikai kifejezés. Ha összeadunk két egész számot, az eredmény egy újabb egész lesz. Ha összehasonlítasz két számot, az eredmény logikai érték lesz. Ez egy teljesen szokásos adattípus, a neve boolean és összesen két értéke lehet, true és false. Mondhatod tehát ezt:
boolean f;
f = true;
Sõt mondhatod ezt:
int x,y;
boolean f;
x = 0; y = 1;
f = x < y;
A logikai kifejezések és operátorok tehát pont úgy viselkednek mint bármilyen kifejezés és értékül is adhatók logikai típusú változóknak. A következõ logikai mûveletek vannak két egész vagy lebegõpontos kifejezés között.
• x < y : true az eredmény, ha x kisebb, mint y, különben false
• x > y : true az eredmény, ha x nagyobb, mint y, különben false
• x == y : true az eredmény, ha x egyenlõ y-nal
• x != y : true az eredmény, ha x nem egyenlõ y-nal
• x <= y : true ha x kisebb vagy egyenlõ y-nal
• x >= y : true, ha x nagyobb vagy egyenlõ y-nal
Vannak operátorok, amik már eleve logikai értékeken dolgoznak. Ezek:
• x && y : true akkor és csak akkor ha x true és y is true
• x || y : true akkor, ha x vagy y közül valamelyik, esetleg mindkettõ true
• !x : true, ha x false és viszont.
Nézzünk akkor egy jópofa logikai kifejezést feltételezve, hogy van x és y egész típusú változónk:
( ( x > -5 ) && ( x < 5 ) ) || ( ( y > -10 ) && ( y < 10 ) )
Ez a kifejezés akkor ad true értéket, ha x -5 és 5 közé esik vagy y 10 és -10 közé esik, esetleg mindkettõ igaz.
De térjünk vissza oda, ahonnan elindultunk, az if-hez. Az if elsõ paramétere egy logikai kifejezés, második egy programblokk, amit a kifejezés értékétõl függõen végre kell hajtani vagy átugrani. Lássunk egy praktikus példát és írjunk egy programot, ami kiírja, ha a program paramétere negatív. Ilyet még nem láttunk és most nem is fogok belemenni a részletes magyarázatába, legyen elég annyi, hogy át lehet venni a paramétereket, amivel a programot a parancssorban elindították.
public class Negativ {
public void prog( String args[] ) {
int x;
try {
x = Integer.parseInt( args[0] );
} catch( NumberFormatException ex ) {
System.out.println( args[0]+" nem kiertekelheto" );
return;
}
if( x < 0 ) {
System.out.println( x+" negativ" );
}
}
public static void main( String args[] ) {
Negativ e = new Negativ();
e.prog( args );
}
}
Ezt a programot, mint írtam, parancssori paraméterrel kell futtatni, különben hibaüzenetet kapsz (próbáld ki!). Próbáld ki továbbá különbözõ paraméterekkel.
java Negativ 5
java Negativ -10
java Negativ 0
java Negativ -29.34
java Negativ 2.3e12
Az utolsó két paraméter hatására hibaüzenetet kapunk. Ennek oka, hogy a számot egészként értékeltük ki és a két utolsó paraméterrel ezt nem lehet megtenni.
Programunknak van egy barátságtalan vonása: ha pozitív számot vagy 0-t kap, nem ír ki semmit. Szüntessük ezt meg! Megoldhatnánk a feladatot úgy, hogy a két kiírást két if-hez kötnénk pl. így:
if( x < 0 ) {
System.out.println( "Negativ" );
}
if( x >= 0 ) {
System.out.println( "Pozitiv vagy 0" );
}
Nem szép, ugye? Nehéz is karbantartani, ha netán megváltoztatnánk a feltételt, a másikat sem szabad elfelejtenünk megváltoztatni. Sokkal szebben megoldható a feladat az if-hez opcionálisan kapcsolható else-ág segítségével. Ha az if utasításnak van else ága, az else mögött levõ blokk akkor hajtódik végre, ha az if feltétele hamis volt. Lássuk az elõzõ példát!
if( x < 0 ) {
System.out.println( "Negativ" );
} else {
System.out.println( "Pozitiv vagy 0" );
}
Feladat
Írj egy olyan programot, ami egész paraméterérõl kiírja, pozitív, negatív, vagy nulla-e. Itt a megoldás ellenõrzésképpen.
Ha emlékszel a 2. fejezetre, volt ott egy olyan feladatunk, ahol 2 elsõ 8 hatványát írattuk ki. Most mi lenne, ha az elsõ 16 hatványra volnánk kiváncsiak? Abban a példaprogramban egy újabb hatványhoz egyszerûen megismételtük a szükséges sorokat. Ha a számítógépet el tudjuk terelni az egyenes vonalú végrehajtástól, akkor megspórolhatunk jó sok gépelést, ha megismételtetjük vele a programblokkot. Ezt Jávában is meg tudjuk csinálni a while utasítás segítségével. A while paramétere egy logikai kifejezés és ha ez a kifejezés igaz, a while végrehajtja a mögötte levõ programblokkot. Ezek után a gép újra kiértékeli a kifejezést, újra végrehajtja a blokkot és ez így megy addig, amíg a feltétel hamissá nem válik. Ha a feltétel már a legelsõ végrehajtásnál hamis volt, a programblokk egyszer sem hajtódik végre. A feltétel vizsgálata tehát a programblokk - a ciklusmag - elõtt van, ezért ezt a ciklust elöltesztelõnek hívjuk. A következõ kis példaprogram kiírja a számokat 1-tõl 20-ig.
int n;
n = 1;
while( n <= 20 ) {
System.out.println( n );
n = n+ 1;
}
public class pozneg {
public void prog( String args[] ) {
int x;
try {
x = Integer.parseInt( args[0] );
} catch( NumberFormatException ex ) {
System.out.println( args[0]+" nem kiertekelheto" );
return;
}
if( x < 0 )
System.out.println( x+" negativ" );
else
if( x == 0 )
System.out.println( "Nulla" );
else
System.out.println( x+" pozitiv" );
}
public static void main( String args[] ) {
pozneg e = new pozneg();
e.prog( args );
}
}
Feladat
Csinálj a fenti kis programrészletbõl végrehajtható programot, tehát tedd köré mindazt, amitõl Jáva programmá válik. Ellenõrizd a megoldást itt.
public class szamol {
public void prog( String args[] ) {
int n;
n = 1;
while( n <= 20 ) {
System.out.println( n );
n = n + 1;
}
}
public static void main( String args[] ) {
szamol e = new szamol();
e.prog( args );
}
}
Feladat
Most csináld meg azt a programot, ami 2 elsõ 16 hatványát. Használd fel a 2. fejezet programját, ami az elsõ 8 hatványt írta ki a számító rutin ismételgetésével és szervezd ciklusba, amit akkor egyszerû kódismétléssel oldottunk meg. A megoldást itt ellenõrizheted.
public class kettowhatvany {
public void prog( String args[] ) {
int n,x;
n = 0;
x = 1;
while( n <= 16 ) {
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
}
}
public static void main( String args[] ) {
kettowhatvany e = new kettowhatvany();
e.prog( args );
}
}
A ciklusok remek dolgok, egy rendes program tartalmaz belõlük jócskán. A while ciklusokkal minden feladat megoldható, de nem mindig áll kézre. A while-nak mindenekelõtt van egy hátultesztelõ változata, a do-while. Ez ugyanazt tudja, mint a while, azzal a különbséggel, hogy az ellenõrzés a ciklus végén történik, tehát a ciklusmag egyszer mindenképpen végrehajtódik. Példa:
int n;
n = 1;
do {
System.out.println( n );
n = n+1;
} while( n <= 20 );
A másik fontos szerkezet a fentiekben bemutatott szerkezet zanzásított változata, amivel sok írásmunka megspórolható. A for ciklus magába foglalja a ciklusváltozó inicializálását, a ciklusfeltétel ellenõrzését (elõltesztelõ formában) és a ciklusváltozó megváltoztatását. Az általános forma a következõ:
for( ciklusváltozó inicializálás ; feltétel ellenõrzés ; ciklusváltozó módosítás )
ciklusmag
Elõzõ számolós példánkat a következõképpen tudod for ciklusba átírni.
int n;
for( n = 0 ; n <= 20 ; n = n + 1 )
System.out.println( n );
Fontos megjegyezni, hogy a for fejének mindhárom része opcionális, ha elmarad, akkor a ciklusutasítás maga nem végzi el az adott mûveletet. Ez lehetõvé teszi számunkra, hogy pl. a ciklusváltozó növelését a ciklus belsejében hajtsuk végre, ha az olyan bonyolult, hogy egy utasításban nehéz leírni. Ad abszurdum a következõ program értelmes:
for( ; ; )
System.out.println( "Hello!" );
Ez a programrész végtelen ciklusban fogja az üzenetet kiírni. Nincs inicializálás és nincs ciklusváltozó-növelés valamint a ciklusfeltétel elmaradása azt jelenti, hogy a feltételt nem kell ellenõrizni, hanem feltétel nélkül folytatni a ciklust.
Feladat
Elõzõ feladatunkat a 2 elsõ 16 hatványáról írd át for ciklusos változatba! Ha megírtad és mûködik, ellenõrizd a megoldást itt!
public class kettofhatvany {
public void prog( String args[] ) {
int n,x;
x = 1;
for( n = 0; n <= 16 ; n = n + 1 ) {
System.out.println( "2^"+n+"="+x );
x = x * 2;
}
}
public static void main( String args[] ) {
kettofhatvany e = new kettofhatvany();
e.prog( args );
}
}
Feladat
Írj egy programot, ami kinyomtatja a kisegyszeregyet! Használd fel a System.out.println rokonát, a System.out.print-et, ami nem tesz soremelést a kinyomtatott sor végére, így több eredményt nyomtathatsz egy sorba. A szorzótábla kinyomtatásához használj két for hurkot egymás hasában, a belsõ hurok nyomtat egy sort, a külsõ ciklus ismételgeti a sornyomtató ciklust. Küzdj meg a programmal, majd ellenõrizd az eredményt! Figyeld meg, a külsõ ciklus magját kapcsos zárójelek fogják közre, mert az két utasításból áll, a belsõ for-ból és az üres szöveget (plusz egy soremelést) nyomtató System.out.println-bõl. A belsõ ciklus magja csak egy utasítást, a System.out.print-et tartalmaz, így ott a kapcsos zárójelek nem szükségesek (bár ottlétük nem lenne hiba). Figyeld meg a struktúrált programozást: a külsõ ciklus teljesen magába foglalja a belsõt!
public class egyszeregy {
public void prog( String args[] ) {
int i,n;
for( i = 1 ; i <= 10 ; i = i + 1 ) {
for( n = 1 ; n <= 10 ; n = n + 1 )
System.out.print( n*i+" " );
System.out.println( "" );
}
}
public static void main( String args[] ) {
egyszeregy e = new egyszeregy();
e.prog( args );
}
}
Ellenõrzõ kérdések
1. Mit jelent a programblokk?
2. Mit jelent, hogy a programblokkok struktúráltak?
3. Hogyan kell egy programblokkot leírni Jávában?
4. Programblokkot alkothat-e egy Jáva utasítás?
5. Mit jelent a logikai kifejezés?
6. Hogyan kell logikai típusú változókat deklarálni?
7. Mit csinál az != operátor?
8. Mit csinál a || operátor?
9. Mi az if utasítás
10. Mit jelent, ha az if utasításnak else ága van?
11. Mi a while utasítás?
12. Mi a ciklusmag?
13. Mit jelent, hogy a while elöltesztelõ ciklus?
14. Hogyan lehet hátultesztelõ while ciklust írni?
15. Mi a for ciklus?
16. Elöl- vagy hátultesztelõ a for ciklus?
17. Mit jelent az egymásba ágyazott ciklus?
4. fejezet
Megjegyzések. Írni utálunk, ezért törekszünk az újra felhasználható programrészekre. Függvények a matematikában és Jávában. Paraméterek, visszatérési értékek és változó láthatósági szabályok.
Programjaink most már kezdenek kellõen bonyolultak lenni ahhoz, hogy magyarázat nélkül ne értsük õket. Minden programozási nyelv, köztük a Jáva is lehetõséget ad arra, hogy ember számára értelmes magyarázó szövegeket, megjegyzéseket tegyünk a kódba. A megjegyzéseket a számítógép egyszerûen figyelmen kívül hagyja, semmilyen hatása nincsen a program mûködésére. Nem is neki szólnak, hanem a program olvasójának. Jávában kétfajta megjegyzés van. Az egyik /* karakterekkel kezdõdik és a legközelebbi */ karakterekig tart, a programban bárhol állhat, több soros is lehet. Pl.
/* Itt egy megjegyzés */
i = 1; /* A sor végén egy megjegyzés */
/* Hosszú,
több sort
átfogó megjegyzés
*/
i /* a változó, amit használunk */ = /* értékadó utasítás */ 2 /* 2 egy jó érték */;
Az utolsó variációt nem kell követendõ példának tekintened, csak azt akartam vele megmutatni, hogy ez a fajta megjegyzés tényleg mindenhol lehet. A másikfajta megjegyzést // jelek vezetik be és a sor végéig tart. Kevesebbet kell írni vele, rövid megjegyzéseknél ezért szeretik. Példák:
// Egy rövid kis megjegyzés
i = 3; // Végül is igazában 3-at szeretnénk i-be
// Ha hosszú megjegyzést akarunk,
// Minden sor elejére kell a //
// jel.
Feladat
Írjunk egy programot, ami kiírja az y=x2 függvény értékeit x=(-1,1) intervallumban, 0.1-es lépésenként. Megjegyzésekkel tedd világosabbá, mikor mi történik! A feladat nem okozhat nehézséget, azért a biztonság kedvéért ellenõrizd a megoldást!
/* A program kinyomtatja az y=x^2 fuggveny ertekeit a (-1,1) intervallumban */
public class x2ertek_1 {
public void prog( String args[] ) {
// x a fuggveny x argumentuma, y az erteke
double x,y;
// A ciklus felso erteke egy kicsit tobb 1-nel, hogy 1 kornyeket elerje.
// Ez a 0.1-gyel kapcsolatos szamitasi problemak miatt van;
for( x = -1 ; x < 1.01 ; x += 0.1 ) {
y = x*x;
System.out.println( x+"^2="+y );
}
}
public static void main( String args[] ) {
x2ertek_1 e = new x2ertek_1();
e.prog( args );
}
}
A megoldásban felhasználtunk egy újdonságot, a += operátort. Ez az operátor hozzáadja a bal oldalán levõ változóhoz a jobb oldalán levõ értéket, tehát kiveszi a változó értékét, hozzáadja az operátor jobb oldalán levõ kifejezés értékét és visszatárolja a változóba.
x += 0.1;
tehát egyenértékû
x = x + 0.1;
kifejezéssel.
A kiírásban furcsa eredményeket találhattál, pl. íme egy részlet:
...
-0.20000000000000015^2=0.04000000000000006
-0.10000000000000014^2=0.01000000000000003
-1.3877787807814457E-16^2=1.9259299443872359E-32
0.09999999999999987^2=0.009999999999999974
...
Nagyjából stimmel, de mik ezek a furcsa értékek a végén? És pláne, miért nem kapunk 0-t (habár 10-16 elég közel van hozzá ...)? Tudnod kell, hogy a számítógép számára csak az egész, int értékek pontosak. Mivel a gép másként tárolja a számokat, mint az ember, nem biztos, hogy az olyan szép, kerek értékek, mint a 0.1 a gép számára is ugyanolyan szépek. A 0.1 pont egy olyan szám, amit a gép nem tud teljesen pontosan ábrázolni, ezért az összeadások során apró hibák halmozódnak fel. Éppen ez okból a lebegõpontos számokat sose vizsgáljuk egyenlõségre, mert könnyen lehet, hogy az egyenlõség sohase teljesül. Esetünkben például x sohase lesz pontosan 0 vagy 1, ezért a furcsa határértékösszehasonlítás a for ciklusban. Hadd ismételjem meg, hogy ez a probléma csak a lebegõpontos számoknál léphet fel, egészeknél soha, ez egy újabb ok arra, hogy mindenhol egészeket használjunk, ahol csak lehet.
Akárhogy is, nem erre akartam kilyukadni. Maga a kiszámítandó függvény a program közepében van, ha cserélni akarnánk, ki kellene keresnünk a programszövegbõl. Rosszabb, ha a függvény netán bonyolultabb lenne és nem lehetne egy sorban kiszámolni, netán esetleg belsõ változókat használna, a kiszámítandó függvény kódja teljesen összekeveredne a táblázatnyomtató program kódjával. Ezen okból a Jáva rendelkezésünkre bocsátja a saját függvények definiálásának lehetõségét. Igazából már van két saját függvényünk, de azok vadító kék színben tündököltek. Lássuk elõzõ programunk módosítását olymódon, hogy az y = x2 számítását végzõ részt kiemeljük saját függvénybe!
public class x2ertek_2 {
double fuggveny( double xi ) {
double yi;
yi = xi * xi;
return yi;
}
public void prog( String args[] ) {
double x;
for( x = -1 ; x < 1.001 ; x += 0.1 ) {
System.out.println( x+"^2="+fuggveny( x ));
}
}
public static void main( String args[] ) {
x2ertek_2 e = new x2ertek_2();
e.prog( args );
}
}
Ha a programot lefuttatjuk, persze ugyanazt az eredményt kapjuk, mint az elõzõ példában. Mik akkor a változások? Vegyük mindenekelõtt észre a kék sorok drasztikus megfogyatkozását! Vegyük továbbá észre, hogy a táblázatnyomtató ciklusunkban könnyed eleganciával kijelentettük, hogy fuggveny( x ) által visszaadott értéket is ki akarjuk írni. Ez egy függvényhívás.A függvényhívás során a számítógép egy alprogram végrehajtásával folytatja munkáját. Az alprogramot valahol lezárják egy visszatérõ utasítással és megmondják, mi az érték, amit az alprogram visszaad. Ekkor a számítógép elõkeresi, hol hagyta abba a fõprogram végrehajtását és a függvényhívást helyettesíti azzal az értékkel, amit az alprogram visszaadott. A függvény tehát bármilyen kifejezésben állhat, ahol a függvény visszatérési értékének típusa megegyezik az adott kifejezésben várt adattípussal. Például ha van egy függvényünk, ami double-t ad vissza, akkor az felhasználható double számokkal dolgozó kifejezésben.
double fuggv() {
...
}
...
double x;
x = 3.14*fuggv()+2.5;
Maga a függvény deklarációja bárhol lehet a programban, a függvényhívás elõtt és után is. Tilos azonban ugyanazt a függvényt kétszer deklarálni ugyanazzal az aláírással vagyis ugyanazzal a névvel, visszatérési értékkel és paraméterlistával. Azonos nevû, de különbözõ aláírású függvények (tehát például ahol az egyiknek nincs paramétere, a másiknak pedig van) különbözõnek számítanak, mert a Jáva a függvényhívás alapján rájön, hogy melyik függvényt kell meghívnia. Példa:
// Elsõ függvény
int fuggv() {
...
}
// Második függvény
int fuggv( int i ) {
...
}
int i = fuggv(); // Ez az elsõ függvényt hívja meg
int k = fuggv( 2 ); // Ez a második függvényt hívja meg
A függvénynek lehet egy speciális visszatérési értéke, a void. Ez azt jelenti, hogy a függvény nem ad vissza értéket, az ilyen függvényt nem lehet kifejezésben felhasználni, de kifejezésen kívül meg lehet hívni. Pont ezen a módon kell alprogramokat, procedurákat definiálni.
void fuggv2() {
...
}
...
fuggv2();
A függvényeknek lehetnek bemeneti paramétereik is. Ezek azok az értékek, amelyeket átadunk az alprogramnak, hogy számoljon belõle kimeneti értéket. A bemeneti paramétereket a függvényfejben soroljuk fel.
double fuggv3( double elso_param, int masodik_param ) {
double eredm;
...
return eredm;
}
...
double eredm;
eredm = fuggv3( 3.14, 4 );
Amikor a függvényt meghívod, bonyolult dolgok játszódnak le.
• Kiértékelõdnek a függvény meghívásakor a függvénynek átadott kifejezések
• Új kontextus jön létre a változóknak, az eddigi használt változókat nem lehet többet elérni. Ezt késõbb majd pontosítjuk (vannak olyan változók, amelyek túlélik a függvényhívást), de most jegyezzük meg inkább úgy, hogy az eddig deklarált változók a függvény futása alatt nem látszanak. Megmaradnak, nem vesznek el, de a függvény nem látja õket, mert õket egy másik kontextusban deklarálták.
• A függvénynek átadott paraméterek megfelelõ sorrendben hozzárendelõdnek a bemeneti paraméterekhez. Valójában az történik, hogy a bemeneti paraméterek mint változók létrejönnek a függvény saját kontextusában és kezdõértékül megkapják a hívási értékeket. A példánkban tehát az elso_param nevû változó értéke 3.14 lesz, a masodik_param-é pedig 4.
• A függvény futni kezd és úgy viselkedik, mint bármely Jáva program - teheti, mert valójában õ is teljes jogú program, aminek saját környezete van. Saját változókat deklarálhat, más függvényeket és procedúrákat hívhat meg. Ámde eljön egyszer az igazság pillanata és befejezi a futását. Ez kétféleképpen történhet. Elõször is elérhet egy return utasítást. A return mögött a függvény visszatérési értékének megfelelõ típusú kifejezés vagy - void visszatérési érték esetén - semmi sem található. A return hatására a függvény befejezi a futását és visszatérünk a fõprogramba. Második lehetõség csak és kizárólag void visszatérési értékû függvényeknél fordulhat elõ: ilyenkor egyszerûen elérhetjük a függvényt lezáró kapcsos zárójelt és ezt a gép magától, mint egy üres return; utasítást értelmezi.
• Ha a függvény futása befejezõdik, a visszatérési értéket a gép biztos helyre menti, majd egész egyszerûen felszámolja az összes változót, amit a függvény kontextusában deklaráltak, ezek törlõdnek és örökre elvesznek.
• A függvényt hívó program kontextusa visszaállítódik, változói láthatóvá válnak.
• A gép most elõveszi a biztos helyrõl a függvény visszatérési értékét és mintha mi sem történt volna, folytatja a kifejezés kiértékelését, amiben a függvényhívás volt. Az egész függvényhívás egy darab számmá, a függvény visszatérési értékévé zsugorodott össze és ezzel a számmal helyettesítõdik.
Hasonlatos ez ahhoz, mint amikor a cikkedhez ki akarsz számolni egy értéket, de csak a szám érdekel. Néhány sajtpapíron elkészíted a számításokat, a számot beírod a cikkbe, majd a sajtpaírokat eldobod. Így mûködik a függvény is, a részeredmények érdektelenek a függvény lefutása után és ami még fontosabb, a függvény belsõ ügyei nem zavarják a program futását.
A függvénybõl természetesen újabb függvényt is meghívhatsz és a hívásra ugyanazok a szabályok vonatkoznak. Ezt nem csinálhatod a végtelenségig, valamekkora korláta van annak, milyen mélységben hivogathatják a függvények egymást, de ez sok ezres mélység.
Lássuk, megértetted-e!
Feladat
Olvasd el az itt látható programot és mondd meg nekem, mi a nyomtatási kép! Utána próbáld is ki és találd ki az okát, ha netán tévedtél volna. Segítség: nézd meg, melyik függvényhívás melyik fuggv függvényt hívja meg. Ahol a függvényhívásban paramétert adunk át, ott a paraméterrel rendelkezõ fuggv fut, ahol nem, ott a paraméter nélküli.
A megjegyzések használata persze magánügy, de talán a fenti példa is demonstrálja, hogy legalább a függvények fejét (tehát a deklarációjának az elejét, a visszatérési értékének, nevének és paramétereinek megadását) mindenképpen ajánlott megjegyzéssel ellátni, milyen paramétert vár a függvény, azokkal mit csinál és mit ad vissza.
public class fuggvteszt {
int fuggv() {
int i,j,k;
i = 10;
j = 20;
k = 30;
return j;
}
int fuggv( int i ) {
int j,k;
j = 20;
k = 30;
return i+10;
}
public void prog( String args[] ) {
int i,j,k;
i = 1;
j = fuggv();
k = fuggv( 100 );
System.out.println( "i: "+i );
System.out.println( "j: "+j );
System.out.println( "k: "+k );
}
public static void main( String args[] ) {
fuggvteszt e = new fuggvteszt();
e.prog( args );
}
}
Feladat
Bergengóciában az adórendszert lényegesen leegyszerûsítették és a Totális Adótábla XXVIII. lapja a következõképpen néz ki:
-100 fbtk* a jövedelem 9%-a
100-250 9+a 100 fbtk fölötti rész 13%-a
250-750 30+a 250 fbtk fölötti rész 25%-a
750- 180+a 750 fbtk fölötti rész 50%-a
*- fbtk: konvertibilis fabatka, éves szinten
Az utolsó pillanatban a Tüntetõ Bergengóc Anyák Antidemokratikus Szövetségének sikerült keresztülvinnie követeléseit és a családosokoknak kedvezõ intézkedésekkel egyszerûsítették a táblát. Ezek szerint a 1 gyerek nevelése esetén az adó 2%-át, kettõ esetén 8%-át, 3 és a felett gyerekenként 4%-át, de legfeljebb 25%-ot engednek el. Írjunk most egy programot, amit meghívhatunk az éves jövedelemmel és a gyerekek számával és az megmondja az adónkat! Emlékezzünk a 3. lecke Negativ programjára, hogyan értékeltük ki ott a paramétereket! Lebegõpontos számot még nem értékeltünk ki, ezért leshetsz egy kicsit a megoldásban. A programunkban igyekezzünk olyan függvényt írni, ami a leginkább lerövidíti a programot! Íme a megoldás, de csak a paraméterek kiértékelését lesheted ki belõle, a többit neked kell megoldanod!
public class ado {
/* Ez a fuggveny szamitja az adot. Bemeneti parameterei: jovedelem, a jovedelemsav
also hatara, a jovedelemsav also hatarahoz tartozo ado, az ado merteke a
jovedelemsavban (tizedestort, nem %!) es a gyerekek szama */
double adoszamitas( double jov, double alap, double alapado, double ado, int gyerekszam ) {
double adoertek,kedvezmeny;
adoertek = ( ( jov - alap ) * ado ) + alapado;
if( gyerekszam == 1 )
adoertek = adoertek*0.98;
else
if( gyerekszam == 2 )
adoertek = adoertek*0.92;
else
if( gyerekszam >= 3 ) {
kedvezmeny = gyerekszam*0.04;
if( kedvezmeny > 0.25 )
kedvezmeny = 0.25;
adoertek = adoertek*( 1.0-kedvezmeny );
}
return adoertek;
}
public void prog( String args[] ) {
double jovedelem,ado;
int gyerekszam;
try {
jovedelem = Double.parseDouble( args[0] );
gyerekszam = Integer.parseInt( args[1] );
} catch( NumberFormatException ex ) { return; }
if( jovedelem < 100 )
ado = adoszamitas( jovedelem,0,0,0.09,gyerekszam );
else
if( jovedelem >= 100 && jovedelem < 250 )
ado = adoszamitas( jovedelem,100,9,0.13,gyerekszam );
else
if( jovedelem >= 250 && jovedelem < 750 )
ado = adoszamitas( jovedelem,250,30,0.25,gyerekszam );
else
/* Ide csak akkor juthatunk, ha a jovedeleme nagyobb 750-nel */
ado = adoszamitas( jovedelem,750,180,0.5,gyerekszam );
System.out.println( "Jovedelem: "+jovedelem+" gyerekszam: "+gyerekszam+
" ado: "+ado );
}
public static void main( String args[] ) {
ado e = new ado();
e.prog( args );
}
}
Ellenõrzõ kérdések
1. Mire szolgálnak a megjegyzések a programban?
2. Mi a különbség a /* */ és a // megjegyzésszintaktika között?
3. Miért nem vizsgáljuk a lebegõpontos számokat egyenlõségre?
4. Mi a függvény aláírása?
5. Mi a void típus?
6. Mik játszódnak le egy függvényhíváskor?
7. Hogyan adja vissza a függvény a visszatérési értékét?
8. Mi történik a függvény által deklarált változókkal?
9. Mi történik, ha a hívó függvény és a hívott függvény megegyezõ nevû változókat használ?
10. Milyen mélységben hívhatják függvények egymást?
5. fejezet
Matematikai függvények, a Jáva matematikai függvényei és azok használata.Egyszerû matematikai problémák programnyelvi megoldásai. Feladatok a függvények használatára.
Mint már említettük, a számítógép másodpercenként több milliárd gépi szintû mûvelet elvégzésére képes, míg függvényhívásokból is képes több tízezer elvégzésére. Megfogalmazódik bennünk a kérdés, hogy miként is lehetne ezt ésszerûen használni olyan problémák megoldására, amelyek a mindennapi életben is elôfordulhatnak. A számítógép igazi ereje akkor mutatkozik meg, ha olyan algoritmus (elemi lépések véges számú, jól meghatározott sorozata) végrehajtására utasítjuk, mely nekünk hosszas papíron történô számolást igényelne.
A négy alapmûveletet kiterjesztendô, ismerkedjünk meg a hatványozás, gyökvonás, az exponenciális és trigonometrikusfüggvények Java nyelvi megfelelôivel.
A következô program segítségével hatványozást és gyökvonást végezhetünk, melyek a java.lang csomag Math osztályában találhatók.
A hatványozás függvénye: double pow(double alap, double kitevo)
A négyzetgyökvonás: double sqrt(double alap)
Nézzük a programot! Math1
public class math1 {
public double Hatvany(double x, double n){
return Math.pow(x,n); //A beépített hatványfv. meghívása
}
public double Negyzetgyok(double y){
return Math.sqrt(y); //A beépített gyökfv. meghívása
}
public void prog( String args[] ) {
System.out.println("Példa a hatványozás és gyökvonás használatára:");
double alap, kitevo; //Változót deklarálhatunk szinte bárhol
/*
Az áltlánosítás kedvéért az args[] segítségével töltjük fel függvényeinket paraméterekkel.
A főprogram első két paramétere lesz a hatványfüggvényben az alap és a kitevő értéke.
A 3.-nak megadott paramétert pedig a gyokfüggvény kapja értékül.
*/
alap=Double.parseDouble(args[0]);
kitevo=Double.parseDouble(args[1]);
/*
A hatványfv double értékeket vár, amit az értékadásnál így közlünk.
*/
System.out.println(args[0]+"^"+args[1]+"="+Math.round(Hatvany(alap, kitevo)));
//Math.round: a double értéket így egész számokként iratjuk ki.
int gyokalap;
gyokalap=Integer.parseInt(args[2]);
System.out.println(args[2]+" Négyzetgyöke="+Negyzetgyok( gyokalap ) );
System.out.println("Pi értéke: "+Math.PI);
System.out.println("A természetes alapú logaritmus alapszáma: "+Math.E);
}
public static void main( String args[] ) {
math1 e = new math1();
e.prog( args );
}
}
public class math1 {
public double Hatvany(double x, double n){
return Math.pow(x,n);
//A beépített hatványfv. Meghívása
}
public double Negyzetgyok(double y){
return Math.sqrt(y);
//A beépített gyökfv. meghívása
}
public void prog( String args[] ) {
System.out.println("Példa a hatványozás és gyökvonáshasználatára:");
double alap, kitevo;
//Változót deklarálhatunk itt is
/* A fôprogram elsô két paramétere lesz a hatványfüggvényben
az alap és a kitevôértéke. A 3.-nak megadott paramétert pedig
a gyökfüggvény kapja értékül.*/
alap=Double.parseDouble(args[0]);
kitevo=Double.parseDouble(args[1]);
/* A hatványfv double értékeket vár, amit az értékadásnál
így közlünk. */
System.out.println(args[0]+"^"+args[1]+"="+Math.round(Hatvany(alap, kitevo)));
//Math.round: a double értéket egész számként íratjuk ki.
int gyokalap;
gyokalap=Integer.parseInt(args[2]);
System.out.println(args[2]+"Negyzetgyoke="+Negyzetgyok(gyokalap ) );
}
public static void main( String args[] ) {
math1 e = new math1();
e.prog( args );
}
}
Megjegyzések:
1. A Hatvany és a Negyzetgyok függvények double paramétereket fogadnakés adnak is vissza, a függvénytörzsben meghívják a Math osztály megfelelô (pow és sqrt) függvényeit.
2. Az általánosítás kedvéért az args[] segítségével töltjük fel függvényeinket paraméterekkel. Így minden futtatáskor változtathatjuk a bemeneti paraméterek értékeit.Az args[] használatát szintén egy késôbbi fejezetben magyarázzuk részletesen, legyen elég annyi, hogy a programnak a parancssorban megadott 1.,2.,3. stb. paraméter rendre az args[0],args[1], args[2] változók tárolják.
3. Az alap éskitevo double típusú változókrendre a fôprogram elsô két paraméterét kapják.Ahhoz, hogy a program az általunk megadott értékeket double-ként kezelje, Double.parseDouble() függvényt használjuk. Hasonlóképpen, a gyokalap változó a harmadik fôprogram paraméter lesz, integer-ként értelmezve.
4. A hatványfüggvény double értéketad vissza, amit a Math.round() függvénnyel alsó egészértéket kapunk vissza.
Fordítás után a futtatáshoz adjuk ki az alábbi parancsot:
java math1 1.5 2 8
A futás eredménye:
Példa a hatványozás és gyökvonás használatára:
1.5^2=2
8 Négyzetgyöke=2.8284271247461903
A hatványozásnál láthatjuk, hogy a 2.25 helyett a kerekített értéket kapjuk, míg a 8 négyzetgyökét double alakban, 16 tizedesjegy pontossággal.
Az alábbitáblázatban foglaljuk össze a Math osztály legfontosabb függvényeit:
Visszatérési érték típusa Függvényfej Funkció
Double abs(double a) Abszolút érték fv *
Double cos/sin/tan/asin/acos/atan(double) Trigonometrikus fvek és inverzeik
Double Exp/log(double) és ln(x)
Double toDegrees/toRadians(double) Radián érték fok-ba váltása és fordítva
A trigonometrikus függvények használatához szükségünk lehet a matematika középiskolából jól ismert két nevezetes konstansára: e-re és pi-re. Ezeket a Math osztály speciális változóiként kell kezelnünk, matematikai szempontból az y=e és y=pi konstansfüggvények felelnek meg:
public static final double PI
public static final double E
Arra, hogy ezek konstans értékû változók, arra a final jelzô utal (értékük nem változik). Használatuk a program szövegében: Math.PI illetve Math.E hivatkozással történik, vagyis meg kellmondanunk expliciten, hogy a Math osztály konstans értékûváltozóiról van szó.
1. Feladat
Egyszerû gyakorlatként írjuk át a kör kerületének és területének kiszámítását elvégzô programunkat a Math.PI használatával! Az egyszerûsített megoldást itt ellenôrizheted.
public class Circle {
public double kerulet(double r) {return 2*r*Math.PI;}
public double terulet(double r) {return r*r*Math.PI;}
public void prog(String[] args) {
System.out.println("Az "+args[0]+" sugarú kör kerülete: K="+kerulet(Double.parseDouble(args[0])));
System.out.println(" és területe: T="+terulet(Double.parseDouble(args[0])));
}
public static void main(String[] args) {
Circle e = new Circle();
e.prog(args);
}
}
2. Feladat
Készítsünk függvénytáblázatot, mely kiírja a nevezetesebb hegyesszögek (0, 30, 45, 60, 90) sin, cos,tan, ctg értékeit! A megoldást itt ellenôrizd.
public class trigofunc {
public void kiir(double fok){
double fi=Math.toRadians(fok);
System.out.println("------------------------------------------------------------------------------");
System.out.println(" | "+fok+" | "+Math.sin(fi)+" | "+Math.cos(fi)+" | "+Math.tan(fi)+" | "+(1/Math.tan(fi))+" | ");
}
public void prog(String[] args) {
System.out.println("| Szög | Sin | Cos | Tan | Ctg");
kiir(0);
kiir(15);
kiir(30);
kiir(45);
kiir(60);
kiir(90);
System.out.println("----------------------------------------------------------------------");
}
public static void main(String[] args) {
trigofunc e=new trigofunc();
e.prog(args);
}
}
3. Feladat
Készítsünk programot, mely megoldja a másodfokú egyenletet, a megoldhatóság teljes vizsgálatával. A fôprogram 3 bemeneti paramétere az másodfokú egyenlet a, b , c paramétere. A megoldást itt találhatod.
public class Masodfoku {
public double diszkriminans(double a, double b, double c){
return Math.sqrt(b*b-4*a*c);
}
public void prog(String[] args){
double a=Double.parseDouble(args[0]);
double b=Double.parseDouble(args[1]);
double c=Double.parseDouble(args[2]);
if (a==0) {
if (b==0){
if (c==0) { System.out.println("Azonosság, x tetszőleges valós szám.");}
else {System.out.println("Ellentmondásos, nincs megoldás");}
}//b==0
else //b!=0
{if (c==0) { System.out.println("Els\u0151fokú, 1 megoldás: x=0") ;}
else {System.out.println("Elsőfokú, x="+(-1*c/b)) ;}
}
}
else {//a!=0
if ((b*b-4*a*c)>0) {//D>0
double x1=(-b+diszkriminans(a,b,c))/(2*a);
double x2=(-b-diszkriminans(a,b,c))/(2*a);
System.out.println("2 különböz\u0151 valós gyök: x1="+x1+" x2="+x2);
}
else if ((b*b-4*a*c)==0) {
System.out.println("2 megegyez\u0151 valós gyök: x1=x2="+(-b/a));
}
else {System.out.println("Nincs valós megoldás.");}
}
}
public static void main(String[] args) {
Masodfoku e=new Masodfoku();
e.prog(args);
}
}
A Math függvények közt találhatunk egy véletlen számokat generáló függvényt is:
double random ()
A függvény aláírásából láthatjuk.hogy paraméter nélküli és double értéket advissza, mégpedig egy 0<=x<1 pszeudo-véletlenszámot generál. (A pszeudo jelzés arra utal, hogy nem kaphatunk tökéletes véletlenszerûséget,hiszen a generált szám valamilyen módon függ a pocesszorórajelétôl.)
A függvény használata elég egyszerû: ha az [a;b] intervallumba esô véletlen számokat szeretnénk generálni, akkor nem kell mást tennünk, mint venni az
a+(b-a+1)*Math.random() kifejezést.
4. Feladat
Készítsünk 5-ös és 6-os lottó programot a Math osztály véletlenszám generátorának segítségével! Elôször írjuk meg úgy, hogy ne legyen benne ellenôrzés, nem fordul-e elô kétszer ugyanaz a lottószám. A megoldást itt ellenôrizheted! Ezek után oldjuk meg, hogy a számok garantáltan különbözôek legyenek. Itt a megoldás ellenôrzésképpen.
public class lotto {
public void lottogenerator(int n) {
if (n==5){
System.out.println("Az "+n+"-öslotto e heti nyer\u0151számai: ");
for (int i=1;i<=5;i++){
System.out.print((Math.round(89*Math.random())+1)+", ");
}
}
else if (n==6){
System.out.println("A "+n+"-oslottó e heti nyer\u0151számai: ");
for (int i=1;i<=6;i++){
System.out.print((Math.round(44*Math.random())+1)+", ");
}
}
else {System.out.println("Jelenleg csak 5-ös és 6-ös lottót ismerek...");}
}
public void prog(String[] args) {
int tipus=Integer.parseInt(args[0]);
lottogenerator(tipus);
}
public static void main(String[] args) {
lotto e = new lotto();
e.prog(args);
}
}
public class lotto1 {
public void prog( String args[] ) {
int i,j;
double y1,y2,y3,y4,y5,y6,y;
y1=0;
y2=0;
y3=0;
y4=0;
y5=0;
y6=0;
i=Integer.parseInt ( args[0]);
if (i==5) {
System.out.print("Az e heti nyeroszamok: ");
for (j=1;j<6;j++){
do
y=Math.round(1+89*Math.random());
while (y==y1 || y==y2 || y==y3 || y==y4);
if (j==1) y1=y;
if (j==2) y2=y;
if (j==3) y3=y;
if (j==4) y4=y;
if (j==5) y5=y;
}
System.out.println(y1+" "+y2+" "+y3+" "+y4+" "+y5);
}
else if (i==6){
System.out.print("Az e heti nyeroszamok: ");
for (j=1;j<7;j++){
do
y=Math.round(1+44*Math.random());
while (y==y1 || y==y2 || y==y3 || y==y4 || y==y5);
if (j==1) y1=y;
if (j==2) y2=y;
if (j==3) y3=y;
if (j==4) y4=y;
if (j==5) y5=y;
if (j==6) y6=y;
}
System.out.print(y1+" "+y2+" "+y3+" "+y4+" "+y5+" "+y6);
}
else {System.out.println ("Ilyen lotto nincs is!");}
}
public static void main( String args[] ) {
lotto1 e = new lotto1();
e.prog( args );
}
}
5. Feladat*
Készítsünk totó programot a Math osztály véletlenszám generátorának segítségével! A program elkészítéséhez ismerjük meg és használjuk a % operátort! A % ugyanúgy mûködik, mint az osztás (/), de a hányados helyett a maradékot eredm´nyezi. Egészre és lebegôpontos számra is használható. A megoldást itt ellenôrizheted!
public class Toto {
public void prog() {
System.out.println("A toto e heti nyeroszamai:");
for (int i=1;i<=14;i++){
double j=(Math.round(Math.random()*100000))%3;
if (i<10) {System.out.print(" ");} // Ez a sor a kiiras rendezettsegen javit.
if (j==0) { System.out.println(i+". x");}
if (j==1) { System.out.println(i+". 1");}
if (j==2) { System.out.println(i+". 2");}
}
}
public static void main(String[] args) {
Toto e = new Toto();
e.prog();
}
}
6. Feladat*
Írjunk programot, mely az Euklidészi algoritmus segítségével keresi meg meg két egész szám legnagyobb közös osztóját! Íme egy példa, hogyan mûködik az algoritmus. Keressük 60 és 22 legnagyobb közös osztóját.
60 = 2*22 + 16
22 = 1*16 + 6
16 = 2*6 + 4
6 = 1*4 + 2
4 = 2*2 + 0
2 a legnagyobb közös osztó, mivel ez volt az utolsó nem 0 maradék. A megoldást itt ellenôrizheted!
public class LNKO {
public void prog(String[] args){
long x1=Long.parseLong(args[0]);
long x2=Long.parseLong(args[1]);
/*
Ha a felso ket ertekadast toroljuk es helyettesitjuk az alabbi random ertekadassal, akkor
programunkat 1-1000 kozotti veletlen osztoparokkal tesztekhetjuk.
*/
//long x1=Math.round(Math.random()*999+1);
//long x2=Math.round(Math.random()*999+1);
if (x2>=x1) {long e=x2; x2=x1; x1=e;} //az egyszerubb kovethetoseg erdekeben a 2 valtozo erteket elcsereljuk
System.out.print("LNKO("+x1+","+x2+")=");
long maradek=x1%x2;
while (maradek!=0)
{
x1=x2;
x2=maradek;
maradek=(x1%x2);
}
System.out.print(x2);
}
public static void main(String[] args) {
LNKO e=new LNKO();
e.prog(args);
}
}
Ellenõrzõ kérdések
1. Mit nevezünk "beépített" függvényknek?
2. Hogyan tudunk a beépített függvényekre hivatkozni?
3. Hogyan tudunk trigonometriai feladatokat Java program segítségével megoldani?
4. Mit nevezünk konstans értékû változónak? Mondj példát konstans értékû Java változóra!
5. Mire kell vigyáznunk a trigonometrikus Java függvények bemeneti paramétereinek megadásánál?
6. Hogyan tudunk java programjainkban kerekített értékeket használni?
7. Milyen lehetôségeink vannak a Java-ban véletlen számok generálására?
6. fejezet
További gyakorló feladatok matematikai problémák megoldására. Cimke. Többszörös elágazás. Kilépés programblokkból: break. Kilépés ciklusból: continue. 2 ismeretlenes lineáris egyenletrendszerek megoldási módszerei és azok JAVA nyelvû megvalósítása. A mátrixelmélet elemei: mátrix, determináns. A 3- és többismeretlenes egyenletrendszerek megoldási lehetõségei és JAVA nyelvû megvalósítása.
Bármely java utasítást azonosíthatunk úgynevezett címkékkel.
Szerkezete:
cimke: utasítás
Egyszerû példa egy ciklusból való kilépés való kilépés:
kulso: for (int j=100; j>=10;j--){
for (int i=0; i<10;i++){
Math.pow(2,i)*j;
if (i>5) break kulso;
}
}
A fenti ciklus futása megszakad abban az esetben, ha (i>5).Nézzük mi is történik! Elôször is elhelyeztünk egy cimkét a küsõ ciklusfej elé, mely azonosítja a ciklust.Ezután a küsõ ciklust bizonyos feltétel fennállása esetén megszakítjuk, vagyis kilépünk belôle. Ez azt jelenti, hogy a vezérlés (végrehajtás) a külsõ ciklusmag utáni, tehát a ciklust követô utasításra kerül. A break utasítás, tehát arra szolgál, hogy egy ciklust, illetve egy programblokkot elhagyhassunk vele.
2 féle használata létezik:
break; - ekkor a vezérlés a break-et tartalmazó utasításblokkból kilép.
break cimke; - ekkor pedig a cimkével megjelölt blokkot hagyjuk el.
Ha a fenti példában a break cimke nélkül állna, akkor csak a belsõ ciklusbõl lépnénk ki.
Jól jegyezzük meg, hogy a break utasítás nem alkalmas függvénybôl (metódusból - lásd késôbb) vagy inicializáló blokkból való kilépésre.
A másik lehetôségünk egy ciklus normál menetének megváltoztatására a continue utasítás. Amennyiben continue szerepel a ciklusmagban egy feltétel után, akkor a feltétel teljesülése esetén a ciklusmagban lévô további utasítások nem kerülnek végrehajtásra, a vezérlésa ciklusfejre kerül. Épp úgy, mint a break esetében,a continue-val sem lehet függvénybôl vagy inicializáló programblokkból kilépni.
Mit ír ki a képernyõre az alábbi programrészlet utolsó utasítása?
int s=0;
for (int i=0;i<=20;i++) {
if (i>=10)&&(i<=14))
continue;
s=s+i;
}
System.out.println(s);
A continue hasznos lehet, ha meg szeretnénk kímélni magunkat attól, hogy bonyolult feltételeket írjunk a ciklusmagba.
Feladat
1. Készítsünk programot, mely 1-100-ig kiírja a 3-al, 5-el és 7-el nem osztható számokat. A ciklusmagban használjuk a continue utasítást! Megoldás itt.
public class Osztas {
public void prog(){
System.out.println("A 3-al, 5-el, 7-el nem oszthato szamok 1-100-ig:");
for (int i=0;i<=100;i++){
if ( i % 3 ==0 ||
i % 5 ==0 ||
i % 7 ==0 )
continue;
System.out.print(i+" ");
}
}
public static void main(String[] args) {
Osztas e=new Osztas();
e.prog();
}
}
Aki megnézte a másodfokú egyenlet megoldó programjának megoldását, az valószínûleg bonyolultnak találta a sok 'if'-es szerkezetet. Ennek orvoslására a JAVA felkínálja a switch-case szerkezetet. Ezzel az utasítással töbszörös elágazást valósíthatunk meg egy egész értékû kifejezés értékei szerint.
Példaként tekintsük az alábbi programrészletet:
void osztalyzat(int n){
switch (n){
case 1: System.out.println("Elegtelen");
break;
case 2: System.out.println("Elegseges");
break;
case 3: System.out.println("Kozepes");
break;
case 4: System.out.println("Jo");
break;
case 5: System.out.println("Jeles");
default: System.out.println("Gratulálok!");
}
}
Mi történik abban az esetben, ha n=5 és mi, ha n!=5? Amikor a case ág végén break szerepel, akkor a vezérlés kilép a switch utasításból; amennyiben nincs break, a végrehajtás a következô cimkén folytatódik. A default (alapértelmezett) ág használata opcionális.
Ily módon, ha n=1, akkor a break miatt kilépünk a swith utasításból, míg, n=5 esetén break hiányában a deafult ág is végrehajtódik, vagyis gratulálunk a jeles osztályzathoz.
A teljes programot itt találod.
public class Jegyek {
public void prog(String[] args){
int i=Integer.parseInt(args[0]);
switch (i){
case 1: System.out.println("Eletgtelen"); break;
case 2: System.out.println("Elegseges"); break;
case 3: System.out.println("Kozepes"); break;
case 4: System.out.println("Jo"); break;
case 5: System.out.print("Jeles");
default: System.out.println("! Gratulálok!");
}
}
public static void main(String[] args) {
Jegyek e=new Jegyek();
e.prog(args);
}
}
Feladat
2. a) Készítsünk programot, mely a fôprogram paraméterként kapott 1..7 közötti egész szám, mint sorszám függvényében kiírja szövegesen a hét megfelelô napját! Megoldás itt.
public class Napok {
public void prog(String[] args){
int i=Integer.parseInt(args[0]);
switch (i) {
case 1: System.out.println("Hetfo"); break;
case 2: System.out.println("Kedd"); break;
case 3: System.out.println("Szerda"); break;
case 4: System.out.println("Csutortok"); break;
case 5: System.out.println("Pentek"); break;
case 6: System.out.println("Szombat"); break;
default: System.out.println("Vasarnap");
}
}//end of prog
public static void main(String[] args) {
Napok e=new Napok();
e.prog(args);
}
}
b) Vegyük alapul a 2001. évet. Fejlesszük tovább az elõzõ programot úgy, hogy a fõprogram két bemenõ paraméterét, rendre a napot és hónapot, értékelje ki és modja meg, hogy az így megadott dátum a hét melyik napja. A megoldás itt található.
public class Naptar {
int n=-1;
int h=-1;
int s=0;
public int kiertekel(int honap){
//Megmondja, hogy az adott sorszamu honap hany napbol all
if ((honap<1)||(honap>12)) {return 0;}
else if ((honap==1)||(honap==3)||(honap==5)||
(honap==7)||(honap==8)||(honap==10)||
(honap==12)) {return 31;}
else if (honap==2) {return 28;}
else {return 30;}
}
public void melyik_nap(int nap){
//Megmondja, hogy az ev x. napja, milyen nap
System.out.print("2001. "+h+". "+n+". ");
switch (nap) {
case 0: {System.out.print(" Vasarnap"); break;}
case 1: {System.out.print(" Hetfo"); break;}
case 2: {System.out.print(" Kedd"); break;}
case 3: {System.out.print(" Szerda"); break;}
case 4: {System.out.print(" Csutortok"); break;}
case 5: {System.out.print(" Pentek"); break;}
default : {System.out.print(" Szombat");}
}
}
public void prog(String[] args){
if (args.length!=2) { System.out.println("Megfelelo parameterezes: nap honap");}
else { n=Integer.parseInt(args[0]);
h=Integer.parseInt(args[1]);
for (int i = 0; i < h; i++) {
s+=kiertekel(i);
}
s+=n;
//Ekkor s erteke megmondja, hogy a megadott nap hanyadik napja az evnek
s=s%7;
melyik_nap(s);
}
}
public static void main(String[] args) {
Naptar e=new Naptar();
e.prog(args);
}
}
Most készítsünk egy új változatot, ami ugyancsak a 2001 évre érvényes, egy hónapszámot vár paraméterül és kinyomtatja az adott hónap naptárát csinos formában, ahogy a zsebnaptárokon megszokott. Ellenôrizd a megoldást itt!
public class Naptar2 {
// Kiszamolja, 2001-ben az adott honap hany napbol all
int honapok (int m){
if (m==2)
return 28;
else if ((m==4)||(m==6)||(m==9)||(m==11))
return 30;
else return 31;
}
public void prog( String args[] ) {
int m = Integer.parseInt (args[0]);
if ((m<1) || (m>12))
System.out.println ("Ilyen honap nincs is!");
else {
System.out.println("H K Sz Cs P Sz V");
int mo = 0;
for (int i=1; i < m ; i++) // osszes napok szama (mo) elseje elott
mo = mo + honapok( i );
int n = ( mo+1 ) % 7; // milyen napra esik elseje
if( n == 0 ) n = 7;
for( int i = 1; i < n ; i++)
System.out.print (" "); // szokozok nyomtatasa ures napokra
int napok_szama = honapok( m ); // hogy ne ertekeljuk ki a honapok( m ) fuggvenyt
// a ciklus minden lefutasakor
for ( int i = 1 ; i <= napok_szama ; i++) { // napok nyomtatasa
System.out.print (i+" ");
if (i < 10) // nyomtass egy plusz szokozt, ha egykarakteres a szam
System.out.print(" ");
// Ha vasarnapot nyomtattunk, soremeles. Minthogy i 1-tol halad es
// n = 7 vasarnap eseten, i-bol le kell vonnunk egyet, hogy az eredmeny helyes legyen.
if ( ( ( i - 1 + n ) % 7 )==0)
System.out.println();
}
}
}
public static void main( String args[] ) {
Naptar2 e = new Naptar2();
e.prog( args );
}
}
Egyenletrendszerek megoldása
A lineáris egyenletrendszerek elég fontos szerepet játszanak a természettudományokban, különösen a matematika néhány területén. Nem árt megismernünk néhány megoldási módszert, s persze ennek JAVA megvalósítását is.
Középiskolában találkoztunk a 2 ismeretlenes lineáris egyenletrendszerrel.
I. a*x+b*y=p
II. c*x+d*y=q ahol a,b,c,d, p, q az egyenlet (például valós) konstansai, míg x1, x2 valós változók, az egyenletrendszer ismeretlenei.
Ennek megoldása nem jelenthet túl nagy gondot, hiszen a változó helyettesítés módszerével könnyen célt érünk. Kiválasztjuk az egyik egyenletet, az egyik ismeretlent kifejezzük, majd az így kifejezett ismeretlent behelyettesítjük a másik egyenletbe, mely így márcsak egy ismeretlent tartalmaz. Kiszámítva az ismeretlen konkrét értékét, azt visszahelyettesítve a másik, már kifejezett ismeretlent tartalmazó egyenletbe, kész vagyunk.
Példa:
Oldjuk meg a valós számok halmazán az alábbi egyenletrendszert!
I. 4x-5y=22
II. 7x+2y=17
---------------
II. y=(17-7x)/2
y-t I.-be: 4x-5*[(17-7x)/2]=22
I.. 4x-(85-35x)/2=22
I. 8x-85+35x=44
I. 43x=129 azaz x=3
II. y=(17-21)/2= -2
A másik, az egyenlô együtthatók módszere, amikor is a két egyenlet mindkét oldalát úgy szorzom meg konstans értékekkel, hogy az egyik ismeretlen együtthatói megegyezzenek. Ha például az x ismeretlen kiküszöbölése a célunk a fenti általános egyenletrendszerbôl, akkor az elsô egyenletet LKKT(a,c)/c-vel, míg a második egyenletet LKKT(a,c)/a-val kell megszoroznunk, ahol a LKKT a két szám legkisebbközös többszöröse.
[A LKKT-t legyszerûbben úgy számíthatjuk ki, ha a két szám szorzatát elosztom a legnagyobb közös osztójukkal (LNKO).
Ha ez bonyolultnak tûnik, akkor megfelelô a c-vel, illetve a-val történô szorzásis.]
Ezután az egyik egyenletbôl kivonom a másikat,saz így kapott egyenlet már csak 1 ismeretlent tartalmaz.
Nézzük meg az elôzô egyenletrendszer megoldását az egyenlô együtthatók módszerével is:
7* I.: 28x-35y=154
4*II: 28x+8y =68
I-II: -43y=86 azaz y=-2
II: 7x-4=17 azaz x=3
A két módszer egyenértékû, bármlyikkel dolgozhatunk. A megoldások vizsgálata során 2 esetre kell kitérnünk.
1. Létezik olyan eset, amikor a két egyenlet egymással ekvivalens. Ilyenkor az egyenletrendszer határozatlan, azaz az egyik ismeretlent tetszôleges t valós paraméternek választva, kifejezhetjük a másikat, vagyis ekkor végtelen sok megoldás létezik.
2. Lehetséges az is, hogy az egyenletrendszer megoldása során ellentmondásra jutunk (tipikusan akkor, ha az átalakítások soárn azt tapasztaljuk, hogy a 2 egyenlet bal oldala egyenlô, míg jobb oldaluk nem). Ekkor nincs megoldás.
Íme, egy egyszerû feltétel a határozatlanság, illetve ellentmondásosság vizsgálatára. Ha a fenti általános egyenletrendszerben:
a*d-b*c==0 és
1) p*c==a*q, akkor az egyenletrendszer határozatlan;
2) p*c!= a*q, akkor az egyenletrendszer ellentmondásos.
Feladat
A fenti ismeretek értelmében írjunk programot, mely megold egy 2 ismeretlenes, lineáris egyenletrendszert a megoldási lehetôségek teljes vizsgálatával! A mogoldást itt találod.
public class Ketismeretlenes {
public void prog(){
int a=4;
int b=5;
int c=8;
int d=1;
int p=3;
int q=5;
double x,y;
if (a*d-b*c==0){
if (p*c==a*q) {
System.out.println("Hatarozatlan");
System.out.println("x=t");//y=p-b*t
System.out.println("y="+p+"-"+b+"t");
}
else {
System.out.println("Ellentmondasos, nincs valos megoldas.");
}
}
else
{//egyenlo egyutthatok modszerevel
y=((p*c-q*a)/(c*b-d*a));
x=(p-b*y)/a;
System.out.println("A megoldas: x="+x+" y="+y);
}
}
public static void main(String[] args) {
Ketismeretlenes e=new Ketismeretlenes();
e.prog();
}
}
A helyzet kicsit bonyolultabb 3 ismeretlenes egyenletrendszerek esetén.A megoldási módszerek ismeretéhez szükség van egy kis felsôbb 'matek'-ra.
Az elsô fogalom, amit bevezetünk aza mártix. Egy mátrixot elég úgy elképzelnünk,mint egy n*m-esszámtáblázatot. A mátrix elemeire indexeléssel tudunk hivatkozni. Az a(i,j) elem a mátrix i. sorának j. oszpában lévô elemet jelenti. Középsikolában tanultuk a vektorfogalmát. Nos, a mátrix úgy is elképzelhetô, mintegy olyan sorvektor, melynek elemei oszlopvektorok vagy fordítva: olyan oszlopvektor, melynek elemei sorvektorok.
Az alábbi példa egy 3*3-as mátrixot mutat:
Elnevezési konvenció, hogy a mátrixokat nagybetûvel, elemeit pedig az adott nagybetû indexelt kisbetûivel jelöljük. Ha a fenti mátrixot A-val jelöljük, akkor elemeire könnyen hivatkozhatunk:
a(1,1)=2, a(1,2)=3, ..., a(3,2)=4, a(3,3)=3
A mátrixok szép matematikai struktúrákat alkotnak és nagyszerû példaprogramokat lehet rá írni,de ehhez szükség lenne arra, hogy indexelt adatstruktúrákat könnyebben kezeljünk. Ennek lehetôsége egy késôbbi fejezetben nyílik meg számunkra, amikor is a JAVA tömb kezelését tanuljuk. A fenti példa mátrix sorfolytonos felírása alatt az
A=(2 3 1; 4 2 4; 1 4 3)
jelölést értjük.A 3 ismeretlenes egyenletek megoldásához a mátrixoknak egy fontos jellemzôjét,a determinánst, kell megértenünk. Egy n*n-es mátrix fôátlóját az a(1,1), a(2,2), a(3,3), ..., a(n,n) elemek alkotják, formálisan:
a(i,i) ahol i=1..n
A másik átlóban elhelyezkedô elemek a mellékátlót alkotják.
A determináns. Az A mátrix determinánsát detA-val jelöljük.
1. Egy 1*1-es mátrix determinánsa maga az egyetlen eleme.
2. Egy 2*2-es mátrixdeterminánsa alatt a fôátlóban illetve mellékátlóbanlévô elemek szorzatának küléönbségétértjük.
Példa:
Legyen
Ekkor detB=det(5 3; 4 2)=5*2-4*3=10-12=-2.
Tovább lépünk egyet. Egy n*n-es mátrix egyik eleméhez tartozó aldeterminánsa alatt azt az (n-1)*(n-1)-es determinánst értjük, mely azon mátrixnak a determinánsa, mely az adott elemhez tartozó sorba, illetve oszlopban szereplô elemek törlésével keletkezik.
Egy 3*3-as mátrix determinánsát úgy képezzük, hogy kiválasztjuk az egyik sort/oszlopot és egy olyan elôjelben alternáló összeget képezünk, melynek tagjai a kiválasztott sor/oszlop elemei megszorozva az elemhez tartozó 2*2-es aldeterminánsokkal.
Ez a fenti 3*3-as példa mátrixra nézve, kiválasztva például az elsô oszlopot az alábbiak szerint alakul:
detA=2*det(2 4; 4 3)-4*det(3 1; 4 3)+1*det(3 1; 2 4)=2*(-2)-4*5+1*10= -12
Vagyis az elsô elemhez tartozó aldetermináns az elsô sor és elsô oszlop törlésével keletkezô almátrix determinánsa, az a(2,1) elemhez a második sor és az elsô oszlop törlésével kapott 2*2-es mátrix- , s végül az a(3,1) elemhez pedig a 3. sor és elsô oszlop törlésével kapot 2*2-es mátrix determinánsa tartozik.
Formálisan egy n*n-es A mátrix determinánsa a k. oszlop szerint kifejtve:
elemhez tartozó aldetermináns.
Egy A mátrix egy
oszlopvektorral való szorzatán azt az oszlopvektort értjük, melynek i. komponense az a(i,1)*x1+a(i,2)*x2+...+a(i,n)*xn összeg.
Formálisan, ha A n*n-es mátrix és x n-dimenziós oszlopvektor, akkor
Ezzel el is érkeztünk ahhoz a ponthoz, ahonnan foglalkozhatunk az eredeti problémánkkal.
A fenti módszerrel egy egyenletrendszer együtthatói egy mátrixot, egy ún. együttható-mátrixot alkotnak, míg a kiszámítandó ismeretlenek egy oszlopvektort. Ezáltal az egyenletrendszer egyszerûen
Ax= b
alakban írható fel.
Például a 3*3-as esetben:
Vegyük észre, hogy az ábrában szereplô A mátrix-ot soronként megszorozva az x oszlopvektor elemeivel, akkor éppen az egyenletrendszerünk egyenleteit kapjuk:
1) a11*x1+a12*x2+a13*x3=b1
2) a21*x1+a22*x2+a23*x3=b1
3) a31*x1+a32*x2+a33*x3=b1
A 3 vagy többismeretlenes egyenletrendszerek megoldására az egyenlô együtthatók módszerének általánosítása a Gauss-féle elimináció, illetve a változó helyettesítés módszerének általánosításaként ismert Cramer-szabály kiválóan alkalmas. A Gauss-féle elimináció JAVA megvalósítása a még nem tanult tömb adatszerekezet ismerete nélkül igencsak körülményes lenne, így most a Cramer-szabályt ismerjük meg.A dolog elég egyszerû, mindössze a fentiekben megtanult 3*3-as determinánsok számítását kell gyakorolnunk.
Amennyiben az A mátrix determinánsa nemzérus (detA!=0), akkor az x1, x2, x3 (...xn) ismeretlenek elôállnak a következô hányadosok képzésével:
D1/detA, D2/detA, D3/detA,
ahol D1, D2, D3, ... azon mátrixok determinánsai, melyeket úgy képezünk, hogy az A mátrix 1, 2, 3, ...oszlopait kicseréljük a jobb oldalon szereplô b együttható vektor elemeivel kicseréljük.
Például:
Amennyiben az együttható mátrix determinánsa nemzérus, akkor az egyenletrendszer határozatlan, ennek vizsgálatára azonban további matematikai ismeretek hiányában nem térünk ki.
Nézzünk egy konkrét példát a Cramer-szabály alkalmazásával történõ megoldásra!
1) 4x1-3x2+ x3=2
2) x1+ x2-2x3=9
3) 2x1+ x2-3x3=14
azaz mátrixos alakban:
A determinánsokat az elsô oszlop szerint kifejtve:
detA=4*(-3+2)-(9-1)+2*(6-1)=-4-8+10=-2
detD1=2*(-3+2)-9(9-1)+14*(6-1)=-2-72+70=-4
detD2=4*(-27+28)-(-6-14)+2*(-4-9)=-2
detD3=4*(14-9)-(-42-2)+2*(-27-2)=6
Ily módon a Cramer-szabály szerint:x1=-4/-2=2
x2=-2/-2=1
x3=6/-2 =-3 Visszahelyettesítéssel ellenôrizve kész vagyunk.
Feladat
A fenti ismeretek értelmében készítsünk programot, mely megold egy 3*3-as, lineáris egyenletrendszert! A program tartalmazzon függvényt a 2*2-es, illetve 3*3-as determináns kiszámítására, valamint vizsgálja meg az alapmátrix determinánsának nemzérus voltát. A megoldást itt találod.
public class Egyenletrendszer {
int tipus=3;
int a11=2; int a12=2; int a13=2; int b1=2;
int a21=1; int a22=1; int a23=1; int b2=1;
int a31=2; int a32=1; int a33=-3; int b3=14;
/*
Annak erdekeben, hogy minden fuggveny lassa az egyenlet egyutthatoit,
az osztaly belsejeben deklaraljuk oket
*/
int det2(int a, int b, int c, int d){
return (a*d-c*b);
}
int det3(){
return ((a11*det2(a22, a23, a32, a33))-(a21*det2(a12, a13, a32, a33))+(a31*det2(a12, a13, a22, a23)));
}
int det3(int i){
int d=0;
switch (i) {
case 1: {
d=b1*det2(a22, a23, a32, a33)-b2*det2(a12, a13, a32, a33)+b3*det2(a12, a13, a22, a23);
break;
}
case 2: {
d=a11*det2(b2, a23, b3, a33)-a21*det2(b1, a13, b3, a33)+a31*det2(b1, a13, b2, a23);
break;
}
case 3: {
d=a11*det2(a22, b2, a32, b3)-a21*det2(a12, b1, a32, b3)+a31*det2(a12, b1, a22, b2);
break;
}
}//switch
return d;
}
public void prog(){
if (tipus==2){;}
if (tipus==3){
if (Math.round(det3())==0){
System.out.println("Az egyenletrendszer hatarozatlan vagy ellentmondasos...");
}
else{
double x1=(det3(1)/det3());
double x2=(det3(2)/det3());
double x3=(det3(3)/det3());
System.out.println("A gyokok: ");
System.out.println("x1="+x1);
System.out.println("x2="+x2);
System.out.println("x3="+x3);
}
//System.out.println(det3(1)+" "+det3(2)+" "+det3(3)+" "+ det3());
}//3-as
}//prog
public static void main(String[] args) {
Egyenletrendszer e=new Egyenletrendszer();
e.prog();
}
}
Ellenõrzõ kérdések
1. Mit nevezünk cimkének, hogyan cimkézhetünkutasításokat?
2. Ismertesd a többszörös elágazás készítésére alklamas utasítást!
3. Ismertesd a break utasítás használatát!
4. Mire használhatjuk a continue utasítást?
5. Mikor nem használhatunk case és continue utasításokat?
6. Milyen módszereket ismersz 2*2-es, linaáris egyenletrendszer megoldására?
7. Mikor mondunk egy 2*2-es egyenletrendszert határozatlannak, illetve ellentmondásosnak? Hány megoldás létezik ezekben az esetekben?
8. Mit nevezünk mátrixnak?
9. Definiáld az alábbi fogalmakat!
Egy mátrix
- fôátlója,
- mellékátlója,
- determinánsa (2*2-es és 3*3-as esetben).
10. Mit értünk egy A n*n-es mátrix x n-dimenziós oszlopvektorral való szorzatán?
11. Hogyan hozható kapcsolatba az egyenletrendszerek megoldása és a mátrixok?
12. Ismertesd a Cramer-szabály-t 3 ismeretlenes, elsôfokú egyenletrendszerek esetén! Mi a megoldhatóság feltétele?
7. fejezet
Fiókos szekrények garmadája, mindegyik hozzá való mamával. A Jáva alapépítõelemei, az objektumok. Objektumok deklarálása változókkal és függvényekkel, amelyeket ezek után metódusoknak fogunk hívni. Objektumok létrehozása és halála, életciklus a Jávában. A szemétgyûjtõ.
Emlékszel a mamára és a fiókos szekrényére a második fejezetben? Eddig határozottan egy készletnyi változóban és függvényben gondolkoztunk. Ez meg is felel a számítógépprogramozás hagyományos modelljének, ahol van egy darab fiókos szekrényünk, azaz adattárolónk és egy rakat függvényünk, ami ezen az egy állapottárolón manipulál. Ez nagyon kellemes is mindaddig, míg a programod kellõen nagyra nem nõ, utána azonban ekkor a program különbözõ funkciói és azoknak különbözõ változói igencsak összekavarják a dolgokat. Képzelj el most egy olyan modellt, ahol a program különbözõ funkcióit a hozzájuk tartozó változókkal egy egységbe fogjuk. Ez még nem elég: ilyen egységeket szabadon hozhatunk létre, egyfajtából akár többet is, mindegyiket saját változókészlettel, hívhatjuk függvényeiket, amelyek a saját változókészleten manipulálnak és aztán dönthetünk az egység elpusztításáról is, ha nincsen már rá szükségünk. Olyan ez, mintha a programunkat úgy építhetnénk, mint valami gépet; elõször megtervezzük az alkatrészeit, majd legyártjuk belõlük a megfelelõ mennyiséget - csavarból százával, fõdarabból csak egyet-kettõt. Ezek után megmondjuk, az alkatrészek hogyan legyenek összekötve, majd beindítjuk a gépet, mire az mûködni kezd. A fent leírt folyamat vészesen hasonlít az objektumorientált programok készítésére, mint amilyeneket Jávában is írunk.
Az objektumorientált programozásban az alkatrészeket objektumnak hívjuk. Objektumokat tervrajz alapján készítünk, pont annyit, amennyi kell. A tervrajzot, amibõl egy bizonyos fajta objektumokat legyártunk, az adott objektumhoz tartozó osztálynak hívjuk. Az objektumok összeköttetéseit az objektum függvényeinek, objektumorientált terminológiával metódusainak hívásával valósítjuk meg. Valahányszor létrehozunk egy objektumot, lefoglalódik hozzá az összes, az osztállyal deklarált változó (tehát nem azok, amelyeket a metódusokban deklaráltunk), ezeket a változókat egyedváltozónak hívjuk, merthogy minden objektumegyednek van belõlük egy teljes készlet. Minden objektum tehát az egyedváltozóin keresztül önálló egyéniség lehet.
Hagyjuk most a hosszas fejtegetést és nézzünk egy példát! Ismerõs?
public class Elso {
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso();
e.prog( args );
}
}
Van itt egy rettenetes titok, ami az egész tananyag eleje óta lappang itt a kék ködök mögött: igazából mindig is objektumorientált programokat írtunk, mert Jávában máshogy nem lehet. Minden programunk egy osztálydefiníció volt és mindegyikben saját magunk is létrehoztunk legalább egy objektumot. Nézzük most át ezt a programot ebbõl a szempontból!
Mindenekelõtt definiáltuk az Elso nevû osztályt. Ezt a következõképpen tettük:
class Elso { ... }
ahol a kapcsos zárójelek között az osztályhoz tartozó cuccok vannak, ebben az esetben két metódus. Egészen pontosan nem ezt tettük, hanem még elé írtuk azt, hogy public. Egyelõre errõl elég annyit tudnunk, hogy a programunk futtathatósága miatt szükséges, hogy a program fõosztálya (amit a java parancs után írunk) public legyen. Ha egy forrásfájlban van egy public osztály, annak neve meg kell egyezzen a forrásfájl nevével. Ezen felül a fájlban tetszõleges számú egyéb class lehet public nélkül. Egyelõre jegyezzük meg azt, hogy a nem public osztályt csak az õt tartalmazó forrásfájlból lehet elérni. Ez nem teljesen pontos így, de egyelõre elég nekünk. Úgyse írunk még olyan programot, aminek forrása több fájlban van.
Nézzük meg most ezt a sort:
Elso e = new Elso();
Ez egy értékadás, de furcsa fajtájú, nem? Mindenekelõtt mi az Elso típus, mi csak int-et, double-t és boolean-t ismerünk. Az Elso egy új típus, egy objektumreferencia. A referencia típusú változó is csak egy fiók, de ebben egy adott osztályú objektumra hivatkozó referenciát, mutatót tárolunk. Egy referenciát úgy lehet elképzelni, mint egy elérési instrukciót: vedd balról harmadik bögrét a második polcon. Maga a bögre felel meg az objektumnak. Ha most kicseréljük a fiókban levõ cetlit egy másikra, ami a harmadik polcon a legszélsõt jelöli meg, akkor az, aki kiveszi a cetlit a fiókból, ezt a bögrét fogja megtalálni és nem az elõzõt. Tányérra mutató cetlit nem lehet a fiókba elhelyezni, ez a fiók a bögrereferenciáké.
Az e változó tehát egy Elso objektumra mutató referenciát tárol, de hogy jutunk ilyenhez? A new operátor használható arra, hogy egy már definiált osztályból egyedeket gyártson. Most egy Elso osztályú objektumból hoztunk létre egy új egyedet, erre az egyedre mutató referenciát szereztünk a new-tól és ezt rögtön hozzá is rendeltük az e változóhoz. Az e-n keresztül tehát most ezt a példányt tudjuk elérni.
Az objektumreferencia típusnak egyetlen definiált konstans értéke van, egy speciális referencia, ami nem mutat sehova. Ennek a konstansnak a neve null. Ha egy objektumreferenciának null értéket adunk, ezzel azt jeleztük, hogy a referencia "nem használható", mert nem áll mögötte objektum. Példa:
e = null;
A null egy különleges érték mert nincs típusa, bármilyen típusú objektumreferenciának értékül adható.
Mit tudunk tenni egy referenciaváltozóval, milyen mûveletek állnak rendelkezésre ilyen típusú értékekkel? Mindenekelõtt más megegyezõ típusú változónak lehet értékül adni. Ha azt mondanánk
Elso m;
m = e;
akkor az m és az e ugyanarra az objektumra hivatkozik, mindkettõn keresztül ugyanazt az objektumpéldányt érhetjük el. Ha ezek után tovább variálunk:
e = new Elso();
akkor létrehoztunk egy új egyedet az Elso-bõl és most az e erre mutat. A két változón keresztül most két különbözõ objektumpéldányt érhetünk el. De mit jelent az, hogy elérünk egy objektumot egy referencián keresztül?
e.prog( args );
A referenciát most felhasználtuk arra, hogy meghívjuk az általa hivatkozott objektum egy metódusát. A függvényhívás pont úgy néz ki, mint egy eddigi mezei függvényhívás, de most a referencia megadásával jeleztük, hogy nem a saját objektumunknak, hanem egy másiknak, a referencia által mutatottnak a metódusát akarjuk.
Egyelõre nem mondom el, miért van szükség arra, hogy programunk saját osztályát példányosítsuk, az majd egy kicsit késõbb jön. A static a kulcszó.
Minden objektumnak van egy speciális metódusa, amit konstruktornak hivunk. A konstruktor arról ismerkszik meg, hogy nincsen visszatérési értéke (void sem!) és a metódusneve megegyezik az osztály nevével. Paraméterei akárhányan lehetnek és persze egy objektumnak lehet több konstruktora is különbözõ fajtájú és típusú paraméterekkel. Nézzünk egy példát! Ha az Elso objektumnak lenne konstruktora, azt így kellene leírni.
Elso( int i ) {
System.out.println( "Konstruktor lefutott, parameter: "+i );
}
Ez speciel egy olyan konstruktor, ami egy darab egészet vár paraméterként és akkor hívódik meg, ha pont egy egészt adtunk a meghívásakor. De hogyan és mikor hívódik meg a konstruktor? A konstruktort az egyed létrehozásakor hívja meg a rendszer (tehát nem mi) és a paramétereit a new mögött álló osztálynév mögött levõ gömbölyû zárójelek közül szerzi. Ha azt akarjuk, hogy ez a konstruktor hívódjon meg, az Elso egyedet így kell létrehozni:
Elso e = new Elso( 2 );
Mint mondtam, minden objektumnak van konstruktora. De akkor hol az Elso konstruktora, nincs olyasmi a kódban, hogy
Elso() { ... }
pedig mi eddig vígan hivatkoztunk rá, amikor azt mondtuk
Elso e = new Elso();
A megoldás az, hogy a rendszer van olyan kedves és egy konstruktort, a paraméter nélkülit automatikusan létrehozza nekünk, ha mi nem deklaráljuk. Így aztán még ha nem is foglalkozunk olyan földi hívságokkal, mint a konstruktor, objektumunkat biztosan létrehozhatjuk a paraméter nélküli konstruktoron keresztül. A paraméter nélküli konstruktort alapértelmezett konstruktornak (angolul default constructor) hívjuk.
Feladat
Módosítsd népszerû Elso programunkat úgy, hogy legyen az Elso osztálynak egy paraméterrel rendelkezõ konstruktora is továbbá az is hívódjon meg! Ha valamiért nem megy, itt a megoldás , de a lecke szövege tartalmazza az összes szükséges programdarabot, úgyhogy ezt meg kell tudni oldanod! Futtasd le a megoldást és értelmezd az eredményt!
public class Elso {
Elso( int i ) {
System.out.println( "Konstruktor meghivodott, parameter: "+i );
}
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso( 2 );
e.prog( args );
}
}
Ezek után deklarálj egy paraméterek nélküli konstruktort is és alakítsd át a programot úgy, hogy az hívódjon meg! A paraméter nélküli konstruktorba is tegyél egy kiírást, hogy biztosak legyünk, tényleg az hívódott-e meg. Futtasd le, értelmezd az eredményt majd ellenõrizd a megoldást!
// Ezt a programot Elso.java neven kell elmenteni, hogy helyesen
// forduljon le.
public class Elso {
Elso() {
System.out.println( "A konstruktor parameter nelkul hivodott meg" );
}
Elso( int i ) {
System.out.println( "Konstruktor meghivodott, parameter: "+i );
}
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso();
e.prog( args );
}
}
A konstruktorokat persze elsõsorban nem arra használják, hogy buta kiírásokat eszközöljenek velük, hanem a születõ objektumpéldány belsõ állapotának beállítására, vagyis fõképp arra, hogy az objektum egyedváltozói (emlékszel, ezek azok a változók, amelyeket nem a metódusok törzsében, hanem az osztály törzsében, a metódusokkal egy szinten deklarálunk) megfelelõ kezdõértéket kapjanak. Így aztán egy osztály törzse így nézhet ki:
class ValamiOsztaly {
int parameter;
ValamiOsztaly( int parameter_init ) {
parameter = parameter_init;
}
void irdkiaParametert() {
System.out.println( "Parameter: "+parameter );
}
}
Aztán valahol egy felhasználó kódrészletben mondhatunk valami ilyesmit:
ValamiOsztaly v = new ValamiOsztaly( 12 );
...
v.irdkiaParametert();
és ez persze 12-t írna ki. Az objektum létrehozasakor a konstruktor meghívodik a new utan levõ értékkel. Ezt a konstruktor berakja az objektumegyed parameter nevû változójába. Amikor késõbb meghívjuk ugyanazon objektumegyed egy metódusát, ami a parameter változót felhasználja és mellékesen ki is írja. Egy kicsit olyan, mint az itt-a-piros-hol-a-piros, te tudtad követni?
Ha már az egyedváltozóknál tartunk, az objektumreferenciát felhasználhatjuk arra, hogy egy egyedváltozót direktben elérjünk. Elõzõ példánkban például azt is mondhatjuk:
int k = v.parameter;
ahol is egy frissen létrehozott k nevû változóba pakoltuk a v referenciával hivatkozott objektum parameter nevû változójának értékét.
Megjegyzendõ, hogy az objektum egyedváltozóinak direkt elérése nem javasolt módszer, az obejktum belsõ állapotát a metódusain keresztül illik módosítani és lekérdezni. Ez azonban nem kötelezõ, csak afféle ajánlott programtervezési módszertan.
Feladat
Ragadd meg nagy sikerû Elso programunkat és alakítsd át Masodik-ká! Az új változatban próbáld ki a következõ kunsztokat:
• Legyen az osztálynak egy ertek nevû egész típusú egyedváltozója
• Az alapértelmezett konstruktor adja ennek a 0 értéket
• Legyen egy olyan konstruktor, ami egy egész paramétert vár és az ertek változóba írja
• Legyen az osztálynak egy olyan metódusa, ami kiírja az ertek-ben levõ értéket
• A programodban hozzál létre két objektumegyedet, az egyiket az alapértelmezett konstruktorral, a másikat az egész paraméterrel rendelkezõ konstruktorral.
• Hívd meg mind a kettõ objektumegyednek az ertek-et kiíró metódusát
• Érd el az ertek változót direktben a fõprogramodból és írd ki úgy is!
Mindenképpen próbáld a programot megírni magad és ha készen van, nézd meg a megoldást!
// Ezt a programot Elso.java neven kell elmenteni, hogy helyesen
// forduljon le.
public class Masodik {
int ertek;
Masodik() {
ertek = 0;
}
Masodik( int i ) {
ertek = i;
}
void irdkiazErteket() {
System.out.println( "Ertek: "+ertek );
}
public static void main( String args[] ) {
Masodik m1 = new Masodik();
Masodik m2 = new Masodik( 3 );
System.out.println( "m1" );
m1.irdkiazErteket();
System.out.println( "m1.ertek: "+m1.ertek );
System.out.println( "m2" );
m2.irdkiazErteket();
System.out.println( "m2.ertek: "+m2.ertek );
}
}
Most már tudunk objektumokat létrehozni, de hogyan tûnnek ezek el? Minden objektumhoz tartozik memóriaterület, amelyet a rendszer lefoglal számára, amikor az objektum létrejön. Ha egyre csak foglalgatunk, felesszük elõbb-utóbb a gép memóriáját és a programunkra szomorú vég vár.
A Jáva sajátos módon oldja meg a már nem használt objektumok felszabadítását. A háttérben egy szemétgyûjtõ programot futtat, ami megtalálja a már nem használt objektumpéldányokat és felszabadítja õket. Mi alapján dönti el, ki a szükségtelen és ki nem? Minden objektum szükségtelen, amire már nincsen referencia. Például kiváló Masodik programunkban ha azt írnánk:
m1 = new Masodik( 3 );
m1 = new Masodik( 4 );
akkor a 3-as paraméterrel létrehozott Masodik egyedre a második értékadás után már nem mutatna referencia. Ugyanez a trükk akkor is igaz, ha a referenciaváltozó automatikusan takarítódik el, például egy metódus belsõ változójának megszûnése miatt.
void objektumLetrehozo() {
Masodik m1 = new Masodik();
}
Ebben az esetben az m1 változó megszûnik, amikor a metódus visszatér a hívóhoz és minthogy a referenciát nem mentettük el egy biztos helyre (pl. objektum egyedváltozóba vagy a metódus visszatérési értékébe, ahol a hívó esetleg felhasználhatja), a létrehozott Masodik egyed szemétté válik.
A szemétgyûjtõ nem azonnal takarítja el a feleslegessé vált objektumokat, hiszen akkor programunkat alaposan lelassítaná állandó sepregetésével. Hasonlatosan egy rutinos házmesterhez, elõbb megvárja, hogy elég sok szemét legyen és akkor lendül akcióba. Mindez megmagyarázza, miért olyan hihetetlenül magas a Jáva programok memóriaigénye. Egy jól irányzott Jáva programsorral akár 3-4 szemétobjektumot is tudunk csinálni és egy komoly Jáva program terhelés alatt akár 2-3KBájt szemetet is létrehoz másodpercenként. Ezért cserébe viszont nem kell törõdni az egyik legundokabb hibával, a memóriafolyással, amikor a programozó elfelejti felszabadítani a lefoglalt és már nem használt memóriaterületet és a gép szabad memóriája lassan elfogy.
A szemétgyûjtõ mûködésének demonstrálására nem tudtam látványos és egyszerû példát kitalálni, így sajnos el kell hinned, hogy ez így van. Lássuk inkább a lecke fináléját, íme egy nagyszerû
Feladat
Írjál egy programot, ami egy játékost és egy bankot modellez, akik fej vagy írást játszanak forintos alapon. A játékos fogad fejre vagy írásra, a bank feldobja a pénzt és ha a játékos eltalálja, kap egy forintot a banktól, ha nem, õ fizet egyet. A játékos fogadjon véletlenszerûen, játsszanak ilyen mérkõzésbõl 10-et majd a végén írd ki, kinek mennyi pénze maradt. A feladat megoldásához mindenekelõtt meg kell tervezned a programban használt objektumokat. Minden szereplõt azonosítson egy objektum! Ezek szerint biztosan van Játékos és Bank objektumunk. Írd fel a két objektum tulajdonságait a következõ formában:
xxx objektumnak van
- ... (az objektum által tárolt adatok listája)
xxx objektum képes
- ... (az objektumon végrehajtható mûveletek listája)
xxx objektum kommunikál
- ... milyen objektumokkal kommunikál az objektum (csak ha mi vagyunk a kezdeményezõ)
Alkalmazd a fenti módszert a feladatunkra! A megfejtéshez görgess le néhány sort, de ennek mennie kell.
A Játékosnak van
- pénze
A Játékos képes
- fogadásokat tenni
- kiírni, mennyi pénze van
A Játékos kommunikál
- A Bankkal, amikor fogadásokat tesz
A Banknak van
- pénze
A Bank képes
- Fogadásokat fogadni, dobni, eldönteni, hogy a játékos vesztett vagy nyert
- kiírni, mennyi pénze van
A Bank kommunikál
- Senkivel, a Játékos kezdeményezi a fogadásokat
Ezen felül van még egy szereplõ, a Fogadó, ami a szálakat mozgatja. Õ veszi rá a Játékost, hogy 10-szer fogadjon a Banknál.
A Fogadónak van
- Játékos objektuma
- Bank objektuma
A Fogadó képes
- lejátszatni a mérkõzést
A Fogadó kommunikál
- A Játékossal, hogy fogadjon
A "van" szekcióból derülnek ki az objektum egyedváltozói. A példában a Játékosnak és a Banknak van pénz változója, a Fogadó meg referenciát tárol a Játékos és Bank objektumokra. A "képes" szekcióból derül ki, milyen metódusai lesznek. A Játékosnak lesz egy metódusa, amivel fogad és egy másik, ami kiírja a Játékos objektumban tárolt pénzt. A Banknak lesz egy metódusa, aminél fogadásokat lehet tenni és egy másik, ami kiírja a bank pénzét. A Fogadónak egy metódusa lesz, ami a játszmát lejátsza. A "kommunikál" szekcióból derül ki, melyik objektum melyik másik metódusait hívja meg, kire tárol referenciákat. A Játékos referenciát tárol a Bankra és meghívja annak fogadáskiértékelõ metódusát, a Fogadó pedig a Játékosra tárol referenciát. A továbbiak kedvéért a Bank objektumot is a Fogadó hozza létre és passzolja a Játékosnak a refernciáját a Játékos konstruktorán keresztül. Az alábbi ábrán ezeket a relációkat ábrázoltam.
A fentiekben leírt egyszerû módszert objektumelemzésnek fogom hívni ezután. A módszer valóban egy profi programozók által is használt programtervezési módszertan lényegesen leegyszerûsített változata, amely most oktatásra teljesen megfelel.
A sok duma után írd meg a programot! Nézd meg az ábra függõségi listáját és ebbõl kiderül, célszerû a Bankkal kezdeni, mert az nem függ semmitõl, utána jöhet a Játékos, majd a Fogadó. Ha készen van, csinálj egy olyan változatot, ahol három játékos játszik ugyanannál a banknál. Ha nem untad meg, csinálj olyat is, ahol három játékos játszik három különbözõ banknál! Pusztán csak ellenõrzésképpen itt a megoldás.
/* A Bank objektum reprezentalja a bankot.
A Banknak van
- penze
kepes
- fogadasokat fogadni es kiertekelni, nyert-e vagy vesztett a fogado
- kiirni, mennyi penze van
kommunikal
- a jatekosokkal, akik fogadnak nala
*/
class Bank {
int penz;
// Konstruktor, megkapja a Bank kezdeti penzet
Bank( int penz_p ) {
penz = penz_p;
}
// Fogadas kiertekeles, megkapja a fogadast (0 vagy 1), o is fogad
// es visszaadja, mennyit nyert a jatekos (-1 vagy 1). Sajat penzet
// a jatekos nyeremenyevel ellenkezoleg modositja
int FogadasErtekeles( int fogadas ) {
// Dob fejet vagy irast
int penzerme,jatekos_nyeremeny;
if( Math.random() < 0.5 ) {
System.out.println( "Fej nyer" );
penzerme = 0;
}
else {
System.out.println( "Iras nyer" );
penzerme = 1;
}
if( fogadas == penzerme )
jatekos_nyeremeny = 1;
else
jatekos_nyeremeny = -1;
penz = penz - jatekos_nyeremeny;
System.out.println( "Jatekos nyeremenye: "+jatekos_nyeremeny );
return jatekos_nyeremeny;
}
void PenzKiir() {
System.out.println( "A banknak "+penz+" penze van" );
}
}
/* A Jatekos objektum reprezentalja a fogadot.
A Jatekosnak van
- azonositosszama
- penze
kepes
- fogadni
- kiirni, mennyi penze van
kommunikal
- a bankkal, amikor bekuldi a fogadasat es visszakapja, nyert-e.
*/
class Jatekos {
Bank bank;
int penz;
int jatekos_szam;
// Konstruktor. Megkapja a jatekos azonositojat, a penzenek a mennyiseget
// es egy referenciat a bankra, hogy fogadhasson
Jatekos( int jatekos_szam_p, int penz_p, Bank bank_p ) {
jatekos_szam = jatekos_szam_p;
penz = penz_p;
bank = bank_p;
}
// Egy fogadas a banknal.
void Fogad() {
int fogadas,jatekos_nyeremeny;
if( Math.random() < 0.5 ) {
System.out.println( "Fogadas fejre" );
fogadas = 0;
}
else {
System.out.println( "Fogadas irasra" );
fogadas = 1;
}
penz = penz + bank.FogadasErtekeles( fogadas );
}
void PenzKiir() {
System.out.println( "A "+jatekos_szam+". jatekosnak "+penz+" penze van" );
}
}
/* Fogado objektum reprezentalja vezerlot, ami a Jatekost (Jatekosokat)
fogadtatja a Banknal
A Fogadonak van
- Bank objektuma
- Egy vagy tobb Jatekos objektuma
kepes
- Vegrehajtatni a fogadasi sorozatot
kommunikal
- A Jatekossal, amikor fogadast ker tole
*/
public class Fogado {
Bank bank;
Jatekos j1;
/* Harom jatekosos modhoz torold a megjegyzest
Jatekos j2;
Jatekos j3;
*/
void Jatszma() {
// 1000 egyseg kezdeti penz a banknal
Bank bank = new Bank( 1000 );
// 1. jatekos 20 egyseg penzzel
j1 = new Jatekos( 1,20,bank );
/* Harom jatekosos modhoz torold ki a kommentart
// 2. jatekos 20 egyseg penzzel
j2 = new Jatekos( 2,20,bank );
// 3. jatekos 20 egyseg penzzel
j3 = new Jatekos( 3,20,bank );
*/
// Kezdodik 10 kornyi fogadas
for( int i = 1 ; i <= 10 ; ++i ) {
System.out.println( i+". kor" );
// A jatekosok sorban fogadnak
j1.Fogad();
/* 3 jatekosos modhoz torold ki a kommentart
j2.Fogad();
j3.Fogad();
*/
// Kiirjuk a jatekosok es a bank penzet
j1.PenzKiir();
/* 3 jatekosos modhoz torold ki a kommentart
j2.PenzKiir();
j3.PenzKiir();
*/
bank.PenzKiir();
}
}
public static void main( String args[] ) {
Fogado f = new Fogado();
f.Jatszma();
}
}
Feladat
Írjál egy olyan programot, ami három játékost modellez, akik a kõ-papír-ollót játszanak körmérkõzéses alapon. Ha emlékszel, a kõ-papír-olló az a klasszikus játék, ahol a két játékos egyszerre választ a három tárgy közül és egy körkörös szabályrendszer alapján döntik el, ki gyõzött: a kõ gyõz az olló ellen, de veszít a papír ellen, a papír pedig veszít az olló ellen. Futtasd le a körmérkõzést 20-szor és irasd ki, melyik játékos hányszor nyert. Minden játékosnak legyen egy száma, ami azonosítja.
Elõször végezd el az objektumelemzést, ezt itt ellenõrizheted. Ezután készítsd el a programot, ha készen vagy, ellenõrizd a megoldást! Figyeld meg, hogy a megoldásunkban most nem tároltuk el a játékostárs refernciáját a saját objektumunkba (pedig megtehettük volna, hiszen a párok változatlanok), hanem a rugalmasabb szerkezet érdekében paraméterként adjuk át a fogadó metódusnak. Ha netán eltároltad volna apartner referenciáját, az is helyes.
A KőPapírOlló feladat objektumelemzése
A feladatban vannak Játékosok és Fogadó, aki a meccseket bonyolítja.
A Játékosnak van
- azonosítója
- nyert meccsek számlálója
A Játékos képes
- fogadni, megkérni egy másik játékost hogy ő is fogadjon és megváltoztatni az eredményszámlálót a meccs kimenetelének megfelelően
- fogadni egy másik játékos fogadási kérését, kiértékelni az eredményt, megváltoztatni a saját eseményszámlálót a meccs eredményének megfelelően és visszaadni a meccs eredményét a kezdeményező játékosnak
- Kiírni, mennyi meccset nyert már
A Játékos kommunikál
- Egy másik Játékossal
A Fogadónak van
- Három Játékos objektuma
A Fogadó képes
- meccset játszatni a három játékossal
A Fogadó kommunikál
- Az éppen meccset kezdeményező játékossal.
A kommunikációs ábra a következő (csak egy fogadó Játékospárt ábrázolva, ez körbejár a három játékos között).
[KőPapírOlló kommunikációs ábra]
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return 0;
if( veletlen < 2 )
return 1;
return 2;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == 2 ) && ( sajat_tipp == 0 ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == 0 ) && ( sajat_tipp == 2 ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp )
return 0; // Dontetlen
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo {
void Jatszma() {
Jatekos j1 = new Jatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
public static void main( String args[] ) {
KoPapirOllo k = new KoPapirOllo();
k.Jatszma();
}
}
8. fejezet
Újabb megvilágosodás: statikus változók és metódusok. Egyedváltozók és osztályváltozók kavalkádja. Öröklõdés és konverziók.
Mint említettem, egy normális, mezei osztálynak egyedváltozói vannak, amelyek minden egyedben külön létrejönnek és külön életet élnek az egyes egyedekben. Miután létrejöttek, semmi közük egymáshoz. Ugyanúgy, a mezei osztály metódusai az egyedváltozókon manipulálnak, így amikor meghívunk egy metódust pl.
j1.eredmenyKiiras();
akkor implicit módon odaadtuk a j1 által hivatkozott objektumegyedet is az eredménykiíras metódusnak, ami aztán buzgón felhasználja ezen objektumegyed változókészletét. Ez eddig rendben is van. Lehetnek azonban esetek, amikor nem akarjuk, hogy a változó minden egyedben létrejöjjön, hanem csak egyet akarnánk, közöset minden objektumegyednek, ami az adott osztályból példányosítódott. Ha netán ilyenben törnénk a fejünket, a statikus változók pont nekünk valók. A statikus változó pont olyan, mint bármely változó, csak a típusdeklarációja elé még oda van írva az hogy static. Így:
static int szamlalo;
Az ilyen változó nem jön létre újra meg újra, valahányszor az osztályt példányosítjuk, csak egyszer, amikor a Jáva virtuális gép elõször tölti be az osztálydefiníciót. A konstruktorral összeházasítva például felhasználhatjuk ezt a fajta változót arra, hogy megszámolja, hányszor példányosították az adott osztályt. A következõ példában ezt tesszük.
class Szamlalo {
static int peldanyok = 0;
Szamlalo() {
++peldanyok;
}
}
Ezek után valahányszor azt mondjuk: new Szamlalo(), a peldanyok valtozo megnõ eggyel.
Statikus változóra kétféleképpen hivatkozhatunk: vagy egy referencián keresztül, mint más becsületes változóra vagy egyszerûen az osztály nevével pl. így:
int k = Szamlalo.peldanyok;
Ez utóbbi azért lehetséges, mert a peldanyok változó nem az objektumegyedeknek, hanem az osztálynak lefoglalt memóriaterületen tárolódik, eléréséhez nincs szükség objektumreferenciára. A statikus változóknak ezt a tulajdonságát elõszeretettel használják fel arra, hogy konstansokat deklaráljanak vele. Ha pl. a Szamlalo osztályban azt mondom:
static int Milennium = 2000;
azt késõbb bárhonnan kényelmesen elérhetem Szamlalo.Milennium formában anélkül, hogy mindenféle referenciákra kellene vadászni. A konvenció szerint (amit a Jáva nem tesz kötelezõvé, csak követni ajánlják) a konstansok nevét csupa nagybetûvel kell írni, hogy megkülönböztessük a változó változóktól. Így e:
static int MILENNIUM = 2000;
Feladat
Vedd elõ nagy sikerû kõ-papír-olló programunkat és valósíts meg benne két szolgáltatást a statikus változók segítségével! Elõször is számolja meg és a futás végén írja ki, hány döntetlen eredmény született összesen. A Jatekos osztályban hozzál létre egy statikus változót és növelgesd, valahányszor döntetlen fordult elõ. Másodszor is a kõ, papír, olló tárgyakhoz rendelt számokat helyettesítsd konstansokkal. Íme az én változatom!
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return 0;
if( veletlen < 2 )
return 1;
return 2;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == 2 ) && ( sajat_tipp == 0 ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == 0 ) && ( sajat_tipp == 2 ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp )
return 0; // Dontetlen
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo {
void Jatszma() {
Jatekos j1 = new Jatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
public static void main( String args[] ) {
KoPapirOllo k = new KoPapirOllo();
k.Jatszma();
}
}
Metódusok is lehetnek statikusok. Ha egy metódusnév elõtt áll a static kulcsszó, ennek három következménye lesz.
1. A metódus csak statikus változókat érhet el. Ne feledd: a statikus metódus hívásakor nem ismeri az objektumegyed kontextusát, így aztán az egyedváltozókat nem tudja elérni.
2. A metódus hívható osztálynév.metódusnév formában. Példa: Math.random() (ugye ismerõs?)
3. A metódus nem hívhat nem statikus metódusokat. Ez ugyanazon ok miatt van, amiért egyedváltozókat sem érhet el.
Statikus metódusoknak a legfõbb haszna az, hogy nem kell érte példányosítani az osztályt, meghívható egyszerûen osztálynév alapján. Ezzel kitörhetünk a Jáva erõszakos objektumszemléletébõl és mindenhonnan elérhetõ függvényeket írhatunk.
Feladat
Fogd az Elso.java programot és érd el, hogy ne kelljen az Elso-t példányosítani! Töröld ki tehát a következõ sort
Elso e = new Elso();
és tedd meg a szükséges változtatásokat, utána ellenõrizd a megoldást!
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
- dontetlen meccs szamlaloja (egy az osszes objektumra)
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
static int dontetlen_meccsek = 0;
static int KO = 0;
static int PAPIR = 1;
static int OLLO = 2;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return KO;
if( veletlen < 2 )
return PAPIR;
return OLLO;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == OLLO ) && ( sajat_tipp == KO ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == KO ) && ( sajat_tipp == OLLO ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp ) {
++dontetlen_meccsek;
return 0; // Dontetlen
}
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo2 {
void Jatszma() {
Jatekos j1 = new Jatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
System.out.println( "Dontetlen meccsek osszesen: "+Jatekos.dontetlen_meccsek );
}
public static void main( String args[] ) {
KoPapirOllo2 k = new KoPapirOllo2();
k.Jatszma();
}
}
Most már talán megértheted, miért hagytuk ilyen sokáig burjánzani a misztikumot a main-ban található két sor körül. Minthogy a main statikus (így szól a Jáva specifikáció), mert a Jáva virtuális gép nem példányosítja a java parancs által hivatkozott osztályt, így nem érhet el nem statikus változót vagy metódust. A sok static megzavart volna és nem is tudtuk volna ilyen könnyedén bevezetni az egyedváltozókat. Mostantól azonban tudjuk, mit csinálunk, így a programosztályt csak akkor példányosítjuk, ha kell.
És most valami teljesen más! Megmutatom, hogyan lehet minimális munkával okos objektumokat készíteni. Eddig azt tanultuk, hogy az osztályok változóit és metódusait nekünk kell deklarálni. Ha azonban olyan lusta vagy, mint amilyen én, inkább veszünk valami készet és átalakítjuk csupán azt módosítva benne, amiben a kész nem tesz eleget igényeinknek. A módszert, ami objektumorientált rendszerekben erre a célra rendelkezésre áll, öröklõdésnek hívjuk.
Öröklõdéssel azt jelenthetjük ki, hogy egy osztályt nem a nulláról hozunk létre, hanem átveszünk egy létezõ osztályból mindent, majd bõvítjük illetve fölüldefiniáljuk az új funkciókkal. Vegyük a következõ példát:
class Eredeti {
int i;
void novel() {
++i;
}
}
class Leszarmazott extends Eredeti {
void novel() {
i = i + 2;
}
void csokkent() {
i = i -2;
}
}
A kicsit buta példánkban (miért kell ahhoz leszármazás, hogy megspóroljunk egy int i;-t?) a leszármazott osztályban felülírtuk a novel() metódust, hogy ezentúl kettõvel növelgesse i-t és hozzáadtunk egy csokkent metódust, ami csökkentgetni is tud. A Leszarmazott osztályt pont úgy kell használni, mint az Eredeti-t.
Még egy megjegyzés: a konstruktorok nem öröklõdnek. Ha a leszármazott osztálynak is szükségük van spéci (tehát paramétert fogadó) konstruktorokra, azt újra kell definiálni a leszármazott osztályban is. Ha nincs szükség változtatásra, használhatod a super() hívást, amivel a szülõosztály konstruktora felé passzolhatod a paramétereket. Vegyünk egy példát, ahol egy egész paramétert passzolunk felfelé:
class Leszarmazott extends Eredeti {
Leszarmazott( int i ) {
super( i );
}
...
}
Az öröklõdés kiválóan alkalmas arra, hogy bevezessek egy régen esedékes fogalmat, a típuskonverziót. Típuskonverzióval hasonló típusú értékeket alakíthatunk egymásba. Kézenfekvõ példa a számok: miért kell fallal elválasztani egymástól az int-et és double-t? Nem kell és íme egy kis példa, hogyan lehet típuskonverzióval egymásba alakítani õket.
double n = 2.7;
int ni = (int)n;
n = (double)ni;
Az ni változóba átkonvertáltuk n-t. Csoda azonban most se történt, ni-be 2 került, a törtrész levágódott. A következõ lépésben az n-be visszakonvertáltuk az ni értéket, minek eredményeképpen n-ben most 2.0 van.
Ez eddig nagyon egyszerû, de mik lehetnek hasonló típusok még? A Jáva nagyon szigorú típuskonverzió terén és a számok mellett csak a leszármazott osztályok referenciáit engedi egymásba átkonvertálni. Például az elõbbi példánknál maradva ha netán azt mondanánk
Leszarmazott l = new Leszarmazott();
Eredeti e = (Eredeti)e;
Ez sikerülne, mert az Eredeti és Leszarmazott "hasonló" típusok. Azért megengedett a mûvelet, mert az Eredeti legalább azt tudja, amit a Leszarmazott, mert belõle jött létre módosítással. Ha ugyanezt visszafelé csinálnánk, kicsit bonyolultabb a helyzet.
Leszarmazott l2 = (Leszarmazott)e;
Ezt a fordító elfogadja, de azért még nem biztos, hogy helyes. Ha a három utasítás úgy követte egymást, ahogy az elõbb leírtuk, akkor helyes, hiszen e-ben valójában egy Leszarmazott referenciája van. Ha azonban netán ez lenne a sorrend:
Eredeti e = new Eredeti();
Leszarmazott l2 = (Leszarmazott)e;
ezt a fordító ugyan nem tekinti hibának (rokon osztályok között a konverzió megengedett), de a Jáva virtuális gép a futás során észreveszi a turpisságot és hibaüzenettel leáll.
Most már csak az a kérdés, ha vesszük az elõzõ példát
Leszarmazott l = new Leszarmazott();
Eredeti e = (Eredeti)e;
és meghívjuk a novel metódust
e.novel();
vajon eggyel vagy kettõvel nõ-e az i változó? A Jáva dinamikus típusfeloldással rendelkezik, tehát mindig annak az osztálynak a metódusai hívódnak meg, ami az osztály valójában, nem pedig a referencia típusa szerint, i tehát kettõvel nõ. Referenciáinkat tehát nyugodtan konvertálhatjuk a szülõosztály felé, ha elhasználjuk a referenciát, az a megfelelõ eredménnyel fog járni.
Feladat
Ragadd meg barátunkat, a kõ-papír-olló programot és csináld meg azt, hogy egy játékos azzal a "szisztémával" játszik, hogy mindig pl. kõre fogad. Csinálj egy leszármazottat a Jatekos osztályból és írd felül a fogadas() metódust megfelelõképpen! Ezek után az egyik játékost a leszármazott osztály szerint hozdd létre és használj típuskonverziót a típushibák elkerülésére! Itt a megoldás ellenõrzésképpen. Ki a buta játékos?
// Ezt a programot Elso.java neven kell elmenteni, hogy helyesen
// forduljon le.
public class Elso {
static void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
prog( args );
}
}
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
- dontetlen meccs szamlaloja (egy az osszes objektumra)
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
static int dontetlen_meccsek = 0;
static int KO = 0;
static int PAPIR = 1;
static int OLLO = 2;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return KO;
if( veletlen < 2 )
return PAPIR;
return OLLO;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == OLLO ) && ( sajat_tipp == KO ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == KO ) && ( sajat_tipp == OLLO ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp ) {
++dontetlen_meccsek;
return 0; // Dontetlen
}
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
class ButaJatekos extends Jatekos {
ButaJatekos( int i ) {
super( i );
}
int tipp() {
return KO;
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo3 {
void Jatszma() {
Jatekos j1 = (Jatekos)new ButaJatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
System.out.println( "Dontetlen meccsek osszesen: "+Jatekos.dontetlen_meccsek );
}
public static void main( String args[] ) {
KoPapirOllo3 k = new KoPapirOllo3();
k.Jatszma();
}
}
Ellenõrzõ kérdések
1. Mi a statikus változó?
2. Elérhetõ-e a statikus változó nem statikus metódusból?
3. Milyen referenciával lehet elérni a statikus változót?
4. Mi a statikus metódus?
5. Elérhet-e normál egyedváltozót statikus metódus?
6. Meghívhat-e statikus metódust normál, nem statikus metódus?
7. Meghívhat-e normál metódust statikus metódus?
8. Mit jelent az, hogy egyik osztály leszármazottja a másiknak?
9. Lehet-e egy osztályreferenciát a szülõosztály felé konvertálni?
10. Lehet-e egy osztályreferenciát a leszármazott osztály felé konvertálni?
11. Lehet-e Jávában különbözõ típusú értékek között értékadás?
12. Ha létrehozunk egy egyedet és egy szülõosztály típusa szerinti referenciával hivatkozunk rá, a szülõosztály vagy a leszármazott osztály szerinti metódus hívódik-e meg?
Ellenõrzõ kérdések
1. Mi az objektum, mi az osztály és mi a kapcsolatuk?
2. Mi az összefüggés a függvények és metódusok között?
3. Mi az egyedváltozó?
4. Mi az objektumreferencia
5. Mit jelent a null?
6. Hogyan hívhatjuk meg egy objektum metódusait az objektumreferencián keresztül?
7. Mi a konstruktor?
8. Hogyan hívjuk meg a konstruktort?
9. Mi az alapértelmezett konstruktor?
10. Mikor generál alapértelmezett konstruktort a fordító maga?
11. Hogyan hivatkozhatunk egy objektum egyedváltozóira az objektumreferencián keresztül?
12. Mit jelent az, hogy a Jáva rendszerben egy szemétgyûjtõ mûködik?
9. fejezet
Adatok tömegesen: tömbök a Jávában. Tömbtípusok. A Jáva tömbök is csak objektumok. Objektumok és tömbök tömbje.
Emlékszel lottós példánkra az 5. fejezetben, ott öt változóban tároltuk a lottószámokat. Talán már akkor megfordult a fejedben, hogy esetleg van egy elegánsabb módszer egymáshoz tartozó azonos típusú adatok tárolására. Ha a fiókosszekrénynél maradunk, mód van valami olyasmire, mint egy katalógusszekrény, ahol bizonyos kulcs (általában kezdõbetû) alapján érhetjük el az elemeket. A katalógusszekrénynek megfelelõ eleme a Jávának is van, de mi egy egyszerûbb adattípussal kezdjük, ahol az elemeket egy számmal, az indexszel lehet kiválasztani. Ezt az adattípust szinte minden számítógépes nyelv ismeri, tömbnek hívják.
Legegyszerûbb egy példával kezdeni. Nézzük a következõ sort:
int a[] = new int[20];
Különösebb magyarázat nélkül (majd jön az is!) egyelõre fogadjuk el, hogy ez a sor egy 20-elemû tömböt kreál, amelyben minden tömbelem egy int. Az tömbelemeket egy egész típusú indexszel választjuk ki: a tömb elsõ eleme a[0], a második az a[1], az utolsó az a[19] (minthogy 0-ról kezdtük a számlálást!) A tömbelemeket ugyanúgy kezelhetjük, mintha normális változók lennének, értéket adhatunk nekik:
a[0] = 42;
vagy pedig felhasználhatjuk az értékeit kifejezésekben:
a[2] = a[0] + a[1];
Természetesen az indexnek nem kell konstansnak lennie, tetszõleges egész kifejezés megteszi. Példának okáért a következõ programrészlettel tölthetjük fel az egész tömböt 42-vel.
for( int i = 0 ; i < 20 ; ++i )
a[i] = 42;
Ilyen egyszerû ez a felszínen! Most vizsgáljuk meg a tömböt létrehozó sort, mert érdekességek találhatók ottan. Az objektumoknál megszokott módon nézzük a tömbváltozót magát:
int a[];
Ez a változó egy int-ekbõl álló tömbre mutató referencia. Ugyanúgy, mint egy objektumreferencia (mint ahogy valójában objektumreferencia is) egy mutatón kívül nincs lefoglalt tárterület mögötte. A 20 egész értékünk nem itt van. A referencia akkor lesz használható, ha létrehozunk egy tömbobjektumot, amire a referencia mutathat. Ilyen objektumot gyárt az értékadás jobb oldalán álló kifejezés.
new int[20];
Ez a kifejezés 20 egész számot befogadni képes tömbnek foglal helyet és visszatérési értéke a tömbre mutató referencia. Ezt tesszük bele az "a" tömbreferenciára, minek eredményeképpen az "a" változón keresztül indexelni tudjuk a tömböt. Az "a" változó - hasonlóan bármilyen más objektumrefernciához - tehát attól kap értelmet, amire mutat. Írhatjuk késõbb, hogy
a = new int[40];
Ekkor a régi tömb, amire "a" imént mutatott eltûnik (vagy elérhetõ egy másik referencián keresztül, ha volt ilyen) és "a" egy vadi új, ezúttal 40 elemû tömböt tud elérni.
A tömbök tehát a Jávában teljesen úgy viselkednek, mint az objektumok. Ha van egy tömb típusú referenciánk, azzal "rámutathatunk" egy már létezõ tömbre, így két tömbreferencián keresztül érhetjük el ugyanazt a tömböt. Példa:
int a[] = new int[20];
int b[];
b = a;
a[0] = 1;
b[1] = 13;
A két értékadás a programrész végén ugyanannak a tömbnek a 0. és az 1. elemét manipulálja, minthogy a 3. sorban megtettük, hogy a "b" is ugyanarra a tömbre hivatkozzon.
A tömbök annyira objektumszerûek, hogy saját egyedváltozóik is vannak. Minden tömbobjektumnak van egy length egyedváltozója, ami megmondja a tömb méretét. Elõzõ példánkban
a.length
és
b.length
egyaránt 20-at ad vissza értékként (merthogy igazából ez ugyanaz a tömb).
A Jáva teljes típus- és indexhatárellenõrzéssel rendelkezik. Nem tehetjük meg azt, mint a C-ben, hogy a tömbindexet a tömb méreténél nagyobbra állítjuk és telepiszkítjuk a memóriát. Amikor egy tömbreferencián keresztül elérünk egy tömbobjektumot, a Jáva ellenõrzi a tömbobjektum tényleges méretét (dinamikusan foglaltuk, fordításidõben nem jöhet rá) és kilövi a programot, ha túlindexelni próbálja a tömböt.
Feladat
Írjuk át a már elkészített lottó programot úgy, hogy a véletlen számokat egy 5 elemû int típusú tömbbe helyezze el! ! Gyakorlásképpen használjunk statikus metódusokat és adattagokat! A megoldást itt találhatod.
public class Ujlotto {
//a program nem vizsgalja, hogy minden generalt veletlen szam kulonbozo-e
static int[] nyeroszamok = new int[5];
public Ujlotto() {
}
static void prog(){
System.out.println("A lotto e heti nyeroszamai:");
for (int i = 0; i < nyeroszamok.length; i++) {
nyeroszamok[i]=(int) (Math.round(Math.random()*89)+1);
System.out.print(nyeroszamok[i]+" ");
}
}
public static void main(String[] args) {
Ujlotto.prog();
}
}
Feladat
1. Készítsünk programot, mely feltölt egy 100 elemû, véletlen számokból álló tömböt, majd az elemeket kilistázza a konzolra! Gyakorlásképpen használjunk statikus metódusokat és adattagokat! Az osztály neve legyen "Osszegzes"!
2. Bõvítsük a programot úgy, hogy adjuk össze az így generált tömb elemeit, majd az összeget írjuk ki a konzolra!
3. Válogassok le az eredeti tömbbõl a páros, illetve páratlan számokat egy "paros" illetve "paratlan" nevu tömbbe, listázzuk ki külön a páros, illetve páratlan számokat, illetve írjuk ki ezek arányát! A megoldás az Osszegzes.java programban található, az 2. illetve 3. részfeladat a zárójelek elhagyásával bõvíthetõ.
public class Osszegzes {//az egyenletes eloszlas vizsgalatara
static int[] szamtomb = new int[50];
static int osszeg;
//3. paros-paratlan levalogatas...
/*
static int[] paros = new int[50];
static int[] paratlan = new int[50];
static int paros_mutato=0;
static int paratlan_mutato;
*/
//3. paros-paratlan levalogatas vege...
static void prog(){
for (int i = 0; i < szamtomb.length; i++) {
szamtomb[i]=(int) (Math.round(Math.random()*100));
System.out.print(szamtomb[i]+" ");
}
System.out.println();
//2. bovites osszegzessel
/*
for (int i = 0; i < szamtomb.length; i++) {
osszeg+=szamtomb[i];
}
System.out.println();
System.out.println("Az osszeg: "+osszeg);
*/
//2. bovites osszegzessel vege...
//3. paros-paratlan levalogatas...
/*
paros_mutato=0;
paratlan_mutato=0;
for (int i = 0; i < szamtomb.length; i++) {
if ((szamtomb[i]%2)==0) {
paros[paros_mutato]=szamtomb[i];
paros_mutato++;
}
else {
paratlan[paratlan_mutato]=szamtomb[i];
paratlan_mutato++;}
}
System.out.println(paros_mutato+" db paros szam van:");
for (int i = 0; i < paros_mutato; i++) {
System.out.print(paros[i]+" ");
}
System.out.println();
System.out.println(paratlan_mutato+" db paratlan szam van:");
for (int i = 0; i < paratlan_mutato; i++) {
System.out.print(paratlan[i]+" ");
}
*/
//3. paros-paratlan levalogatas vege...
}//prog vege...
public static void main(String[] args) {
Osszegzes.prog();
}
}
Eddig túlságosan az egész típusú tömbökre összpontosítottunk, pedig a tömb által tárolt értékhalmaz (a tömb alaptípusa) természetesen akármilyen Jáva típus lehet. Lehet természetesen objektumreferencia is és ez agy nagyszerû esélyt ad nekünk arra, hogy objektumokat nagy számban állítsunk elõ. Eddig ugye mindegyikhez kellett egy külön referenciaváltozó, de ha most egy tömböt csinálunk referenciákból, jó sok objektumot létrehozhatunk. Emlékszünk még Elso objektumunkra a 7. fejezetben? Most csinálhatunk belõle mondjuk százat olymódon, hogy létrehozunk egy Elso típusú referenciatömböt és minden tömbelembe belepakolunk egy új Elso egyed referenciáját. Valahogy így:
Elso et[] = new Elso[100];
for( int i = 0 ; i < 100 ; ++i )
et[i] = new Elso();
Az eredmény száz Elso egyed amelyeket az "et" tömb megfelelõ indexszelésével érhetünk el. Például mondhatjuk:
et[42].prog( args );
Ekkor a 43. Elso egyed prog metódusát hívtuk meg args paraméterrel.
Feladat
Írd át népszerû Kõ-Papír-Olló programunkat a 7. fejezetbõl úgy, hogy 5 játékos játsszon! A Jatekos referenciákból csinálj egy tömböt és ciklusokkal oldd meg. amit az eredeti program külön változókkal csinált! Íme a megoldás. Vedd észre, hogy a megoldás rövidebb, mint az eredeti program, tömbökkel sokat lehet spórolni!
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return 0;
if( veletlen < 2 )
return 1;
return 2;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == 2 ) && ( sajat_tipp == 0 ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == 0 ) && ( sajat_tipp == 2 ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp )
return 0; // Dontetlen
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOlloTomb {
void Jatszma() {
Jatekos j[] = new Jatekos[5]; // 5 referencia Jatekos egyedekre
for( int i = 0 ; i < 5 ; ++i )
j[i] = new Jatekos( i+1 );
for( int i = 0 ; i < 20 ; ++i ) {
for( int n = 0 ; n < 5 ; ++n ) {
// Kivalasztja, kivel fog jatszani. Normalisan az eggyel nagyobb
// indexu jatekossal jatszik, de az utolso a 0.-kal jatszik
int partner = n + 1;
if( partner >= 5 )
partner = 0;
j[n].MeccsetJatszik( j[partner] );
}
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
for( int n = 0 ; n < 5 ; ++n )
j[n].IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
for( int n = 0 ; n < 5 ; ++n )
j[n].IrdKiANyertMeccseket();
}
public static void main( String args[] ) {
KoPapirOlloTomb k = new KoPapirOlloTomb();
k.Jatszma();
}
}
Mint mondtam, a tömb alaptípusa bármi lehet, tehát tömb is. A tömb alaptípusú tömböt többdimenziós tömbnek hívjuk és néha nagyon jól tudnak jönni. A példa kedvéért tekintsünk egy kétdimenziós tömböt.
double t[][] = new double[100][100];
Ebben 100x100 double elem van, minden double elem 8 bájtot foglal, tehát a tömb mérete 80 kilobájt meg egy pici. Ez már igazán hatékony eszköz. Címezni a többdimenziós tömböt is csak úgy kell, mint egy egydimenzióst:
t[42][45] = 3.14;
Feladat
Írjunk programot, ami kiszámolja a hõeloszlást vízzel teli csõben. Azt tudjuk, hogy csõ keresztmetszete négyzetes, az alsó oldala egyenletes 12 fokon, a teteje egyenletes 90 fokon van, az oldalfalai mellett pedig a hõmérséklet egyenletesen 12-rõl 90 fokra emelkedik. Oldjuk meg a feladatot olymódon, hogy a csõ keresztmetszetét bontsuk kis kockákra, mondjuk 100x100-ra. A legszélsõ kockákat (tehát a 0. és 99. sorban ill. a 0. és 99. oszlopban levõket) töltsük fel a fix hõmérsékletértékekkel, a többit meg tetszõleges értékkel, pl. 12 és 90 átlagával. Számoljuk újra minden nem fix hõmérsékletû kiskocka hõmérsékletét úgy, hogy az a négy szomszédjának az átlaga, írjuk vissza az átlagot a kiskockába és tartsuk számon, mekkora volt a legnagyobb változás az átlagszámítás elõtti és utáni értékekre vonatkoztatva. Ha egy menetben a legnagyobb hõmérsékletváltozás nem több, mint 0.01 fok, nyomtassuk ki a tömbben tárolt értékeket 4 soronként ill oszloponként. Íme a megoldás. Mit befolyásol az átlagoláson átesett kiskockák kezdõértéke?
public class Laplace {
public static void main( String args[] ) {
double cso[][] = new double[100][100];
// Toltsuk fel a fix kockakat. Eloszor az also es felso szegely jon
// A tombot cso[x][y] formaban indexeljuk. cso[0][0] a bal also sarok
for( int i = 0 ; i < 100 ; ++i ) {
cso[i][0] = 12.0;
cso[i][99] = 90.0;
}
// Most egyenletesen novekvo modon feltoltjuk a fuggoleges eleket
for( int i = 1 ; i < 99 ; ++i ) {
double hom = 12.0 + ( ( 90.0 - 12.0 ) * (double)i / 100.0 );
cso[0][i] = hom;
cso[99][i] = hom;
}
// A maradek atlagos homersekletre toltodik
for( int i = 1 ; i < 99 ; ++i )
for( int n = 1 ; n < 99 ; ++n )
cso[i][n] = 51.0; // 12 es 90 atlaga
int iteracio = 1; // Az iteraciok szamlaloja
double maxvalt; // Maximalis homersekletvaltozas az iteracio alatt
do {
maxvalt = 0; // maximalis valtozas: 0
// Egy iteracio
for( int i = 1 ; i < 99 ; ++i )
for( int n = 1 ; n < 99 ; ++n ) {
double ujhom = ( cso[i-1][n] + cso[i+1][n] + cso[i][n-1] + cso[i][n+1] ) / 4;
double valt = Math.abs( ujhom - cso[i][n] );
if( valt > maxvalt )
maxvalt = valt;
cso[i][n] = ujhom;
}
++iteracio;
System.out.println( "Iteracio: "+iteracio+" maximalis valtozas: "+maxvalt );
} while( maxvalt >= 0.01 );
// Kiirjuk az eredmenyt
System.out.println( "Homersekleti ertekek alulrol felfele balrol jobbra 10 soronkent ugralva" );
for( int i = 0 ; i < 100 ; i += 4 ) {
System.out.print( i+". sor: " );
for( int n = 0 ; n < 100 ; n += 4 ) {
System.out.print( cso[n][i]+ " " );
}
System.out.println();
}
}
}
Ellenõrzõ kérdések
• Mi a tömb?
• Mi a tömb alaptípusa?
• Mi lehet Jávában egy tömb alaptípusa?
• Mit jelent az, hogy Jávában a tömböt tömbreferenciával érhetjük el?
• Mit tárol a tömb length egyedváltozója?
• Mit jelent, hogy egy tömb objektumrefernciákat tárol?
• Mit jelent a többdimenziós tömb?
10. fejezet
Nem csak számok vannak a világon! Dolgozzunk érdekesebb adatokkal: karakterek és azok halmazai. Karaktertípus a Jávában, a char típus. Karaktersorozatok avagy ismerkedés a String osztállyal. String és StringBuffer, a két jóbarát.
Talán már kicsit unalmas, hogy példáink mindig számokról szólnak. Ugyan szövegeket állandóan írunk ki, de nem tudunk velük olyan könnyedén variálni, mint a számokkal. Ezen lecke végére ez a hiányérzetünk is megszûnik, mert most a Jáva karakterkezelési eszközeivel foglalkozunk. A számítógép ugyanolyan könnyedén tárol és manipulál karakter típusú adatokat (emlékszel az elsõ leckére? A karakterek a számítógép által ábrázolható összes betû, számjegy, jel összessége), mint számokat. Számára természetesen ezen adatoknak semmi jelentése nincsen, az egymás után tárolt a,l,m és a betûk semmilyen módon nem idézik fel benne a gyümölcsöt, mint ahogy a 2001 értékû egész típusú szám sem jelenti számára a mostani évet. Minthogy a számítógép nem magának számol, hanem nekünk, így aztán elég, ha a karaktereknek csak számunkra van jelentése.
A karakterek tárolására szolgáló alaptípus a char. Egy char típusú változó egy karaktert képes tárolni. Értékül létezõ karakter típusú érték vagy karakterkonstans adható. Példa:
char c;
c = 'A';
A c változó értéke most tehát az 'A' karakter, egészen pontosan annak a kódja. A számítógép számokkal képes dolgozni, így a karaktereket is számokként ábrázolja, minden karakternek saját kódja van. A számítógépes gyakorlatban millióféle karakterkódolás létezik, a Jáva belsõleg a Unicode karakterkészletet használja. A Unicode Jáva által használt változata nem egy, hanem két bájton ír le egy karaktert, így több, mint 65000 karaktert képes ábrázolni. A fenti gondolatmenet eredménye az, hogy char és egész típusok egymásba alakíthatók, minthogy mindegyik csak egész szám. Példa:
int i = (int)c;
char d = (char)i;
Feladat
Írj egy programot, ami kiírja a Jáva karakterkészletet 32 és 127 között, a határokat beleértve. Egy sorba írd ki a karakter kódját majd a karaktert magát is és ezt végezd el az összes kódra. Nagyon egyszerû feladat, remélhetõen menni fog, ha nem, itt van a megoldás.
public class KarakterKod {
public static void main( String args[] ) {
for( int i = 32 ; i < 128 ; ++i ) {
char c = (char)i;
System.out.println( "Karakterkod: "+i+" karakter: "+c );
}
}
}
Igazában az esetek többségében nem egyedi karakterek érdekelnek, hanem azokból összeállított sorozatok, karaktersorozatok vagyis népszerû angol nevükön stringek. Stringet könnyedén csinálhatnánk karakterekbõl készített tömb segítségével
char string[] = new char[20];
a Jáva azonban elõzékenyen rendelkezésünkre bocsátja a String osztályt, teljes nevén java.lang.String-et. A csomagokról, mint például a java.lang majd a következõ fejezetben olvasunk, most elég annyi, hogy ez az osztály a beépített osztálykönyvtár része és alapból a rendelkezésünkre áll, nem kell érte semmit tennünk, hogy felbukkanjon. A String egy nem megváltoztatható karaktersorozat. Ez azt jelenti, hogy egy Stringet ha egyszer létrehoztunk adott szöveggel, akkor a szöveg a továbbiakban nem változtatható meg. Ez nem tûnik túl ígéretesnek, viszont a megváltoztathatatlan String egyedet felhasználhatjuk további Stringek építésére.
Stringet lértehozni nagyon egyszerû.
String s = new String( "Hello, Jáva" );
A String tehát egy mezei objektum, olyan, amilyeneket eddig láttunk, kivéve, hogy nem nekünk kellett definiálnunk, hanem már megtették helyettünk. Az s egy String egyedre mutató referencia, ilyet is láttunk már. Egy trükk van a képben: a "Helló, Jáva" konstans. A String egy kivételes osztály a Jávában abban a tekintetben, hogy String típusú konstansokat a nyelv közvetlenül támogat. A "Helló, Jáva" igazából létrehoz egy String egyedet, amiben a "Helló, Jáva" string van, majd ennek alapján a new String létrehoz egy új string egyedet. Nem túl hatékony, én is csak azért írtam le, hogy határozottan megmutassam, hogy a String is csak egy objektum. Igazából elegendõ lenne ennyi:
String s = "Hello, Jáva";
Nézzük, mit is csináltunk elõbb. Itt a soha vissza nem térõ alkalom, hogy egy pillantást vessünk a Jáva könyvtár leírására, ami elérhetõ a weben vagy letölthetõ a Sun-tól a JDK weblapjáról (a cikk írásának a pillanatában a Java 2 1.3-as változata a legújabb. Keressük meg innen kiindulva a JDK API dokumentációját és nézzük a String lapját! Megtalálhatjuk a választ, hogyan mûködött az elsõ sor. A Stringnek van egy konstruktora, ami Stringet fogad.
String(String value);
Ezt használtuk fel az imént. Két sorra bontva:
String t = "Hello, Jáva";
String s = new String( t );
De sokat beszéltünk errõl az egyszerû sorról, nézzünk valami érdekesebbet! Tekintsük a következõ jól ismert sort:
public static void main( String args[] ) ...
Hát igen, ez a main definíciója. Most már megérthetjük a paraméter részét is: a main egy Stringekbõl álló tömböt kap, mindegyik Stringben egy paraméter. Ha tehát a programot így hívjuk meg:
java Program P1 P2 P3 P4
akkor a main egy 4-elemû tömböt fog kapni, a tömb elemeiben egy-egy String egyedre van referencia, és ezek a String egyedek sorban a P1, P2, P3 és P4 karaktersorozatokat tárolják. Amint a tömböknél tanultuk, minden tömbnek van egy length nevû egyedváltozója, ami megadja a tömb hosszát. Az összes paraméter kiírása ezek után nem nehéz:
for( int i = 0 ; i < args.length ; ++i )
System.out.println( args[i] );
Írjunk most rövid programot, ami kiírja az összes paraméterét, amiben megtalálható az "al" karaktersorozat. Tehát ezeket mind kiírja: alma, fal, falu de nem írja ki az akol vagy az olaj szavakat. Alaposan végigtanulmányozva a String metódusainak leírását, megtaláljuk az indexOf(String str) metódust, ami megkeresi a String egyedben, amire meghívták a paraméterül kapott stringet és megmondja a pozícióját ill. -1-et ad vissza, ha nem találja. A program igazán egyszerû, de itt a megoldás ellenõrzésképpen. Ha megvan, írd át úgy a programot, hogy a keresett részletet kiemeli úgy, hogy elé és mögé _ jeleket tesz, pl. _al_ma, t_al. Ehhez a substring metódusra lesz szükséged, ellenõrizheted a megoldást itt.
public class AlKeres2 {
public static void main( String args[] ) {
for( int i = 0 ; i < args.length ; ++i ) {
int pos = args[i].indexOf( "al" );
if( pos >= 0 ) {
System.out.print( args[i].substring( 0,pos ) );
System.out.print( "_" );
System.out.print( args[i].substring( pos,pos+2 ) );
System.out.println( "_"+args[i].substring( pos+2 ) );
}
}
}
}
Szép dolog, hogy van egy megváltoztathatatlan String-ünk, de sokkal érdekesebb lenne, ha felhasználhatnánk más String-ek alkotására. Két String-bõl egy harmadikat csinálni Jávában nagyon egyszerû, mert String objektumokra definiálva van a + operátor és összefûzést jelent. Példa:
String s1 = "eleje";
String s2 = "vége";
String s3 = s1 + s2;
A dolog végén s3 az "elejevége" szöveget fogja tartalmazni. Egy baja van ennek a megoldásnak: ha sokat használjuk, jócskán teleszemeteli a memóriát, minthogy minden lépésben egy nem változtatható String keletkezik. A Jáva készítõi a változtatható StringBuffer könyvtári osztállyal oldották meg a problémát.
A StringBuffer arra való, amire a String nem alkalmas: karaktersorozatok manipulálására. A StringBuffer képes a benne levõ karaktersorozat végére egy másikat fûzni, törölni, felülírni a tartalom egy részét vagy beszúrni tetszõleges pontra. A könnyedségnek persze ára van: a StringBuffer több helyet foglal, mint egy String. A legjobb, ha mindkettõt a helyén használjuk és ez nem nehéz, mert mindkettõnek van olyan konstruktora, amely Stringet vagy StringBuffert tud csinálni a másikból. Mondhatjuk tehát:
String s = "Hello";
StringBuffer sb = new StringBuffer( s );
String s2 = new String( sb );
Igazából a Jáva fordító a Stringek összefûzését is StringBufferrel oldja meg: eleje-vége példánknál elõször egy ideiglenes StringBuffer jön létre, ebben összefûzõdik a két String, majd ebbõl legyártódik az s3-ba kerülõ eredménystring. Ha mondjuk egy jóféle hurokban tesszük ezt, könnyedén legyárthatunk néhány száz ideiglenes objektumot.
Feladat
Írj egy programot, ami összefûzi a paramétereit egy Stringgé és ezt a Stringet kiírja. A paraméterek között _ jelek legyenek. Hozz létre egy StringBuffert, ebben fûzd össze a paramétereket, majd az eredményt alakítsd Stringgé és írd ki! Íme a megoldás ellenõrzésképpen.
public class ParameterFuzo {
public static void main( String args[] ) {
StringBuffer sb = new StringBuffer();
for( int i = 0 ; i < args.length ; ++i ) {
if( sb.length() > 0 ) // Ha nem az elso parameter
sb.append( "_" );
sb.append( args[i] );
}
String eredmeny = new String( sb );
System.out.println( eredmeny );
}
}
Ellenõrzõ kérdések
• Mi a karakter?
• Hányféle jelet képes tárolni a Jáva char típus?
• Hogy hívják a Jáva által támogatott karaktertípust?
• Mi a karaktersorozat (string?)
• Mit jelent, hogy a String nem megváltoztatható?
• Hogyan lehet egy Stringnek kezdõértéket adni?
• Mire való a String indexOf metódusa?
• Mire való String substring metódusa?
• Mi a különbség a StringBuffer és a String között?
11. fejezet
A Jáva osztályok is csak fájlok; Jáva osztályok elhelyezése és fellelése. További káoszteremtõ eszközök: package és import. Jó helyek a fájlrendszeren: a CLASSPATH környezeti változó.
Ha tényleg megcsináltad a feladatokat és nem csak végigfutottál a megoldásokon (ebben az esetben nem fogsz megtanulni Jávául), a játékterül szolgáló könyvtáradban jókora zûrzavar lehet már. Az Elso.java-t már legalább háromszor felülírtad különbözõ változatokkal és ki tudja, mi mûködik ott és mi nem. Ha néhány Jáva gyakorlóprogrammal ekkora zûrzavart lehet csinálni, vajon mire lehet képes egy csapat termelékeny programozó? Világos: végtelen káoszra. A káosz látszólagos rendezettségének növelésére a Jáva bevezeti a csomag (package) fogalmát.
A csomag vészesen analóg a fájloknál megszokott fájlnév-könytárnév szerkezettel. Minden fájlnak van egy neve, de ez még kevés az eléréséhez; általában tudni kell azt is, milyen könyvtárban van. Hasonlóképpen van ez a Jáva osztályokkal; általában nem elég, ha a nevüket tudjuk, tudni kell, milyen csomagban vannak. Íme néhány példa Jáva osztálynevekre csomagnevekkel kibõvítve.
java.awt.color.ColorSpace
java.util.Vector
pelda.Elso
Az elsõ kettõ egy-egy ténylegesen létezõ Jáva könyvtári osztályt jelöl. Vegyük a másodikat: ez a java.util csomagban elhelyezkedõ Vector osztály. A harmadik az én agyam szüleménye, de attól még létezhet. Mindjárt meglátjuk, hogyan!
Ha a Jáva forrásfájlba betesszük a package direktívát, a fájlban mögötte deklarált osztályokat abba a csomagba teszi. pl.
package a.b;
class C {
...
}
A fenti varázslat eredménye az, hogy az osztály neve nem egyszerûen C, hanem a.b.C lesz, vagyis az a.b csomagban található.
Mindez nagyon egyszerû, de mi hasznunk ebbõl? Hogyan lehet elérni csodálatos a.b.C osztályunkat? Mi sem egyszerûbb ennél. Ha egy osztályra referenciát akarunk gyártani, az osztály nevét kell használni típusnak. Így ni:
a.b.C ref = new a.b.C();
Egyszerû és elegáns, nemdebár? Egy apró probléma van a dologgal: elõbb-utóbb bele fogunk unni hosszú nevû osztályunk nevének gépelésébe, pláne, ha nem a.b.C az osztály neve, hanem a fent megemlített java.awt.color.ColorSpace és ezzel még egy szelídebb példát választottam. Minthogy a programozók lusta népek, meg lehet ezt úszni kevesebb gépelésbõl is. Ha a programunk elejére odaírjuk az import direktívát, megadhatjuk, milyen osztályoknál vagy csomagoknál nem akarjuk kiírni a teljes nevet. Például mondhatjuk azt, hogy
import a.b.C;
Ezzel kijelentettük, hogy az a.b.C osztályra egyszerûen, mint C-re kívánunk hivatkozni ebben a forrásfájlban. Leírhatjuk, hogy
C ref = new C();
Mindjárt barátságosabb. Ennél többet is tehetünk, azt is mondhatjuk, hogy a csomagban található összes osztályt egyszerûsített formában akarjuk látni.
import a.b.*;
Ez az a.b csomag összes osztályát egyszerûsített formában teszi elérhetõvé.
Nagyon fontos megjegyezni, hogy az import direktíva csupán azt befolyásolja, hogyan oldja fel a fordító az osztályneveket. Akármit is varázsolunk, a lefordított kódba mindenképpen az a.b.C-re való hivatkozás kerül be, import ide vagy oda. Az import ugyancsak nem tölt be semmit se a tárba, mint azt naívan hinnénk. Szerepe csupán a gépelés csökkentésében van, futásidõben semmit se számít.
Még egy fontos megjegyzés: a fordító minden Jáva forrásfájl elejére odaképzel egy import java.lang.*; direktívát, így a java.lang csomagot mindig importálja. A java.lang csomagban régi barátaink vannak, pl. a java.lang.Integer, aminek parseInt metódusához oly sok kedves élményünk fûzõdik, vagy a java.lang.Math, amivel a kapcsolat már tényleg mélyebbé vált egyszerû barátságnál.
De vissza a package-re! Tekintsük a következõ programot!
package pelda;
public class Elso {
public static void main( String args[] ) {
System.out.println( "Elso Java programunk" );
}
}
Mi lenne ez más, mint népszerû Elso programunknak egy egyszerûsített változata azzal a csavarral, hogy a programot befoglaló osztályt pelda.Elso-nek neveztük el. Fordítsd le fürgén a javac Elso.java paranccsal, majd futtasd le a java pelda.Elso utasítással. (ne feledd: a java parancsnak a futtatandó osztály nevét kell megadni, ebben az esetben pelda.Elso-t). Ilyen egyszerû egy osztályt csomagba tenni.
Eeeee ... valami nem stimmel? A java pelda.Elso hatására nem az unalomig ismert kiírás jelent meg, hanem az, hogy a pelda/Elso osztály nem található. Mindez arra világít rá, hogy még mindig nem tudunk mindent az osztályok betöltõdésérõl.
Eddig ezzel nem sokat törõdtünk. A lefordított .class fájlokat az aktuális könyvtárban tároltuk és elvártuk, hogy a virtuális gép megtalálja õket. A csomagok bevezetésével a helyzet megváltozik és meg kell ismerkednünk egy osztályt tartalmazó .class fájl fellelésének módjával. Amikor a virtuális gép meglát egy osztálynevet, azonnal elkezdi keresni a CLASSPATH környezeti változó által megadott könyvtárakban. Ha esetleg nem tudnád, mi a környezeti változó, íme egy kis bevezetõ:
2. kitérõ: környezeti változók
2. kitérõ: környezeti változók
Operációs rendszereknek gyakori szolgáltatása a környezeti változók lehetõsége. Ez azt jelenti, hogy egy futó alkalmazás elérhet és módosíthat egy adag, névvel azonosított változót. A változók értéke mindig karaktersorozat. Ha Windows-on meg akarod nézni az aktuálisan érvényes készletet, add ki a set parancsot. Te magad is beállíthatsz egyet pl. így:
set ENYIM="ez az en valtozom"
Ha most újra kiadod a set parancsot, a listában megtalálod az ENYIM változót is.
A környezeti változók értelme függ a rendszerben futó programoktól. Bizonyos alkalmazások és magához az operációs rendszerhez tartozó programok kiolvasnak bizonyos környezeti változókat és az értékük szerint mûködnek. Ezen változók módosítása megváltoztatja a rendszer mûködését. Jó példa a PATH nevû változó, amiben azok a könyvtárak vannak felsorolva (Microsoft operációs rendszerek esetén pontosvesszõvel elválasztva), ahol a rendszer keresi az elindítandó programokat tartalmazó fájlokat. Ha egy könyvtárat ráteszünk a PATH listájára, a benne levõ .exe (vagy egyéb végrehajtható) fájlokat is megtalálja a rendszer a könyvtár megadása nélkül.
ENYIM nevû változónk persze nem érdekel senkit, így aztán beállítása semmiféle változást nem okoz, hacsak nem írunk egy programot, ami kiolvassa.
A Jáva a CLASSPATH környezeti változót használja a következõ módon. A CLASSPATH értéke könytárak sorából áll (Windows-on pontosvesszõvel, Unix-on kettõsponttal elválasztva). Amikor a rendszer keresni kezdi az a.b.C nevû osztályt, veszi az elsõ könyvtárat a CLASSPATH-en. Ehhez hozzáfûzi a csomag nevét, így lesz egy alkönytárnév és ebben keresi meg az osztálynév.class fájlt. Ha nem találja, továbblép a következõ könyvtárra a CLASSPATH-en és ezt addig csinálja, amíg a könyvtárak a CLASSPATH-en el nem fogynak. Ha nem tudta megtalálni azt osztályt a CLASSPATH-en felsorolt összes könyvtárban, hibajelzést küld.
Lássunk egy egyszerû példát! Tegyük fel, hogy a CLASSPATH értéke a következõ:
c:\Users\javadev;c:\Users\javalib
Tegyük fel továbbá, hogy a rendszer az a.b.C nevû osztályt keresi. Veszi tehát az elsõ könyvtárat a CLASSPATH-en, a c:\Users\javadev-et. Ehhez hozzáilleszti a csomagnevet, vagyis az a.b-t és kapja a c:\Users\javadev\a\b könyvtárat. Itt megpróbálja fellelni a C.class fájlt. Tegyük fel, hogy nem találta meg. Ekkor továbblép a c:\Users\javalib-re és megpróbálja ugyanezt, vagyis felkeresi a c:\Users\javalib\a\b\C.class fájlt. Ha ez megvan, siker. Ha nem, hibaüzenet.
Hosszadalmas magyarázat után vissza a pelda.Elso alkalmazásra! Ha figyelmesen megtanulmányozod a fájlokat a könyvtáradban, rájöhetsz, hol a hiba: a Jáva fordító az aktuális könyvtárba pakolta le a .class fájlt. Ez egy hülye tulajdonsága a javac-nak, amely a Jáva elsõ változata óta kísért. Tedd helyre olymódon, hogy létrehozol egy pelda alkönyvtárat és átmozgatod az Elso.class-t ebbe. Tedd meg! Ha most kiadod a java pelda.Elso parancsot, két dolog történhet: vagy mûködik, vagy nem. Sohase tudtam rájönni, miért, de bizonyos Jáva installációk ilyenkor képtelenek megtalálni a kurrens könyvtárban elhelyezkedõ alcsomagokat, csak ha varázsolunk a CLASSPATH-szel. He netán ez a hiba ütné fel undok fejét, vedd fel az aktuális könyvtárat a CLASSPATH-re.
set CLASSPATH=.
mondd a parancspromptnál Windows-on. Ha most megismétled a parancsot, mûködnie kell.
A javac-nak a tulajdonsága, miszerint mindenképpen az aktuális könytárba pakolja a .class fájlt, még ha az valamilyen csomagba fordítódik igazán kellemetlen és erre a Sun-nál is rájöttek. A Javac-ot meghívhatod a nagyszerû -d kapcsolóval, ekkor létrehozza a csomagok alkönyvtárait és mindent a helyére pakol. Próbáld ki! Töröld le az elõbb létrehozott pelda alkönyvtárat a benne levõ Elso.class-szel együtt, majd mondd azt:
javac -d . Elso.java
ekkor a pelda alkönyvtár rendben létrejön az aktuális könyvtárban és belekerül az Elso.class. Ekkor már probléma nélkül lehet futtatni a java pelda.Elso paranccsal.
A csomagok kezelésének a lehetõsége apróság a Nagy Egészben, de igazán fontos õket ismerni, ha bele akarunk kalandozni a Jáva osztálykönyvtárba.
Ellenõrzõ kérdések
1. Mi a hasonlóság az alkönyvtárak és a csomagok között?
2. Hogyan kell egy Jáva osztályt egy adott csomagba tenni?
3. Mit jelent a package direktíva és mi a paramétere?
4. Hogyan lehet egy csomagban levõ osztályt elérni?
5. Mit egyszerûsít az import direktíva?
6. Hogyan leli fel a virtuális gép a class fájlokat?
7. Mi a CLASSPATH környezeti változó?
8. Mi történik, ha a CLASSPATH értéke c:\Users\javalib;c:\Users\enyem és az a.b.C nevû osztályt keressük?
9. Hova teszi a javac a lefordított osztályokat, ha egyszerûen csak a forrásfájl nevével hívjuk meg?
10. Hogyan lehet elérni, hogy a javac a csomagokat a megfelelõ alkönyvtárba tegye?
12. fejezet
Mindenki a saját hibáinak kovácsa: személyre szabott hibajelzések a Jávában. Kivételek élete és halála: throw utasítás, a throws kulcsszó valamint a try-catch blokk.
Feladat
Írjunk programot, ami kiírja a bementi paraméter faktoriálisát. Ha emlékszel, n faktoriálisa az 1-tõl n-ig terjedõ egész számok szorzata, 0! = 1. Negatív számokra a faktoriális nincs értelmezve. A faktoriális értékek rendkívül gyorsan nõnek, így 20!-nál nagyobb értékek a long típusból (az int 64 bites változatából) is kilógnak. Írj egy függvényt, amelyiket meg lehet hívni egy egész számmal és visszaadja a faktoriális értéket! A függvény adjon vissza hibajelzést, ha érvénytelen bemeneti értéket kapott. A main függvény hívja meg ezt a faktoriálisszámító függvényt az args[0]-ban kapott bemeneti paraméterrel (Integer.parseInt ...) majd írja ki a hibaüzenetet, ha a faktoriálisszámító függvény hibajelzést adott vissza, különben írja ki az eredményt. A feladat nem okozhat gondot, íme a megoldás.
/* Faktorialisszamito program */
public class Faktorialis {
static long fakt( int k ) {
if( k < 0 || k > 20 )
return -1;
long tmp = 1;
for( int i = 2 ; i <= k ; ++i )
tmp *= i;
return tmp;
}
public static void main( String args[] ) {
long r = fakt( Integer.parseInt( args[0] ) );
if( r < 0 )
System.out.println( "Hibas bemeneti parameter: "+args[0] );
else
System.out.println( args[0]+"! = "+r );
}
}
A programban valóban nincs semmi újdonság. Ugye, a 9. lecke után nem lep meg, hogy a fakt függvénynek szintén statikusnak kell lennie? (mivel a statikus main-ból hívjuk) A fakt függvény önmaga nem írhatja ki a hibajelzést (hiszen a függvényben nem tudhatjuk, egyáltalán szöveges hibajelzést kell-e adnunk, így szólt a feladat kitûzése), ezért negatív értékkel jelez hibát. Mivel a faktoriális nem lehet negatív szám, ezt észreveheti a hívó függvény és hibát jelezhet.
Nem túl ronda ez? De igen. Összekeveredik itt két dolog, a függvény visszatérési értéke és a hibakód. A mostani helyzet viszonylag egyszerû, mivel itt egy hibaeset van és a függvény bizonyos számokat nem adhat vissza, nehezebb lenne azonban a dolgunk, ha nem lenne ilyen "üres tartomány" a visszatérési értékek között és több hibakódunk lenne. A hibák kultúrált jelzésére a Jáva az kivétel (exception) eszközét adja.
A kivételt úgy lehet felfogni, mint egy vészféket. Ha a program futása során kivétel keletkezik (akár a rendszer generál egyet, akár mi generáljuk) a futás megszakad és a Jáva virtuális gép a kivétel feldolgozásával folytatja a tevékenységét. Hogy ez pontosan hogyan zajlik, arról egy kicsit késõbb. Most nézzük meg, hogyan generálhatunk kivételt. Ez roppant egyszerû.
throw new IllegalArgumentException();
A throw utasítás paramétere egy kivételobjektum. A kivételobjektum pont olyan mezei objektum, mint a többi; egyetlen speciális tulajdonsága van: a java.lang.Exception könyvtári objektum leszármazottjának kell lennie. A példánkban szereplõ java.lang.IllegalArgumentException is ilyen és õ is része az alap objektumkönyvtárnak, tehát bátran használhatjuk minden további nélkül. Ugye, emlékszel, hogy a minden Jáva program elejére egy import java.lang.*; utasítást képzel oda a fordító, barátainkat tehát nyugodtan hívhatjuk Exception-nek és IllegalArgumentException-nek.
Kivételekbõl lehet de-luxe változatot is létrehozni, ha úgynevezett részletezõ üzenettel (detail message) hozzuk õket létre. Van ugyanis nekik egy olyan konstruktoruk, ami egy String-et fogad, itt passzolható át a részletezõ üzenet. Ennek a szerepe csupán csak annyi, hogy informaciót közvetíthet a kivételt megkapó számára, pontosan mi is volt a baj. Példa:
throw new IllegalArgumentException( "Az argumentum túl kicsi" );
A metódusban generált kivételeket mindig deklarálni kell a metódus fejlécében, erre való a throws klauza. Példa:
void func() throws IllegalArgumentException {
...
throw new IllegalArgumentException();
...
}
A Jáva fordítónak mindig megvan a módja rá, hogy összehasonlítsa a metódusban dobott kivételeket a throws után felsoroltakkal (vesszõvel elválasztva tetszõleges számú kivételt felsorolhatunk) és ha eltérést lát, a maga egyenes, de kissé nyers módján hibaüzenetet küld. Próbáld ki!
Feladat
Írd át a faktoriálisszámító programot úgy, hogy a fakt metódus IllegalArgumentException-nel jelezze, ha a metódus hibás bemeneti paramétert kapott. Emitt a megoldas. Futtasd le a programot néhány hibás bemenõ értékkel és ellenõrizd az eredményt!
/* Faktorialisszamito program */
public class Faktorialis2 {
static long fakt( int k ) throws IllegalArgumentException {
if( k < 0 )
throw new IllegalArgumentException( "Tul kicsi bemeneti parameter" );
if( k > 20 )
throw new IllegalArgumentException( "Tul nagy bemeneti parameter" );
long tmp = 1;
for( int i = 2 ; i <= k ; ++i )
tmp *= i;
return tmp;
}
public static void main( String args[] ) {
long r = fakt( Integer.parseInt( args[0] ) );
if( r < 0 )
System.out.println( "Hibas bemeneti parameter: "+args[0] );
else
System.out.println( args[0]+"! = "+r );
}
}
Ilyen választ fogsz kapni:
Exception in thread "main" java.lang.IllegalArgumentException: Tul kicsi
bemeneti parameter
at Faktorialis2.fakt(Faktorialis2.java:5)
at Faktorialis2.main(Faktorialis2.java:15)
Ez egy hívási verem lista. Azt mondja meg, hogy az elsõ sorban részletezett kivétel a Faktorialis2 osztály fakt metódusában keletkezett, majd továbbterjedt a main metódusba (methogy a fakt-ot innen hívták meg) és ezzel elérte a hívási verem tetejét, a program futását megszakította. Ez azért volt lehetséges, mert primkó programunkban nem okozott gondot, hogy a kivétel megszakítsa a program futását. Normális esetben azonban ennél kifinomultabb kivételkezelésre van szükség és erre való a try-catch blokk.
A try-catch szintaktikája a következõképpen néz ki:
try {
... utasítások ...
} catch( Exception1 e1 ) {
... Exception1 típusú hibát lekezelõ utasítások ...
} catch( Exception2 e2 ) {
... Exception2 típusú hibát lekezelõ utasítások ...
} finally {
... Az egész blokk legvégén (hiba bekövetkezésétõl függetlenül) végrehajtódó utasítások ...
}
A try után bekövetkezõ blokkban vannak az utasítások, amelyek Exception1 vagy Exception2 típusú hibát generálnak. A fordító ellenõrizni fogja, tényleg megvan-e erre a lehetõség (minthogy a meghívott metódusok throws klauzájából tudja, azok milyen kivételt dobhatnak), ha valamelyik kivétel nem következhet be, hibaüzenetet ad. Ha nem történik hiba, a try blokk rendben végrehajtódik és a finally mögött levõ blokkal folytatódik a program futása. Ezen blokk végrehajtása után a szerkezet végetér.
Ha azonban a try blokkban kivétel következik be, a blokk futása megszakad és a kivételobjektum (amit a throw-val generáltunk) beíródik a megfelelõ catch blokk referenciaváltozójába. Ha például Exception1 következett be, az e1 mutat a kivételobjektumra, amikor a catch blokkba kerülünk és a futás az Exception1 catch blokkjában folytatódik. Ezt elvégezve a finally blokkja kerül sorra, majd elhagyjuk a szerkezetet. Egynél több catch blokk és a finally blokk opcionális. Legegyszerûbb formájában a szerkezet így néz ki:
try {
... utasítások ...
} catch( Exception1 e1 } {
... Exception1 típusú hibát lekezelõ utasítások ...
}
Mostanra eljutottunk oda, hogy le tudjuk írni, pontosan mi történik egy kivétel keletkezésekor. Kivétel keletkezésekor a program futása megszakad és megszakad a hívóé is, ha nem definiált try-catch blokkot a kivételre, hanem a throws klauzával azt jelezte, hogy tovább akarja dobni. Így megy ez addig, amíg a hívási lánc tetejére nem érünk (mint elõzõ példánkban) vagy bele nem ütközünk egy aktív trí-catch blokkba. Ekkor a try blokk megszakad és a futás a megfelelõ catch blokkon folytatódik. A fentiekbõl következik, hogy ha egy metódust meghívunk, ami dobhat valami kivételt és ezt jelzi is a throws klauzájában, akkor két eset lehetséges: vagy elkapjuk try-catch-csel, vagy továbbdobjuk throws-szal. Harmadik eset nincs, az összes további próbálkozás szintaktikai hiba.
Egy teljesen logikus dolgot megemlítenék, amiba kezdõ, de még tapasztaltabb Jáva programozók is beleesnek: a try blokk minden utasítását úgy tekinti a fordító, hogy az esetleg nem hajtódik végre, ezért a try blokkban elkövetett változóinicializálásokat nem veszi figyelembe, amikor azt nézi, hogy inicializálták-e a változót. A következõ tehát szintaktikai hiba:
int i;
try {
i = 1;
...
} catch( Exception1 ex ) { ... }
System.out.println( "i: "+i );
A System.out.println soránál a fordító figyelmeztetni fog, hogy az i változót nem inicializáltuk. Ezt a megnyugtatására meg kell tennünk, tehát int i = 0; szükséges.
Mielõtt leckénk zárófeladatához érkeznénk, még egy pár szót a nem jelzett kivételekrõl. Ezek valóban kivételesek, mert általában rendszerszintû programrészek generálják õket gépi kódú részekbõl. Sok esetben nincsenek throws-szal jelölve, hiszen nagyon sokféle utasítás végrehajtásakor keletkezhetnek. Ilyen pl. minden Jáva programozó legõszintébb barátja, a NullPointerException, ami akkor lesz, ha null értékû referenciát akarunk felhasználni egy objektumhivatkozásban. Ezt nem metódusok dobják, hanem ártalmatlan sorok pl. ref.valtozo formájú hivatkozás. Nem jelzett kivételeket is elkaphatunk, ha a problémás környékre egy try-catch blokkot telepítünk a catch ágban minden kivétel õsével, az Exception típussal. Példa:
Object o = null;
try {
o.toString();
} catch( Exception ex ) {
... NullPointerException-t lekezelõ programrész ...
}
Feladat
Bõvítsd ki elõzõ programunkat úgy, hogy a keletkezõ kivételt a main-ben kapd el és írd ki valami kultúrált formában. Használd az Exception objektum getMessage() metódusát, amivel megszerezheted a részletezõ üzenetet a catch blokkban. Emitt a megoldás.
/* Faktorialisszamito program */
public class Faktorialis3 {
static long fakt( int k ) throws IllegalArgumentException {
if( k < 0 )
throw new IllegalArgumentException( "Tul kicsi bemeneti parameter" );
if( k > 20 )
throw new IllegalArgumentException( "Tul nagy bemeneti parameter" );
long tmp = 1;
for( int i = 2 ; i <= k ; ++i )
tmp *= i;
return tmp;
}
public static void main( String args[] ) {
try {
long r = fakt( Integer.parseInt( args[0] ) );
if( r < 0 )
System.out.println( "Hibas bemeneti parameter: "+args[0] );
else
System.out.println( args[0]+"! = "+r );
} catch( IllegalArgumentException ex ) {
System.out.println( "Ervenytelen argumentum: "+ex.getMessage() );
}
}
}
http://www.bakaibalazs.hu/2014/11/oracle-1z0-804-vizsga-feladatok.html
http://subecz.pe.hu/JavaProgramozas/JavaProgramozas.php
https://www.webotlet.hu/?p=69
http://www.informatika-programozas.hu/informatika_java_programozas_elmelet_adattipus_2_tomb.html
http://www.4kor.hu/mellekletek/AngsterErzsebet_Java1.pdf
http://www.inczedy.hu/~szikszai/oop/oop.html
http://www.ms.sapientia.ro/~manyi/teaching/oop/oop.pdf
https://duende.itk.ppke.hu/~ppolcz/index.php/main/view/anal3.php
Operátorok
Aritmetikai operátorok
+-*/
Relációs operátorok
Összehasonlító operátorok
!= nem egyenlő
== egyenlő true false
a%b==0 maradék nélkül
Logikai operátorok
&& and || or
true && true = true
true && false = false
true || false =true
false && false = false
Feltételek
if (a<5)
Sytem.out.println("rossz");
else Sytem.out.println("jo");
For ciklus
for(int a=0; a< 10; a++)
osztályszintű nagybetű
bemenet kezelés
import java.util Scanner;
Scanner bemenet=new Scanner (system.in);
String nev=bemenet.nextLine();
if (Integer.parse.Int(); < 5)
numerikus
Tömbök
dinamikus tömbök
Bitléptető és bitenkénti logikai operátorok
Értékadó operátorok
Egyéb operátorok
Logikai operátorok
public class Elso {
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso();
e.prog( args );
}
}
Mentsd el a fenti szöveget az Elso.java nevû fájlba. Fontos, hogy a fájl neve mindenképpen ez legyen! Jóval késõbb fogjuk kifejteni, miért , de a forrásban a "public class" mögött levõ névvel megegyezõnek kell legyen a forrásfájl neve .java kiterjesztéssel. Most mondd azt:
javac Elso.java
Ha minden jól ment, a parancs némi motozás után üzenet kiírása nélkül befejezi a futását. Ha most megnézed a könyvtárat, ahol a forrásszöveg volt, találsz egy Elso.class nevû fájlt. Ez a lefordított Jáva program, ebben van benne a programod Jáva bájtkódban. Most már csak egy Jáva virtuális gépet kell találnunk, ami le is futtatja. A java parancs pont ezt tudja.
java Elso
És megjelenik a következõ szöveg:
Hello, ez az elso Java programunk!
Hihetetlen, már mûködõképes programot írtunk! Igaz ugyan, hogy az egészbõl nem értünk semmit, de elsõ lépésnek nem rossz! A sok kékséggel most ne foglalkozzunk, egy sort azért elmagyarázok. A System.out.println arra jó, hogy amit a mögötte levõ zárójelek közé írtunk, kiírja a konzolra. Az idézõjelek fontosak: ez mondja meg a fordítóprogramnak, hogy amit mögötte lát, az nem programszöveg, nincs értelme a számítógép számára, csak egy, a gép számára értelmetlen, önkényes karaktersorozat, vagyis betûk, számok és szimbólumok sora. Errõl bõvebben majd egy késõbbi fejezetben! Végül az utasítást egy pontosvesszõ zárja, ez kötelezõ. Minden Jáva utasítás végén pontosvesszõ kell legyen.
Azt tanultuk a fejezet elején, hogy a számítógépnek a program elõre mondja meg, mit kell majd csinálnia. Ugyan a mi kis programunk elindításakor már így is jó sok mindent csinál (el se tudod képzelni, mennyi mindent!), mégis, demonstráljuk ezt a képességét most szuperprogramunk egy jelentõs bõvítésével: írassunk ki vele egy újabb sort.
public class Masodik {
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
System.out.println( "Ez pedig egy masodik sor tole" );
}
public static void main( String args[] ) {
Masodik e= new Masodik();
e.prog( args );
}
}
Másoljuk ki a szövegbõl a szövegszerkesztõbe, mentsük el, fordítsuk le és futtassuk. (Most utoljára súgok: Copy-Paste Notepad-be, elmenteni a Notepad-bõl Masodik.java néven, javac Masodik.java, java Masodik). A futtatási eredmény nem meglepõ:
Hello, ez az elso Java programunk!
Ez pedig egy masodik sor tole
Figyeljük meg, hogy a két szöveg egymás alá íródott ki. Ez a System.out.println tulajdonsága, ez minden, általa kiírt karaktersorozat után egy soremelést is kiír. A soremelés egy olyan speciális karakter, ami a következõ karakter kiírása pozícióját egy új sor elejére lépteti.
Feladat:
írasd ki a saját nevedet a számítógéppel!
Ezzel a lecke végére is értünk. Jókora utat jártunk be, a számítógép fogalmától eljutottunk egy mûködõképes Jáva programig. A következõ fejezetben bemutatjuk, miféle számításokat tudunk végeztetni szilíciumagyú barátunkkal.
Ellenõrzõ kérdések
1. Mi a számítógépes program?
2. Mi a processzor szerepe a számítógépben?
3. Mi az operációs rendszer?
4. Miért szükségesek fordítóprogramok?
5. Miért elõnyös a McDonald's?
6. Mi a különbség a gépi kód és a Jáva bájtkód között?
7. Mi a Jáva virtuális gép?
8. Milyen parancs indítja el a Jáva fordítóprogramot?
9. Milyen parancs indítja el a Jáva virtuális gépet?
10. Mi az a Jáva szerkezet, amivel a konzolra lehet írni?
11. Mit jelent a karakter fogalma?
12. Mi az a soremelés karakter?
2. fejezet
Fiókos szekrény szilíciumból. A számítógép mindent képes tárolni, csak mi nem felejtsük el, mit hová tettünk: a változók. Fiókméretek és adattípusok. Két egyszerû adattípus, az egész és a lebegõpontos. Néhány alapmûvelet kezdetnek.
A számítógép olyan, mint a mamátok fiókos szekrénye. Ez egy nagyon jó hasonlat, magamtól nem is tudtam volna kitalálni, Roger Kaufman örökbecsû FORTRAN képeskönyvébõl vettem. FORTRAN-t ma már kevesen használnak, de a könyv még ma is a legjobb bevezetõ kezdõknek a számítógépes programozásba. De vissza a fiókos szekrényhez! A szekrénynek, akarom mondani a gépnek rengeteg fiókja van a legkülönbözõbb cuccok tárolására. A gép persze nem zoknikat vagy több éves villanyszámlákat tárol, hanem adatokat. Hogy a sok fiók között ne vesszünk el, megcímkézzük a fiókokat, egyszerû és találó neveket adunk nekik. Egy ilyen fióknév egy változó neve, tartalma pedig a változóban tárolt érték.
A változó egy igen fontos fogalom, ezért lássunk néhány hasonlatot. Pénztárcánkban levõ pénz változó mennyiség, ez megfelel a változó értékének. Egy konkrét pénztárca megfelel a változónak magának, holott pénztárcák általában nincsenek felcímkézve. Vagy vegyünk egy teherautót: a rajta levõ sóder mennyisége változó érték, a teherautó rendszáma pedig azonosítja, melyik értéket keressük.
A változó egy analógia, ami lehetõvé teszi nekünk, hogy a gép sok milliónyi memóriarekeszébõl kiválasszuk a minket érdeklõt. Egy változó mindig egy adag memóriarekeszre vonatkozik (hogy mennyire, azt majd az adattípusok tárgyalásánál meglátjuk) és a Jáva nyelv azt a kényelmességet adja nekünk, hogy erre az adag memóriarekeszre egy általunk választott névvel hivatkozhatunk. A névnek egyedinek kell lennie és betûvel kezdõdnie, a további karakterei lehetnek betûk, számok vagy aláhúzásjel. Néhány példa érvényes változónevekre:
• i
• j17
• PI
• nagyon_hosszu_valtozonev
Itt van három érvénytelen is:
• 32ev
• a$
• nagyon hosszu valtozonev
A Jáva nyelv különbözõnek veszi a kis- és nagybetûket, így a pi és a PI két külön változót jelöl.
Mint ahogy a fiók kiválasztásánál is fontos, hogy cetliket tárolunk-e benne vagy lábosokat, úgy a számítógépnek is tudnia kell, mennyi memóriát kell egy változóhoz rendelnie. Ezt az határozza meg, milyen típusú adatokat tárolunk a fiókban, akarom mondani a memóriarekeszben. A tárolni kívánt adat típusát a változó adattípusának megadásával kell meghatároznunk, hasonló módon, mint ahogy a változó nevét is a változónévvel. A Jáva nyelvnek van néhány beépített adattípusa továbbá a képessége, hogy új adattípusokat hozhasson létre a programozó. Most csak két adattípus vizsgálunk meg, az egész és a lebegõpontos típust.
Az egész típus a számítógépünk legalapvetõbb típusa, leginkább ezzel szeret számolni a gép, ezért mi is használjuk, ahol csak lehet. Az egész típusú szám 0-tól nagyjából 2 milliárdig nõhet és csökkenhet pozitív és negatív irányban egyaránt és nevéhez illõen törtrésze nem lehet. A típust az int kulcsszó leírásával deklaráljuk. Pl.:
int i;
int K,nagyon_hosszu_valtozonev;
A deklaráció a Jáva nyelvben kötelezõ. Minden változót elõbb deklarálni kell és csak aztán lehet használni. Deklarálás bárhol lehet a programban a változó felhasználása elõtt, tehát akár a program közepén is. A deklarálás hatására a változóhoz hozzárendelõdik a tárterület, ahol a változó értékét tárolja gépünk, de a változó értékérõl semmit sem tudunk. Sõt mi több, a Jáva fordító, ha felderíti, hogy olyan változót használtunk, aminek az értékérõl még nem rendelkeztünk, hibaüzenetet küld.
De mit beszélek én itt a változó értékének megadásáról és a változó felhasználásáról, amikor még csak a deklarálást ismerjük? Ha egy változót már deklaráltunk, következõ lépésben adjunk neki gyorsan értéket. Pl.:
i = 3;
K = -10;
Ez az értékadó utasítás. Fontos, hogy ne tekintsd az értékadó utasítást a matematikából ismert egyenlõség teljes szinonímájának. A matematikai egyenlõség azt fejezi ki, hogy két kifejezés a levezetés teljes idõtartamára bizonyos feltételek mellett megegyezik. Ha tehát y = 2x, akkor ez így van bármilyen x-re a megadott peremfeltételek mellett. Az értékadó utasítás azonban, hogy a FORTRAN képeskönyvbõl orozzak megint, egy "bemén" jel, tehát abban a pillanatban, amikor kiértékelõdik, a jobb oldalán egy szám lesz, ami beleíródik, "bemén" a változó mögött meghúzódó memóriába csak egyszer és akkor, amikor az utasítás végrehajtódik. Ha késõbb frissíteni akarjuk a változó értékét, újra végre kell hajtanunk egy értékadó utasítást. Ha tehát én azt mondom, hogy y = 2*x, akkor a gép veszi az x változó értékét, megszorozza kettõvel és beteszi az y változóba (figyelj, mostantól slamposabban fogalmazok! eddig mindig hangsúlyoztam, hogy a változónév mögött hozzárendelt memória húzódik meg, de remélhetõleg ez már jól a fejedbe vésõdött. Mostantól csak azt mondom, hogy, beleíródik a változóba). Ez csak egyszer és az x aktuális értékével történik meg, ha x késõbb megváltozik és fontos, hogy ezt y is kövesse, újra végre kell hajtani az értékadást.
Suttyomban megint elõreszaladtam egy kicsit és leírtam egy Jáva kifejezést. Végre is erre tartjuk a számítógépet, számoljon gyorsan. A Jáva program is mindent képes kiszámolni, ami leírható egy véges mûveletsorozattal, azaz algoritmussal. A Jáva program jónéhány mûveletet képes számokkal elvégezni, ezek közül nézzük most a négy alapmûveletet.
• + : összeadás
• - : kivonás
• * : szorzás
• / : osztás
A mûveletek a matematikában megszokott módon precedenciával rendelkeznek, tehát zárójelek nélkül a szorzás és az osztás "erõsebb" az összeadásnál és a kivonásnál. Ezen (szokásos módon) zárójelekkel változtathatunk. Pl. a 5+2*10 értéke 25, míg az (5+2)*10 értéke 70. Zárójeleket tetszõleges mélységben ágyazhatunk egymásba, csak párosan legyenek. A kifejezésekben a konstansokat, esetünkben egész számokat és a változókat egyenértékûen használhatjuk. Ha tehát az y változóba az x változó kétszeresét akarnád tenni, a helyes válasz: y = x*2. Lássuk ezt egy mûködõ programban is!
public class Szamit1{
public void prog( String args[] ) {
int x,y;
x = 5;
y = x*2;
System.out.println( "Eredmeny: "+y );
}
public static void main( String args[] ) {
Szamit1 e = new Szamit1();
e.prog( args );
}
}
Nem hiszem, hogy a System.out.println során kívül bármi is magyarázatot igényelne. A már megismert karaktersorozat-konstanshoz (az idézõjelek között levõ részhez) hozzáfûztük az y változót, ami egy egész. Ekkor a Jáva fordító automatikusan olyan kódot fordít, ami az y-ban levõ értéket karaktersorozattá alakítja és hozzáfûzi a karaktersorozat-konstanshoz. Tehát az eredmény:
Eredmeny: 10
Feladat
írj egy olyan programot, ami kiírja 2 elsõ nyolc hatványát. A kiírási kép legyen tehát:
2^0 = 1
2^1 = 2
...
2^7 = ....
Oldd meg a feladatot önállóan, majd a saját programodat hasonlítsd össze a megoldással .
Ugye nem ért váratlanul az elõzõ feladatban az n=n+1 típusú értékadás? Matematikailag ez egy teljes csõd, de számítógépül annál több értelme van: vedd az n értékét, adj hozzá egyet, majd tárold vissza n-be, vagyis növeld meg n-t eggyel.
Most csavarjunk egyet a dolgon es 0-tol kezdve negatív irányba haladjunk kettõ hatványaival. Vagyis a kiírási kép legyen:
2^0 = 1
2^-1 = 0.5
...
(Ugye emlékszel: valaminek a negatív hatványa megegyezik a pozitív hatvány reciprokával. Vagyis: 2-4 = 1/24 ).
Ha programunkat lelkesen megmódosítanánk úgy, hogy az összes n = n + 1-et n = n - 1-re és az összes x = x * 2-t x = x / 2-re cserélnénk, szomorú nyomtatási kép fogadna.
2^0 = 1
2^-1 = 0
2^-2 = 0
...
ami egyértelmûen helytelen. A baj okára magad is rájöhetsz: egész számok osztása során elvesznek a törtrészek és a 1/2 osztás 0.5 eredménye mint 0 tárolódik vissza x-be. Egészekkel ezt a problémát nem tudod megoldani, szükségünk lesz a Jáva egy másik alaptípusára, a lebegõpontos típusra. A lebegõpontos változót így deklarálod:
double pi;
double z0,z1;
és így adhatsz neki értéket:
pi = 3.1415927;
A lebegõpontos szám már elég nagy ahhoz, hogy nagyobb számokat írjál le vele, mint amit kényelmesen be tudsz csépelni egy sorba, ezért jól jöhet a tudományos számábrázolás. Ha emlékszel, ekkor a számot egy 0-10 közötti szám és egy 10 hatvány szorzataként írjuk le. Pl: 2500 = 2.5*1000 = 2.5*103. Jávában ezt úgy kell leírni: 2.5E3. Tehát:
z0 = 2.5e3;
Persze ha nem akarod ezt a kis számot exponenciális ábrázolásban leírni, nem kell.
z1 = 2500;
A double-ban leírható maximális szám kb. 10308 , a legkisebb 10-308, ugyanez persze negatív irányban is. Ekkora számokkal a gép persze jóval lassabban számol, mint egészekkel, így double-t csak akkor használjunk, ha szükséges.
public class kettohatvany {
public void prog( String args[] ) {
int n,x;
n = 0;
x = 1;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
System.out.println( "2^"+n+"="+x );
}
public static void main( String args[] ) {
kettohatvany e = new kettohatvany();
e.prog( args );
}
}
Feladat
Írjuk meg most az elõzõ, balsikerû feladványunkat a kettõ negatív hatványaival úgy, hogy x-et egész helyett double-nak deklaráljuk. Próbáld meg magad megírni, majd ellenõrizd a megoldást .
public class kettonhatvany {
public void prog( String args[] ) {
int n;
double x;
n = 0;
x = 1;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
n = n - 1;
x = x / 2;
System.out.println( "2^"+n+"="+x );
}
public static void main( String args[] ) {
kettonhatvany e = new kettonhatvany();
e.prog( args );
}
}
Ennyit elsõ közelítésben a számokról és a velük való mûveletekrõl. A Jáva persze jóval több adattípussal és mûvelettel rendelkezik, mint amit eddig láttunk, de a következõ részben inkább a program végrehajtását befolyásoló szerkezetekkel foglalkozunk.
Ellenõrzõ kérdések
1. Mi az összefüggés a változó neve és értéke között?
2. Hogy jön a fiókos szekrény a számítógéphez?
3. Mit jelent, hogy egy változót deklarálni kell?
4. Hol kell lennie egy változó deklarációjának?
5. Hogyan kell leírni egy egész típusú változó deklarációját?
6. Mi a legnagyobb és legkisebb érték, amit egy egész típusú változóban tárolhatunk?
7. Mi a különbség a Jáva értékadó utasítása és a matematikai egyenlõség között?
8. Mi az algoritmus?
9. Hogyan kell a zárójeleket elhelyezni a 5+6*3 kifejezésben, hogy a kifejezés értéke 33 legyen? És ha 23-at akarunk?
10. Mi a lebegõpontos típus?
11. Mekkora a legnagyobb és legkisebb szám, ami a lebegõpontos típussal leírható?
12. Mi az exponenciális számábrázolás?
13. Mennyi 2.001e3?
14. Írd le 0.12-t exponenciális ábrázolásban!
3. fejezet
Terelgetjük a számítógépet; a program futásának menete. Struktúrált programozás, építkezés Matrjoska babákból. Elágazások és logikai kifejezések. Megdolgoztatjuk a gépet: a ciklusok.
A számítógép, mint mondtam, akár százmillió vagy újabban akár egymilliárd utasítást is képes végrehajtani másodpercenként. A Jáva ugyan nagyban lassít rajta, azonban egy modern számítógép még a Jáva bájtkódot is jócskán másodpercenként tízmillió utasítás felett hajtja végre. Ekkora sebességgel száguldó vonatot terelgetni kell, nehogy valami falnak ütközzön. Ezt a feladatot látják el a programvezérlõ utasítások.
A programvezérlõ utasításokkal feltétellel vagy feltétel nélkül a program egy másik pontjára adhatjuk a vezérlést; a feltétel valamilyen kifejezés kiértékelésébõl adódik. Klasszikusan ezt a C vagy a BASIC goto utasításával egyszerû bemutatni, ez az utasítás egy paramétert kap, amely paraméter egy helyet határoz meg a programban (egy sorszámot vagy egy címkét pédául). Amikor a goto végrehajtódik, a program futása a paraméter által meghatározott helyen folytatódik.
Jávában nincsen goto és ez egy vallás elterjedésének köszönhetõ. A strukturált programozás papjai a 70-es évek elején kezdték terjeszteni, hogy nem szép dolog a programot teleszórni goto-val, mert a végrehajtás nagyban követhetetlen lesz. Ez így is van. Az egyedül üdvözítõ megoldás a vallás hívei szerint az egymást teljesen magába foglaló, struktúrált programblokkok rendszere. Egy ilyen programblokk egy vagy több (vagy sok) Jáva utasítást foglal magába és késõbb fogjuk meglátni, mit lehet velük csinálni. Elõzetesként: feltételtõl függõen végre lehet hajtani a blokkot (vagyis a benne levõ utasításokat), ismételni lehet a blokkot, egy feltételtõl függõen újra végre lehet hajtani, stb. Akárhogy is, struktúrált a program attól lesz, hogy a blokkok határai nem keresztezik egymást, a fentebb levõ blokk teljesen magában foglalja a benne levõ blokkot vagy blokkokat. A Jávában a programblokkot egy { jel vezeti be és egy } jel zárja. Megjegyzendõ, hogy egy utasítás önmagában blokkot képez, tehát ha a kapcsos zárójelek elmaradnak, a blokk csupán egy utasításból áll. Természetesen nem hiba egy utasítást kapcsos zárójelek közé fogni, csak nem szükséges. Legegyszerûbb ezt egy példával megvilágítani!
Az if utasításnak van egy paramétere. A paraméter kiértékelésétõl függõen a mögötte levõ blokkot végrehajtja vagy sem. Példa:
if( x < 0 ) {
System.out.println( "x kisebb volt, mint 0!" );
}
Minthogy a blokk csak egy utasításból áll, a kapcsos zárójelek elhagyhatók.
if( x < 0 )
System.out.println( "x kisebb volt, mint 0!" );
A paraméter egy új típusú, logikai kifejezés. Ha összeadunk két egész számot, az eredmény egy újabb egész lesz. Ha összehasonlítasz két számot, az eredmény logikai érték lesz. Ez egy teljesen szokásos adattípus, a neve boolean és összesen két értéke lehet, true és false. Mondhatod tehát ezt:
boolean f;
f = true;
Sõt mondhatod ezt:
int x,y;
boolean f;
x = 0; y = 1;
f = x < y;
A logikai kifejezések és operátorok tehát pont úgy viselkednek mint bármilyen kifejezés és értékül is adhatók logikai típusú változóknak. A következõ logikai mûveletek vannak két egész vagy lebegõpontos kifejezés között.
• x < y : true az eredmény, ha x kisebb, mint y, különben false
• x > y : true az eredmény, ha x nagyobb, mint y, különben false
• x == y : true az eredmény, ha x egyenlõ y-nal
• x != y : true az eredmény, ha x nem egyenlõ y-nal
• x <= y : true ha x kisebb vagy egyenlõ y-nal
• x >= y : true, ha x nagyobb vagy egyenlõ y-nal
Vannak operátorok, amik már eleve logikai értékeken dolgoznak. Ezek:
• x && y : true akkor és csak akkor ha x true és y is true
• x || y : true akkor, ha x vagy y közül valamelyik, esetleg mindkettõ true
• !x : true, ha x false és viszont.
Nézzünk akkor egy jópofa logikai kifejezést feltételezve, hogy van x és y egész típusú változónk:
( ( x > -5 ) && ( x < 5 ) ) || ( ( y > -10 ) && ( y < 10 ) )
Ez a kifejezés akkor ad true értéket, ha x -5 és 5 közé esik vagy y 10 és -10 közé esik, esetleg mindkettõ igaz.
De térjünk vissza oda, ahonnan elindultunk, az if-hez. Az if elsõ paramétere egy logikai kifejezés, második egy programblokk, amit a kifejezés értékétõl függõen végre kell hajtani vagy átugrani. Lássunk egy praktikus példát és írjunk egy programot, ami kiírja, ha a program paramétere negatív. Ilyet még nem láttunk és most nem is fogok belemenni a részletes magyarázatába, legyen elég annyi, hogy át lehet venni a paramétereket, amivel a programot a parancssorban elindították.
public class Negativ {
public void prog( String args[] ) {
int x;
try {
x = Integer.parseInt( args[0] );
} catch( NumberFormatException ex ) {
System.out.println( args[0]+" nem kiertekelheto" );
return;
}
if( x < 0 ) {
System.out.println( x+" negativ" );
}
}
public static void main( String args[] ) {
Negativ e = new Negativ();
e.prog( args );
}
}
Ezt a programot, mint írtam, parancssori paraméterrel kell futtatni, különben hibaüzenetet kapsz (próbáld ki!). Próbáld ki továbbá különbözõ paraméterekkel.
java Negativ 5
java Negativ -10
java Negativ 0
java Negativ -29.34
java Negativ 2.3e12
Az utolsó két paraméter hatására hibaüzenetet kapunk. Ennek oka, hogy a számot egészként értékeltük ki és a két utolsó paraméterrel ezt nem lehet megtenni.
Programunknak van egy barátságtalan vonása: ha pozitív számot vagy 0-t kap, nem ír ki semmit. Szüntessük ezt meg! Megoldhatnánk a feladatot úgy, hogy a két kiírást két if-hez kötnénk pl. így:
if( x < 0 ) {
System.out.println( "Negativ" );
}
if( x >= 0 ) {
System.out.println( "Pozitiv vagy 0" );
}
Nem szép, ugye? Nehéz is karbantartani, ha netán megváltoztatnánk a feltételt, a másikat sem szabad elfelejtenünk megváltoztatni. Sokkal szebben megoldható a feladat az if-hez opcionálisan kapcsolható else-ág segítségével. Ha az if utasításnak van else ága, az else mögött levõ blokk akkor hajtódik végre, ha az if feltétele hamis volt. Lássuk az elõzõ példát!
if( x < 0 ) {
System.out.println( "Negativ" );
} else {
System.out.println( "Pozitiv vagy 0" );
}
Feladat
Írj egy olyan programot, ami egész paraméterérõl kiírja, pozitív, negatív, vagy nulla-e. Itt a megoldás ellenõrzésképpen.
Ha emlékszel a 2. fejezetre, volt ott egy olyan feladatunk, ahol 2 elsõ 8 hatványát írattuk ki. Most mi lenne, ha az elsõ 16 hatványra volnánk kiváncsiak? Abban a példaprogramban egy újabb hatványhoz egyszerûen megismételtük a szükséges sorokat. Ha a számítógépet el tudjuk terelni az egyenes vonalú végrehajtástól, akkor megspórolhatunk jó sok gépelést, ha megismételtetjük vele a programblokkot. Ezt Jávában is meg tudjuk csinálni a while utasítás segítségével. A while paramétere egy logikai kifejezés és ha ez a kifejezés igaz, a while végrehajtja a mögötte levõ programblokkot. Ezek után a gép újra kiértékeli a kifejezést, újra végrehajtja a blokkot és ez így megy addig, amíg a feltétel hamissá nem válik. Ha a feltétel már a legelsõ végrehajtásnál hamis volt, a programblokk egyszer sem hajtódik végre. A feltétel vizsgálata tehát a programblokk - a ciklusmag - elõtt van, ezért ezt a ciklust elöltesztelõnek hívjuk. A következõ kis példaprogram kiírja a számokat 1-tõl 20-ig.
int n;
n = 1;
while( n <= 20 ) {
System.out.println( n );
n = n+ 1;
}
public class pozneg {
public void prog( String args[] ) {
int x;
try {
x = Integer.parseInt( args[0] );
} catch( NumberFormatException ex ) {
System.out.println( args[0]+" nem kiertekelheto" );
return;
}
if( x < 0 )
System.out.println( x+" negativ" );
else
if( x == 0 )
System.out.println( "Nulla" );
else
System.out.println( x+" pozitiv" );
}
public static void main( String args[] ) {
pozneg e = new pozneg();
e.prog( args );
}
}
Feladat
Csinálj a fenti kis programrészletbõl végrehajtható programot, tehát tedd köré mindazt, amitõl Jáva programmá válik. Ellenõrizd a megoldást itt.
public class szamol {
public void prog( String args[] ) {
int n;
n = 1;
while( n <= 20 ) {
System.out.println( n );
n = n + 1;
}
}
public static void main( String args[] ) {
szamol e = new szamol();
e.prog( args );
}
}
Feladat
Most csináld meg azt a programot, ami 2 elsõ 16 hatványát. Használd fel a 2. fejezet programját, ami az elsõ 8 hatványt írta ki a számító rutin ismételgetésével és szervezd ciklusba, amit akkor egyszerû kódismétléssel oldottunk meg. A megoldást itt ellenõrizheted.
public class kettowhatvany {
public void prog( String args[] ) {
int n,x;
n = 0;
x = 1;
while( n <= 16 ) {
System.out.println( "2^"+n+"="+x );
n = n + 1;
x = x * 2;
}
}
public static void main( String args[] ) {
kettowhatvany e = new kettowhatvany();
e.prog( args );
}
}
A ciklusok remek dolgok, egy rendes program tartalmaz belõlük jócskán. A while ciklusokkal minden feladat megoldható, de nem mindig áll kézre. A while-nak mindenekelõtt van egy hátultesztelõ változata, a do-while. Ez ugyanazt tudja, mint a while, azzal a különbséggel, hogy az ellenõrzés a ciklus végén történik, tehát a ciklusmag egyszer mindenképpen végrehajtódik. Példa:
int n;
n = 1;
do {
System.out.println( n );
n = n+1;
} while( n <= 20 );
A másik fontos szerkezet a fentiekben bemutatott szerkezet zanzásított változata, amivel sok írásmunka megspórolható. A for ciklus magába foglalja a ciklusváltozó inicializálását, a ciklusfeltétel ellenõrzését (elõltesztelõ formában) és a ciklusváltozó megváltoztatását. Az általános forma a következõ:
for( ciklusváltozó inicializálás ; feltétel ellenõrzés ; ciklusváltozó módosítás )
ciklusmag
Elõzõ számolós példánkat a következõképpen tudod for ciklusba átírni.
int n;
for( n = 0 ; n <= 20 ; n = n + 1 )
System.out.println( n );
Fontos megjegyezni, hogy a for fejének mindhárom része opcionális, ha elmarad, akkor a ciklusutasítás maga nem végzi el az adott mûveletet. Ez lehetõvé teszi számunkra, hogy pl. a ciklusváltozó növelését a ciklus belsejében hajtsuk végre, ha az olyan bonyolult, hogy egy utasításban nehéz leírni. Ad abszurdum a következõ program értelmes:
for( ; ; )
System.out.println( "Hello!" );
Ez a programrész végtelen ciklusban fogja az üzenetet kiírni. Nincs inicializálás és nincs ciklusváltozó-növelés valamint a ciklusfeltétel elmaradása azt jelenti, hogy a feltételt nem kell ellenõrizni, hanem feltétel nélkül folytatni a ciklust.
Feladat
Elõzõ feladatunkat a 2 elsõ 16 hatványáról írd át for ciklusos változatba! Ha megírtad és mûködik, ellenõrizd a megoldást itt!
public class kettofhatvany {
public void prog( String args[] ) {
int n,x;
x = 1;
for( n = 0; n <= 16 ; n = n + 1 ) {
System.out.println( "2^"+n+"="+x );
x = x * 2;
}
}
public static void main( String args[] ) {
kettofhatvany e = new kettofhatvany();
e.prog( args );
}
}
Feladat
Írj egy programot, ami kinyomtatja a kisegyszeregyet! Használd fel a System.out.println rokonát, a System.out.print-et, ami nem tesz soremelést a kinyomtatott sor végére, így több eredményt nyomtathatsz egy sorba. A szorzótábla kinyomtatásához használj két for hurkot egymás hasában, a belsõ hurok nyomtat egy sort, a külsõ ciklus ismételgeti a sornyomtató ciklust. Küzdj meg a programmal, majd ellenõrizd az eredményt! Figyeld meg, a külsõ ciklus magját kapcsos zárójelek fogják közre, mert az két utasításból áll, a belsõ for-ból és az üres szöveget (plusz egy soremelést) nyomtató System.out.println-bõl. A belsõ ciklus magja csak egy utasítást, a System.out.print-et tartalmaz, így ott a kapcsos zárójelek nem szükségesek (bár ottlétük nem lenne hiba). Figyeld meg a struktúrált programozást: a külsõ ciklus teljesen magába foglalja a belsõt!
public class egyszeregy {
public void prog( String args[] ) {
int i,n;
for( i = 1 ; i <= 10 ; i = i + 1 ) {
for( n = 1 ; n <= 10 ; n = n + 1 )
System.out.print( n*i+" " );
System.out.println( "" );
}
}
public static void main( String args[] ) {
egyszeregy e = new egyszeregy();
e.prog( args );
}
}
Ellenõrzõ kérdések
1. Mit jelent a programblokk?
2. Mit jelent, hogy a programblokkok struktúráltak?
3. Hogyan kell egy programblokkot leírni Jávában?
4. Programblokkot alkothat-e egy Jáva utasítás?
5. Mit jelent a logikai kifejezés?
6. Hogyan kell logikai típusú változókat deklarálni?
7. Mit csinál az != operátor?
8. Mit csinál a || operátor?
9. Mi az if utasítás
10. Mit jelent, ha az if utasításnak else ága van?
11. Mi a while utasítás?
12. Mi a ciklusmag?
13. Mit jelent, hogy a while elöltesztelõ ciklus?
14. Hogyan lehet hátultesztelõ while ciklust írni?
15. Mi a for ciklus?
16. Elöl- vagy hátultesztelõ a for ciklus?
17. Mit jelent az egymásba ágyazott ciklus?
4. fejezet
Megjegyzések. Írni utálunk, ezért törekszünk az újra felhasználható programrészekre. Függvények a matematikában és Jávában. Paraméterek, visszatérési értékek és változó láthatósági szabályok.
Programjaink most már kezdenek kellõen bonyolultak lenni ahhoz, hogy magyarázat nélkül ne értsük õket. Minden programozási nyelv, köztük a Jáva is lehetõséget ad arra, hogy ember számára értelmes magyarázó szövegeket, megjegyzéseket tegyünk a kódba. A megjegyzéseket a számítógép egyszerûen figyelmen kívül hagyja, semmilyen hatása nincsen a program mûködésére. Nem is neki szólnak, hanem a program olvasójának. Jávában kétfajta megjegyzés van. Az egyik /* karakterekkel kezdõdik és a legközelebbi */ karakterekig tart, a programban bárhol állhat, több soros is lehet. Pl.
/* Itt egy megjegyzés */
i = 1; /* A sor végén egy megjegyzés */
/* Hosszú,
több sort
átfogó megjegyzés
*/
i /* a változó, amit használunk */ = /* értékadó utasítás */ 2 /* 2 egy jó érték */;
Az utolsó variációt nem kell követendõ példának tekintened, csak azt akartam vele megmutatni, hogy ez a fajta megjegyzés tényleg mindenhol lehet. A másikfajta megjegyzést // jelek vezetik be és a sor végéig tart. Kevesebbet kell írni vele, rövid megjegyzéseknél ezért szeretik. Példák:
// Egy rövid kis megjegyzés
i = 3; // Végül is igazában 3-at szeretnénk i-be
// Ha hosszú megjegyzést akarunk,
// Minden sor elejére kell a //
// jel.
Feladat
Írjunk egy programot, ami kiírja az y=x2 függvény értékeit x=(-1,1) intervallumban, 0.1-es lépésenként. Megjegyzésekkel tedd világosabbá, mikor mi történik! A feladat nem okozhat nehézséget, azért a biztonság kedvéért ellenõrizd a megoldást!
/* A program kinyomtatja az y=x^2 fuggveny ertekeit a (-1,1) intervallumban */
public class x2ertek_1 {
public void prog( String args[] ) {
// x a fuggveny x argumentuma, y az erteke
double x,y;
// A ciklus felso erteke egy kicsit tobb 1-nel, hogy 1 kornyeket elerje.
// Ez a 0.1-gyel kapcsolatos szamitasi problemak miatt van;
for( x = -1 ; x < 1.01 ; x += 0.1 ) {
y = x*x;
System.out.println( x+"^2="+y );
}
}
public static void main( String args[] ) {
x2ertek_1 e = new x2ertek_1();
e.prog( args );
}
}
A megoldásban felhasználtunk egy újdonságot, a += operátort. Ez az operátor hozzáadja a bal oldalán levõ változóhoz a jobb oldalán levõ értéket, tehát kiveszi a változó értékét, hozzáadja az operátor jobb oldalán levõ kifejezés értékét és visszatárolja a változóba.
x += 0.1;
tehát egyenértékû
x = x + 0.1;
kifejezéssel.
A kiírásban furcsa eredményeket találhattál, pl. íme egy részlet:
...
-0.20000000000000015^2=0.04000000000000006
-0.10000000000000014^2=0.01000000000000003
-1.3877787807814457E-16^2=1.9259299443872359E-32
0.09999999999999987^2=0.009999999999999974
...
Nagyjából stimmel, de mik ezek a furcsa értékek a végén? És pláne, miért nem kapunk 0-t (habár 10-16 elég közel van hozzá ...)? Tudnod kell, hogy a számítógép számára csak az egész, int értékek pontosak. Mivel a gép másként tárolja a számokat, mint az ember, nem biztos, hogy az olyan szép, kerek értékek, mint a 0.1 a gép számára is ugyanolyan szépek. A 0.1 pont egy olyan szám, amit a gép nem tud teljesen pontosan ábrázolni, ezért az összeadások során apró hibák halmozódnak fel. Éppen ez okból a lebegõpontos számokat sose vizsgáljuk egyenlõségre, mert könnyen lehet, hogy az egyenlõség sohase teljesül. Esetünkben például x sohase lesz pontosan 0 vagy 1, ezért a furcsa határértékösszehasonlítás a for ciklusban. Hadd ismételjem meg, hogy ez a probléma csak a lebegõpontos számoknál léphet fel, egészeknél soha, ez egy újabb ok arra, hogy mindenhol egészeket használjunk, ahol csak lehet.
Akárhogy is, nem erre akartam kilyukadni. Maga a kiszámítandó függvény a program közepében van, ha cserélni akarnánk, ki kellene keresnünk a programszövegbõl. Rosszabb, ha a függvény netán bonyolultabb lenne és nem lehetne egy sorban kiszámolni, netán esetleg belsõ változókat használna, a kiszámítandó függvény kódja teljesen összekeveredne a táblázatnyomtató program kódjával. Ezen okból a Jáva rendelkezésünkre bocsátja a saját függvények definiálásának lehetõségét. Igazából már van két saját függvényünk, de azok vadító kék színben tündököltek. Lássuk elõzõ programunk módosítását olymódon, hogy az y = x2 számítását végzõ részt kiemeljük saját függvénybe!
public class x2ertek_2 {
double fuggveny( double xi ) {
double yi;
yi = xi * xi;
return yi;
}
public void prog( String args[] ) {
double x;
for( x = -1 ; x < 1.001 ; x += 0.1 ) {
System.out.println( x+"^2="+fuggveny( x ));
}
}
public static void main( String args[] ) {
x2ertek_2 e = new x2ertek_2();
e.prog( args );
}
}
Ha a programot lefuttatjuk, persze ugyanazt az eredményt kapjuk, mint az elõzõ példában. Mik akkor a változások? Vegyük mindenekelõtt észre a kék sorok drasztikus megfogyatkozását! Vegyük továbbá észre, hogy a táblázatnyomtató ciklusunkban könnyed eleganciával kijelentettük, hogy fuggveny( x ) által visszaadott értéket is ki akarjuk írni. Ez egy függvényhívás.A függvényhívás során a számítógép egy alprogram végrehajtásával folytatja munkáját. Az alprogramot valahol lezárják egy visszatérõ utasítással és megmondják, mi az érték, amit az alprogram visszaad. Ekkor a számítógép elõkeresi, hol hagyta abba a fõprogram végrehajtását és a függvényhívást helyettesíti azzal az értékkel, amit az alprogram visszaadott. A függvény tehát bármilyen kifejezésben állhat, ahol a függvény visszatérési értékének típusa megegyezik az adott kifejezésben várt adattípussal. Például ha van egy függvényünk, ami double-t ad vissza, akkor az felhasználható double számokkal dolgozó kifejezésben.
double fuggv() {
...
}
...
double x;
x = 3.14*fuggv()+2.5;
Maga a függvény deklarációja bárhol lehet a programban, a függvényhívás elõtt és után is. Tilos azonban ugyanazt a függvényt kétszer deklarálni ugyanazzal az aláírással vagyis ugyanazzal a névvel, visszatérési értékkel és paraméterlistával. Azonos nevû, de különbözõ aláírású függvények (tehát például ahol az egyiknek nincs paramétere, a másiknak pedig van) különbözõnek számítanak, mert a Jáva a függvényhívás alapján rájön, hogy melyik függvényt kell meghívnia. Példa:
// Elsõ függvény
int fuggv() {
...
}
// Második függvény
int fuggv( int i ) {
...
}
int i = fuggv(); // Ez az elsõ függvényt hívja meg
int k = fuggv( 2 ); // Ez a második függvényt hívja meg
A függvénynek lehet egy speciális visszatérési értéke, a void. Ez azt jelenti, hogy a függvény nem ad vissza értéket, az ilyen függvényt nem lehet kifejezésben felhasználni, de kifejezésen kívül meg lehet hívni. Pont ezen a módon kell alprogramokat, procedurákat definiálni.
void fuggv2() {
...
}
...
fuggv2();
A függvényeknek lehetnek bemeneti paramétereik is. Ezek azok az értékek, amelyeket átadunk az alprogramnak, hogy számoljon belõle kimeneti értéket. A bemeneti paramétereket a függvényfejben soroljuk fel.
double fuggv3( double elso_param, int masodik_param ) {
double eredm;
...
return eredm;
}
...
double eredm;
eredm = fuggv3( 3.14, 4 );
Amikor a függvényt meghívod, bonyolult dolgok játszódnak le.
• Kiértékelõdnek a függvény meghívásakor a függvénynek átadott kifejezések
• Új kontextus jön létre a változóknak, az eddigi használt változókat nem lehet többet elérni. Ezt késõbb majd pontosítjuk (vannak olyan változók, amelyek túlélik a függvényhívást), de most jegyezzük meg inkább úgy, hogy az eddig deklarált változók a függvény futása alatt nem látszanak. Megmaradnak, nem vesznek el, de a függvény nem látja õket, mert õket egy másik kontextusban deklarálták.
• A függvénynek átadott paraméterek megfelelõ sorrendben hozzárendelõdnek a bemeneti paraméterekhez. Valójában az történik, hogy a bemeneti paraméterek mint változók létrejönnek a függvény saját kontextusában és kezdõértékül megkapják a hívási értékeket. A példánkban tehát az elso_param nevû változó értéke 3.14 lesz, a masodik_param-é pedig 4.
• A függvény futni kezd és úgy viselkedik, mint bármely Jáva program - teheti, mert valójában õ is teljes jogú program, aminek saját környezete van. Saját változókat deklarálhat, más függvényeket és procedúrákat hívhat meg. Ámde eljön egyszer az igazság pillanata és befejezi a futását. Ez kétféleképpen történhet. Elõször is elérhet egy return utasítást. A return mögött a függvény visszatérési értékének megfelelõ típusú kifejezés vagy - void visszatérési érték esetén - semmi sem található. A return hatására a függvény befejezi a futását és visszatérünk a fõprogramba. Második lehetõség csak és kizárólag void visszatérési értékû függvényeknél fordulhat elõ: ilyenkor egyszerûen elérhetjük a függvényt lezáró kapcsos zárójelt és ezt a gép magától, mint egy üres return; utasítást értelmezi.
• Ha a függvény futása befejezõdik, a visszatérési értéket a gép biztos helyre menti, majd egész egyszerûen felszámolja az összes változót, amit a függvény kontextusában deklaráltak, ezek törlõdnek és örökre elvesznek.
• A függvényt hívó program kontextusa visszaállítódik, változói láthatóvá válnak.
• A gép most elõveszi a biztos helyrõl a függvény visszatérési értékét és mintha mi sem történt volna, folytatja a kifejezés kiértékelését, amiben a függvényhívás volt. Az egész függvényhívás egy darab számmá, a függvény visszatérési értékévé zsugorodott össze és ezzel a számmal helyettesítõdik.
Hasonlatos ez ahhoz, mint amikor a cikkedhez ki akarsz számolni egy értéket, de csak a szám érdekel. Néhány sajtpapíron elkészíted a számításokat, a számot beírod a cikkbe, majd a sajtpaírokat eldobod. Így mûködik a függvény is, a részeredmények érdektelenek a függvény lefutása után és ami még fontosabb, a függvény belsõ ügyei nem zavarják a program futását.
A függvénybõl természetesen újabb függvényt is meghívhatsz és a hívásra ugyanazok a szabályok vonatkoznak. Ezt nem csinálhatod a végtelenségig, valamekkora korláta van annak, milyen mélységben hivogathatják a függvények egymást, de ez sok ezres mélység.
Lássuk, megértetted-e!
Feladat
Olvasd el az itt látható programot és mondd meg nekem, mi a nyomtatási kép! Utána próbáld is ki és találd ki az okát, ha netán tévedtél volna. Segítség: nézd meg, melyik függvényhívás melyik fuggv függvényt hívja meg. Ahol a függvényhívásban paramétert adunk át, ott a paraméterrel rendelkezõ fuggv fut, ahol nem, ott a paraméter nélküli.
A megjegyzések használata persze magánügy, de talán a fenti példa is demonstrálja, hogy legalább a függvények fejét (tehát a deklarációjának az elejét, a visszatérési értékének, nevének és paramétereinek megadását) mindenképpen ajánlott megjegyzéssel ellátni, milyen paramétert vár a függvény, azokkal mit csinál és mit ad vissza.
public class fuggvteszt {
int fuggv() {
int i,j,k;
i = 10;
j = 20;
k = 30;
return j;
}
int fuggv( int i ) {
int j,k;
j = 20;
k = 30;
return i+10;
}
public void prog( String args[] ) {
int i,j,k;
i = 1;
j = fuggv();
k = fuggv( 100 );
System.out.println( "i: "+i );
System.out.println( "j: "+j );
System.out.println( "k: "+k );
}
public static void main( String args[] ) {
fuggvteszt e = new fuggvteszt();
e.prog( args );
}
}
Feladat
Bergengóciában az adórendszert lényegesen leegyszerûsítették és a Totális Adótábla XXVIII. lapja a következõképpen néz ki:
-100 fbtk* a jövedelem 9%-a
100-250 9+a 100 fbtk fölötti rész 13%-a
250-750 30+a 250 fbtk fölötti rész 25%-a
750- 180+a 750 fbtk fölötti rész 50%-a
*- fbtk: konvertibilis fabatka, éves szinten
Az utolsó pillanatban a Tüntetõ Bergengóc Anyák Antidemokratikus Szövetségének sikerült keresztülvinnie követeléseit és a családosokoknak kedvezõ intézkedésekkel egyszerûsítették a táblát. Ezek szerint a 1 gyerek nevelése esetén az adó 2%-át, kettõ esetén 8%-át, 3 és a felett gyerekenként 4%-át, de legfeljebb 25%-ot engednek el. Írjunk most egy programot, amit meghívhatunk az éves jövedelemmel és a gyerekek számával és az megmondja az adónkat! Emlékezzünk a 3. lecke Negativ programjára, hogyan értékeltük ki ott a paramétereket! Lebegõpontos számot még nem értékeltünk ki, ezért leshetsz egy kicsit a megoldásban. A programunkban igyekezzünk olyan függvényt írni, ami a leginkább lerövidíti a programot! Íme a megoldás, de csak a paraméterek kiértékelését lesheted ki belõle, a többit neked kell megoldanod!
public class ado {
/* Ez a fuggveny szamitja az adot. Bemeneti parameterei: jovedelem, a jovedelemsav
also hatara, a jovedelemsav also hatarahoz tartozo ado, az ado merteke a
jovedelemsavban (tizedestort, nem %!) es a gyerekek szama */
double adoszamitas( double jov, double alap, double alapado, double ado, int gyerekszam ) {
double adoertek,kedvezmeny;
adoertek = ( ( jov - alap ) * ado ) + alapado;
if( gyerekszam == 1 )
adoertek = adoertek*0.98;
else
if( gyerekszam == 2 )
adoertek = adoertek*0.92;
else
if( gyerekszam >= 3 ) {
kedvezmeny = gyerekszam*0.04;
if( kedvezmeny > 0.25 )
kedvezmeny = 0.25;
adoertek = adoertek*( 1.0-kedvezmeny );
}
return adoertek;
}
public void prog( String args[] ) {
double jovedelem,ado;
int gyerekszam;
try {
jovedelem = Double.parseDouble( args[0] );
gyerekszam = Integer.parseInt( args[1] );
} catch( NumberFormatException ex ) { return; }
if( jovedelem < 100 )
ado = adoszamitas( jovedelem,0,0,0.09,gyerekszam );
else
if( jovedelem >= 100 && jovedelem < 250 )
ado = adoszamitas( jovedelem,100,9,0.13,gyerekszam );
else
if( jovedelem >= 250 && jovedelem < 750 )
ado = adoszamitas( jovedelem,250,30,0.25,gyerekszam );
else
/* Ide csak akkor juthatunk, ha a jovedeleme nagyobb 750-nel */
ado = adoszamitas( jovedelem,750,180,0.5,gyerekszam );
System.out.println( "Jovedelem: "+jovedelem+" gyerekszam: "+gyerekszam+
" ado: "+ado );
}
public static void main( String args[] ) {
ado e = new ado();
e.prog( args );
}
}
Ellenõrzõ kérdések
1. Mire szolgálnak a megjegyzések a programban?
2. Mi a különbség a /* */ és a // megjegyzésszintaktika között?
3. Miért nem vizsgáljuk a lebegõpontos számokat egyenlõségre?
4. Mi a függvény aláírása?
5. Mi a void típus?
6. Mik játszódnak le egy függvényhíváskor?
7. Hogyan adja vissza a függvény a visszatérési értékét?
8. Mi történik a függvény által deklarált változókkal?
9. Mi történik, ha a hívó függvény és a hívott függvény megegyezõ nevû változókat használ?
10. Milyen mélységben hívhatják függvények egymást?
5. fejezet
Matematikai függvények, a Jáva matematikai függvényei és azok használata.Egyszerû matematikai problémák programnyelvi megoldásai. Feladatok a függvények használatára.
Mint már említettük, a számítógép másodpercenként több milliárd gépi szintû mûvelet elvégzésére képes, míg függvényhívásokból is képes több tízezer elvégzésére. Megfogalmazódik bennünk a kérdés, hogy miként is lehetne ezt ésszerûen használni olyan problémák megoldására, amelyek a mindennapi életben is elôfordulhatnak. A számítógép igazi ereje akkor mutatkozik meg, ha olyan algoritmus (elemi lépések véges számú, jól meghatározott sorozata) végrehajtására utasítjuk, mely nekünk hosszas papíron történô számolást igényelne.
A négy alapmûveletet kiterjesztendô, ismerkedjünk meg a hatványozás, gyökvonás, az exponenciális és trigonometrikusfüggvények Java nyelvi megfelelôivel.
A következô program segítségével hatványozást és gyökvonást végezhetünk, melyek a java.lang csomag Math osztályában találhatók.
A hatványozás függvénye: double pow(double alap, double kitevo)
A négyzetgyökvonás: double sqrt(double alap)
Nézzük a programot! Math1
public class math1 {
public double Hatvany(double x, double n){
return Math.pow(x,n); //A beépített hatványfv. meghívása
}
public double Negyzetgyok(double y){
return Math.sqrt(y); //A beépített gyökfv. meghívása
}
public void prog( String args[] ) {
System.out.println("Példa a hatványozás és gyökvonás használatára:");
double alap, kitevo; //Változót deklarálhatunk szinte bárhol
/*
Az áltlánosítás kedvéért az args[] segítségével töltjük fel függvényeinket paraméterekkel.
A főprogram első két paramétere lesz a hatványfüggvényben az alap és a kitevő értéke.
A 3.-nak megadott paramétert pedig a gyokfüggvény kapja értékül.
*/
alap=Double.parseDouble(args[0]);
kitevo=Double.parseDouble(args[1]);
/*
A hatványfv double értékeket vár, amit az értékadásnál így közlünk.
*/
System.out.println(args[0]+"^"+args[1]+"="+Math.round(Hatvany(alap, kitevo)));
//Math.round: a double értéket így egész számokként iratjuk ki.
int gyokalap;
gyokalap=Integer.parseInt(args[2]);
System.out.println(args[2]+" Négyzetgyöke="+Negyzetgyok( gyokalap ) );
System.out.println("Pi értéke: "+Math.PI);
System.out.println("A természetes alapú logaritmus alapszáma: "+Math.E);
}
public static void main( String args[] ) {
math1 e = new math1();
e.prog( args );
}
}
public class math1 {
public double Hatvany(double x, double n){
return Math.pow(x,n);
//A beépített hatványfv. Meghívása
}
public double Negyzetgyok(double y){
return Math.sqrt(y);
//A beépített gyökfv. meghívása
}
public void prog( String args[] ) {
System.out.println("Példa a hatványozás és gyökvonáshasználatára:");
double alap, kitevo;
//Változót deklarálhatunk itt is
/* A fôprogram elsô két paramétere lesz a hatványfüggvényben
az alap és a kitevôértéke. A 3.-nak megadott paramétert pedig
a gyökfüggvény kapja értékül.*/
alap=Double.parseDouble(args[0]);
kitevo=Double.parseDouble(args[1]);
/* A hatványfv double értékeket vár, amit az értékadásnál
így közlünk. */
System.out.println(args[0]+"^"+args[1]+"="+Math.round(Hatvany(alap, kitevo)));
//Math.round: a double értéket egész számként íratjuk ki.
int gyokalap;
gyokalap=Integer.parseInt(args[2]);
System.out.println(args[2]+"Negyzetgyoke="+Negyzetgyok(gyokalap ) );
}
public static void main( String args[] ) {
math1 e = new math1();
e.prog( args );
}
}
Megjegyzések:
1. A Hatvany és a Negyzetgyok függvények double paramétereket fogadnakés adnak is vissza, a függvénytörzsben meghívják a Math osztály megfelelô (pow és sqrt) függvényeit.
2. Az általánosítás kedvéért az args[] segítségével töltjük fel függvényeinket paraméterekkel. Így minden futtatáskor változtathatjuk a bemeneti paraméterek értékeit.Az args[] használatát szintén egy késôbbi fejezetben magyarázzuk részletesen, legyen elég annyi, hogy a programnak a parancssorban megadott 1.,2.,3. stb. paraméter rendre az args[0],args[1], args[2] változók tárolják.
3. Az alap éskitevo double típusú változókrendre a fôprogram elsô két paraméterét kapják.Ahhoz, hogy a program az általunk megadott értékeket double-ként kezelje, Double.parseDouble() függvényt használjuk. Hasonlóképpen, a gyokalap változó a harmadik fôprogram paraméter lesz, integer-ként értelmezve.
4. A hatványfüggvény double értéketad vissza, amit a Math.round() függvénnyel alsó egészértéket kapunk vissza.
Fordítás után a futtatáshoz adjuk ki az alábbi parancsot:
java math1 1.5 2 8
A futás eredménye:
Példa a hatványozás és gyökvonás használatára:
1.5^2=2
8 Négyzetgyöke=2.8284271247461903
A hatványozásnál láthatjuk, hogy a 2.25 helyett a kerekített értéket kapjuk, míg a 8 négyzetgyökét double alakban, 16 tizedesjegy pontossággal.
Az alábbitáblázatban foglaljuk össze a Math osztály legfontosabb függvényeit:
Visszatérési érték típusa Függvényfej Funkció
Double abs(double a) Abszolút érték fv *
Double cos/sin/tan/asin/acos/atan(double) Trigonometrikus fvek és inverzeik
Double Exp/log(double) és ln(x)
Double toDegrees/toRadians(double) Radián érték fok-ba váltása és fordítva
A trigonometrikus függvények használatához szükségünk lehet a matematika középiskolából jól ismert két nevezetes konstansára: e-re és pi-re. Ezeket a Math osztály speciális változóiként kell kezelnünk, matematikai szempontból az y=e és y=pi konstansfüggvények felelnek meg:
public static final double PI
public static final double E
Arra, hogy ezek konstans értékû változók, arra a final jelzô utal (értékük nem változik). Használatuk a program szövegében: Math.PI illetve Math.E hivatkozással történik, vagyis meg kellmondanunk expliciten, hogy a Math osztály konstans értékûváltozóiról van szó.
1. Feladat
Egyszerû gyakorlatként írjuk át a kör kerületének és területének kiszámítását elvégzô programunkat a Math.PI használatával! Az egyszerûsített megoldást itt ellenôrizheted.
public class Circle {
public double kerulet(double r) {return 2*r*Math.PI;}
public double terulet(double r) {return r*r*Math.PI;}
public void prog(String[] args) {
System.out.println("Az "+args[0]+" sugarú kör kerülete: K="+kerulet(Double.parseDouble(args[0])));
System.out.println(" és területe: T="+terulet(Double.parseDouble(args[0])));
}
public static void main(String[] args) {
Circle e = new Circle();
e.prog(args);
}
}
2. Feladat
Készítsünk függvénytáblázatot, mely kiírja a nevezetesebb hegyesszögek (0, 30, 45, 60, 90) sin, cos,tan, ctg értékeit! A megoldást itt ellenôrizd.
public class trigofunc {
public void kiir(double fok){
double fi=Math.toRadians(fok);
System.out.println("------------------------------------------------------------------------------");
System.out.println(" | "+fok+" | "+Math.sin(fi)+" | "+Math.cos(fi)+" | "+Math.tan(fi)+" | "+(1/Math.tan(fi))+" | ");
}
public void prog(String[] args) {
System.out.println("| Szög | Sin | Cos | Tan | Ctg");
kiir(0);
kiir(15);
kiir(30);
kiir(45);
kiir(60);
kiir(90);
System.out.println("----------------------------------------------------------------------");
}
public static void main(String[] args) {
trigofunc e=new trigofunc();
e.prog(args);
}
}
3. Feladat
Készítsünk programot, mely megoldja a másodfokú egyenletet, a megoldhatóság teljes vizsgálatával. A fôprogram 3 bemeneti paramétere az másodfokú egyenlet a, b , c paramétere. A megoldást itt találhatod.
public class Masodfoku {
public double diszkriminans(double a, double b, double c){
return Math.sqrt(b*b-4*a*c);
}
public void prog(String[] args){
double a=Double.parseDouble(args[0]);
double b=Double.parseDouble(args[1]);
double c=Double.parseDouble(args[2]);
if (a==0) {
if (b==0){
if (c==0) { System.out.println("Azonosság, x tetszőleges valós szám.");}
else {System.out.println("Ellentmondásos, nincs megoldás");}
}//b==0
else //b!=0
{if (c==0) { System.out.println("Els\u0151fokú, 1 megoldás: x=0") ;}
else {System.out.println("Elsőfokú, x="+(-1*c/b)) ;}
}
}
else {//a!=0
if ((b*b-4*a*c)>0) {//D>0
double x1=(-b+diszkriminans(a,b,c))/(2*a);
double x2=(-b-diszkriminans(a,b,c))/(2*a);
System.out.println("2 különböz\u0151 valós gyök: x1="+x1+" x2="+x2);
}
else if ((b*b-4*a*c)==0) {
System.out.println("2 megegyez\u0151 valós gyök: x1=x2="+(-b/a));
}
else {System.out.println("Nincs valós megoldás.");}
}
}
public static void main(String[] args) {
Masodfoku e=new Masodfoku();
e.prog(args);
}
}
A Math függvények közt találhatunk egy véletlen számokat generáló függvényt is:
double random ()
A függvény aláírásából láthatjuk.hogy paraméter nélküli és double értéket advissza, mégpedig egy 0<=x<1 pszeudo-véletlenszámot generál. (A pszeudo jelzés arra utal, hogy nem kaphatunk tökéletes véletlenszerûséget,hiszen a generált szám valamilyen módon függ a pocesszorórajelétôl.)
A függvény használata elég egyszerû: ha az [a;b] intervallumba esô véletlen számokat szeretnénk generálni, akkor nem kell mást tennünk, mint venni az
a+(b-a+1)*Math.random() kifejezést.
4. Feladat
Készítsünk 5-ös és 6-os lottó programot a Math osztály véletlenszám generátorának segítségével! Elôször írjuk meg úgy, hogy ne legyen benne ellenôrzés, nem fordul-e elô kétszer ugyanaz a lottószám. A megoldást itt ellenôrizheted! Ezek után oldjuk meg, hogy a számok garantáltan különbözôek legyenek. Itt a megoldás ellenôrzésképpen.
public class lotto {
public void lottogenerator(int n) {
if (n==5){
System.out.println("Az "+n+"-öslotto e heti nyer\u0151számai: ");
for (int i=1;i<=5;i++){
System.out.print((Math.round(89*Math.random())+1)+", ");
}
}
else if (n==6){
System.out.println("A "+n+"-oslottó e heti nyer\u0151számai: ");
for (int i=1;i<=6;i++){
System.out.print((Math.round(44*Math.random())+1)+", ");
}
}
else {System.out.println("Jelenleg csak 5-ös és 6-ös lottót ismerek...");}
}
public void prog(String[] args) {
int tipus=Integer.parseInt(args[0]);
lottogenerator(tipus);
}
public static void main(String[] args) {
lotto e = new lotto();
e.prog(args);
}
}
public class lotto1 {
public void prog( String args[] ) {
int i,j;
double y1,y2,y3,y4,y5,y6,y;
y1=0;
y2=0;
y3=0;
y4=0;
y5=0;
y6=0;
i=Integer.parseInt ( args[0]);
if (i==5) {
System.out.print("Az e heti nyeroszamok: ");
for (j=1;j<6;j++){
do
y=Math.round(1+89*Math.random());
while (y==y1 || y==y2 || y==y3 || y==y4);
if (j==1) y1=y;
if (j==2) y2=y;
if (j==3) y3=y;
if (j==4) y4=y;
if (j==5) y5=y;
}
System.out.println(y1+" "+y2+" "+y3+" "+y4+" "+y5);
}
else if (i==6){
System.out.print("Az e heti nyeroszamok: ");
for (j=1;j<7;j++){
do
y=Math.round(1+44*Math.random());
while (y==y1 || y==y2 || y==y3 || y==y4 || y==y5);
if (j==1) y1=y;
if (j==2) y2=y;
if (j==3) y3=y;
if (j==4) y4=y;
if (j==5) y5=y;
if (j==6) y6=y;
}
System.out.print(y1+" "+y2+" "+y3+" "+y4+" "+y5+" "+y6);
}
else {System.out.println ("Ilyen lotto nincs is!");}
}
public static void main( String args[] ) {
lotto1 e = new lotto1();
e.prog( args );
}
}
5. Feladat*
Készítsünk totó programot a Math osztály véletlenszám generátorának segítségével! A program elkészítéséhez ismerjük meg és használjuk a % operátort! A % ugyanúgy mûködik, mint az osztás (/), de a hányados helyett a maradékot eredm´nyezi. Egészre és lebegôpontos számra is használható. A megoldást itt ellenôrizheted!
public class Toto {
public void prog() {
System.out.println("A toto e heti nyeroszamai:");
for (int i=1;i<=14;i++){
double j=(Math.round(Math.random()*100000))%3;
if (i<10) {System.out.print(" ");} // Ez a sor a kiiras rendezettsegen javit.
if (j==0) { System.out.println(i+". x");}
if (j==1) { System.out.println(i+". 1");}
if (j==2) { System.out.println(i+". 2");}
}
}
public static void main(String[] args) {
Toto e = new Toto();
e.prog();
}
}
6. Feladat*
Írjunk programot, mely az Euklidészi algoritmus segítségével keresi meg meg két egész szám legnagyobb közös osztóját! Íme egy példa, hogyan mûködik az algoritmus. Keressük 60 és 22 legnagyobb közös osztóját.
60 = 2*22 + 16
22 = 1*16 + 6
16 = 2*6 + 4
6 = 1*4 + 2
4 = 2*2 + 0
2 a legnagyobb közös osztó, mivel ez volt az utolsó nem 0 maradék. A megoldást itt ellenôrizheted!
public class LNKO {
public void prog(String[] args){
long x1=Long.parseLong(args[0]);
long x2=Long.parseLong(args[1]);
/*
Ha a felso ket ertekadast toroljuk es helyettesitjuk az alabbi random ertekadassal, akkor
programunkat 1-1000 kozotti veletlen osztoparokkal tesztekhetjuk.
*/
//long x1=Math.round(Math.random()*999+1);
//long x2=Math.round(Math.random()*999+1);
if (x2>=x1) {long e=x2; x2=x1; x1=e;} //az egyszerubb kovethetoseg erdekeben a 2 valtozo erteket elcsereljuk
System.out.print("LNKO("+x1+","+x2+")=");
long maradek=x1%x2;
while (maradek!=0)
{
x1=x2;
x2=maradek;
maradek=(x1%x2);
}
System.out.print(x2);
}
public static void main(String[] args) {
LNKO e=new LNKO();
e.prog(args);
}
}
Ellenõrzõ kérdések
1. Mit nevezünk "beépített" függvényknek?
2. Hogyan tudunk a beépített függvényekre hivatkozni?
3. Hogyan tudunk trigonometriai feladatokat Java program segítségével megoldani?
4. Mit nevezünk konstans értékû változónak? Mondj példát konstans értékû Java változóra!
5. Mire kell vigyáznunk a trigonometrikus Java függvények bemeneti paramétereinek megadásánál?
6. Hogyan tudunk java programjainkban kerekített értékeket használni?
7. Milyen lehetôségeink vannak a Java-ban véletlen számok generálására?
6. fejezet
További gyakorló feladatok matematikai problémák megoldására. Cimke. Többszörös elágazás. Kilépés programblokkból: break. Kilépés ciklusból: continue. 2 ismeretlenes lineáris egyenletrendszerek megoldási módszerei és azok JAVA nyelvû megvalósítása. A mátrixelmélet elemei: mátrix, determináns. A 3- és többismeretlenes egyenletrendszerek megoldási lehetõségei és JAVA nyelvû megvalósítása.
Bármely java utasítást azonosíthatunk úgynevezett címkékkel.
Szerkezete:
cimke: utasítás
Egyszerû példa egy ciklusból való kilépés való kilépés:
kulso: for (int j=100; j>=10;j--){
for (int i=0; i<10;i++){
Math.pow(2,i)*j;
if (i>5) break kulso;
}
}
A fenti ciklus futása megszakad abban az esetben, ha (i>5).Nézzük mi is történik! Elôször is elhelyeztünk egy cimkét a küsõ ciklusfej elé, mely azonosítja a ciklust.Ezután a küsõ ciklust bizonyos feltétel fennállása esetén megszakítjuk, vagyis kilépünk belôle. Ez azt jelenti, hogy a vezérlés (végrehajtás) a külsõ ciklusmag utáni, tehát a ciklust követô utasításra kerül. A break utasítás, tehát arra szolgál, hogy egy ciklust, illetve egy programblokkot elhagyhassunk vele.
2 féle használata létezik:
break; - ekkor a vezérlés a break-et tartalmazó utasításblokkból kilép.
break cimke; - ekkor pedig a cimkével megjelölt blokkot hagyjuk el.
Ha a fenti példában a break cimke nélkül állna, akkor csak a belsõ ciklusbõl lépnénk ki.
Jól jegyezzük meg, hogy a break utasítás nem alkalmas függvénybôl (metódusból - lásd késôbb) vagy inicializáló blokkból való kilépésre.
A másik lehetôségünk egy ciklus normál menetének megváltoztatására a continue utasítás. Amennyiben continue szerepel a ciklusmagban egy feltétel után, akkor a feltétel teljesülése esetén a ciklusmagban lévô további utasítások nem kerülnek végrehajtásra, a vezérlésa ciklusfejre kerül. Épp úgy, mint a break esetében,a continue-val sem lehet függvénybôl vagy inicializáló programblokkból kilépni.
Mit ír ki a képernyõre az alábbi programrészlet utolsó utasítása?
int s=0;
for (int i=0;i<=20;i++) {
if (i>=10)&&(i<=14))
continue;
s=s+i;
}
System.out.println(s);
A continue hasznos lehet, ha meg szeretnénk kímélni magunkat attól, hogy bonyolult feltételeket írjunk a ciklusmagba.
Feladat
1. Készítsünk programot, mely 1-100-ig kiírja a 3-al, 5-el és 7-el nem osztható számokat. A ciklusmagban használjuk a continue utasítást! Megoldás itt.
public class Osztas {
public void prog(){
System.out.println("A 3-al, 5-el, 7-el nem oszthato szamok 1-100-ig:");
for (int i=0;i<=100;i++){
if ( i % 3 ==0 ||
i % 5 ==0 ||
i % 7 ==0 )
continue;
System.out.print(i+" ");
}
}
public static void main(String[] args) {
Osztas e=new Osztas();
e.prog();
}
}
Aki megnézte a másodfokú egyenlet megoldó programjának megoldását, az valószínûleg bonyolultnak találta a sok 'if'-es szerkezetet. Ennek orvoslására a JAVA felkínálja a switch-case szerkezetet. Ezzel az utasítással töbszörös elágazást valósíthatunk meg egy egész értékû kifejezés értékei szerint.
Példaként tekintsük az alábbi programrészletet:
void osztalyzat(int n){
switch (n){
case 1: System.out.println("Elegtelen");
break;
case 2: System.out.println("Elegseges");
break;
case 3: System.out.println("Kozepes");
break;
case 4: System.out.println("Jo");
break;
case 5: System.out.println("Jeles");
default: System.out.println("Gratulálok!");
}
}
Mi történik abban az esetben, ha n=5 és mi, ha n!=5? Amikor a case ág végén break szerepel, akkor a vezérlés kilép a switch utasításból; amennyiben nincs break, a végrehajtás a következô cimkén folytatódik. A default (alapértelmezett) ág használata opcionális.
Ily módon, ha n=1, akkor a break miatt kilépünk a swith utasításból, míg, n=5 esetén break hiányában a deafult ág is végrehajtódik, vagyis gratulálunk a jeles osztályzathoz.
A teljes programot itt találod.
public class Jegyek {
public void prog(String[] args){
int i=Integer.parseInt(args[0]);
switch (i){
case 1: System.out.println("Eletgtelen"); break;
case 2: System.out.println("Elegseges"); break;
case 3: System.out.println("Kozepes"); break;
case 4: System.out.println("Jo"); break;
case 5: System.out.print("Jeles");
default: System.out.println("! Gratulálok!");
}
}
public static void main(String[] args) {
Jegyek e=new Jegyek();
e.prog(args);
}
}
Feladat
2. a) Készítsünk programot, mely a fôprogram paraméterként kapott 1..7 közötti egész szám, mint sorszám függvényében kiírja szövegesen a hét megfelelô napját! Megoldás itt.
public class Napok {
public void prog(String[] args){
int i=Integer.parseInt(args[0]);
switch (i) {
case 1: System.out.println("Hetfo"); break;
case 2: System.out.println("Kedd"); break;
case 3: System.out.println("Szerda"); break;
case 4: System.out.println("Csutortok"); break;
case 5: System.out.println("Pentek"); break;
case 6: System.out.println("Szombat"); break;
default: System.out.println("Vasarnap");
}
}//end of prog
public static void main(String[] args) {
Napok e=new Napok();
e.prog(args);
}
}
b) Vegyük alapul a 2001. évet. Fejlesszük tovább az elõzõ programot úgy, hogy a fõprogram két bemenõ paraméterét, rendre a napot és hónapot, értékelje ki és modja meg, hogy az így megadott dátum a hét melyik napja. A megoldás itt található.
public class Naptar {
int n=-1;
int h=-1;
int s=0;
public int kiertekel(int honap){
//Megmondja, hogy az adott sorszamu honap hany napbol all
if ((honap<1)||(honap>12)) {return 0;}
else if ((honap==1)||(honap==3)||(honap==5)||
(honap==7)||(honap==8)||(honap==10)||
(honap==12)) {return 31;}
else if (honap==2) {return 28;}
else {return 30;}
}
public void melyik_nap(int nap){
//Megmondja, hogy az ev x. napja, milyen nap
System.out.print("2001. "+h+". "+n+". ");
switch (nap) {
case 0: {System.out.print(" Vasarnap"); break;}
case 1: {System.out.print(" Hetfo"); break;}
case 2: {System.out.print(" Kedd"); break;}
case 3: {System.out.print(" Szerda"); break;}
case 4: {System.out.print(" Csutortok"); break;}
case 5: {System.out.print(" Pentek"); break;}
default : {System.out.print(" Szombat");}
}
}
public void prog(String[] args){
if (args.length!=2) { System.out.println("Megfelelo parameterezes: nap honap");}
else { n=Integer.parseInt(args[0]);
h=Integer.parseInt(args[1]);
for (int i = 0; i < h; i++) {
s+=kiertekel(i);
}
s+=n;
//Ekkor s erteke megmondja, hogy a megadott nap hanyadik napja az evnek
s=s%7;
melyik_nap(s);
}
}
public static void main(String[] args) {
Naptar e=new Naptar();
e.prog(args);
}
}
Most készítsünk egy új változatot, ami ugyancsak a 2001 évre érvényes, egy hónapszámot vár paraméterül és kinyomtatja az adott hónap naptárát csinos formában, ahogy a zsebnaptárokon megszokott. Ellenôrizd a megoldást itt!
public class Naptar2 {
// Kiszamolja, 2001-ben az adott honap hany napbol all
int honapok (int m){
if (m==2)
return 28;
else if ((m==4)||(m==6)||(m==9)||(m==11))
return 30;
else return 31;
}
public void prog( String args[] ) {
int m = Integer.parseInt (args[0]);
if ((m<1) || (m>12))
System.out.println ("Ilyen honap nincs is!");
else {
System.out.println("H K Sz Cs P Sz V");
int mo = 0;
for (int i=1; i < m ; i++) // osszes napok szama (mo) elseje elott
mo = mo + honapok( i );
int n = ( mo+1 ) % 7; // milyen napra esik elseje
if( n == 0 ) n = 7;
for( int i = 1; i < n ; i++)
System.out.print (" "); // szokozok nyomtatasa ures napokra
int napok_szama = honapok( m ); // hogy ne ertekeljuk ki a honapok( m ) fuggvenyt
// a ciklus minden lefutasakor
for ( int i = 1 ; i <= napok_szama ; i++) { // napok nyomtatasa
System.out.print (i+" ");
if (i < 10) // nyomtass egy plusz szokozt, ha egykarakteres a szam
System.out.print(" ");
// Ha vasarnapot nyomtattunk, soremeles. Minthogy i 1-tol halad es
// n = 7 vasarnap eseten, i-bol le kell vonnunk egyet, hogy az eredmeny helyes legyen.
if ( ( ( i - 1 + n ) % 7 )==0)
System.out.println();
}
}
}
public static void main( String args[] ) {
Naptar2 e = new Naptar2();
e.prog( args );
}
}
Egyenletrendszerek megoldása
A lineáris egyenletrendszerek elég fontos szerepet játszanak a természettudományokban, különösen a matematika néhány területén. Nem árt megismernünk néhány megoldási módszert, s persze ennek JAVA megvalósítását is.
Középiskolában találkoztunk a 2 ismeretlenes lineáris egyenletrendszerrel.
I. a*x+b*y=p
II. c*x+d*y=q ahol a,b,c,d, p, q az egyenlet (például valós) konstansai, míg x1, x2 valós változók, az egyenletrendszer ismeretlenei.
Ennek megoldása nem jelenthet túl nagy gondot, hiszen a változó helyettesítés módszerével könnyen célt érünk. Kiválasztjuk az egyik egyenletet, az egyik ismeretlent kifejezzük, majd az így kifejezett ismeretlent behelyettesítjük a másik egyenletbe, mely így márcsak egy ismeretlent tartalmaz. Kiszámítva az ismeretlen konkrét értékét, azt visszahelyettesítve a másik, már kifejezett ismeretlent tartalmazó egyenletbe, kész vagyunk.
Példa:
Oldjuk meg a valós számok halmazán az alábbi egyenletrendszert!
I. 4x-5y=22
II. 7x+2y=17
---------------
II. y=(17-7x)/2
y-t I.-be: 4x-5*[(17-7x)/2]=22
I.. 4x-(85-35x)/2=22
I. 8x-85+35x=44
I. 43x=129 azaz x=3
II. y=(17-21)/2= -2
A másik, az egyenlô együtthatók módszere, amikor is a két egyenlet mindkét oldalát úgy szorzom meg konstans értékekkel, hogy az egyik ismeretlen együtthatói megegyezzenek. Ha például az x ismeretlen kiküszöbölése a célunk a fenti általános egyenletrendszerbôl, akkor az elsô egyenletet LKKT(a,c)/c-vel, míg a második egyenletet LKKT(a,c)/a-val kell megszoroznunk, ahol a LKKT a két szám legkisebbközös többszöröse.
[A LKKT-t legyszerûbben úgy számíthatjuk ki, ha a két szám szorzatát elosztom a legnagyobb közös osztójukkal (LNKO).
Ha ez bonyolultnak tûnik, akkor megfelelô a c-vel, illetve a-val történô szorzásis.]
Ezután az egyik egyenletbôl kivonom a másikat,saz így kapott egyenlet már csak 1 ismeretlent tartalmaz.
Nézzük meg az elôzô egyenletrendszer megoldását az egyenlô együtthatók módszerével is:
7* I.: 28x-35y=154
4*II: 28x+8y =68
I-II: -43y=86 azaz y=-2
II: 7x-4=17 azaz x=3
A két módszer egyenértékû, bármlyikkel dolgozhatunk. A megoldások vizsgálata során 2 esetre kell kitérnünk.
1. Létezik olyan eset, amikor a két egyenlet egymással ekvivalens. Ilyenkor az egyenletrendszer határozatlan, azaz az egyik ismeretlent tetszôleges t valós paraméternek választva, kifejezhetjük a másikat, vagyis ekkor végtelen sok megoldás létezik.
2. Lehetséges az is, hogy az egyenletrendszer megoldása során ellentmondásra jutunk (tipikusan akkor, ha az átalakítások soárn azt tapasztaljuk, hogy a 2 egyenlet bal oldala egyenlô, míg jobb oldaluk nem). Ekkor nincs megoldás.
Íme, egy egyszerû feltétel a határozatlanság, illetve ellentmondásosság vizsgálatára. Ha a fenti általános egyenletrendszerben:
a*d-b*c==0 és
1) p*c==a*q, akkor az egyenletrendszer határozatlan;
2) p*c!= a*q, akkor az egyenletrendszer ellentmondásos.
Feladat
A fenti ismeretek értelmében írjunk programot, mely megold egy 2 ismeretlenes, lineáris egyenletrendszert a megoldási lehetôségek teljes vizsgálatával! A mogoldást itt találod.
public class Ketismeretlenes {
public void prog(){
int a=4;
int b=5;
int c=8;
int d=1;
int p=3;
int q=5;
double x,y;
if (a*d-b*c==0){
if (p*c==a*q) {
System.out.println("Hatarozatlan");
System.out.println("x=t");//y=p-b*t
System.out.println("y="+p+"-"+b+"t");
}
else {
System.out.println("Ellentmondasos, nincs valos megoldas.");
}
}
else
{//egyenlo egyutthatok modszerevel
y=((p*c-q*a)/(c*b-d*a));
x=(p-b*y)/a;
System.out.println("A megoldas: x="+x+" y="+y);
}
}
public static void main(String[] args) {
Ketismeretlenes e=new Ketismeretlenes();
e.prog();
}
}
A helyzet kicsit bonyolultabb 3 ismeretlenes egyenletrendszerek esetén.A megoldási módszerek ismeretéhez szükség van egy kis felsôbb 'matek'-ra.
Az elsô fogalom, amit bevezetünk aza mártix. Egy mátrixot elég úgy elképzelnünk,mint egy n*m-esszámtáblázatot. A mátrix elemeire indexeléssel tudunk hivatkozni. Az a(i,j) elem a mátrix i. sorának j. oszpában lévô elemet jelenti. Középsikolában tanultuk a vektorfogalmát. Nos, a mátrix úgy is elképzelhetô, mintegy olyan sorvektor, melynek elemei oszlopvektorok vagy fordítva: olyan oszlopvektor, melynek elemei sorvektorok.
Az alábbi példa egy 3*3-as mátrixot mutat:
Elnevezési konvenció, hogy a mátrixokat nagybetûvel, elemeit pedig az adott nagybetû indexelt kisbetûivel jelöljük. Ha a fenti mátrixot A-val jelöljük, akkor elemeire könnyen hivatkozhatunk:
a(1,1)=2, a(1,2)=3, ..., a(3,2)=4, a(3,3)=3
A mátrixok szép matematikai struktúrákat alkotnak és nagyszerû példaprogramokat lehet rá írni,de ehhez szükség lenne arra, hogy indexelt adatstruktúrákat könnyebben kezeljünk. Ennek lehetôsége egy késôbbi fejezetben nyílik meg számunkra, amikor is a JAVA tömb kezelését tanuljuk. A fenti példa mátrix sorfolytonos felírása alatt az
A=(2 3 1; 4 2 4; 1 4 3)
jelölést értjük.A 3 ismeretlenes egyenletek megoldásához a mátrixoknak egy fontos jellemzôjét,a determinánst, kell megértenünk. Egy n*n-es mátrix fôátlóját az a(1,1), a(2,2), a(3,3), ..., a(n,n) elemek alkotják, formálisan:
a(i,i) ahol i=1..n
A másik átlóban elhelyezkedô elemek a mellékátlót alkotják.
A determináns. Az A mátrix determinánsát detA-val jelöljük.
1. Egy 1*1-es mátrix determinánsa maga az egyetlen eleme.
2. Egy 2*2-es mátrixdeterminánsa alatt a fôátlóban illetve mellékátlóbanlévô elemek szorzatának küléönbségétértjük.
Példa:
Legyen
Ekkor detB=det(5 3; 4 2)=5*2-4*3=10-12=-2.
Tovább lépünk egyet. Egy n*n-es mátrix egyik eleméhez tartozó aldeterminánsa alatt azt az (n-1)*(n-1)-es determinánst értjük, mely azon mátrixnak a determinánsa, mely az adott elemhez tartozó sorba, illetve oszlopban szereplô elemek törlésével keletkezik.
Egy 3*3-as mátrix determinánsát úgy képezzük, hogy kiválasztjuk az egyik sort/oszlopot és egy olyan elôjelben alternáló összeget képezünk, melynek tagjai a kiválasztott sor/oszlop elemei megszorozva az elemhez tartozó 2*2-es aldeterminánsokkal.
Ez a fenti 3*3-as példa mátrixra nézve, kiválasztva például az elsô oszlopot az alábbiak szerint alakul:
detA=2*det(2 4; 4 3)-4*det(3 1; 4 3)+1*det(3 1; 2 4)=2*(-2)-4*5+1*10= -12
Vagyis az elsô elemhez tartozó aldetermináns az elsô sor és elsô oszlop törlésével keletkezô almátrix determinánsa, az a(2,1) elemhez a második sor és az elsô oszlop törlésével kapott 2*2-es mátrix- , s végül az a(3,1) elemhez pedig a 3. sor és elsô oszlop törlésével kapot 2*2-es mátrix determinánsa tartozik.
Formálisan egy n*n-es A mátrix determinánsa a k. oszlop szerint kifejtve:
elemhez tartozó aldetermináns.
Egy A mátrix egy
oszlopvektorral való szorzatán azt az oszlopvektort értjük, melynek i. komponense az a(i,1)*x1+a(i,2)*x2+...+a(i,n)*xn összeg.
Formálisan, ha A n*n-es mátrix és x n-dimenziós oszlopvektor, akkor
Ezzel el is érkeztünk ahhoz a ponthoz, ahonnan foglalkozhatunk az eredeti problémánkkal.
A fenti módszerrel egy egyenletrendszer együtthatói egy mátrixot, egy ún. együttható-mátrixot alkotnak, míg a kiszámítandó ismeretlenek egy oszlopvektort. Ezáltal az egyenletrendszer egyszerûen
Ax= b
alakban írható fel.
Például a 3*3-as esetben:
Vegyük észre, hogy az ábrában szereplô A mátrix-ot soronként megszorozva az x oszlopvektor elemeivel, akkor éppen az egyenletrendszerünk egyenleteit kapjuk:
1) a11*x1+a12*x2+a13*x3=b1
2) a21*x1+a22*x2+a23*x3=b1
3) a31*x1+a32*x2+a33*x3=b1
A 3 vagy többismeretlenes egyenletrendszerek megoldására az egyenlô együtthatók módszerének általánosítása a Gauss-féle elimináció, illetve a változó helyettesítés módszerének általánosításaként ismert Cramer-szabály kiválóan alkalmas. A Gauss-féle elimináció JAVA megvalósítása a még nem tanult tömb adatszerekezet ismerete nélkül igencsak körülményes lenne, így most a Cramer-szabályt ismerjük meg.A dolog elég egyszerû, mindössze a fentiekben megtanult 3*3-as determinánsok számítását kell gyakorolnunk.
Amennyiben az A mátrix determinánsa nemzérus (detA!=0), akkor az x1, x2, x3 (...xn) ismeretlenek elôállnak a következô hányadosok képzésével:
D1/detA, D2/detA, D3/detA,
ahol D1, D2, D3, ... azon mátrixok determinánsai, melyeket úgy képezünk, hogy az A mátrix 1, 2, 3, ...oszlopait kicseréljük a jobb oldalon szereplô b együttható vektor elemeivel kicseréljük.
Például:
Amennyiben az együttható mátrix determinánsa nemzérus, akkor az egyenletrendszer határozatlan, ennek vizsgálatára azonban további matematikai ismeretek hiányában nem térünk ki.
Nézzünk egy konkrét példát a Cramer-szabály alkalmazásával történõ megoldásra!
1) 4x1-3x2+ x3=2
2) x1+ x2-2x3=9
3) 2x1+ x2-3x3=14
azaz mátrixos alakban:
A determinánsokat az elsô oszlop szerint kifejtve:
detA=4*(-3+2)-(9-1)+2*(6-1)=-4-8+10=-2
detD1=2*(-3+2)-9(9-1)+14*(6-1)=-2-72+70=-4
detD2=4*(-27+28)-(-6-14)+2*(-4-9)=-2
detD3=4*(14-9)-(-42-2)+2*(-27-2)=6
Ily módon a Cramer-szabály szerint:x1=-4/-2=2
x2=-2/-2=1
x3=6/-2 =-3 Visszahelyettesítéssel ellenôrizve kész vagyunk.
Feladat
A fenti ismeretek értelmében készítsünk programot, mely megold egy 3*3-as, lineáris egyenletrendszert! A program tartalmazzon függvényt a 2*2-es, illetve 3*3-as determináns kiszámítására, valamint vizsgálja meg az alapmátrix determinánsának nemzérus voltát. A megoldást itt találod.
public class Egyenletrendszer {
int tipus=3;
int a11=2; int a12=2; int a13=2; int b1=2;
int a21=1; int a22=1; int a23=1; int b2=1;
int a31=2; int a32=1; int a33=-3; int b3=14;
/*
Annak erdekeben, hogy minden fuggveny lassa az egyenlet egyutthatoit,
az osztaly belsejeben deklaraljuk oket
*/
int det2(int a, int b, int c, int d){
return (a*d-c*b);
}
int det3(){
return ((a11*det2(a22, a23, a32, a33))-(a21*det2(a12, a13, a32, a33))+(a31*det2(a12, a13, a22, a23)));
}
int det3(int i){
int d=0;
switch (i) {
case 1: {
d=b1*det2(a22, a23, a32, a33)-b2*det2(a12, a13, a32, a33)+b3*det2(a12, a13, a22, a23);
break;
}
case 2: {
d=a11*det2(b2, a23, b3, a33)-a21*det2(b1, a13, b3, a33)+a31*det2(b1, a13, b2, a23);
break;
}
case 3: {
d=a11*det2(a22, b2, a32, b3)-a21*det2(a12, b1, a32, b3)+a31*det2(a12, b1, a22, b2);
break;
}
}//switch
return d;
}
public void prog(){
if (tipus==2){;}
if (tipus==3){
if (Math.round(det3())==0){
System.out.println("Az egyenletrendszer hatarozatlan vagy ellentmondasos...");
}
else{
double x1=(det3(1)/det3());
double x2=(det3(2)/det3());
double x3=(det3(3)/det3());
System.out.println("A gyokok: ");
System.out.println("x1="+x1);
System.out.println("x2="+x2);
System.out.println("x3="+x3);
}
//System.out.println(det3(1)+" "+det3(2)+" "+det3(3)+" "+ det3());
}//3-as
}//prog
public static void main(String[] args) {
Egyenletrendszer e=new Egyenletrendszer();
e.prog();
}
}
Ellenõrzõ kérdések
1. Mit nevezünk cimkének, hogyan cimkézhetünkutasításokat?
2. Ismertesd a többszörös elágazás készítésére alklamas utasítást!
3. Ismertesd a break utasítás használatát!
4. Mire használhatjuk a continue utasítást?
5. Mikor nem használhatunk case és continue utasításokat?
6. Milyen módszereket ismersz 2*2-es, linaáris egyenletrendszer megoldására?
7. Mikor mondunk egy 2*2-es egyenletrendszert határozatlannak, illetve ellentmondásosnak? Hány megoldás létezik ezekben az esetekben?
8. Mit nevezünk mátrixnak?
9. Definiáld az alábbi fogalmakat!
Egy mátrix
- fôátlója,
- mellékátlója,
- determinánsa (2*2-es és 3*3-as esetben).
10. Mit értünk egy A n*n-es mátrix x n-dimenziós oszlopvektorral való szorzatán?
11. Hogyan hozható kapcsolatba az egyenletrendszerek megoldása és a mátrixok?
12. Ismertesd a Cramer-szabály-t 3 ismeretlenes, elsôfokú egyenletrendszerek esetén! Mi a megoldhatóság feltétele?
7. fejezet
Fiókos szekrények garmadája, mindegyik hozzá való mamával. A Jáva alapépítõelemei, az objektumok. Objektumok deklarálása változókkal és függvényekkel, amelyeket ezek után metódusoknak fogunk hívni. Objektumok létrehozása és halála, életciklus a Jávában. A szemétgyûjtõ.
Emlékszel a mamára és a fiókos szekrényére a második fejezetben? Eddig határozottan egy készletnyi változóban és függvényben gondolkoztunk. Ez meg is felel a számítógépprogramozás hagyományos modelljének, ahol van egy darab fiókos szekrényünk, azaz adattárolónk és egy rakat függvényünk, ami ezen az egy állapottárolón manipulál. Ez nagyon kellemes is mindaddig, míg a programod kellõen nagyra nem nõ, utána azonban ekkor a program különbözõ funkciói és azoknak különbözõ változói igencsak összekavarják a dolgokat. Képzelj el most egy olyan modellt, ahol a program különbözõ funkcióit a hozzájuk tartozó változókkal egy egységbe fogjuk. Ez még nem elég: ilyen egységeket szabadon hozhatunk létre, egyfajtából akár többet is, mindegyiket saját változókészlettel, hívhatjuk függvényeiket, amelyek a saját változókészleten manipulálnak és aztán dönthetünk az egység elpusztításáról is, ha nincsen már rá szükségünk. Olyan ez, mintha a programunkat úgy építhetnénk, mint valami gépet; elõször megtervezzük az alkatrészeit, majd legyártjuk belõlük a megfelelõ mennyiséget - csavarból százával, fõdarabból csak egyet-kettõt. Ezek után megmondjuk, az alkatrészek hogyan legyenek összekötve, majd beindítjuk a gépet, mire az mûködni kezd. A fent leírt folyamat vészesen hasonlít az objektumorientált programok készítésére, mint amilyeneket Jávában is írunk.
Az objektumorientált programozásban az alkatrészeket objektumnak hívjuk. Objektumokat tervrajz alapján készítünk, pont annyit, amennyi kell. A tervrajzot, amibõl egy bizonyos fajta objektumokat legyártunk, az adott objektumhoz tartozó osztálynak hívjuk. Az objektumok összeköttetéseit az objektum függvényeinek, objektumorientált terminológiával metódusainak hívásával valósítjuk meg. Valahányszor létrehozunk egy objektumot, lefoglalódik hozzá az összes, az osztállyal deklarált változó (tehát nem azok, amelyeket a metódusokban deklaráltunk), ezeket a változókat egyedváltozónak hívjuk, merthogy minden objektumegyednek van belõlük egy teljes készlet. Minden objektum tehát az egyedváltozóin keresztül önálló egyéniség lehet.
Hagyjuk most a hosszas fejtegetést és nézzünk egy példát! Ismerõs?
public class Elso {
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso();
e.prog( args );
}
}
Van itt egy rettenetes titok, ami az egész tananyag eleje óta lappang itt a kék ködök mögött: igazából mindig is objektumorientált programokat írtunk, mert Jávában máshogy nem lehet. Minden programunk egy osztálydefiníció volt és mindegyikben saját magunk is létrehoztunk legalább egy objektumot. Nézzük most át ezt a programot ebbõl a szempontból!
Mindenekelõtt definiáltuk az Elso nevû osztályt. Ezt a következõképpen tettük:
class Elso { ... }
ahol a kapcsos zárójelek között az osztályhoz tartozó cuccok vannak, ebben az esetben két metódus. Egészen pontosan nem ezt tettük, hanem még elé írtuk azt, hogy public. Egyelõre errõl elég annyit tudnunk, hogy a programunk futtathatósága miatt szükséges, hogy a program fõosztálya (amit a java parancs után írunk) public legyen. Ha egy forrásfájlban van egy public osztály, annak neve meg kell egyezzen a forrásfájl nevével. Ezen felül a fájlban tetszõleges számú egyéb class lehet public nélkül. Egyelõre jegyezzük meg azt, hogy a nem public osztályt csak az õt tartalmazó forrásfájlból lehet elérni. Ez nem teljesen pontos így, de egyelõre elég nekünk. Úgyse írunk még olyan programot, aminek forrása több fájlban van.
Nézzük meg most ezt a sort:
Elso e = new Elso();
Ez egy értékadás, de furcsa fajtájú, nem? Mindenekelõtt mi az Elso típus, mi csak int-et, double-t és boolean-t ismerünk. Az Elso egy új típus, egy objektumreferencia. A referencia típusú változó is csak egy fiók, de ebben egy adott osztályú objektumra hivatkozó referenciát, mutatót tárolunk. Egy referenciát úgy lehet elképzelni, mint egy elérési instrukciót: vedd balról harmadik bögrét a második polcon. Maga a bögre felel meg az objektumnak. Ha most kicseréljük a fiókban levõ cetlit egy másikra, ami a harmadik polcon a legszélsõt jelöli meg, akkor az, aki kiveszi a cetlit a fiókból, ezt a bögrét fogja megtalálni és nem az elõzõt. Tányérra mutató cetlit nem lehet a fiókba elhelyezni, ez a fiók a bögrereferenciáké.
Az e változó tehát egy Elso objektumra mutató referenciát tárol, de hogy jutunk ilyenhez? A new operátor használható arra, hogy egy már definiált osztályból egyedeket gyártson. Most egy Elso osztályú objektumból hoztunk létre egy új egyedet, erre az egyedre mutató referenciát szereztünk a new-tól és ezt rögtön hozzá is rendeltük az e változóhoz. Az e-n keresztül tehát most ezt a példányt tudjuk elérni.
Az objektumreferencia típusnak egyetlen definiált konstans értéke van, egy speciális referencia, ami nem mutat sehova. Ennek a konstansnak a neve null. Ha egy objektumreferenciának null értéket adunk, ezzel azt jeleztük, hogy a referencia "nem használható", mert nem áll mögötte objektum. Példa:
e = null;
A null egy különleges érték mert nincs típusa, bármilyen típusú objektumreferenciának értékül adható.
Mit tudunk tenni egy referenciaváltozóval, milyen mûveletek állnak rendelkezésre ilyen típusú értékekkel? Mindenekelõtt más megegyezõ típusú változónak lehet értékül adni. Ha azt mondanánk
Elso m;
m = e;
akkor az m és az e ugyanarra az objektumra hivatkozik, mindkettõn keresztül ugyanazt az objektumpéldányt érhetjük el. Ha ezek után tovább variálunk:
e = new Elso();
akkor létrehoztunk egy új egyedet az Elso-bõl és most az e erre mutat. A két változón keresztül most két különbözõ objektumpéldányt érhetünk el. De mit jelent az, hogy elérünk egy objektumot egy referencián keresztül?
e.prog( args );
A referenciát most felhasználtuk arra, hogy meghívjuk az általa hivatkozott objektum egy metódusát. A függvényhívás pont úgy néz ki, mint egy eddigi mezei függvényhívás, de most a referencia megadásával jeleztük, hogy nem a saját objektumunknak, hanem egy másiknak, a referencia által mutatottnak a metódusát akarjuk.
Egyelõre nem mondom el, miért van szükség arra, hogy programunk saját osztályát példányosítsuk, az majd egy kicsit késõbb jön. A static a kulcszó.
Minden objektumnak van egy speciális metódusa, amit konstruktornak hivunk. A konstruktor arról ismerkszik meg, hogy nincsen visszatérési értéke (void sem!) és a metódusneve megegyezik az osztály nevével. Paraméterei akárhányan lehetnek és persze egy objektumnak lehet több konstruktora is különbözõ fajtájú és típusú paraméterekkel. Nézzünk egy példát! Ha az Elso objektumnak lenne konstruktora, azt így kellene leírni.
Elso( int i ) {
System.out.println( "Konstruktor lefutott, parameter: "+i );
}
Ez speciel egy olyan konstruktor, ami egy darab egészet vár paraméterként és akkor hívódik meg, ha pont egy egészt adtunk a meghívásakor. De hogyan és mikor hívódik meg a konstruktor? A konstruktort az egyed létrehozásakor hívja meg a rendszer (tehát nem mi) és a paramétereit a new mögött álló osztálynév mögött levõ gömbölyû zárójelek közül szerzi. Ha azt akarjuk, hogy ez a konstruktor hívódjon meg, az Elso egyedet így kell létrehozni:
Elso e = new Elso( 2 );
Mint mondtam, minden objektumnak van konstruktora. De akkor hol az Elso konstruktora, nincs olyasmi a kódban, hogy
Elso() { ... }
pedig mi eddig vígan hivatkoztunk rá, amikor azt mondtuk
Elso e = new Elso();
A megoldás az, hogy a rendszer van olyan kedves és egy konstruktort, a paraméter nélkülit automatikusan létrehozza nekünk, ha mi nem deklaráljuk. Így aztán még ha nem is foglalkozunk olyan földi hívságokkal, mint a konstruktor, objektumunkat biztosan létrehozhatjuk a paraméter nélküli konstruktoron keresztül. A paraméter nélküli konstruktort alapértelmezett konstruktornak (angolul default constructor) hívjuk.
Feladat
Módosítsd népszerû Elso programunkat úgy, hogy legyen az Elso osztálynak egy paraméterrel rendelkezõ konstruktora is továbbá az is hívódjon meg! Ha valamiért nem megy, itt a megoldás , de a lecke szövege tartalmazza az összes szükséges programdarabot, úgyhogy ezt meg kell tudni oldanod! Futtasd le a megoldást és értelmezd az eredményt!
public class Elso {
Elso( int i ) {
System.out.println( "Konstruktor meghivodott, parameter: "+i );
}
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso( 2 );
e.prog( args );
}
}
Ezek után deklarálj egy paraméterek nélküli konstruktort is és alakítsd át a programot úgy, hogy az hívódjon meg! A paraméter nélküli konstruktorba is tegyél egy kiírást, hogy biztosak legyünk, tényleg az hívódott-e meg. Futtasd le, értelmezd az eredményt majd ellenõrizd a megoldást!
// Ezt a programot Elso.java neven kell elmenteni, hogy helyesen
// forduljon le.
public class Elso {
Elso() {
System.out.println( "A konstruktor parameter nelkul hivodott meg" );
}
Elso( int i ) {
System.out.println( "Konstruktor meghivodott, parameter: "+i );
}
public void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
Elso e = new Elso();
e.prog( args );
}
}
A konstruktorokat persze elsõsorban nem arra használják, hogy buta kiírásokat eszközöljenek velük, hanem a születõ objektumpéldány belsõ állapotának beállítására, vagyis fõképp arra, hogy az objektum egyedváltozói (emlékszel, ezek azok a változók, amelyeket nem a metódusok törzsében, hanem az osztály törzsében, a metódusokkal egy szinten deklarálunk) megfelelõ kezdõértéket kapjanak. Így aztán egy osztály törzse így nézhet ki:
class ValamiOsztaly {
int parameter;
ValamiOsztaly( int parameter_init ) {
parameter = parameter_init;
}
void irdkiaParametert() {
System.out.println( "Parameter: "+parameter );
}
}
Aztán valahol egy felhasználó kódrészletben mondhatunk valami ilyesmit:
ValamiOsztaly v = new ValamiOsztaly( 12 );
...
v.irdkiaParametert();
és ez persze 12-t írna ki. Az objektum létrehozasakor a konstruktor meghívodik a new utan levõ értékkel. Ezt a konstruktor berakja az objektumegyed parameter nevû változójába. Amikor késõbb meghívjuk ugyanazon objektumegyed egy metódusát, ami a parameter változót felhasználja és mellékesen ki is írja. Egy kicsit olyan, mint az itt-a-piros-hol-a-piros, te tudtad követni?
Ha már az egyedváltozóknál tartunk, az objektumreferenciát felhasználhatjuk arra, hogy egy egyedváltozót direktben elérjünk. Elõzõ példánkban például azt is mondhatjuk:
int k = v.parameter;
ahol is egy frissen létrehozott k nevû változóba pakoltuk a v referenciával hivatkozott objektum parameter nevû változójának értékét.
Megjegyzendõ, hogy az objektum egyedváltozóinak direkt elérése nem javasolt módszer, az obejktum belsõ állapotát a metódusain keresztül illik módosítani és lekérdezni. Ez azonban nem kötelezõ, csak afféle ajánlott programtervezési módszertan.
Feladat
Ragadd meg nagy sikerû Elso programunkat és alakítsd át Masodik-ká! Az új változatban próbáld ki a következõ kunsztokat:
• Legyen az osztálynak egy ertek nevû egész típusú egyedváltozója
• Az alapértelmezett konstruktor adja ennek a 0 értéket
• Legyen egy olyan konstruktor, ami egy egész paramétert vár és az ertek változóba írja
• Legyen az osztálynak egy olyan metódusa, ami kiírja az ertek-ben levõ értéket
• A programodban hozzál létre két objektumegyedet, az egyiket az alapértelmezett konstruktorral, a másikat az egész paraméterrel rendelkezõ konstruktorral.
• Hívd meg mind a kettõ objektumegyednek az ertek-et kiíró metódusát
• Érd el az ertek változót direktben a fõprogramodból és írd ki úgy is!
Mindenképpen próbáld a programot megírni magad és ha készen van, nézd meg a megoldást!
// Ezt a programot Elso.java neven kell elmenteni, hogy helyesen
// forduljon le.
public class Masodik {
int ertek;
Masodik() {
ertek = 0;
}
Masodik( int i ) {
ertek = i;
}
void irdkiazErteket() {
System.out.println( "Ertek: "+ertek );
}
public static void main( String args[] ) {
Masodik m1 = new Masodik();
Masodik m2 = new Masodik( 3 );
System.out.println( "m1" );
m1.irdkiazErteket();
System.out.println( "m1.ertek: "+m1.ertek );
System.out.println( "m2" );
m2.irdkiazErteket();
System.out.println( "m2.ertek: "+m2.ertek );
}
}
Most már tudunk objektumokat létrehozni, de hogyan tûnnek ezek el? Minden objektumhoz tartozik memóriaterület, amelyet a rendszer lefoglal számára, amikor az objektum létrejön. Ha egyre csak foglalgatunk, felesszük elõbb-utóbb a gép memóriáját és a programunkra szomorú vég vár.
A Jáva sajátos módon oldja meg a már nem használt objektumok felszabadítását. A háttérben egy szemétgyûjtõ programot futtat, ami megtalálja a már nem használt objektumpéldányokat és felszabadítja õket. Mi alapján dönti el, ki a szükségtelen és ki nem? Minden objektum szükségtelen, amire már nincsen referencia. Például kiváló Masodik programunkban ha azt írnánk:
m1 = new Masodik( 3 );
m1 = new Masodik( 4 );
akkor a 3-as paraméterrel létrehozott Masodik egyedre a második értékadás után már nem mutatna referencia. Ugyanez a trükk akkor is igaz, ha a referenciaváltozó automatikusan takarítódik el, például egy metódus belsõ változójának megszûnése miatt.
void objektumLetrehozo() {
Masodik m1 = new Masodik();
}
Ebben az esetben az m1 változó megszûnik, amikor a metódus visszatér a hívóhoz és minthogy a referenciát nem mentettük el egy biztos helyre (pl. objektum egyedváltozóba vagy a metódus visszatérési értékébe, ahol a hívó esetleg felhasználhatja), a létrehozott Masodik egyed szemétté válik.
A szemétgyûjtõ nem azonnal takarítja el a feleslegessé vált objektumokat, hiszen akkor programunkat alaposan lelassítaná állandó sepregetésével. Hasonlatosan egy rutinos házmesterhez, elõbb megvárja, hogy elég sok szemét legyen és akkor lendül akcióba. Mindez megmagyarázza, miért olyan hihetetlenül magas a Jáva programok memóriaigénye. Egy jól irányzott Jáva programsorral akár 3-4 szemétobjektumot is tudunk csinálni és egy komoly Jáva program terhelés alatt akár 2-3KBájt szemetet is létrehoz másodpercenként. Ezért cserébe viszont nem kell törõdni az egyik legundokabb hibával, a memóriafolyással, amikor a programozó elfelejti felszabadítani a lefoglalt és már nem használt memóriaterületet és a gép szabad memóriája lassan elfogy.
A szemétgyûjtõ mûködésének demonstrálására nem tudtam látványos és egyszerû példát kitalálni, így sajnos el kell hinned, hogy ez így van. Lássuk inkább a lecke fináléját, íme egy nagyszerû
Feladat
Írjál egy programot, ami egy játékost és egy bankot modellez, akik fej vagy írást játszanak forintos alapon. A játékos fogad fejre vagy írásra, a bank feldobja a pénzt és ha a játékos eltalálja, kap egy forintot a banktól, ha nem, õ fizet egyet. A játékos fogadjon véletlenszerûen, játsszanak ilyen mérkõzésbõl 10-et majd a végén írd ki, kinek mennyi pénze maradt. A feladat megoldásához mindenekelõtt meg kell tervezned a programban használt objektumokat. Minden szereplõt azonosítson egy objektum! Ezek szerint biztosan van Játékos és Bank objektumunk. Írd fel a két objektum tulajdonságait a következõ formában:
xxx objektumnak van
- ... (az objektum által tárolt adatok listája)
xxx objektum képes
- ... (az objektumon végrehajtható mûveletek listája)
xxx objektum kommunikál
- ... milyen objektumokkal kommunikál az objektum (csak ha mi vagyunk a kezdeményezõ)
Alkalmazd a fenti módszert a feladatunkra! A megfejtéshez görgess le néhány sort, de ennek mennie kell.
A Játékosnak van
- pénze
A Játékos képes
- fogadásokat tenni
- kiírni, mennyi pénze van
A Játékos kommunikál
- A Bankkal, amikor fogadásokat tesz
A Banknak van
- pénze
A Bank képes
- Fogadásokat fogadni, dobni, eldönteni, hogy a játékos vesztett vagy nyert
- kiírni, mennyi pénze van
A Bank kommunikál
- Senkivel, a Játékos kezdeményezi a fogadásokat
Ezen felül van még egy szereplõ, a Fogadó, ami a szálakat mozgatja. Õ veszi rá a Játékost, hogy 10-szer fogadjon a Banknál.
A Fogadónak van
- Játékos objektuma
- Bank objektuma
A Fogadó képes
- lejátszatni a mérkõzést
A Fogadó kommunikál
- A Játékossal, hogy fogadjon
A "van" szekcióból derülnek ki az objektum egyedváltozói. A példában a Játékosnak és a Banknak van pénz változója, a Fogadó meg referenciát tárol a Játékos és Bank objektumokra. A "képes" szekcióból derül ki, milyen metódusai lesznek. A Játékosnak lesz egy metódusa, amivel fogad és egy másik, ami kiírja a Játékos objektumban tárolt pénzt. A Banknak lesz egy metódusa, aminél fogadásokat lehet tenni és egy másik, ami kiírja a bank pénzét. A Fogadónak egy metódusa lesz, ami a játszmát lejátsza. A "kommunikál" szekcióból derül ki, melyik objektum melyik másik metódusait hívja meg, kire tárol referenciákat. A Játékos referenciát tárol a Bankra és meghívja annak fogadáskiértékelõ metódusát, a Fogadó pedig a Játékosra tárol referenciát. A továbbiak kedvéért a Bank objektumot is a Fogadó hozza létre és passzolja a Játékosnak a refernciáját a Játékos konstruktorán keresztül. Az alábbi ábrán ezeket a relációkat ábrázoltam.
A fentiekben leírt egyszerû módszert objektumelemzésnek fogom hívni ezután. A módszer valóban egy profi programozók által is használt programtervezési módszertan lényegesen leegyszerûsített változata, amely most oktatásra teljesen megfelel.
A sok duma után írd meg a programot! Nézd meg az ábra függõségi listáját és ebbõl kiderül, célszerû a Bankkal kezdeni, mert az nem függ semmitõl, utána jöhet a Játékos, majd a Fogadó. Ha készen van, csinálj egy olyan változatot, ahol három játékos játszik ugyanannál a banknál. Ha nem untad meg, csinálj olyat is, ahol három játékos játszik három különbözõ banknál! Pusztán csak ellenõrzésképpen itt a megoldás.
/* A Bank objektum reprezentalja a bankot.
A Banknak van
- penze
kepes
- fogadasokat fogadni es kiertekelni, nyert-e vagy vesztett a fogado
- kiirni, mennyi penze van
kommunikal
- a jatekosokkal, akik fogadnak nala
*/
class Bank {
int penz;
// Konstruktor, megkapja a Bank kezdeti penzet
Bank( int penz_p ) {
penz = penz_p;
}
// Fogadas kiertekeles, megkapja a fogadast (0 vagy 1), o is fogad
// es visszaadja, mennyit nyert a jatekos (-1 vagy 1). Sajat penzet
// a jatekos nyeremenyevel ellenkezoleg modositja
int FogadasErtekeles( int fogadas ) {
// Dob fejet vagy irast
int penzerme,jatekos_nyeremeny;
if( Math.random() < 0.5 ) {
System.out.println( "Fej nyer" );
penzerme = 0;
}
else {
System.out.println( "Iras nyer" );
penzerme = 1;
}
if( fogadas == penzerme )
jatekos_nyeremeny = 1;
else
jatekos_nyeremeny = -1;
penz = penz - jatekos_nyeremeny;
System.out.println( "Jatekos nyeremenye: "+jatekos_nyeremeny );
return jatekos_nyeremeny;
}
void PenzKiir() {
System.out.println( "A banknak "+penz+" penze van" );
}
}
/* A Jatekos objektum reprezentalja a fogadot.
A Jatekosnak van
- azonositosszama
- penze
kepes
- fogadni
- kiirni, mennyi penze van
kommunikal
- a bankkal, amikor bekuldi a fogadasat es visszakapja, nyert-e.
*/
class Jatekos {
Bank bank;
int penz;
int jatekos_szam;
// Konstruktor. Megkapja a jatekos azonositojat, a penzenek a mennyiseget
// es egy referenciat a bankra, hogy fogadhasson
Jatekos( int jatekos_szam_p, int penz_p, Bank bank_p ) {
jatekos_szam = jatekos_szam_p;
penz = penz_p;
bank = bank_p;
}
// Egy fogadas a banknal.
void Fogad() {
int fogadas,jatekos_nyeremeny;
if( Math.random() < 0.5 ) {
System.out.println( "Fogadas fejre" );
fogadas = 0;
}
else {
System.out.println( "Fogadas irasra" );
fogadas = 1;
}
penz = penz + bank.FogadasErtekeles( fogadas );
}
void PenzKiir() {
System.out.println( "A "+jatekos_szam+". jatekosnak "+penz+" penze van" );
}
}
/* Fogado objektum reprezentalja vezerlot, ami a Jatekost (Jatekosokat)
fogadtatja a Banknal
A Fogadonak van
- Bank objektuma
- Egy vagy tobb Jatekos objektuma
kepes
- Vegrehajtatni a fogadasi sorozatot
kommunikal
- A Jatekossal, amikor fogadast ker tole
*/
public class Fogado {
Bank bank;
Jatekos j1;
/* Harom jatekosos modhoz torold a megjegyzest
Jatekos j2;
Jatekos j3;
*/
void Jatszma() {
// 1000 egyseg kezdeti penz a banknal
Bank bank = new Bank( 1000 );
// 1. jatekos 20 egyseg penzzel
j1 = new Jatekos( 1,20,bank );
/* Harom jatekosos modhoz torold ki a kommentart
// 2. jatekos 20 egyseg penzzel
j2 = new Jatekos( 2,20,bank );
// 3. jatekos 20 egyseg penzzel
j3 = new Jatekos( 3,20,bank );
*/
// Kezdodik 10 kornyi fogadas
for( int i = 1 ; i <= 10 ; ++i ) {
System.out.println( i+". kor" );
// A jatekosok sorban fogadnak
j1.Fogad();
/* 3 jatekosos modhoz torold ki a kommentart
j2.Fogad();
j3.Fogad();
*/
// Kiirjuk a jatekosok es a bank penzet
j1.PenzKiir();
/* 3 jatekosos modhoz torold ki a kommentart
j2.PenzKiir();
j3.PenzKiir();
*/
bank.PenzKiir();
}
}
public static void main( String args[] ) {
Fogado f = new Fogado();
f.Jatszma();
}
}
Feladat
Írjál egy olyan programot, ami három játékost modellez, akik a kõ-papír-ollót játszanak körmérkõzéses alapon. Ha emlékszel, a kõ-papír-olló az a klasszikus játék, ahol a két játékos egyszerre választ a három tárgy közül és egy körkörös szabályrendszer alapján döntik el, ki gyõzött: a kõ gyõz az olló ellen, de veszít a papír ellen, a papír pedig veszít az olló ellen. Futtasd le a körmérkõzést 20-szor és irasd ki, melyik játékos hányszor nyert. Minden játékosnak legyen egy száma, ami azonosítja.
Elõször végezd el az objektumelemzést, ezt itt ellenõrizheted. Ezután készítsd el a programot, ha készen vagy, ellenõrizd a megoldást! Figyeld meg, hogy a megoldásunkban most nem tároltuk el a játékostárs refernciáját a saját objektumunkba (pedig megtehettük volna, hiszen a párok változatlanok), hanem a rugalmasabb szerkezet érdekében paraméterként adjuk át a fogadó metódusnak. Ha netán eltároltad volna apartner referenciáját, az is helyes.
A KőPapírOlló feladat objektumelemzése
A feladatban vannak Játékosok és Fogadó, aki a meccseket bonyolítja.
A Játékosnak van
- azonosítója
- nyert meccsek számlálója
A Játékos képes
- fogadni, megkérni egy másik játékost hogy ő is fogadjon és megváltoztatni az eredményszámlálót a meccs kimenetelének megfelelően
- fogadni egy másik játékos fogadási kérését, kiértékelni az eredményt, megváltoztatni a saját eseményszámlálót a meccs eredményének megfelelően és visszaadni a meccs eredményét a kezdeményező játékosnak
- Kiírni, mennyi meccset nyert már
A Játékos kommunikál
- Egy másik Játékossal
A Fogadónak van
- Három Játékos objektuma
A Fogadó képes
- meccset játszatni a három játékossal
A Fogadó kommunikál
- Az éppen meccset kezdeményező játékossal.
A kommunikációs ábra a következő (csak egy fogadó Játékospárt ábrázolva, ez körbejár a három játékos között).
[KőPapírOlló kommunikációs ábra]
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return 0;
if( veletlen < 2 )
return 1;
return 2;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == 2 ) && ( sajat_tipp == 0 ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == 0 ) && ( sajat_tipp == 2 ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp )
return 0; // Dontetlen
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo {
void Jatszma() {
Jatekos j1 = new Jatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
public static void main( String args[] ) {
KoPapirOllo k = new KoPapirOllo();
k.Jatszma();
}
}
8. fejezet
Újabb megvilágosodás: statikus változók és metódusok. Egyedváltozók és osztályváltozók kavalkádja. Öröklõdés és konverziók.
Mint említettem, egy normális, mezei osztálynak egyedváltozói vannak, amelyek minden egyedben külön létrejönnek és külön életet élnek az egyes egyedekben. Miután létrejöttek, semmi közük egymáshoz. Ugyanúgy, a mezei osztály metódusai az egyedváltozókon manipulálnak, így amikor meghívunk egy metódust pl.
j1.eredmenyKiiras();
akkor implicit módon odaadtuk a j1 által hivatkozott objektumegyedet is az eredménykiíras metódusnak, ami aztán buzgón felhasználja ezen objektumegyed változókészletét. Ez eddig rendben is van. Lehetnek azonban esetek, amikor nem akarjuk, hogy a változó minden egyedben létrejöjjön, hanem csak egyet akarnánk, közöset minden objektumegyednek, ami az adott osztályból példányosítódott. Ha netán ilyenben törnénk a fejünket, a statikus változók pont nekünk valók. A statikus változó pont olyan, mint bármely változó, csak a típusdeklarációja elé még oda van írva az hogy static. Így:
static int szamlalo;
Az ilyen változó nem jön létre újra meg újra, valahányszor az osztályt példányosítjuk, csak egyszer, amikor a Jáva virtuális gép elõször tölti be az osztálydefiníciót. A konstruktorral összeházasítva például felhasználhatjuk ezt a fajta változót arra, hogy megszámolja, hányszor példányosították az adott osztályt. A következõ példában ezt tesszük.
class Szamlalo {
static int peldanyok = 0;
Szamlalo() {
++peldanyok;
}
}
Ezek után valahányszor azt mondjuk: new Szamlalo(), a peldanyok valtozo megnõ eggyel.
Statikus változóra kétféleképpen hivatkozhatunk: vagy egy referencián keresztül, mint más becsületes változóra vagy egyszerûen az osztály nevével pl. így:
int k = Szamlalo.peldanyok;
Ez utóbbi azért lehetséges, mert a peldanyok változó nem az objektumegyedeknek, hanem az osztálynak lefoglalt memóriaterületen tárolódik, eléréséhez nincs szükség objektumreferenciára. A statikus változóknak ezt a tulajdonságát elõszeretettel használják fel arra, hogy konstansokat deklaráljanak vele. Ha pl. a Szamlalo osztályban azt mondom:
static int Milennium = 2000;
azt késõbb bárhonnan kényelmesen elérhetem Szamlalo.Milennium formában anélkül, hogy mindenféle referenciákra kellene vadászni. A konvenció szerint (amit a Jáva nem tesz kötelezõvé, csak követni ajánlják) a konstansok nevét csupa nagybetûvel kell írni, hogy megkülönböztessük a változó változóktól. Így e:
static int MILENNIUM = 2000;
Feladat
Vedd elõ nagy sikerû kõ-papír-olló programunkat és valósíts meg benne két szolgáltatást a statikus változók segítségével! Elõször is számolja meg és a futás végén írja ki, hány döntetlen eredmény született összesen. A Jatekos osztályban hozzál létre egy statikus változót és növelgesd, valahányszor döntetlen fordult elõ. Másodszor is a kõ, papír, olló tárgyakhoz rendelt számokat helyettesítsd konstansokkal. Íme az én változatom!
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return 0;
if( veletlen < 2 )
return 1;
return 2;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == 2 ) && ( sajat_tipp == 0 ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == 0 ) && ( sajat_tipp == 2 ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp )
return 0; // Dontetlen
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo {
void Jatszma() {
Jatekos j1 = new Jatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
public static void main( String args[] ) {
KoPapirOllo k = new KoPapirOllo();
k.Jatszma();
}
}
Metódusok is lehetnek statikusok. Ha egy metódusnév elõtt áll a static kulcsszó, ennek három következménye lesz.
1. A metódus csak statikus változókat érhet el. Ne feledd: a statikus metódus hívásakor nem ismeri az objektumegyed kontextusát, így aztán az egyedváltozókat nem tudja elérni.
2. A metódus hívható osztálynév.metódusnév formában. Példa: Math.random() (ugye ismerõs?)
3. A metódus nem hívhat nem statikus metódusokat. Ez ugyanazon ok miatt van, amiért egyedváltozókat sem érhet el.
Statikus metódusoknak a legfõbb haszna az, hogy nem kell érte példányosítani az osztályt, meghívható egyszerûen osztálynév alapján. Ezzel kitörhetünk a Jáva erõszakos objektumszemléletébõl és mindenhonnan elérhetõ függvényeket írhatunk.
Feladat
Fogd az Elso.java programot és érd el, hogy ne kelljen az Elso-t példányosítani! Töröld ki tehát a következõ sort
Elso e = new Elso();
és tedd meg a szükséges változtatásokat, utána ellenõrizd a megoldást!
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
- dontetlen meccs szamlaloja (egy az osszes objektumra)
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
static int dontetlen_meccsek = 0;
static int KO = 0;
static int PAPIR = 1;
static int OLLO = 2;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return KO;
if( veletlen < 2 )
return PAPIR;
return OLLO;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == OLLO ) && ( sajat_tipp == KO ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == KO ) && ( sajat_tipp == OLLO ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp ) {
++dontetlen_meccsek;
return 0; // Dontetlen
}
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo2 {
void Jatszma() {
Jatekos j1 = new Jatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
System.out.println( "Dontetlen meccsek osszesen: "+Jatekos.dontetlen_meccsek );
}
public static void main( String args[] ) {
KoPapirOllo2 k = new KoPapirOllo2();
k.Jatszma();
}
}
Most már talán megértheted, miért hagytuk ilyen sokáig burjánzani a misztikumot a main-ban található két sor körül. Minthogy a main statikus (így szól a Jáva specifikáció), mert a Jáva virtuális gép nem példányosítja a java parancs által hivatkozott osztályt, így nem érhet el nem statikus változót vagy metódust. A sok static megzavart volna és nem is tudtuk volna ilyen könnyedén bevezetni az egyedváltozókat. Mostantól azonban tudjuk, mit csinálunk, így a programosztályt csak akkor példányosítjuk, ha kell.
És most valami teljesen más! Megmutatom, hogyan lehet minimális munkával okos objektumokat készíteni. Eddig azt tanultuk, hogy az osztályok változóit és metódusait nekünk kell deklarálni. Ha azonban olyan lusta vagy, mint amilyen én, inkább veszünk valami készet és átalakítjuk csupán azt módosítva benne, amiben a kész nem tesz eleget igényeinknek. A módszert, ami objektumorientált rendszerekben erre a célra rendelkezésre áll, öröklõdésnek hívjuk.
Öröklõdéssel azt jelenthetjük ki, hogy egy osztályt nem a nulláról hozunk létre, hanem átveszünk egy létezõ osztályból mindent, majd bõvítjük illetve fölüldefiniáljuk az új funkciókkal. Vegyük a következõ példát:
class Eredeti {
int i;
void novel() {
++i;
}
}
class Leszarmazott extends Eredeti {
void novel() {
i = i + 2;
}
void csokkent() {
i = i -2;
}
}
A kicsit buta példánkban (miért kell ahhoz leszármazás, hogy megspóroljunk egy int i;-t?) a leszármazott osztályban felülírtuk a novel() metódust, hogy ezentúl kettõvel növelgesse i-t és hozzáadtunk egy csokkent metódust, ami csökkentgetni is tud. A Leszarmazott osztályt pont úgy kell használni, mint az Eredeti-t.
Még egy megjegyzés: a konstruktorok nem öröklõdnek. Ha a leszármazott osztálynak is szükségük van spéci (tehát paramétert fogadó) konstruktorokra, azt újra kell definiálni a leszármazott osztályban is. Ha nincs szükség változtatásra, használhatod a super() hívást, amivel a szülõosztály konstruktora felé passzolhatod a paramétereket. Vegyünk egy példát, ahol egy egész paramétert passzolunk felfelé:
class Leszarmazott extends Eredeti {
Leszarmazott( int i ) {
super( i );
}
...
}
Az öröklõdés kiválóan alkalmas arra, hogy bevezessek egy régen esedékes fogalmat, a típuskonverziót. Típuskonverzióval hasonló típusú értékeket alakíthatunk egymásba. Kézenfekvõ példa a számok: miért kell fallal elválasztani egymástól az int-et és double-t? Nem kell és íme egy kis példa, hogyan lehet típuskonverzióval egymásba alakítani õket.
double n = 2.7;
int ni = (int)n;
n = (double)ni;
Az ni változóba átkonvertáltuk n-t. Csoda azonban most se történt, ni-be 2 került, a törtrész levágódott. A következõ lépésben az n-be visszakonvertáltuk az ni értéket, minek eredményeképpen n-ben most 2.0 van.
Ez eddig nagyon egyszerû, de mik lehetnek hasonló típusok még? A Jáva nagyon szigorú típuskonverzió terén és a számok mellett csak a leszármazott osztályok referenciáit engedi egymásba átkonvertálni. Például az elõbbi példánknál maradva ha netán azt mondanánk
Leszarmazott l = new Leszarmazott();
Eredeti e = (Eredeti)e;
Ez sikerülne, mert az Eredeti és Leszarmazott "hasonló" típusok. Azért megengedett a mûvelet, mert az Eredeti legalább azt tudja, amit a Leszarmazott, mert belõle jött létre módosítással. Ha ugyanezt visszafelé csinálnánk, kicsit bonyolultabb a helyzet.
Leszarmazott l2 = (Leszarmazott)e;
Ezt a fordító elfogadja, de azért még nem biztos, hogy helyes. Ha a három utasítás úgy követte egymást, ahogy az elõbb leírtuk, akkor helyes, hiszen e-ben valójában egy Leszarmazott referenciája van. Ha azonban netán ez lenne a sorrend:
Eredeti e = new Eredeti();
Leszarmazott l2 = (Leszarmazott)e;
ezt a fordító ugyan nem tekinti hibának (rokon osztályok között a konverzió megengedett), de a Jáva virtuális gép a futás során észreveszi a turpisságot és hibaüzenettel leáll.
Most már csak az a kérdés, ha vesszük az elõzõ példát
Leszarmazott l = new Leszarmazott();
Eredeti e = (Eredeti)e;
és meghívjuk a novel metódust
e.novel();
vajon eggyel vagy kettõvel nõ-e az i változó? A Jáva dinamikus típusfeloldással rendelkezik, tehát mindig annak az osztálynak a metódusai hívódnak meg, ami az osztály valójában, nem pedig a referencia típusa szerint, i tehát kettõvel nõ. Referenciáinkat tehát nyugodtan konvertálhatjuk a szülõosztály felé, ha elhasználjuk a referenciát, az a megfelelõ eredménnyel fog járni.
Feladat
Ragadd meg barátunkat, a kõ-papír-olló programot és csináld meg azt, hogy egy játékos azzal a "szisztémával" játszik, hogy mindig pl. kõre fogad. Csinálj egy leszármazottat a Jatekos osztályból és írd felül a fogadas() metódust megfelelõképpen! Ezek után az egyik játékost a leszármazott osztály szerint hozdd létre és használj típuskonverziót a típushibák elkerülésére! Itt a megoldás ellenõrzésképpen. Ki a buta játékos?
// Ezt a programot Elso.java neven kell elmenteni, hogy helyesen
// forduljon le.
public class Elso {
static void prog( String args[] ) {
System.out.println( "Hello, ez az elso Java programunk!" );
}
public static void main( String args[] ) {
prog( args );
}
}
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
- dontetlen meccs szamlaloja (egy az osszes objektumra)
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
static int dontetlen_meccsek = 0;
static int KO = 0;
static int PAPIR = 1;
static int OLLO = 2;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return KO;
if( veletlen < 2 )
return PAPIR;
return OLLO;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == OLLO ) && ( sajat_tipp == KO ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == KO ) && ( sajat_tipp == OLLO ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp ) {
++dontetlen_meccsek;
return 0; // Dontetlen
}
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
class ButaJatekos extends Jatekos {
ButaJatekos( int i ) {
super( i );
}
int tipp() {
return KO;
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOllo3 {
void Jatszma() {
Jatekos j1 = (Jatekos)new ButaJatekos( 1 );
Jatekos j2 = new Jatekos( 2 );
Jatekos j3 = new Jatekos( 3 );
for( int i = 0 ; i < 20 ; ++i ) {
j1.MeccsetJatszik( j2 );
j2.MeccsetJatszik( j3 );
j3.MeccsetJatszik( j1 );
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
j1.IrdKiANyertMeccseket();
j2.IrdKiANyertMeccseket();
j3.IrdKiANyertMeccseket();
System.out.println( "Dontetlen meccsek osszesen: "+Jatekos.dontetlen_meccsek );
}
public static void main( String args[] ) {
KoPapirOllo3 k = new KoPapirOllo3();
k.Jatszma();
}
}
Ellenõrzõ kérdések
1. Mi a statikus változó?
2. Elérhetõ-e a statikus változó nem statikus metódusból?
3. Milyen referenciával lehet elérni a statikus változót?
4. Mi a statikus metódus?
5. Elérhet-e normál egyedváltozót statikus metódus?
6. Meghívhat-e statikus metódust normál, nem statikus metódus?
7. Meghívhat-e normál metódust statikus metódus?
8. Mit jelent az, hogy egyik osztály leszármazottja a másiknak?
9. Lehet-e egy osztályreferenciát a szülõosztály felé konvertálni?
10. Lehet-e egy osztályreferenciát a leszármazott osztály felé konvertálni?
11. Lehet-e Jávában különbözõ típusú értékek között értékadás?
12. Ha létrehozunk egy egyedet és egy szülõosztály típusa szerinti referenciával hivatkozunk rá, a szülõosztály vagy a leszármazott osztály szerinti metódus hívódik-e meg?
Ellenõrzõ kérdések
1. Mi az objektum, mi az osztály és mi a kapcsolatuk?
2. Mi az összefüggés a függvények és metódusok között?
3. Mi az egyedváltozó?
4. Mi az objektumreferencia
5. Mit jelent a null?
6. Hogyan hívhatjuk meg egy objektum metódusait az objektumreferencián keresztül?
7. Mi a konstruktor?
8. Hogyan hívjuk meg a konstruktort?
9. Mi az alapértelmezett konstruktor?
10. Mikor generál alapértelmezett konstruktort a fordító maga?
11. Hogyan hivatkozhatunk egy objektum egyedváltozóira az objektumreferencián keresztül?
12. Mit jelent az, hogy a Jáva rendszerben egy szemétgyûjtõ mûködik?
9. fejezet
Adatok tömegesen: tömbök a Jávában. Tömbtípusok. A Jáva tömbök is csak objektumok. Objektumok és tömbök tömbje.
Emlékszel lottós példánkra az 5. fejezetben, ott öt változóban tároltuk a lottószámokat. Talán már akkor megfordult a fejedben, hogy esetleg van egy elegánsabb módszer egymáshoz tartozó azonos típusú adatok tárolására. Ha a fiókosszekrénynél maradunk, mód van valami olyasmire, mint egy katalógusszekrény, ahol bizonyos kulcs (általában kezdõbetû) alapján érhetjük el az elemeket. A katalógusszekrénynek megfelelõ eleme a Jávának is van, de mi egy egyszerûbb adattípussal kezdjük, ahol az elemeket egy számmal, az indexszel lehet kiválasztani. Ezt az adattípust szinte minden számítógépes nyelv ismeri, tömbnek hívják.
Legegyszerûbb egy példával kezdeni. Nézzük a következõ sort:
int a[] = new int[20];
Különösebb magyarázat nélkül (majd jön az is!) egyelõre fogadjuk el, hogy ez a sor egy 20-elemû tömböt kreál, amelyben minden tömbelem egy int. Az tömbelemeket egy egész típusú indexszel választjuk ki: a tömb elsõ eleme a[0], a második az a[1], az utolsó az a[19] (minthogy 0-ról kezdtük a számlálást!) A tömbelemeket ugyanúgy kezelhetjük, mintha normális változók lennének, értéket adhatunk nekik:
a[0] = 42;
vagy pedig felhasználhatjuk az értékeit kifejezésekben:
a[2] = a[0] + a[1];
Természetesen az indexnek nem kell konstansnak lennie, tetszõleges egész kifejezés megteszi. Példának okáért a következõ programrészlettel tölthetjük fel az egész tömböt 42-vel.
for( int i = 0 ; i < 20 ; ++i )
a[i] = 42;
Ilyen egyszerû ez a felszínen! Most vizsgáljuk meg a tömböt létrehozó sort, mert érdekességek találhatók ottan. Az objektumoknál megszokott módon nézzük a tömbváltozót magát:
int a[];
Ez a változó egy int-ekbõl álló tömbre mutató referencia. Ugyanúgy, mint egy objektumreferencia (mint ahogy valójában objektumreferencia is) egy mutatón kívül nincs lefoglalt tárterület mögötte. A 20 egész értékünk nem itt van. A referencia akkor lesz használható, ha létrehozunk egy tömbobjektumot, amire a referencia mutathat. Ilyen objektumot gyárt az értékadás jobb oldalán álló kifejezés.
new int[20];
Ez a kifejezés 20 egész számot befogadni képes tömbnek foglal helyet és visszatérési értéke a tömbre mutató referencia. Ezt tesszük bele az "a" tömbreferenciára, minek eredményeképpen az "a" változón keresztül indexelni tudjuk a tömböt. Az "a" változó - hasonlóan bármilyen más objektumrefernciához - tehát attól kap értelmet, amire mutat. Írhatjuk késõbb, hogy
a = new int[40];
Ekkor a régi tömb, amire "a" imént mutatott eltûnik (vagy elérhetõ egy másik referencián keresztül, ha volt ilyen) és "a" egy vadi új, ezúttal 40 elemû tömböt tud elérni.
A tömbök tehát a Jávában teljesen úgy viselkednek, mint az objektumok. Ha van egy tömb típusú referenciánk, azzal "rámutathatunk" egy már létezõ tömbre, így két tömbreferencián keresztül érhetjük el ugyanazt a tömböt. Példa:
int a[] = new int[20];
int b[];
b = a;
a[0] = 1;
b[1] = 13;
A két értékadás a programrész végén ugyanannak a tömbnek a 0. és az 1. elemét manipulálja, minthogy a 3. sorban megtettük, hogy a "b" is ugyanarra a tömbre hivatkozzon.
A tömbök annyira objektumszerûek, hogy saját egyedváltozóik is vannak. Minden tömbobjektumnak van egy length egyedváltozója, ami megmondja a tömb méretét. Elõzõ példánkban
a.length
és
b.length
egyaránt 20-at ad vissza értékként (merthogy igazából ez ugyanaz a tömb).
A Jáva teljes típus- és indexhatárellenõrzéssel rendelkezik. Nem tehetjük meg azt, mint a C-ben, hogy a tömbindexet a tömb méreténél nagyobbra állítjuk és telepiszkítjuk a memóriát. Amikor egy tömbreferencián keresztül elérünk egy tömbobjektumot, a Jáva ellenõrzi a tömbobjektum tényleges méretét (dinamikusan foglaltuk, fordításidõben nem jöhet rá) és kilövi a programot, ha túlindexelni próbálja a tömböt.
Feladat
Írjuk át a már elkészített lottó programot úgy, hogy a véletlen számokat egy 5 elemû int típusú tömbbe helyezze el! ! Gyakorlásképpen használjunk statikus metódusokat és adattagokat! A megoldást itt találhatod.
public class Ujlotto {
//a program nem vizsgalja, hogy minden generalt veletlen szam kulonbozo-e
static int[] nyeroszamok = new int[5];
public Ujlotto() {
}
static void prog(){
System.out.println("A lotto e heti nyeroszamai:");
for (int i = 0; i < nyeroszamok.length; i++) {
nyeroszamok[i]=(int) (Math.round(Math.random()*89)+1);
System.out.print(nyeroszamok[i]+" ");
}
}
public static void main(String[] args) {
Ujlotto.prog();
}
}
Feladat
1. Készítsünk programot, mely feltölt egy 100 elemû, véletlen számokból álló tömböt, majd az elemeket kilistázza a konzolra! Gyakorlásképpen használjunk statikus metódusokat és adattagokat! Az osztály neve legyen "Osszegzes"!
2. Bõvítsük a programot úgy, hogy adjuk össze az így generált tömb elemeit, majd az összeget írjuk ki a konzolra!
3. Válogassok le az eredeti tömbbõl a páros, illetve páratlan számokat egy "paros" illetve "paratlan" nevu tömbbe, listázzuk ki külön a páros, illetve páratlan számokat, illetve írjuk ki ezek arányát! A megoldás az Osszegzes.java programban található, az 2. illetve 3. részfeladat a zárójelek elhagyásával bõvíthetõ.
public class Osszegzes {//az egyenletes eloszlas vizsgalatara
static int[] szamtomb = new int[50];
static int osszeg;
//3. paros-paratlan levalogatas...
/*
static int[] paros = new int[50];
static int[] paratlan = new int[50];
static int paros_mutato=0;
static int paratlan_mutato;
*/
//3. paros-paratlan levalogatas vege...
static void prog(){
for (int i = 0; i < szamtomb.length; i++) {
szamtomb[i]=(int) (Math.round(Math.random()*100));
System.out.print(szamtomb[i]+" ");
}
System.out.println();
//2. bovites osszegzessel
/*
for (int i = 0; i < szamtomb.length; i++) {
osszeg+=szamtomb[i];
}
System.out.println();
System.out.println("Az osszeg: "+osszeg);
*/
//2. bovites osszegzessel vege...
//3. paros-paratlan levalogatas...
/*
paros_mutato=0;
paratlan_mutato=0;
for (int i = 0; i < szamtomb.length; i++) {
if ((szamtomb[i]%2)==0) {
paros[paros_mutato]=szamtomb[i];
paros_mutato++;
}
else {
paratlan[paratlan_mutato]=szamtomb[i];
paratlan_mutato++;}
}
System.out.println(paros_mutato+" db paros szam van:");
for (int i = 0; i < paros_mutato; i++) {
System.out.print(paros[i]+" ");
}
System.out.println();
System.out.println(paratlan_mutato+" db paratlan szam van:");
for (int i = 0; i < paratlan_mutato; i++) {
System.out.print(paratlan[i]+" ");
}
*/
//3. paros-paratlan levalogatas vege...
}//prog vege...
public static void main(String[] args) {
Osszegzes.prog();
}
}
Eddig túlságosan az egész típusú tömbökre összpontosítottunk, pedig a tömb által tárolt értékhalmaz (a tömb alaptípusa) természetesen akármilyen Jáva típus lehet. Lehet természetesen objektumreferencia is és ez agy nagyszerû esélyt ad nekünk arra, hogy objektumokat nagy számban állítsunk elõ. Eddig ugye mindegyikhez kellett egy külön referenciaváltozó, de ha most egy tömböt csinálunk referenciákból, jó sok objektumot létrehozhatunk. Emlékszünk még Elso objektumunkra a 7. fejezetben? Most csinálhatunk belõle mondjuk százat olymódon, hogy létrehozunk egy Elso típusú referenciatömböt és minden tömbelembe belepakolunk egy új Elso egyed referenciáját. Valahogy így:
Elso et[] = new Elso[100];
for( int i = 0 ; i < 100 ; ++i )
et[i] = new Elso();
Az eredmény száz Elso egyed amelyeket az "et" tömb megfelelõ indexszelésével érhetünk el. Például mondhatjuk:
et[42].prog( args );
Ekkor a 43. Elso egyed prog metódusát hívtuk meg args paraméterrel.
Feladat
Írd át népszerû Kõ-Papír-Olló programunkat a 7. fejezetbõl úgy, hogy 5 játékos játsszon! A Jatekos referenciákból csinálj egy tömböt és ciklusokkal oldd meg. amit az eredeti program külön változókkal csinált! Íme a megoldás. Vedd észre, hogy a megoldás rövidebb, mint az eredeti program, tömbökkel sokat lehet spórolni!
/*
A Jatekos objektumnak van
- azonositoja
- nyert meccs szamlaloja
A Jatekos kepes
- meccset kezdemenyezni
- fogadni egy masik Jatekos kezdemenyezeset
- kiirni a nyert meccsek szamat
A Jatekos kommunikal
- A partnerjatekossal
*/
class Jatekos {
int jatekos_szama;
int nyert_meccsek = 0;
Jatekos( int jsz ) {
jatekos_szama = jsz;
}
// 0 - ko, 1 - papir, 2 - ollo
int tipp() {
double veletlen = Math.random()*3; // Megszorozzuk 3-mal, hogy a hatarertek
// ne 0.3333 ... legyen, hanem 1 es 2
if( veletlen < 1 )
return 0;
if( veletlen < 2 )
return 1;
return 2;
}
int MeccsetFogad( int kezdemenyezo_tipp ) {
int sajat_tipp = tipp();
System.out.println( "Sajat tipp: "+sajat_tipp+" ellenfel tippje: "+kezdemenyezo_tipp );
// Az ollo (2) csak a ko (0) ellen veszit, ez az egyetlen helyzet, amikor
// a nagyobb szamu fogadas veszit egy kisebb szamuval szemben. Ha ilyen eset
// van, megvaltoztatjuk az ollo szamat -1-re, hogy az altalanos kiertekelo logika
// mukodjon.
if( ( kezdemenyezo_tipp == 2 ) && ( sajat_tipp == 0 ) )
kezdemenyezo_tipp = -1;
if( ( kezdemenyezo_tipp == 0 ) && ( sajat_tipp == 2 ) )
sajat_tipp = -1;
if( sajat_tipp == kezdemenyezo_tipp )
return 0; // Dontetlen
if( sajat_tipp < kezdemenyezo_tipp )
return 1; // A partner nyert
++nyert_meccsek; // Kulonben mi nyertunk
return 0;
}
// Jatszik egy meccset egy ellenfellel. Megkapja az ellenfel referenciajat.
void MeccsetJatszik( Jatekos ellenfel ) {
nyert_meccsek = nyert_meccsek + ellenfel.MeccsetFogad( tipp() );
}
void IrdKiANyertMeccseket() {
System.out.println( jatekos_szama+". jatekos "+nyert_meccsek+" meccset nyert" );
}
}
/* A KoPapirOllo a Fogado osztaly neve */
public class KoPapirOlloTomb {
void Jatszma() {
Jatekos j[] = new Jatekos[5]; // 5 referencia Jatekos egyedekre
for( int i = 0 ; i < 5 ; ++i )
j[i] = new Jatekos( i+1 );
for( int i = 0 ; i < 20 ; ++i ) {
for( int n = 0 ; n < 5 ; ++n ) {
// Kivalasztja, kivel fog jatszani. Normalisan az eggyel nagyobb
// indexu jatekossal jatszik, de az utolso a 0.-kal jatszik
int partner = n + 1;
if( partner >= 5 )
partner = 0;
j[n].MeccsetJatszik( j[partner] );
}
// Ezt ugyan nem kerte a feladat, de nyomkovetesnek hasznos
for( int n = 0 ; n < 5 ; ++n )
j[n].IrdKiANyertMeccseket();
}
System.out.println( "-------------- Vegeredmeny --------------" );
for( int n = 0 ; n < 5 ; ++n )
j[n].IrdKiANyertMeccseket();
}
public static void main( String args[] ) {
KoPapirOlloTomb k = new KoPapirOlloTomb();
k.Jatszma();
}
}
Mint mondtam, a tömb alaptípusa bármi lehet, tehát tömb is. A tömb alaptípusú tömböt többdimenziós tömbnek hívjuk és néha nagyon jól tudnak jönni. A példa kedvéért tekintsünk egy kétdimenziós tömböt.
double t[][] = new double[100][100];
Ebben 100x100 double elem van, minden double elem 8 bájtot foglal, tehát a tömb mérete 80 kilobájt meg egy pici. Ez már igazán hatékony eszköz. Címezni a többdimenziós tömböt is csak úgy kell, mint egy egydimenzióst:
t[42][45] = 3.14;
Feladat
Írjunk programot, ami kiszámolja a hõeloszlást vízzel teli csõben. Azt tudjuk, hogy csõ keresztmetszete négyzetes, az alsó oldala egyenletes 12 fokon, a teteje egyenletes 90 fokon van, az oldalfalai mellett pedig a hõmérséklet egyenletesen 12-rõl 90 fokra emelkedik. Oldjuk meg a feladatot olymódon, hogy a csõ keresztmetszetét bontsuk kis kockákra, mondjuk 100x100-ra. A legszélsõ kockákat (tehát a 0. és 99. sorban ill. a 0. és 99. oszlopban levõket) töltsük fel a fix hõmérsékletértékekkel, a többit meg tetszõleges értékkel, pl. 12 és 90 átlagával. Számoljuk újra minden nem fix hõmérsékletû kiskocka hõmérsékletét úgy, hogy az a négy szomszédjának az átlaga, írjuk vissza az átlagot a kiskockába és tartsuk számon, mekkora volt a legnagyobb változás az átlagszámítás elõtti és utáni értékekre vonatkoztatva. Ha egy menetben a legnagyobb hõmérsékletváltozás nem több, mint 0.01 fok, nyomtassuk ki a tömbben tárolt értékeket 4 soronként ill oszloponként. Íme a megoldás. Mit befolyásol az átlagoláson átesett kiskockák kezdõértéke?
public class Laplace {
public static void main( String args[] ) {
double cso[][] = new double[100][100];
// Toltsuk fel a fix kockakat. Eloszor az also es felso szegely jon
// A tombot cso[x][y] formaban indexeljuk. cso[0][0] a bal also sarok
for( int i = 0 ; i < 100 ; ++i ) {
cso[i][0] = 12.0;
cso[i][99] = 90.0;
}
// Most egyenletesen novekvo modon feltoltjuk a fuggoleges eleket
for( int i = 1 ; i < 99 ; ++i ) {
double hom = 12.0 + ( ( 90.0 - 12.0 ) * (double)i / 100.0 );
cso[0][i] = hom;
cso[99][i] = hom;
}
// A maradek atlagos homersekletre toltodik
for( int i = 1 ; i < 99 ; ++i )
for( int n = 1 ; n < 99 ; ++n )
cso[i][n] = 51.0; // 12 es 90 atlaga
int iteracio = 1; // Az iteraciok szamlaloja
double maxvalt; // Maximalis homersekletvaltozas az iteracio alatt
do {
maxvalt = 0; // maximalis valtozas: 0
// Egy iteracio
for( int i = 1 ; i < 99 ; ++i )
for( int n = 1 ; n < 99 ; ++n ) {
double ujhom = ( cso[i-1][n] + cso[i+1][n] + cso[i][n-1] + cso[i][n+1] ) / 4;
double valt = Math.abs( ujhom - cso[i][n] );
if( valt > maxvalt )
maxvalt = valt;
cso[i][n] = ujhom;
}
++iteracio;
System.out.println( "Iteracio: "+iteracio+" maximalis valtozas: "+maxvalt );
} while( maxvalt >= 0.01 );
// Kiirjuk az eredmenyt
System.out.println( "Homersekleti ertekek alulrol felfele balrol jobbra 10 soronkent ugralva" );
for( int i = 0 ; i < 100 ; i += 4 ) {
System.out.print( i+". sor: " );
for( int n = 0 ; n < 100 ; n += 4 ) {
System.out.print( cso[n][i]+ " " );
}
System.out.println();
}
}
}
Ellenõrzõ kérdések
• Mi a tömb?
• Mi a tömb alaptípusa?
• Mi lehet Jávában egy tömb alaptípusa?
• Mit jelent az, hogy Jávában a tömböt tömbreferenciával érhetjük el?
• Mit tárol a tömb length egyedváltozója?
• Mit jelent, hogy egy tömb objektumrefernciákat tárol?
• Mit jelent a többdimenziós tömb?
10. fejezet
Nem csak számok vannak a világon! Dolgozzunk érdekesebb adatokkal: karakterek és azok halmazai. Karaktertípus a Jávában, a char típus. Karaktersorozatok avagy ismerkedés a String osztállyal. String és StringBuffer, a két jóbarát.
Talán már kicsit unalmas, hogy példáink mindig számokról szólnak. Ugyan szövegeket állandóan írunk ki, de nem tudunk velük olyan könnyedén variálni, mint a számokkal. Ezen lecke végére ez a hiányérzetünk is megszûnik, mert most a Jáva karakterkezelési eszközeivel foglalkozunk. A számítógép ugyanolyan könnyedén tárol és manipulál karakter típusú adatokat (emlékszel az elsõ leckére? A karakterek a számítógép által ábrázolható összes betû, számjegy, jel összessége), mint számokat. Számára természetesen ezen adatoknak semmi jelentése nincsen, az egymás után tárolt a,l,m és a betûk semmilyen módon nem idézik fel benne a gyümölcsöt, mint ahogy a 2001 értékû egész típusú szám sem jelenti számára a mostani évet. Minthogy a számítógép nem magának számol, hanem nekünk, így aztán elég, ha a karaktereknek csak számunkra van jelentése.
A karakterek tárolására szolgáló alaptípus a char. Egy char típusú változó egy karaktert képes tárolni. Értékül létezõ karakter típusú érték vagy karakterkonstans adható. Példa:
char c;
c = 'A';
A c változó értéke most tehát az 'A' karakter, egészen pontosan annak a kódja. A számítógép számokkal képes dolgozni, így a karaktereket is számokként ábrázolja, minden karakternek saját kódja van. A számítógépes gyakorlatban millióféle karakterkódolás létezik, a Jáva belsõleg a Unicode karakterkészletet használja. A Unicode Jáva által használt változata nem egy, hanem két bájton ír le egy karaktert, így több, mint 65000 karaktert képes ábrázolni. A fenti gondolatmenet eredménye az, hogy char és egész típusok egymásba alakíthatók, minthogy mindegyik csak egész szám. Példa:
int i = (int)c;
char d = (char)i;
Feladat
Írj egy programot, ami kiírja a Jáva karakterkészletet 32 és 127 között, a határokat beleértve. Egy sorba írd ki a karakter kódját majd a karaktert magát is és ezt végezd el az összes kódra. Nagyon egyszerû feladat, remélhetõen menni fog, ha nem, itt van a megoldás.
public class KarakterKod {
public static void main( String args[] ) {
for( int i = 32 ; i < 128 ; ++i ) {
char c = (char)i;
System.out.println( "Karakterkod: "+i+" karakter: "+c );
}
}
}
Igazában az esetek többségében nem egyedi karakterek érdekelnek, hanem azokból összeállított sorozatok, karaktersorozatok vagyis népszerû angol nevükön stringek. Stringet könnyedén csinálhatnánk karakterekbõl készített tömb segítségével
char string[] = new char[20];
a Jáva azonban elõzékenyen rendelkezésünkre bocsátja a String osztályt, teljes nevén java.lang.String-et. A csomagokról, mint például a java.lang majd a következõ fejezetben olvasunk, most elég annyi, hogy ez az osztály a beépített osztálykönyvtár része és alapból a rendelkezésünkre áll, nem kell érte semmit tennünk, hogy felbukkanjon. A String egy nem megváltoztatható karaktersorozat. Ez azt jelenti, hogy egy Stringet ha egyszer létrehoztunk adott szöveggel, akkor a szöveg a továbbiakban nem változtatható meg. Ez nem tûnik túl ígéretesnek, viszont a megváltoztathatatlan String egyedet felhasználhatjuk további Stringek építésére.
Stringet lértehozni nagyon egyszerû.
String s = new String( "Hello, Jáva" );
A String tehát egy mezei objektum, olyan, amilyeneket eddig láttunk, kivéve, hogy nem nekünk kellett definiálnunk, hanem már megtették helyettünk. Az s egy String egyedre mutató referencia, ilyet is láttunk már. Egy trükk van a képben: a "Helló, Jáva" konstans. A String egy kivételes osztály a Jávában abban a tekintetben, hogy String típusú konstansokat a nyelv közvetlenül támogat. A "Helló, Jáva" igazából létrehoz egy String egyedet, amiben a "Helló, Jáva" string van, majd ennek alapján a new String létrehoz egy új string egyedet. Nem túl hatékony, én is csak azért írtam le, hogy határozottan megmutassam, hogy a String is csak egy objektum. Igazából elegendõ lenne ennyi:
String s = "Hello, Jáva";
Nézzük, mit is csináltunk elõbb. Itt a soha vissza nem térõ alkalom, hogy egy pillantást vessünk a Jáva könyvtár leírására, ami elérhetõ a weben vagy letölthetõ a Sun-tól a JDK weblapjáról (a cikk írásának a pillanatában a Java 2 1.3-as változata a legújabb. Keressük meg innen kiindulva a JDK API dokumentációját és nézzük a String lapját! Megtalálhatjuk a választ, hogyan mûködött az elsõ sor. A Stringnek van egy konstruktora, ami Stringet fogad.
String(String value);
Ezt használtuk fel az imént. Két sorra bontva:
String t = "Hello, Jáva";
String s = new String( t );
De sokat beszéltünk errõl az egyszerû sorról, nézzünk valami érdekesebbet! Tekintsük a következõ jól ismert sort:
public static void main( String args[] ) ...
Hát igen, ez a main definíciója. Most már megérthetjük a paraméter részét is: a main egy Stringekbõl álló tömböt kap, mindegyik Stringben egy paraméter. Ha tehát a programot így hívjuk meg:
java Program P1 P2 P3 P4
akkor a main egy 4-elemû tömböt fog kapni, a tömb elemeiben egy-egy String egyedre van referencia, és ezek a String egyedek sorban a P1, P2, P3 és P4 karaktersorozatokat tárolják. Amint a tömböknél tanultuk, minden tömbnek van egy length nevû egyedváltozója, ami megadja a tömb hosszát. Az összes paraméter kiírása ezek után nem nehéz:
for( int i = 0 ; i < args.length ; ++i )
System.out.println( args[i] );
Írjunk most rövid programot, ami kiírja az összes paraméterét, amiben megtalálható az "al" karaktersorozat. Tehát ezeket mind kiírja: alma, fal, falu de nem írja ki az akol vagy az olaj szavakat. Alaposan végigtanulmányozva a String metódusainak leírását, megtaláljuk az indexOf(String str) metódust, ami megkeresi a String egyedben, amire meghívták a paraméterül kapott stringet és megmondja a pozícióját ill. -1-et ad vissza, ha nem találja. A program igazán egyszerû, de itt a megoldás ellenõrzésképpen. Ha megvan, írd át úgy a programot, hogy a keresett részletet kiemeli úgy, hogy elé és mögé _ jeleket tesz, pl. _al_ma, t_al. Ehhez a substring metódusra lesz szükséged, ellenõrizheted a megoldást itt.
public class AlKeres2 {
public static void main( String args[] ) {
for( int i = 0 ; i < args.length ; ++i ) {
int pos = args[i].indexOf( "al" );
if( pos >= 0 ) {
System.out.print( args[i].substring( 0,pos ) );
System.out.print( "_" );
System.out.print( args[i].substring( pos,pos+2 ) );
System.out.println( "_"+args[i].substring( pos+2 ) );
}
}
}
}
Szép dolog, hogy van egy megváltoztathatatlan String-ünk, de sokkal érdekesebb lenne, ha felhasználhatnánk más String-ek alkotására. Két String-bõl egy harmadikat csinálni Jávában nagyon egyszerû, mert String objektumokra definiálva van a + operátor és összefûzést jelent. Példa:
String s1 = "eleje";
String s2 = "vége";
String s3 = s1 + s2;
A dolog végén s3 az "elejevége" szöveget fogja tartalmazni. Egy baja van ennek a megoldásnak: ha sokat használjuk, jócskán teleszemeteli a memóriát, minthogy minden lépésben egy nem változtatható String keletkezik. A Jáva készítõi a változtatható StringBuffer könyvtári osztállyal oldották meg a problémát.
A StringBuffer arra való, amire a String nem alkalmas: karaktersorozatok manipulálására. A StringBuffer képes a benne levõ karaktersorozat végére egy másikat fûzni, törölni, felülírni a tartalom egy részét vagy beszúrni tetszõleges pontra. A könnyedségnek persze ára van: a StringBuffer több helyet foglal, mint egy String. A legjobb, ha mindkettõt a helyén használjuk és ez nem nehéz, mert mindkettõnek van olyan konstruktora, amely Stringet vagy StringBuffert tud csinálni a másikból. Mondhatjuk tehát:
String s = "Hello";
StringBuffer sb = new StringBuffer( s );
String s2 = new String( sb );
Igazából a Jáva fordító a Stringek összefûzését is StringBufferrel oldja meg: eleje-vége példánknál elõször egy ideiglenes StringBuffer jön létre, ebben összefûzõdik a két String, majd ebbõl legyártódik az s3-ba kerülõ eredménystring. Ha mondjuk egy jóféle hurokban tesszük ezt, könnyedén legyárthatunk néhány száz ideiglenes objektumot.
Feladat
Írj egy programot, ami összefûzi a paramétereit egy Stringgé és ezt a Stringet kiírja. A paraméterek között _ jelek legyenek. Hozz létre egy StringBuffert, ebben fûzd össze a paramétereket, majd az eredményt alakítsd Stringgé és írd ki! Íme a megoldás ellenõrzésképpen.
public class ParameterFuzo {
public static void main( String args[] ) {
StringBuffer sb = new StringBuffer();
for( int i = 0 ; i < args.length ; ++i ) {
if( sb.length() > 0 ) // Ha nem az elso parameter
sb.append( "_" );
sb.append( args[i] );
}
String eredmeny = new String( sb );
System.out.println( eredmeny );
}
}
Ellenõrzõ kérdések
• Mi a karakter?
• Hányféle jelet képes tárolni a Jáva char típus?
• Hogy hívják a Jáva által támogatott karaktertípust?
• Mi a karaktersorozat (string?)
• Mit jelent, hogy a String nem megváltoztatható?
• Hogyan lehet egy Stringnek kezdõértéket adni?
• Mire való a String indexOf metódusa?
• Mire való String substring metódusa?
• Mi a különbség a StringBuffer és a String között?
11. fejezet
A Jáva osztályok is csak fájlok; Jáva osztályok elhelyezése és fellelése. További káoszteremtõ eszközök: package és import. Jó helyek a fájlrendszeren: a CLASSPATH környezeti változó.
Ha tényleg megcsináltad a feladatokat és nem csak végigfutottál a megoldásokon (ebben az esetben nem fogsz megtanulni Jávául), a játékterül szolgáló könyvtáradban jókora zûrzavar lehet már. Az Elso.java-t már legalább háromszor felülírtad különbözõ változatokkal és ki tudja, mi mûködik ott és mi nem. Ha néhány Jáva gyakorlóprogrammal ekkora zûrzavart lehet csinálni, vajon mire lehet képes egy csapat termelékeny programozó? Világos: végtelen káoszra. A káosz látszólagos rendezettségének növelésére a Jáva bevezeti a csomag (package) fogalmát.
A csomag vészesen analóg a fájloknál megszokott fájlnév-könytárnév szerkezettel. Minden fájlnak van egy neve, de ez még kevés az eléréséhez; általában tudni kell azt is, milyen könyvtárban van. Hasonlóképpen van ez a Jáva osztályokkal; általában nem elég, ha a nevüket tudjuk, tudni kell, milyen csomagban vannak. Íme néhány példa Jáva osztálynevekre csomagnevekkel kibõvítve.
java.awt.color.ColorSpace
java.util.Vector
pelda.Elso
Az elsõ kettõ egy-egy ténylegesen létezõ Jáva könyvtári osztályt jelöl. Vegyük a másodikat: ez a java.util csomagban elhelyezkedõ Vector osztály. A harmadik az én agyam szüleménye, de attól még létezhet. Mindjárt meglátjuk, hogyan!
Ha a Jáva forrásfájlba betesszük a package direktívát, a fájlban mögötte deklarált osztályokat abba a csomagba teszi. pl.
package a.b;
class C {
...
}
A fenti varázslat eredménye az, hogy az osztály neve nem egyszerûen C, hanem a.b.C lesz, vagyis az a.b csomagban található.
Mindez nagyon egyszerû, de mi hasznunk ebbõl? Hogyan lehet elérni csodálatos a.b.C osztályunkat? Mi sem egyszerûbb ennél. Ha egy osztályra referenciát akarunk gyártani, az osztály nevét kell használni típusnak. Így ni:
a.b.C ref = new a.b.C();
Egyszerû és elegáns, nemdebár? Egy apró probléma van a dologgal: elõbb-utóbb bele fogunk unni hosszú nevû osztályunk nevének gépelésébe, pláne, ha nem a.b.C az osztály neve, hanem a fent megemlített java.awt.color.ColorSpace és ezzel még egy szelídebb példát választottam. Minthogy a programozók lusta népek, meg lehet ezt úszni kevesebb gépelésbõl is. Ha a programunk elejére odaírjuk az import direktívát, megadhatjuk, milyen osztályoknál vagy csomagoknál nem akarjuk kiírni a teljes nevet. Például mondhatjuk azt, hogy
import a.b.C;
Ezzel kijelentettük, hogy az a.b.C osztályra egyszerûen, mint C-re kívánunk hivatkozni ebben a forrásfájlban. Leírhatjuk, hogy
C ref = new C();
Mindjárt barátságosabb. Ennél többet is tehetünk, azt is mondhatjuk, hogy a csomagban található összes osztályt egyszerûsített formában akarjuk látni.
import a.b.*;
Ez az a.b csomag összes osztályát egyszerûsített formában teszi elérhetõvé.
Nagyon fontos megjegyezni, hogy az import direktíva csupán azt befolyásolja, hogyan oldja fel a fordító az osztályneveket. Akármit is varázsolunk, a lefordított kódba mindenképpen az a.b.C-re való hivatkozás kerül be, import ide vagy oda. Az import ugyancsak nem tölt be semmit se a tárba, mint azt naívan hinnénk. Szerepe csupán a gépelés csökkentésében van, futásidõben semmit se számít.
Még egy fontos megjegyzés: a fordító minden Jáva forrásfájl elejére odaképzel egy import java.lang.*; direktívát, így a java.lang csomagot mindig importálja. A java.lang csomagban régi barátaink vannak, pl. a java.lang.Integer, aminek parseInt metódusához oly sok kedves élményünk fûzõdik, vagy a java.lang.Math, amivel a kapcsolat már tényleg mélyebbé vált egyszerû barátságnál.
De vissza a package-re! Tekintsük a következõ programot!
package pelda;
public class Elso {
public static void main( String args[] ) {
System.out.println( "Elso Java programunk" );
}
}
Mi lenne ez más, mint népszerû Elso programunknak egy egyszerûsített változata azzal a csavarral, hogy a programot befoglaló osztályt pelda.Elso-nek neveztük el. Fordítsd le fürgén a javac Elso.java paranccsal, majd futtasd le a java pelda.Elso utasítással. (ne feledd: a java parancsnak a futtatandó osztály nevét kell megadni, ebben az esetben pelda.Elso-t). Ilyen egyszerû egy osztályt csomagba tenni.
Eeeee ... valami nem stimmel? A java pelda.Elso hatására nem az unalomig ismert kiírás jelent meg, hanem az, hogy a pelda/Elso osztály nem található. Mindez arra világít rá, hogy még mindig nem tudunk mindent az osztályok betöltõdésérõl.
Eddig ezzel nem sokat törõdtünk. A lefordított .class fájlokat az aktuális könyvtárban tároltuk és elvártuk, hogy a virtuális gép megtalálja õket. A csomagok bevezetésével a helyzet megváltozik és meg kell ismerkednünk egy osztályt tartalmazó .class fájl fellelésének módjával. Amikor a virtuális gép meglát egy osztálynevet, azonnal elkezdi keresni a CLASSPATH környezeti változó által megadott könyvtárakban. Ha esetleg nem tudnád, mi a környezeti változó, íme egy kis bevezetõ:
2. kitérõ: környezeti változók
2. kitérõ: környezeti változók
Operációs rendszereknek gyakori szolgáltatása a környezeti változók lehetõsége. Ez azt jelenti, hogy egy futó alkalmazás elérhet és módosíthat egy adag, névvel azonosított változót. A változók értéke mindig karaktersorozat. Ha Windows-on meg akarod nézni az aktuálisan érvényes készletet, add ki a set parancsot. Te magad is beállíthatsz egyet pl. így:
set ENYIM="ez az en valtozom"
Ha most újra kiadod a set parancsot, a listában megtalálod az ENYIM változót is.
A környezeti változók értelme függ a rendszerben futó programoktól. Bizonyos alkalmazások és magához az operációs rendszerhez tartozó programok kiolvasnak bizonyos környezeti változókat és az értékük szerint mûködnek. Ezen változók módosítása megváltoztatja a rendszer mûködését. Jó példa a PATH nevû változó, amiben azok a könyvtárak vannak felsorolva (Microsoft operációs rendszerek esetén pontosvesszõvel elválasztva), ahol a rendszer keresi az elindítandó programokat tartalmazó fájlokat. Ha egy könyvtárat ráteszünk a PATH listájára, a benne levõ .exe (vagy egyéb végrehajtható) fájlokat is megtalálja a rendszer a könyvtár megadása nélkül.
ENYIM nevû változónk persze nem érdekel senkit, így aztán beállítása semmiféle változást nem okoz, hacsak nem írunk egy programot, ami kiolvassa.
A Jáva a CLASSPATH környezeti változót használja a következõ módon. A CLASSPATH értéke könytárak sorából áll (Windows-on pontosvesszõvel, Unix-on kettõsponttal elválasztva). Amikor a rendszer keresni kezdi az a.b.C nevû osztályt, veszi az elsõ könyvtárat a CLASSPATH-en. Ehhez hozzáfûzi a csomag nevét, így lesz egy alkönytárnév és ebben keresi meg az osztálynév.class fájlt. Ha nem találja, továbblép a következõ könyvtárra a CLASSPATH-en és ezt addig csinálja, amíg a könyvtárak a CLASSPATH-en el nem fogynak. Ha nem tudta megtalálni azt osztályt a CLASSPATH-en felsorolt összes könyvtárban, hibajelzést küld.
Lássunk egy egyszerû példát! Tegyük fel, hogy a CLASSPATH értéke a következõ:
c:\Users\javadev;c:\Users\javalib
Tegyük fel továbbá, hogy a rendszer az a.b.C nevû osztályt keresi. Veszi tehát az elsõ könyvtárat a CLASSPATH-en, a c:\Users\javadev-et. Ehhez hozzáilleszti a csomagnevet, vagyis az a.b-t és kapja a c:\Users\javadev\a\b könyvtárat. Itt megpróbálja fellelni a C.class fájlt. Tegyük fel, hogy nem találta meg. Ekkor továbblép a c:\Users\javalib-re és megpróbálja ugyanezt, vagyis felkeresi a c:\Users\javalib\a\b\C.class fájlt. Ha ez megvan, siker. Ha nem, hibaüzenet.
Hosszadalmas magyarázat után vissza a pelda.Elso alkalmazásra! Ha figyelmesen megtanulmányozod a fájlokat a könyvtáradban, rájöhetsz, hol a hiba: a Jáva fordító az aktuális könyvtárba pakolta le a .class fájlt. Ez egy hülye tulajdonsága a javac-nak, amely a Jáva elsõ változata óta kísért. Tedd helyre olymódon, hogy létrehozol egy pelda alkönyvtárat és átmozgatod az Elso.class-t ebbe. Tedd meg! Ha most kiadod a java pelda.Elso parancsot, két dolog történhet: vagy mûködik, vagy nem. Sohase tudtam rájönni, miért, de bizonyos Jáva installációk ilyenkor képtelenek megtalálni a kurrens könyvtárban elhelyezkedõ alcsomagokat, csak ha varázsolunk a CLASSPATH-szel. He netán ez a hiba ütné fel undok fejét, vedd fel az aktuális könyvtárat a CLASSPATH-re.
set CLASSPATH=.
mondd a parancspromptnál Windows-on. Ha most megismétled a parancsot, mûködnie kell.
A javac-nak a tulajdonsága, miszerint mindenképpen az aktuális könytárba pakolja a .class fájlt, még ha az valamilyen csomagba fordítódik igazán kellemetlen és erre a Sun-nál is rájöttek. A Javac-ot meghívhatod a nagyszerû -d kapcsolóval, ekkor létrehozza a csomagok alkönyvtárait és mindent a helyére pakol. Próbáld ki! Töröld le az elõbb létrehozott pelda alkönyvtárat a benne levõ Elso.class-szel együtt, majd mondd azt:
javac -d . Elso.java
ekkor a pelda alkönyvtár rendben létrejön az aktuális könyvtárban és belekerül az Elso.class. Ekkor már probléma nélkül lehet futtatni a java pelda.Elso paranccsal.
A csomagok kezelésének a lehetõsége apróság a Nagy Egészben, de igazán fontos õket ismerni, ha bele akarunk kalandozni a Jáva osztálykönyvtárba.
Ellenõrzõ kérdések
1. Mi a hasonlóság az alkönyvtárak és a csomagok között?
2. Hogyan kell egy Jáva osztályt egy adott csomagba tenni?
3. Mit jelent a package direktíva és mi a paramétere?
4. Hogyan lehet egy csomagban levõ osztályt elérni?
5. Mit egyszerûsít az import direktíva?
6. Hogyan leli fel a virtuális gép a class fájlokat?
7. Mi a CLASSPATH környezeti változó?
8. Mi történik, ha a CLASSPATH értéke c:\Users\javalib;c:\Users\enyem és az a.b.C nevû osztályt keressük?
9. Hova teszi a javac a lefordított osztályokat, ha egyszerûen csak a forrásfájl nevével hívjuk meg?
10. Hogyan lehet elérni, hogy a javac a csomagokat a megfelelõ alkönyvtárba tegye?
12. fejezet
Mindenki a saját hibáinak kovácsa: személyre szabott hibajelzések a Jávában. Kivételek élete és halála: throw utasítás, a throws kulcsszó valamint a try-catch blokk.
Feladat
Írjunk programot, ami kiírja a bementi paraméter faktoriálisát. Ha emlékszel, n faktoriálisa az 1-tõl n-ig terjedõ egész számok szorzata, 0! = 1. Negatív számokra a faktoriális nincs értelmezve. A faktoriális értékek rendkívül gyorsan nõnek, így 20!-nál nagyobb értékek a long típusból (az int 64 bites változatából) is kilógnak. Írj egy függvényt, amelyiket meg lehet hívni egy egész számmal és visszaadja a faktoriális értéket! A függvény adjon vissza hibajelzést, ha érvénytelen bemeneti értéket kapott. A main függvény hívja meg ezt a faktoriálisszámító függvényt az args[0]-ban kapott bemeneti paraméterrel (Integer.parseInt ...) majd írja ki a hibaüzenetet, ha a faktoriálisszámító függvény hibajelzést adott vissza, különben írja ki az eredményt. A feladat nem okozhat gondot, íme a megoldás.
/* Faktorialisszamito program */
public class Faktorialis {
static long fakt( int k ) {
if( k < 0 || k > 20 )
return -1;
long tmp = 1;
for( int i = 2 ; i <= k ; ++i )
tmp *= i;
return tmp;
}
public static void main( String args[] ) {
long r = fakt( Integer.parseInt( args[0] ) );
if( r < 0 )
System.out.println( "Hibas bemeneti parameter: "+args[0] );
else
System.out.println( args[0]+"! = "+r );
}
}
A programban valóban nincs semmi újdonság. Ugye, a 9. lecke után nem lep meg, hogy a fakt függvénynek szintén statikusnak kell lennie? (mivel a statikus main-ból hívjuk) A fakt függvény önmaga nem írhatja ki a hibajelzést (hiszen a függvényben nem tudhatjuk, egyáltalán szöveges hibajelzést kell-e adnunk, így szólt a feladat kitûzése), ezért negatív értékkel jelez hibát. Mivel a faktoriális nem lehet negatív szám, ezt észreveheti a hívó függvény és hibát jelezhet.
Nem túl ronda ez? De igen. Összekeveredik itt két dolog, a függvény visszatérési értéke és a hibakód. A mostani helyzet viszonylag egyszerû, mivel itt egy hibaeset van és a függvény bizonyos számokat nem adhat vissza, nehezebb lenne azonban a dolgunk, ha nem lenne ilyen "üres tartomány" a visszatérési értékek között és több hibakódunk lenne. A hibák kultúrált jelzésére a Jáva az kivétel (exception) eszközét adja.
A kivételt úgy lehet felfogni, mint egy vészféket. Ha a program futása során kivétel keletkezik (akár a rendszer generál egyet, akár mi generáljuk) a futás megszakad és a Jáva virtuális gép a kivétel feldolgozásával folytatja a tevékenységét. Hogy ez pontosan hogyan zajlik, arról egy kicsit késõbb. Most nézzük meg, hogyan generálhatunk kivételt. Ez roppant egyszerû.
throw new IllegalArgumentException();
A throw utasítás paramétere egy kivételobjektum. A kivételobjektum pont olyan mezei objektum, mint a többi; egyetlen speciális tulajdonsága van: a java.lang.Exception könyvtári objektum leszármazottjának kell lennie. A példánkban szereplõ java.lang.IllegalArgumentException is ilyen és õ is része az alap objektumkönyvtárnak, tehát bátran használhatjuk minden további nélkül. Ugye, emlékszel, hogy a minden Jáva program elejére egy import java.lang.*; utasítást képzel oda a fordító, barátainkat tehát nyugodtan hívhatjuk Exception-nek és IllegalArgumentException-nek.
Kivételekbõl lehet de-luxe változatot is létrehozni, ha úgynevezett részletezõ üzenettel (detail message) hozzuk õket létre. Van ugyanis nekik egy olyan konstruktoruk, ami egy String-et fogad, itt passzolható át a részletezõ üzenet. Ennek a szerepe csupán csak annyi, hogy informaciót közvetíthet a kivételt megkapó számára, pontosan mi is volt a baj. Példa:
throw new IllegalArgumentException( "Az argumentum túl kicsi" );
A metódusban generált kivételeket mindig deklarálni kell a metódus fejlécében, erre való a throws klauza. Példa:
void func() throws IllegalArgumentException {
...
throw new IllegalArgumentException();
...
}
A Jáva fordítónak mindig megvan a módja rá, hogy összehasonlítsa a metódusban dobott kivételeket a throws után felsoroltakkal (vesszõvel elválasztva tetszõleges számú kivételt felsorolhatunk) és ha eltérést lát, a maga egyenes, de kissé nyers módján hibaüzenetet küld. Próbáld ki!
Feladat
Írd át a faktoriálisszámító programot úgy, hogy a fakt metódus IllegalArgumentException-nel jelezze, ha a metódus hibás bemeneti paramétert kapott. Emitt a megoldas. Futtasd le a programot néhány hibás bemenõ értékkel és ellenõrizd az eredményt!
/* Faktorialisszamito program */
public class Faktorialis2 {
static long fakt( int k ) throws IllegalArgumentException {
if( k < 0 )
throw new IllegalArgumentException( "Tul kicsi bemeneti parameter" );
if( k > 20 )
throw new IllegalArgumentException( "Tul nagy bemeneti parameter" );
long tmp = 1;
for( int i = 2 ; i <= k ; ++i )
tmp *= i;
return tmp;
}
public static void main( String args[] ) {
long r = fakt( Integer.parseInt( args[0] ) );
if( r < 0 )
System.out.println( "Hibas bemeneti parameter: "+args[0] );
else
System.out.println( args[0]+"! = "+r );
}
}
Ilyen választ fogsz kapni:
Exception in thread "main" java.lang.IllegalArgumentException: Tul kicsi
bemeneti parameter
at Faktorialis2.fakt(Faktorialis2.java:5)
at Faktorialis2.main(Faktorialis2.java:15)
Ez egy hívási verem lista. Azt mondja meg, hogy az elsõ sorban részletezett kivétel a Faktorialis2 osztály fakt metódusában keletkezett, majd továbbterjedt a main metódusba (methogy a fakt-ot innen hívták meg) és ezzel elérte a hívási verem tetejét, a program futását megszakította. Ez azért volt lehetséges, mert primkó programunkban nem okozott gondot, hogy a kivétel megszakítsa a program futását. Normális esetben azonban ennél kifinomultabb kivételkezelésre van szükség és erre való a try-catch blokk.
A try-catch szintaktikája a következõképpen néz ki:
try {
... utasítások ...
} catch( Exception1 e1 ) {
... Exception1 típusú hibát lekezelõ utasítások ...
} catch( Exception2 e2 ) {
... Exception2 típusú hibát lekezelõ utasítások ...
} finally {
... Az egész blokk legvégén (hiba bekövetkezésétõl függetlenül) végrehajtódó utasítások ...
}
A try után bekövetkezõ blokkban vannak az utasítások, amelyek Exception1 vagy Exception2 típusú hibát generálnak. A fordító ellenõrizni fogja, tényleg megvan-e erre a lehetõség (minthogy a meghívott metódusok throws klauzájából tudja, azok milyen kivételt dobhatnak), ha valamelyik kivétel nem következhet be, hibaüzenetet ad. Ha nem történik hiba, a try blokk rendben végrehajtódik és a finally mögött levõ blokkal folytatódik a program futása. Ezen blokk végrehajtása után a szerkezet végetér.
Ha azonban a try blokkban kivétel következik be, a blokk futása megszakad és a kivételobjektum (amit a throw-val generáltunk) beíródik a megfelelõ catch blokk referenciaváltozójába. Ha például Exception1 következett be, az e1 mutat a kivételobjektumra, amikor a catch blokkba kerülünk és a futás az Exception1 catch blokkjában folytatódik. Ezt elvégezve a finally blokkja kerül sorra, majd elhagyjuk a szerkezetet. Egynél több catch blokk és a finally blokk opcionális. Legegyszerûbb formájában a szerkezet így néz ki:
try {
... utasítások ...
} catch( Exception1 e1 } {
... Exception1 típusú hibát lekezelõ utasítások ...
}
Mostanra eljutottunk oda, hogy le tudjuk írni, pontosan mi történik egy kivétel keletkezésekor. Kivétel keletkezésekor a program futása megszakad és megszakad a hívóé is, ha nem definiált try-catch blokkot a kivételre, hanem a throws klauzával azt jelezte, hogy tovább akarja dobni. Így megy ez addig, amíg a hívási lánc tetejére nem érünk (mint elõzõ példánkban) vagy bele nem ütközünk egy aktív trí-catch blokkba. Ekkor a try blokk megszakad és a futás a megfelelõ catch blokkon folytatódik. A fentiekbõl következik, hogy ha egy metódust meghívunk, ami dobhat valami kivételt és ezt jelzi is a throws klauzájában, akkor két eset lehetséges: vagy elkapjuk try-catch-csel, vagy továbbdobjuk throws-szal. Harmadik eset nincs, az összes további próbálkozás szintaktikai hiba.
Egy teljesen logikus dolgot megemlítenék, amiba kezdõ, de még tapasztaltabb Jáva programozók is beleesnek: a try blokk minden utasítását úgy tekinti a fordító, hogy az esetleg nem hajtódik végre, ezért a try blokkban elkövetett változóinicializálásokat nem veszi figyelembe, amikor azt nézi, hogy inicializálták-e a változót. A következõ tehát szintaktikai hiba:
int i;
try {
i = 1;
...
} catch( Exception1 ex ) { ... }
System.out.println( "i: "+i );
A System.out.println soránál a fordító figyelmeztetni fog, hogy az i változót nem inicializáltuk. Ezt a megnyugtatására meg kell tennünk, tehát int i = 0; szükséges.
Mielõtt leckénk zárófeladatához érkeznénk, még egy pár szót a nem jelzett kivételekrõl. Ezek valóban kivételesek, mert általában rendszerszintû programrészek generálják õket gépi kódú részekbõl. Sok esetben nincsenek throws-szal jelölve, hiszen nagyon sokféle utasítás végrehajtásakor keletkezhetnek. Ilyen pl. minden Jáva programozó legõszintébb barátja, a NullPointerException, ami akkor lesz, ha null értékû referenciát akarunk felhasználni egy objektumhivatkozásban. Ezt nem metódusok dobják, hanem ártalmatlan sorok pl. ref.valtozo formájú hivatkozás. Nem jelzett kivételeket is elkaphatunk, ha a problémás környékre egy try-catch blokkot telepítünk a catch ágban minden kivétel õsével, az Exception típussal. Példa:
Object o = null;
try {
o.toString();
} catch( Exception ex ) {
... NullPointerException-t lekezelõ programrész ...
}
Feladat
Bõvítsd ki elõzõ programunkat úgy, hogy a keletkezõ kivételt a main-ben kapd el és írd ki valami kultúrált formában. Használd az Exception objektum getMessage() metódusát, amivel megszerezheted a részletezõ üzenetet a catch blokkban. Emitt a megoldás.
/* Faktorialisszamito program */
public class Faktorialis3 {
static long fakt( int k ) throws IllegalArgumentException {
if( k < 0 )
throw new IllegalArgumentException( "Tul kicsi bemeneti parameter" );
if( k > 20 )
throw new IllegalArgumentException( "Tul nagy bemeneti parameter" );
long tmp = 1;
for( int i = 2 ; i <= k ; ++i )
tmp *= i;
return tmp;
}
public static void main( String args[] ) {
try {
long r = fakt( Integer.parseInt( args[0] ) );
if( r < 0 )
System.out.println( "Hibas bemeneti parameter: "+args[0] );
else
System.out.println( args[0]+"! = "+r );
} catch( IllegalArgumentException ex ) {
System.out.println( "Ervenytelen argumentum: "+ex.getMessage() );
}
}
}
http://www.bakaibalazs.hu/2014/11/oracle-1z0-804-vizsga-feladatok.html
http://subecz.pe.hu/JavaProgramozas/JavaProgramozas.php
https://www.webotlet.hu/?p=69
http://www.informatika-programozas.hu/informatika_java_programozas_elmelet_adattipus_2_tomb.html
http://www.4kor.hu/mellekletek/AngsterErzsebet_Java1.pdf
http://www.inczedy.hu/~szikszai/oop/oop.html
http://www.ms.sapientia.ro/~manyi/teaching/oop/oop.pdf
https://duende.itk.ppke.hu/~ppolcz/index.php/main/view/anal3.php
Nincsenek megjegyzések:
Megjegyzés küldése