Kihagyás

2. gyakorlat

Parancssori paraméterek

Parancssori paraméterek használata parancssorból, java-val való futtatásnál:

java ProgramNeve param1 param2 param3...

Parancssori paraméterek beállítása különböző fejlesztőkörnyezetekben:

  • IntelliJ IDEA: Először is létre kell hozni egy konfigurációt, amit a legegyszerűbben úgy tudunk megtenni, hogy a main függvény sorában jobb klikk, és Run 'Fájlnév.main()'. Ezt követően valahol jobb felül lesz a zöld háromszög (Play) ikon, ami mellett található a konfiguráció (ez egy lenyitható választó). Erre kattintva Edit configurations, majd pedig a Program arguments részen írhatjuk be a parancssori paramétereket.
  • Eclipseben: A fordítás és futtatást jelző zöld háromszög (Play) ikon melletti lenyíló menüben Run Configurations, majd pedig itt az Arguments fül alatt adhatjuk meg a parancssori paramétereket.

Tömb bejárása a tömb length (hossz, vagyis elemszám) tulajdonsága használható. (Természetesen nem csak a parancssori paraméterek tömbjének, hanem bármely tömbnek lekérhetjük az elemszámát a length tulajdonsággal.) Az alábbi kódrészlet kiírja a parancssori paraméterek számát (természetesen a main függvényen belülre kell helyezni):

// Parancssori paraméterek száma
System.out.println(args.length);

Ha szeretnénk az egyes elemeket kiíratni, egy jól ismert for ciklust használhatunk. A tömbök indexelése a korábbiakhoz hasonló módon történik.

// parameterek bejarasa
for (int i=0; i < args.length; i++) {
    System.out.println("" + (i + 1) + ". parameter: " + args[i]);
}

Paraméterek összege

Ahogy láttuk, a main függvény egy String tömböt kap, ezt semmilyen módon sem lehet felülírni/megváltoztatni. Ha számokként akarjuk kezelni a kapott paramétereket, akkor a tömb egyes elemeit először át kell konvertálnunk számmá, amihez az Integer.parseInt() függvényt kell használnunk. Az Integer.parseInt() nagyon könnyedén használható függvény, amely átalakít egy szövegesen tárolt számot ténylegesen számmá. Természetesen csodákra ez a függvény sem képes, csak olyan esetben használjuk, ha tudjuk, hogy az érkező szöveg valóban csak számot tartalmaz.

String tetszolegesSzam = "123";
int erteke = Integer.parseInt(tetszolegesSzam);

A parancssori paraméterek összege ezek fényében már gyorsan implementálható.

// parameterek osszege
int osszeg = 0;
for (int i = 0; i < args.length; i++) {
    osszeg += Integer.parseInt(args[i]);
}

System.out.println("A parameterek osszege: " + osszeg);

Adattípusok

Egyszerű (primitív) adattípusok: boolean, char, byte, short, int, long, float, double, ezek nagy része ismerős lehet C-ből. A C-vel ellentétben Javaban nyelvi szinten támogatva van a logikai adattípus, amelyet boolean típusnak nevezünk, és értéke csak true vagy false lehet. További eltérés a C-hez képest, hogy Javaban nincs előjeltelen típus, tehát nem használhatjuk az unsigned kulcsszót, csak és kizárólag előjeles típusokat hozhatunk létre. Bővebben ezen a linken olvashatsz a primitív adattípusokról. Egy érdekes cikk a lebegőpontos számokról, számábrázolásról.

Megjegyzés

Ha valami miatt azonban mégis szükség lenne egy előjeltelen egészre, akkor Java 8 (vagy újabb verzió) esetén használhatjuk az Integer osztály néhány erre a célra létrehozott metódusát, mint például a compareUnsigned, divideUnsigned metódusokat.

