Java programozás 10. – Switch

Switch, avagy sok “egyforma” ajtó

Az előző leckében feltételvizsgálatokkal ismerkedhettél meg. Amint láthattad, nagyon sokféle, rugalmas használati módja van, de vannak esetek, amikor kifejezetten körülményes a használata. Nem azért mert nehéz, hanem van amikor annyira feleslegesnek tűnik sokadszor szinte ugyanazt vizsgálni.

Vannak olyan esetek, amikor a sok if-else if-else ág helyett egyszerűbb lenne egy olyan vizsgálat, ahol egy változó értékeit közvetlenül lehetne vizsgálni, és azokhoz más-más utasításokat rendelhetnénk hozzá. Erre szolgál a switch.

Switch szerkezete

switch( valtozo )
{
// case után a konkrét előfordulás
  case 1_tipus :
// az előforduláshoz kapcsolt egyetlen utasítás
    elso_tipus_utasitasa;
// lezárjuk a kapcsolt utasítást
  break;

// több előforduláshoz ugyanaz az utasítás is tartozhat
  case 2_tipus : case 3_tipus :
    masodik_vagy_harmadik_tipusok_utasitasa;
  break;

// sokszor a rovid egy utasitasos agakat egy sorba irjuk
// a tomorebb forma miatt, a break mehet a vegere
  case 5_tipus : otodik_tipus_utasitasa; break;

// irhattam volna a 2 case agat azonos sorba is, mint a 2-3-nal
  case 4_tipus :
  case 6_tipus :
// több előforduláshoz több utasítás is tartozhat
    negyedik_vagy_hatodik_tipusok_elso_utasitasa;
    negyedik_vagy_hatodik_tipusok_masodik_utasitasa;
    ....
  break;

// a 7-es agnal nem veletlenul hianyzik a break!!
  case 7_tipus : hetedik_tipus_utasitasa;
  case 8_tipus : nyolcadik_tipus_utasitasa; break;

// egy előforduláshoz több utasítás is tartozhat
  case 9 :
    kilencedik_tipus_elso_utasitasa;
    kilencedik_tipus_masodik_utasitasa;
  break;
...
...
// a default ag nem kotelezo! egyfajta else agkent ertelmezheto
  default :
    minden_mas_eset_utasitasai;
    ....
  break;
}

Akkor pár magyarázat ezzel kapcsolatban:

  1. A switch után zárójelben meg kell adni azt a típust, amelynek a különféle értékeihez kapcsolódó ágakat a switch-en belül kezelni szeretnénk. Ezek a típusok jórészt primitív típusok lehetnek, valamint pár speciális típus. A lista nem teljes, de a lényeges elemek benne vannak:
    1. byte
    2. short
    3. int
    4. char
    5. String(!) (java 7 óta)
    6. enum
  2. Az egyes ágakat case kulcsszóval vezetjük be, ami után odaírjuk a konkrét előfordulást, amihez a kettőspont utáni utasításokat kapcsolni szeretnénk. Ha egyetlen rövid utasítást használunk csak, akkor írhatjuk a példa ötödik előfordulásának megfelelő szerkezetben egy sorba.
  3. Több különböző előforduláshoz tartozhatnak közös utasítás vagy utasítások, lásd 2-3 és 4-6 esetek. Az előfordulásoknak nem kötelező egymás utániaknak lenni, pl sorszámok esetén. Több előfordulás közös vizsgálatakor a case elofordulas : utasításokat egy vagy több sorba is írhatjuk (pláne ha sok van), de minden case-t kettőspont választ el az utána következő case-től vagy az utasításoktól!
  4. Az egyes ágak utasításait break-kel zárjuk le a következő előfordulás vizsgálata előtt. Nagyon speciális esetekben a break elhagyható. A példában szereplő 7-es előfordulás esetén végrehajtja a hetedik_tipus_utasitasa-t, de mivel itt hiányzik a break, a 7-eshez végrehajtja a 8-as utasításait is, függetlenül attól, hogy a két előfordulás különbözik egymástól. Ez az utasításszerkezet nagyon elnézhető, éppen ezért csak nagyon speciális esetben szokás használni! Több mint 15 éves programozói munkám során emlékeim szerint egyszer használtam, ott is teleraktam kommentekkel még a környékét is, nehogy valaki jóhiszeműen kirakja az általam ott kihagyott break-et! Attól eléggé fejre állt volna a program 🙂
  5. A default ágat egyfajta else-ként értelmezhetjük, sok esetben arra használjuk, hogy az egyes előfordulások vizsgálata figyelmeztessünk az esetleges hibás értékekre. Nem kötelező, de ha használjuk, mindenképp a switch szerkezet végén kell lennie.
  6. Amilyen előfordulások nem szerepelnek a vizsgálatok között, azokat a switch figyelmen kívül hagyja.