Az alábbiak hasznát csak később fogjuk megérteni. Alapvetően minden Java-beli primitív típusnak létezik egy csomagoló osztálya, amellyel egy primitív típusú adatból objektumot készíthetünk, "becsomagolhatjuk" azt. A csomagoló (wrapper) osztályok a következők (sorrendjük megegyezik a primitív típusoknál történt felsorolás sorrendjével): Boolean, Character, Integer, Long, Float, Double. Egy összefoglaló táblázat a beépített típusokról:

Típus neve Érték Default érték Méret Értéktartomány
boolean 1 bitet reprezentál false nincs precíz definíció true/false
char 16 bites unicode karakter \u0000 2 byte 0 - 65535
byte 8 bites egész 0 1 byte -128 - 127
short 16 bites egész 0 2 byte -32768 - 32767
int 32 bites egész 0 4 byte -2147483648 - 2147483647
long 64 bites egész 0L 8 byte -9223372036854775808 -,9223372036854775807
float 32 bites lebegőpontos (IEEE 754) 0.0f 4 byte 1.40129846432481707e-45 - 3.40282346638528860e38 (pozitív vagy negatív), +/- végtelen, +/- 0, NaN
double 64 bites lebegőpontos (IEEE 754) 0.0d 8 byte 4.94065645841246544e-324d - 1.79769313486231570e+308d (pozitív vagy negatív), +/- végtelen, +/- 0, NaN
public class PrimitivTipusok {

    public static void main(String[] args) {
    boolean bo = true; // logikai tipus
    char c1 = 'a'; // karakter tipus
    char c2 = '\u0054'; // ket bajtos, unicode karaktereket tarol!
    byte b1 = 127; // 8 bites egesz tipus
    byte b2 = -128; // minden egesz tipus elojeles!
    short s = 1024; // 16 bites egesz
    int i = 0x7fffffff; // 32 bites egesz
    long l = 0x7fffffffffffffffL; // 64 bites egesz
    float f = 123.123f; // 32 bites lebegopontos tipus
    double d = 5.0; // 64 bites lebegopontos

    // kiiras konzolra
    System.out.println(bo);
    System.out.println(c1);
    System.out.println(c2);
    System.out.println(b1);
    System.out.println(b2);
    System.out.println(s);
    System.out.println(i);
    System.out.println(l);
    System.out.println(f);
    System.out.println(d);
    }
}

A lebegőpontos számokkal azonban óvatosan kell bánni. Erre egy tökéletes példa a Süti program. A történet a következő: ellátogatunk az Egyesült Államokba, de sajnos hamarosan indulunk is tovább, így csak a reptéri cukrászdában vásárolhatunk sütit. A sietségünket azonban kihasználja a reptéri cukrász: elcsábít minket a konkurencia elől a 0.1 dolláros sütivel, azonban minden egyes következő sütiért 0.1 dollárral többet kér, mint amennyibe az előző került. Vajon hány sütit ehetünk a maradék 1 dollárunkból? Írjunk rá egy programot, mentsük el Suti.java néven.

public class Suti {

    public static void main(String[] args) {
        double penzunk = 1.00;
        int megvettSutik = 0;
        for (double ar = 0.1; penzunk >= ar; ar += 0.1) {
            penzunk -= ar;
            ++megvettSutik;
        }
        System.out.println("Megvett sütik: " + megvettSutik);
        System.out.println("Megmaradt pénz: " + penzunk);
    }
}

Hm... valami nem stimmel. Számoljuk ki kézzel is, egy egyszerű táblázat segítségével:

Pénzünk Következő süti ára Megevett sütik száma
1.0 $ 0.1 $ 0
0.9 $ 0.2 $ 1
0.7 $ 0.3 $ 2
0.4 $ 0.4 $ 3
0.0 $ lényegtelen 4

A fenti példa alapján beláthatjuk, hogy például valuta tárolására sosem érdemes float vagy double típust választani. Bővebben erről a problémáról ezen és ezen linken.

String (szöveges) típus

String típus. A sztringeket az eddigiekkel szemben itt már szeretjük, kezelésük könnyű. Ezt a típust mindig nagy kezdőbetűvel írjuk, mivel ez igaziból nem primitív típus, ám sok esetben tudjuk úgy használni, mintha az lenne.

Szövegek összehasonlítására technikailag az == és != operátorokat használhatjuk. Azonban ez igaziból csak referencia összehasonlítást végez, nem a szövegek tartalmát hasonlítja össze.

Nézzünk egy egyszerű példát.

public class Main {
    public static void main(String[] args) {
        String kedvencGyumolcs = args[0];
        if (kedvencGyumolcs == "korte") {
            System.out.println("A kedvencem a körte.");
        }
    }
}

Hiába futtatjuk a programunkat a java Main korte paranccsal, nem lesz jó az összehasonlítás, erre a fejlesztőkörnyezet is figyelmeztet (sárgán jelöli az egyenlőségjelet).

Valódi összehasonlításra a szoveg.equals() metódus használható, amely ténylegesen a szöveg tartalmát vizsgálja meg.

public class Main {
    public static void main(String[] args) {
        String kedvencGyumolcs = args[0];
        if (kedvencGyumolcs.equals("korte")) {
            System.out.println("A kedvencem a körte.");
        }
    }
}

Természetesen a "korte".equals(szoveg) is működőképes, sőt, hosszabb távon érdemes ezt a fajta összehasonlítást előnyben részesíteni.

A szövegek egyébként nagyon okosok, sok dolgot kérhetünk tőlünk, a pontos listát az IDE is megmutatja, ha bármilyen szöveg után nyomunk egy pontot. Például:

  • contains: visszaadja, hogy a paraméterként átadott szöveget tartalmazza-e a szöveg, amire meghívtuk.
  • startsWith: visszaadja, hogy a paraméterként átadott szöveggel kezdődik-e a szöveg, amire meghívtuk.
  • endsWith: visszaadja, hogy a paraméterként átadott szöveggel végződik-e a szöveg, amire meghívtuk.
  • isEmpty: visszaadja, hogy a szöveg üres-e.
  • toLowerCase: visszaadja a szöveg kisbetűsített változatát.
  • toUpperCase: visszaadja a szöveg nagybetűsített változatát.
  • indexOf: visszaadja, hogy a paraméterként átadott szöveg melyik indexen kezdődik abban a szövegben, amire meghívtuk. -1-gyel tér vissza, ha nem található meg a szövegben a paraméter.
  • length: ez a metódus visszaadja a szöveg hosszát.
public class Main {
    public static void main(String[] args) {
        String kedvencGyumolcs = "korte";
        kedvencGyumolcs = args[0];
        if (kedvencGyumolcs.equals("korte")) {
            System.out.println("A kedvencem a körte.");
        }

        System.out.println(kedvencGyumolcs.toLowerCase());
        System.out.println(kedvencGyumolcs.toUpperCase());
        System.out.println(kedvencGyumolcs.startsWith("alma"));
        System.out.println(kedvencGyumolcs.endsWith("lekvar"));
        System.out.println(kedvencGyumolcs.indexOf("var"));
        System.out.println(kedvencGyumolcs.contains("kutya"));
        System.out.println(kedvencGyumolcs.isEmpty());
        System.out.println(kedvencGyumolcs.length());
    }
}

Operátorok

+, -, *, /, % operátorok és használatuk ugyanaz, mint C-ben.

A + jel sztringekre konkatenációt (összefűzést) jelöl. Használata nagyon intuitív. Például "kutya" + "mutya" -> "kutyamutya"

A C-beli pointereknél használt * (dereferencia) és & (címképzés) operátor itt nem létezik.

>> - aritmetikai eltolás: megőrzi az előjelbitet.

>>> - logikai eltolás: az előjelbitet is tolja. (Magyarán nem őrzi meg a szám negativitását)