Látjuk azt, hogy nagyon rugalmasan variálható szerkezetről van szó, amivel változók tartalmának direkt vizsgálatakor az esetleges sok esetet átlátható módon kezelhetjük. A helyzet az, hogy rugalmassága ellenére is viszonylag ritkán használom. Egyrészt amiatt, mert sok esetben valóban elég egy továbbmásolt if-else if-else alapú feltételvizsgálat, másrészt néhány esetben kifejezetten bonyolítja a switch a helyzetet. Kicsit olyan ez, mint amikor megtanulja az ember a while, do-while és for ciklusokat. Bármelyikkel kiváltható bármelyik, a helyzet azonban az, hogy mindegyiket akkor használjuk, amikor az a feladat azzal a szerkezettel oldható meg egyszerűbben vagy átláthatóbban. A switch esetében a rengeteg fajta testreszabási lehetőséggel együtt is azt javaslom, hogy akkor használjuk, amikor egyértelmű megoldásokat kaphatunk vele.

Switch példák

Hónapok

Tegyük fel, hogy a hónapok sorszámait szeretnénk átalakítani szöveges formává. Az if-else if-else szerkezetekkel ez a következőképpen nézne ki:

int honap = (int)(Math.random() * 12) + 1;
String honev = "";
if( honap == 1 )
{
  honev = "Januar";
}
else if( honap == 2 )
{
  honev = "Februar";
}
else if( .... )
...
...
else
{
  honev = "December";
}

Az általam szeretett ctrl-c és ctrl-v billentyűkombinációval ez viszonylag hamar összerakható, de valójában a legtöbb gépelést a hónapok nevei okozzák. A helyzet az, hogy a switch utasítás sem kímél meg a hónapok gépelésétől, de magát az elágazási szerkezetet leegyszerűsíti. Lássuk hogyan:

int honap = (int)(Math.random() * 12) + 1;
String honev = "";
switch( honap )
{
  case 1 : honev = "Januar"; break;
  case 2 : honev = "Februar"; break;
  case 3 : honev = "Marcius"; break;
  ....
  ....
// mivel a veletlen szam sorsolas csak 1-12-ot sorsolhat
// a default-ot nem hibakezelesre hasznalom, ez a december
  default : honev = "December"; break;
}

Láthatod, hogy a switch a sok case meg break miatt ez sem kevesebb gépelés, mint egy if-else if-else szerkezet, de természetesen megoldható ezzel is. Rád bízom melyiket tekinted egyszerűbbnek.

Napok

Mi van akkor, ha a hét napjának a sorszámából szeretnénk megkapni, hogy az hétköznap vagy hétvége:

int nap = (int)(Math.random() * 7) + 1;
switch( nap )
{
  case 1 : case 2 : case 3 : case 4 : case 5 :
    System.out.println("Hetkoznap");
  break;

  default :
    System.out.println("Hetvege");
  break;
}

És if-else if-else-szel hogy néz ki ugyanez?

int nap = (int)(Math.random() * 7) + 1;
if( nap <= 5 )
{
  System.out.println("Hetkoznap");
}
else
{
  System.out.println("Hetvege");
}

Itt a switch szerintem feleslegesen bonyolította is a megoldást.

Hiányzó break

Lássunk akkor példát arra, hogy mikor lehet elhagyni a break-et. Sorsoljuk ki egy nap sorszámát, írjuk ki a hét hátralévő napjainak nevét:

int nap = (int)(Math.random() * 7) + 1;
switch( nap)
{
  case 1 : System.out.println("Kedd");
  case 2 : System.out.println("Szerda");
  case 3 : System.out.println("Csutortok");
  case 4 : System.out.println("Pentek");
  case 5 : System.out.println("Szombat");
  case 6 : System.out.println("Vasarnap");
}

Itt sehol nincs break. Az előfordulások vizsgálatának sorrendje viszont megadja azt, hogy ha mondjuk 3-as napot sorsoltunk, akkor onnan kezdődően a hiányzó break-ek miatt az utasítások mindegyike végrehajtódik. Amelyik előfordulás vizsgálatnál nincs break, az addig végrehajtja az összes alatta lévő utasítást – az ottani case értékektől függetlenül -, ameddig bele nem szalad egy break utasításba, vagy el nem éri a switch végét. És mi a helyzet a 7-es nappal? Nincs sehol és nincs default ág. Semmi. Amire nincs vizsgálat, azt figyelmen kívül hagyja.

Kerekítés