Az operátorokról bővebben olvashatsz az alábbi linkeken: Summary of Operators, Java Operators Tutorial.

Gyors kiértékelés

A logikai && (és) és a || (vagy) operátorok gyors kiértékeléssel értékelődnek ki (short circuit evaluation). Ez annyit jelent, hogy a logikai kifejezés pontosan addig értékelődik ki, amíg egyértelműen el nem dől az értéke.

if (false && true) {}
if (false || true) {}
if (true && false) {}
if (true || false) {}

Az 1. sorban mivel az && (és) bal oldalán hamis érték áll, ezért a jobb oldali értéket felesleges megnézni, hiszen egy és értéke csak akkor igaz, ha a bal oldala és a jobb oldala is igaz. A 2. sorban lévő || (vagy) bal oldalán hamis érték áll, vagyis itt meg kell nézni a jobb oldalt is, hiszen egy vagy akkor igaz, ha vagy a bal, vagy a jobb oldala igaz. Ezzel szemben a 3. sorban lévő && esetén az és jobb oldalát is meg kell nézni, hiszen egy és értéke csak akkor igaz, ha mindkét oldala igaz. A 4. sorban viszont csak a || bal oldala alapján egyértelműen tudjuk, hogy az adott vagy kifejezés értéke igaz lesz, így felesleges a jobb oldalát is megnézni a vagy operátornak.

Ezek természetesen ugyanígy igazak több részkifejezés esetén is. Talán elsőre nem tűnik fontos dolognak, azonban a való életben sokszor optimalizálhatunk a kódunkon, ha jó sorrendben írjuk meg a logikai kifejezéseinket (például, ha egy és kifejezésünk van, akkor célszerű a bal oldalra tenni a gyorsabban kiértékelhető dolgokat, míg a jobb oldalra tenni az olyan kódot, ami kiértékelése időigényesebb lehet; konkrét példa: az és bal oldalán célszerűbb egy felhasználói inputot ellenőrizni, a kifejezés jobb oldalára pedig kerülhet mondjuk az a kód, ami interneten néz valaminek utána, vagy mondjuk egy adatbázisból olvas be adatot). Arról nem is beszélve, hogy futásidejű hibákat is megelőzhetünk, ha az if-en belüli logikai kifejezésünket jól írjuk meg.

Azonban figyelni kell, hiszen a && és & operátorok nem ugyanazok! Az első a logikai és, a másik pedig a bitenkénti. Természetesen boolean értékek esetén a két operátor ugyanúgy működik, kivéve a gyors kiértékelést! A bináris operátorok esetében mindig kiértékelődik mindkét oldala a bináris operátornak!

public static boolean hosszabMint(String input, int hossz) {
    return input != null && input.length() > hossz;
}

public static boolean hosszabMint2(String input, int hossz) {
    return input != null & input.length() > hossz;
}

A hosszabMint függvényben logikai és hajtódik végre, vagyis ha az input null, az és bal oldala kiértékelődik hamisra, és a jobb oldalt lefutásra sem kerül. Ezzel szemben a hosszabbMint2 függvény bináris műveletet hajt végre, azaz mindkét oldalt megvizsgálja, tehát ha az input null, akkor az és bal oldala hamisra értékelődik ki, majd megpróbálja a jobb oldali kifejezést is kiértékelni, de mivel az input változó null, ezért futásidejű hibát fogunk kapni.

Vezérlési szerkezetek

Majdnem minden működik, ami C-ben is működött, szóval aki idáig eljutott, ezeket már jól ismeri. A relációs operátorok is ugyanúgy működnek, mint C-ben, illetve a ++ és -- operátorok is ugyanúgy használhatóak.

Létezik tehát a már megszokott módon az if() és a ?: operátor is. Az if komplexebb feltételek megadására is alkalmas. Például ha azt szeretnénk, hogy egy x változó 3 és 7 között legyen, vagy 13, vagy negatív és nem -9, akkor ezt a következő kóddal adhatjuk meg:

if ((x >= 3 && x <= 7) || x == 13 || (x < 0 && x != -9)) {
    System.out.println("A feltétel teljesül.");
}

Logikai kifejezéseket összefűzhetünk az && (és) és a || (vagy) operátorok használatával. A zárójelezés ilyenkor kritikus fontosságú lehet.

Javaban minden logikai kifejezés egy boolean értékké értékelődik ki (vagyis vagy igaz, vagy hamis). Ha csak egy boolean értékünk/változónk van, az természetesen önmagában is igazságértéket jelképez, szükségtelen kiírni az == true részt.

boolean pozitiv = true;
if (pozitiv) {
    System.out.println("A szám pozitív.");
}
if (!pozitiv) {
    System.out.println("A szám nem pozitív.");
}
if (pozitiv == true) {
    // Szuksegtelen az == true resz, ugyanazt jelenti, mint a 2. sorban levo kod
}

A ?: operátor használata például a kiíráson belül lehet indokolt. Az előző kódot például jelentősen egyszerűsíti:

boolean pozitiv = true;
System.out.println("A szám " + ((pozitiv) ? "" : "nem ") + "pozitív.");

Viszont, C-vel ellentétben itt nincs implicit konverzió int és boolean között, a Java megköveteli az összehasonlító operátorok használatát ebben az esetben:

int x = 1;
if (x) { //Javaban ez így nem működik!
  System.out.println("Minden rendben!");
}

//A helyes feltétel az alábbi:
if (x > 0) {
  System.out.println("Minden rendben!");
}

A while, do-while és switch utasítás megegyezik a C-ben tanultakkal. A for utasításnál itt is kényelmesen deklarálható ciklusváltozó a for-on belül, ahogy azt C-ben is láttuk már (a ciklusváltozó deklarációja C-ben a C99 szabvány óta lehetséges ezen a módon). Ebben az esetben a cikluson kívül már nem látjuk a ciklusváltozót!

for (int i = 0; i < 5; i++) {
    //ciklus tartalma
}

Néhány dolog létezik azonban, amely nem működik Java-ban, de a C-ben igen, ezek egyike a goto utasítás. A program egyes részeit ugyanúgy címkézhetjük, azonban a goto parancsot nem használhatjuk. Ez Javaban ugyan foglalt kulcsszó, de implementáció nem tartozik hozzá. Ennek oka az, hogy az eredeti, első JVM-ben létezett a goto utasítás, és működött is (ezt James Gosling nyilatkozta, aki a JVM-et készítette). Azonban ezt nem sokkal utána ki is vették, mert nem igazán volt használva Javaban, és a nyelv készítői úgy látták, hogy C-ben is a legtöbben rosszul használják ezt az utasítást. Így a funkcionalitását kivették a goto utasításnak, de meghagyták foglalt kulcsszónak (reserved keyword), hiszen létezhetett olyan kód, amiben ez benne volt, illetve nem zárták ki annak sem a lehetőségét, hogy a jövőben szükség lehet rá, így azóta is foglalt kulcsszó a goto, de semmilyen mögöttes működése nincs. Hasonlóan foglalt kulcszó a C-beli const, aminek ugyanúgy nincs semmilyen működése.

Memóriaterületek

A következőkben röviden bemutatjuk a stacket, illetve a heapet, azonban ezek a témakörök az előadáson ennél jóval részletesebben be lettek mutatva. Bővebben olvashatunk a témáról a következő linkeken: JVM specifikáció ide vonatkozó részei

Stack