Adott a magyar készpénzes fizetés kerekítési problémája. Az 1-2-re végződő összegeket lefelé az alatta lévő tízesre kerekítjük, a 3-4-et a felette lévő 5-ösre, stb. Erre nagyon sokféle elegáns, kevésbé elegáns megoldást lehetne adni. Switch szerkezettel is megoldható sokféleképp. Mutatok két megoldást, 1-1 leheletnyi különbséggel.

Első verzió:

int penz = 0;

for (int i = 0; i < 20; i++)
{
  penz = (int) (Math.random() * 991) + 10;
  System.out.print(penz + " -> ");

  switch (penz % 10)
  {
    case 2: case 7: penz -= 2; break;

    case 1: case 6: penz--;    break;

    case 3: case 8: penz += 2; break;

    case 4: case 9: penz++;    break;
  }
  System.out.println(penz);
}

Második verzió:

int penz = 0;

for (int i = 0; i < 20; i++)
{
  penz = (int) (Math.random() * 991) + 10;
  System.out.print(penz + " -> ");

  switch (penz % 10)
  {
    case 2: case 7: penz--;
    case 1: case 6: penz--; break;

    case 3: case 8: penz++;
    case 4: case 9: penz++; break;
  }
  System.out.println(penz);
}

A második esettel trükkös megoldás, ahol kihasználom azt, hogy a hiányzó break miatt a következő utasításokat is végrehajtja a break nélküli előfordulásokhoz egészen addig, ameddig egy break-be bele nem szalad. A helyzet azonban az, hogy mivel a programozók legtöbbször csapatban dolgoznak, a legkevésbé szeretik mások trükkös megoldásait bogarászni. A lényeg az, hogy a megoldás átlátható és világos legyen, hiszen akkor annak működését, esetleges hibáit is jóval egyszerűbb felismerni. Természetesen vannak ettől egyszerűbb, switch mentes megoldások is, ezeket most csak a szerkezet bemutatása miatt írtam meg.

Switch használata String-gel

Az alábbi feladatban a hónapokat nevük alapján szeretnénk visszaalakítani számokká. Természetesen ennek is van egyszerűbb módja, de a példa kedvéért álljon itt switch szerkezettel:

String[] honapok = { "januar","marcius","december","november",
                     "januar","julius","majus","majus","szeptember",
                     "oktober","aprilis","majus","februar" };
int ho = 0;
      
for( int i = 0; i < honapok.length; i++ )
{
    switch( honapok[i] )
    {
        case "januar"     : ho = 1;  break;
        case "februar"    : ho = 2;  break;
        case "marcius"    : ho = 3;  break;
        case "aprilis"    : ho = 4;  break;
        case "majus"      : ho = 5;  break;
        case "junius"     : ho = 6;  break;
        case "julius"     : ho = 7;  break;
        case "augusztus"  : ho = 8;  break;
        case "szeptember" : ho = 9;  break;
        case "oktober"    : ho = 10; break;
        case "november"   : ho = 11; break;
        case "december"   : ho = 12; break;
        default           : ho = -1; break;
    }
    System.out.print(ho + " " );
}

A java 7-es verziójától kezdődően String is használható a switch bemeneteként. Ilyenkor nem szükséges az egyes előfordulásokat az equals metódussal ellenőrizni, csak adjuk oda a switch-nek a String változót, az egyes előfordulásoknál pedig a vizsgálandó értékeket, a többit a switch megoldja.

Összefoglalásként csak ismételni tudom magam: Nagyon elegáns, és rugalmas eszköz a switch, de itt is ügyelni kell arra, hogy lehetőleg akkor használjuk, amikor ez a legjobb megoldás, akár a megoldás egyszerűsége, akár az átláthatósága a cél. Ne nézzünk mindent szegnek, ha kalapács van a kezünkben 🙂

Következő lecke: Véletlen számok

4 Replies to “Java programozás 10. – Switch”

  1. Szia Csaba,

    Kerekítés -> Első verzió:
    case 3: case 8: penz -= 2; break; szerintem kicsit sántit, szerintem így lenne helyesebben:
    case 3: case 8: penz += 2; break;, mert 3,8 maradéknál felfelé kerekítünk, vagy én ragadtam le? 🙂

    Különben nagyon jó a java oktató anyagod.

    Üdv

    Gábor

  2. Switch példák cím alatt a következp 2 példában ez szerepel: string honev = “”; Nagybetű a String. Kicsit keresgélnem kellett miért visít a szerkesztő, hogy nem jó.

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük

*

Ez az oldal az Akismet szolgáltatást használja a spam csökkentésére. Ismerje meg a hozzászólás adatainak feldolgozását .