A stacken tároljuk a primitív típusú adatokat. Ez a memóriaterület nagyon gyors elérésű, általában lokális változókat, rövid életű adatokat tárolunk benne. A stacken létrehozott változók élettartalma a létrehozó blokkra korlátozódik, azaz pl.: ha van egy lokális változónk egy függvényben, akkor az a változó addig él, és addig lesz a memóriában tárolva, ameddig a függvény futása be nem fejeződik. Ezt követően a foglalt memóriaterület automatikusan felszabadul. Hátránya a heap memóriával szemben az, hogy a stack jóval kisebb. Javaban csak és kizárólag primitív típusú adatokat tárolhatunk a stacken (szám, bájt, karakter vagy logikai érték).

A gyakorlatban, ha egy változó értékét stacken tároljuk (pl.: int x = 42;), akkor akármikor hivatkozunk a változóra, a változó neve gyakorlatilag egyet jelent az értékkel, érték szerint tárolódik.

Heap

A heap memórián tároljuk az objektumokat, tömböket (ezeket referencia típusúaknak hívjuk). Ez a stackkel ellentétben egy lassabb, dinamikus memóriaterület, cserébe viszont jóval nagyobb terület áll rendelkezésre. Az itt létrehozott adatokat hosszabb távon tároljuk, globálisan léteznek, nem csak egy-egy függvény belsejében, lokálisan.

Amikor létrehozunk egy objektumot a heapen, akkor az adott változó az gyakorlatilag csak egy referencia, mutató lesz a heap-beli memóriaterületre, ahol ténylegesen az objektum tárolódik, a változó maga csak ezt a referenciát tárolja (a stacken). Ez ismerős lehet, a pointer fogalma hasonló. Tehát a referencia típusú változó nem közvetlenül tárolja az értéket, csak egy referenciát tárol, és a heapen tárolt értéket közvetetten, ezen a referencián keresztül érjük el.

Gyakorlati példa: Egy nagyon szemléletes példa, Bruce Eckel Thinking in Java című könyvéből: Képzeljük el, hogy van egy Televízió típusú objektumunk a heapen, melyhez tartozik egy referencia, amelyet letárolunk, és ennek a neve legyen távirányító. Ahogy a való életben is, ülünk a kényelmes fotelünkben, és nézzük a televíziót, de amikor szeretnénk a Televízió objektumunkon bármely módosítást eszközölni (halkítani, hangosítani, csatornát váltani, be-, kikapcsolni), akkor a távirányító segítségével tesszük meg ezt. És ha a szobában sétálva, lefekve bárhol szeretnénk a Televízió objektumon machinálni, akkor a távirányítót visszük magunkkal, nem pedig magát a Televíziót.

Java és C-beli különbségek, hasonlóságok

Az egyes szekciókban tárgyalt és azokon túlmutató hasonlóságokról és különbségekről bővebben olvashatsz itt és itt.

Tömbök

Korábban már volt szó konkrét tömbökről (pl.: parancssori paraméterek tömbje). Az akkor tárgyaltak természetesen általánosságban is igazak a tömbökre. Egy rövid összefoglaló: A tömbök azonos típusú, de egynél több érték tárolására vannak kitalálva. Az elemek száma azonban rögzített, tehát adott számú, azonos típusú elemet tartalmazó adattípusról van szó. Az indexelés 0-val kezdődik, ahogy az ismerős lehet Programozás alapjai tárgyból.

Megjegyzés: Habár Javaban, C-ben 0-tól indexelünk, nem feltétlenül van ez így minden nyelvben. Vannak olyan nyelvek, melyek egytől kezdik az indexelést. Ezen nyelvekről itt találsz egy összefoglaló listát.

Fontos lehet, hogy a C-vel ellentétben itt nagyon szeretjük a tömböket, hiszen a Java (mivel menedzselt nyelvről van szó, azaz a kód nem közvetlenül a hardveren fut, hanem a JVM-en, ahogy ezt korábban tárgyaltuk) teljes körű indexhatár-ellenőrzést végez, kivétellel jelzi, ha alul- vagy túlindexelés történik.

A Java-beli tömböket többféleképpen is létre lehet hozni. Statikusan, ha fordítási időben ismerjük a tömb méretét, vagy pedig dinamikusan, amikor nem ismerjük előre a tömb méretét. Mind primitív típusból, mind pedig osztályokból létrejövő objektumok esetén használhatunk tömböket. Tömböket dinamikusan, a new operátorral lehet létrehozni, mérete a már ismert length tulajdonsággal kérdezhető le.

Egydimenziós tömbök

Az egyetlen kiterjedéssel (dimenzióval) rendelkező tömböket szokás vektornak is nevezni, ahogy ez a kifejezés matematikából ismerős lehet. A tömböt deklarálni a következőképp lehet:

típus tömbnév[];
típus[] tömbnév;

A két deklaráció között semmilyen különbség sincs, azonban Javaban az alsó változat az ajánlott (a fentit az IDE-k be is sárgíthatják). Futás közben a már említett new operátorral tudjuk definiálni:

tömbnév = new típus[méret];

Egy konkrét példa:

int[] x = new int[5];

Ennek a tömbnek az elemei: x[0], x[1], x[2], x[3], x[4]. A tömböt legegyszerűbben egy for ciklussal tölthetjük fel.

A Java lehetővé teszi, hogy a tömböket a definiálás során konstans értékekkel töltsük fel. Ilyenkor a tömböt a fordító hozza létre.

típus[] tömbnév = { konstans1, konstans2 }
// egy konkrét példa:
int[] arr = { 1, 2, 3, 4, 5 };

Adott tömb mérete minden esetben egy nemnegatív egész szám, melynek értéke nem lehet nagyobb, mint a lehetséges legnagyobb index. Ez a JVM-től függ, és mivel az indexelés int értékekkel történik, ezért az elméleti legnagyobb elemszám Integer.MAX_VALUE (ez egy definiált konstans), azonban ez a gyakorlatban egy ettől 5-8 értékkel kisebb szám. Derítsük ki, hogy a saját JVM-ünk esetében mekkora ez a szám: ehhez hozzunk létre egy tömböt, és próbáljuk meg lefordítani, és lefuttatni az elkészült fájlt:

String[] tomb = new String[Integer.MAX_VALUE - 4];

Többdimenziós tömbök

Analóg módon az egydimenziós tömbökhöz, illetve a korábban, Programozás alapjain tanultakhoz:

típus[][]...[] tömbnév;
típus tömbnév[][]...[];

Két dimenziós tömb létrehozására egy példa:

típus[][] tömb;
tömb = new típus[méret1][méret2];

Egy konkrét példa kétdimenziós tömb létrehozására és feltöltésére. A tömböt legegyszerűbben egy for ciklussal tölthetjük fel.

int[][] tomb;
tomb = new int[10][9];

for (int i=0; i < tomb.length; i++){
    for (int j = 0; j < tomb[i].length; j++) {
        tomb[i][j] = (i+1)*(j+1)*9;
    }
}

A Java lehetővé teszi, hogy a tömböket a definiálás során konstans értékekkel töltsük fel. Ilyenkor a tömböt a fordító hozza létre

int[][] matrix = { { 1, 2 }, { 3, 4 } };

Tömbök másolása

Mivel a tömbök referencia típusúak, ezért egy egyszerű értékadás esetén csak a referenciát (azaz a tömb címét) másoljuk át egy másik változóba, a konkrét, memóriában létező tömböt nem. Tényleges tömbmásolást kézzel is megvalósíthatunk, egyszerű for ciklus segítségével, de létezik egy, a JDK-ban már előre elkészített metódus épp erre a célra: a tényleges másolás a System.arraycopy() metódussal lehetséges. Ennek fejléce (javadoc):

public static void arraycopy(Object forrás, int forrásKezdetiElem, Object cél, int kezdetiPozíció, int hossz)

Ez a hagyományos, kézi másolással szemben jóval gyorsabb, mert nem elemenként másolja a tömb egyes elemeit, az implementáció Java Native Interface-t (JNI) használ.

Egy konkrét példa tömbök másolására:

   int tomb1[] = { 30, 31, 1, 2, 3, 4, 5, 6, 7 };
   int tomb2[] = { 29, 0, 0, 32, 33 };

   System.arraycopy(tomb1, 0, tomb2, 1, 2);

   for (int i=0; i<tomb2.length; i++){
       System.out.print(tomb2[i] + " ");
   }
   System.out.println();

Ennek kimenete: 29 30 31 32 33.

Ezen kívül használhatjuk az Arrays.copyOf és Arrays.copyOfRange függvényeket is, melyekkel talán picit kényelmesebb a tömbök másolása, azonban ezek használatához be kell importálnunk az Arrays osztályt a kódunkba. Az importálásról később lesz majd szó. Teljes példa tömbök másolására:

import java.util.Arrays;

public class TombMasolas {
    public static void main(String[] args) {
        int[] tomb1 = {30, 31, 1, 2, 3, 4, 5, 6, 7};
        int[] tomb2 = {29, 0, 0, 32, 33};

        System.arraycopy(tomb1, 0, tomb2, 1, 2);
        for (int i = 0; i < tomb2.length; i++) {
            System.out.print(tomb2[i] + " ");
        }
        System.out.println();

        // Tömbmásolás az Arrays.copyOf segítségével (teljes tömb)
        int[] tomb3 = Arrays.copyOf(tomb1, 3);
        for (int elem : tomb3) {
            System.out.print(elem + " ");
        }
        System.out.println();

        // Tömbmásolás az Arrays.copyOfRange segítségével (az eredeti tömb egy része)
        int[] tomb4 = Arrays.copyOfRange(tomb1, 2, 6);
        for (int elem : tomb4) {
            System.out.print(elem + " ");
        }
        System.out.println();
    }
}

Videók

Feladatok

  1. Írd ki a parancssori paramétereket a konzolra.
  2. Írd ki a parancssori paramétereket a konzolra, fordított sorrendben.
  3. Írd ki a parancssori paramétereket úgy, hogy az n. sorban az első n darab parancssori paramétert írja ki: első sorba csak az elsőt, a másodikba az első kettőt szóközzel elválasztva, a harmadikba az első hármat, és így tovább.

    java Main egy ketto kutya cica fagyi

    egy
    egy ketto
    egy ketto kutya
    egy ketto kutya cica
    egy ketto kutya cica fagyi
    
  4. Írd ki a parancssori paraméterek közül a legnagyobbat, legkisebbet, valamint az értékek átlagát.

  5. A parancssori paraméterek alapján döntsd el, hogy a bemenő számok számtani, mértani sorozatot alkotnak-e, vagy esetleg egyiket sem. Feltehetjük, hogy mindegyik egész szám, és legalább 3 db paraméterünk van. Az összegképletek:

    • számtani: an = a1 + (n – 1) * d
    • mértani: an = a1 * q n – 1
  6. Számítsd ki a parancssoron kapott két időpont (óra perc óra perc) között eltelt időt, és írasd ki a konzolra (óra perc formában). A program elkészítése során ügyelj az adatok helyességére

    • bemenő paraméterek száma
    • az órák 0-23 intervallumba kell, hogy essenek
    • a percek 0-59 intervallumba kell, hogy essenek
  7. Hozzunk létre egy 7*10-es int-eket tartalmazó tömböt, töltsük fel őket, az alábbi séma szerint: tömb[x][y] = x*y; (pl.: tömb[5][8] = 40;)

  8. Hozzunk létre egy karakter tömböt 't' 'e' 'l' 'e' 'f' 'o' 'n' karakterekkel! Másoljuk egy új tömbbe a 'l' 'e' karaktereket!

Kapcsolódó linkek

Learning the Java Language

Tömbök, Class Arrays, Tömbök tutorial

Tömbméret a bájtkódban


Utolsó frissítés: 2024-04-11 07:54:27