Télapó probléma

Télapó-problémaAz operációs rendszerek tervezésének fontos része az ütemezési, erőforrás- és szálkezelési feladatok problémamentes, holtpontmentes megoldása, szinkronizálása, amiről sok ismert szerző publikált már, néhányan közülük angol nyelven: W. Stallings, A. B. Downey, A. S. Tanenbaum, A. S. Woodhull., és magyarul is: Galambos Gábor, Knapp Gábor és Adamis Gusztáv. Ehhez a szakterülethez tartozik több népszerű probléma/esettanulmány, például a vacsorázó bölcsek problémája, illetve a Santa Claus Problem, vagyis a Télapó probléma.

A Télapó probléma specifikációját és megoldását a konkurens programozás eszközeire építve J. A. Trono készítette el (szemaforokkal), amire építve is – és kritizálva is azt – több Java implementáció is elkészült (például: P. Steiner), valamint több programozási nyelv szálkezelési lehetőségeinek összehasonlításáról is publikált J. Hurt és J. B. Pedersen és kliens-szerver elosztott környezetben is áttekintette a lehetőségeket D. Marchant és J. Kerridge. Ismert Haskell, Erlang, Polyphonic C# implementáció is.

A Télapó probléma meghatározása

A Télapó alszik az északi-sarki boltjában és csak akkor ébredhet fel, ha mind a 9 rénszarvas visszatér a dél-csendes-óceáni trópusi szigetén töltött rendes évi nyaralásukról, illetve ha akad néhány manó, akiknek nehézségei vannak az ajándékkészítés során. A 10 manó közül 1 manó problémája nem elég komoly ahhoz, hogy felébressze a Télapót (egyébként sosem aludna), így 3 manó megy egyszerre a Télapóhoz. Amikor a 3 manó problémáit közösen megoldották, akkor mind a 3 manónak vissza kell térnie a többi manóhoz, mielőtt egy újabb manólátogatás megtörténne. Ha a Télapó úgy ébred, hogy 3 manó várja őt a bolt ajtajánál és az utolsó rénszarvas is visszatért a trópusokról, akkor a Télapónak fontosabb, hogy olyan gyorsan elinduljon a szánnal, amilyen gyorsan csak lehetséges – így a manóknak várniuk kell karácsony utánig. Feltételezzük, hogy a rénszarvasok nem akarják elhagyni a trópusokat, ezért az utolsó pillanatig maradnak, amíg csak lehetséges. Lehet, hogy egyáltalán nem is jönnének vissza, ameddig a Télapó fizeti a számlát a paradicsomban… Ez is megmagyarázhatja az ajándékok kiszállításának gyorsaságát, hiszen a rénszarvasok alig várják, hogy visszatérhessenek oda, ahol meleg van. Az utolsóként érkező rénszarvas büntetést kap a Télapótól, mialatt a többi rénszarvas a meleg kunyhóban várja, hogy befogják őket a szán elé.

A Télapó probléma – egyik – megoldása

Találtam egy kb. 10 perces kiváló YouTube videót/animációt (The Santa Claus Problem – Thread Synchronization), amely lépésenként felépíti a feladatot, érzékelteti a közben felmerülő problémákat, és megoldást is mutat. Ezt ajánlom december 6-án minden érdeklődő figyelmébe:

A feladatot részletekbe menően és komplex módon gondolkodva kell megtervezni, és implementációi komoly nyelvi eszköztárat igényelnek. Érdemes P. Steiner Java megoldását részletesen átnézni, újragondolva – újabb nyelvi eszköztárral – implementálni.

A feladat a Java EE szoftverfejlesztő tanfolyam 1-4. óra: Elosztott alkalmazások, webszolgáltatások, illetve 5-8. óra: Szálkezelés, párhuzamosság alkalmaihoz kapcsolódik.

Fát építünk

Fát építünkAz adatok strukturális és könnyen értelmezhető formában való megjelenítése egy szoftver felhasználói felületén átgondolt tervezést igényel. Az adatokhoz hozzá kell jutni, ki kell választani a megfelelő grafikus komponenst, a mögötte lévő adatmodellt, össze kell ezeket kötni. Gyakran előforduló feladat, hogy táblázatosan is ábrázolható adatokból – felhasználva az adatok közötti összefüggéseket és kapcsolatokat – csoportosítva jelenítsünk meg hierarchikusan, fa struktúrában, kinyitható-becsukható formában, ahogyan ezt a felhasználók jól ismerik a fájl- és menürendszereket használva.

Fát építünk kétféleképpen

Adatbázisból, az Oracle HR sémából lekérdezünk két összetartozó nevet: részleg és alkalmazott. A lekérdezés során figyelünk a megfelelő sorrendre, ami a későbbi feldolgozást megkönnyíti. Adatainkat részlegnév szerint növekvő, azon belül alkalmazott neve szerint is növekvő – ábécé szerinti – sorrendbe rendezzük. A vezérlő rétegben két függvényt írunk, amely a modell rétegtől jut hozzá az adatokat tartalmazó generikus listához – átvett paraméterként –, és a visszaadott érték a nézet réteghez kerül.

A csoportváltás algoritmust használjuk, amely 5 blokkból épül fel. A külső ciklus előtti 1. blokk és utáni 5. blokk egyszer hajtódik végre, az előkészítő és lezáró tevékenységek tartoznak ide. A külső ciklus elején és végén található 2. és 4. blokk a belső cikluson kívül fut le, csoportonként, kategóriánként, részlegenként egyszer (most összesen 11-szer mindkettő). A 3. blokk a belső cikluson belül található, és alkalmazottanként egyszer hajtódik végre (most összesen 106-szor).

Háromszintű fát építünk: a gyökérbe (0. szint) fix, beégetett szövegként kerül a cég neve és a teljes létszám. Az 1. szinten jelennek meg a részlegek nevei és a hozzájuk tartozó létszámok. A 2. szint az alkalmazottak neveiből áll.

1. megoldás

A megoldás faKeszit1() függvénye szöveges adatot eredményez. Ez jól használható teszteléshez: megvan-e az összes adat, megfelelő-e a részlegek sorrendje azon belül az alkalmazottak sorrendje, működik-e a csoportosítás, rendben van-e a megszámolás?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public String faKeszit1(ArrayList<Alkalmazott> lista) {
  //1
  StringBuilder faGyoker=new StringBuilder("Cég ("+lista.size()+" fő)");
  int i=0;
  while(i<lista.size()) {
    //2
    String aktReszleg=lista.get(i).getReszleg();
    ArrayList<String> faReszlegAlkalmazott=new ArrayList<>();
    while(i<lista.size() && lista.get(i).getReszleg().equals(aktReszleg)) {
      //3
      faReszlegAlkalmazott.add(lista.get(i).getNev());
      i++;
    }
    //4
    String faReszleg="\n "+aktReszleg+" ("+faReszlegAlkalmazott.size()+
      " fő)\n ";
    faGyoker.append(faReszleg+" "+String.join("\n  ",faReszlegAlkalmazott));
  }
  //5
  return faGyoker.toString();
}

 
A faKeszit1() függvény egy sok lépésben összefűzött (konkatenált) szöveget ad vissza. Az 1. blokkban előkészítjük a fa gyökerét, ami StringBuilder típusú, hiszen sokszor manipuláljuk és inicializáljuk a lista indexelésére használt i ciklusváltozót. A 2. blokkban megjegyezzük az aktuális részleget és előkészítjük az ehhez tartozó alkalmazottak nevét tároló generikus listát (faReszlegAlkalmazott). Az aktReszleg-hez tartozó alkalmazottak neveit összegyűjtjük a 3. blokkban. Egy részleg feldolgozását a 4. blokkban fejezzük be a fa aktuális 1. és 2. szinten lévő elemeinek szövegbe való beszúrásával. A belső ciklushoz kötődően megszámolást nem kell alkalmaznunk, hiszen az adott részlegben dolgozó alkalmazottak száma a generikus listától elkérhető (size()). Építünk arra, hogy a külső ciklusból nézve az egymás után végrehajtódó 2. és 4. blokkban az aktReszleg nem változik meg. A 2. blokkban még nem tudjuk a fa aktuális 1. szintjét hozzáfűzni a szöveghez, hiszen a létszám csak a belső ciklusban felépülő kollekciótól kérhető el utólag. Szükséges némi késleltetés, hiszen a szöveg összefűzése és lényegesen egyszerűbb (mint utólag manipulálni megfelelő helyeken). Az 5. blokkban a csoportváltás algoritmushoz kötődő tevékenységünk nincs.

Az 1. megoldás eredménye

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
Cég (106 fő)
 Accounting (2 fő)
  Shelley Higgins
  William Gietz
 Administration (1 fő)
  Jennifer Whalen
 Executive (3 fő)
  Lex De Haan
  Neena Kochhar
  Steven King
 Finance (6 fő)
  Daniel Faviet
  Ismael Sciarra
  John Chen
  Jose Manuel Urman
  Luis Popp
  Nancy Greenberg
 Human Resources (1 fő)
  Susan Mavris
 IT (5 fő)
  Alexander Hunold
  Bruce Ernst
  David Austin
  Diana Lorentz
  Valli Pataballa
 Marketing (2 fő)
  Michael Hartstein
  Pat Fay
 Public Relations (1 fő)
  Hermann Baer
 Purchasing (6 fő)
  Alexander Khoo
  Den Raphaely
  Guy Himuro
  Karen Colmenares
  Shelli Baida
  Sigal Tobias
 Sales (34 fő)
  Alberto Errazuriz
  Allan McEwen
  Alyssa Hutton
  Amit Banda
  Charles Johnson
  Christopher Olsen
  Clara Vishney
  Danielle Greene
  David Bernstein
  David Lee
  Eleni Zlotkey
  Elizabeth Bates
  Ellen Abel
  Gerald Cambrault
  Harrison Bloom
  Jack Livingston
  Janette King
  John Russell
  Jonathon Taylor
  Karen Partners
  Lindsey Smith
  Lisa Ozer
  Louise Doran
  Mattea Marvins
  Nanette Cambrault
  Oliver Tuvault
  Patrick Sully
  Peter Hall
  Peter Tucker
  Sarath Sewall
  Sundar Ande
  Sundita Kumar
  Tayler Fox
  William Smith
 Shipping (45 fő)
  Adam Fripp
  Alana Walsh
  Alexis Bull
  Anthony Cabrio
  Britney Everett
  Curtis Davies
  Donald OConnell
  Douglas Grant
  Girard Geoni
  Hazel Philtanker
  Irene Mikkilineni
  James Landry
  James Marlow
  Jason Mallin
  Jean Fleaur
  Jennifer Dilly
  John Seo
  Joshua Patel
  Julia Dellinger
  Julia Nayer
  Kelly Chung
  Kevin Feeney
  Kevin Mourgos
  Ki Gee
  Laura Bissot
  Martha Sullivan
  Matthew Weiss
  Michael Rogers
  Mozhe Atkinson
  Nandita Sarchand
  Payam Kaufling
  Peter Vargas
  Randall Matos
  Randall Perkins
  Renske Ladwig
  Samuel McCain
  Sarah Bell
  Shanta Vollman
  Stephen Stiles
  Steven Markle
  Timothy Gates
  TJ Olson
  Trenna Rajs
  Vance Jones
  Winston Taylor

2. megoldás

A faKeszit2() függvénynél alkalmazkodunk ahhoz, hogy a JTree vizuális komponenshez DefaultTreeModel observable típusú modell szükséges, így ezzel térünk vissza (faModell). A fa csomópontjai DefaultMutableTreeNode osztályú objektumok lesznek, amelyeknek a userObject tulajdonsága szükség esetén manipulálható. Az 1 blokkban beszúrjuk a fa gyökerét (faGyoker), amihez a későbbiekben csatlakozik a fa többi eleme. A 2. blokkban megjegyezzük az aktuális részleget és előkészítjük – megjelenítendő szöveg nélkül – a faReszleg csomópontot. A 3. blokkban fabeli csomópontként a fa 1. szintjén megjelenő részleghez névtelenül hozzáadjuk a fa 2. szintjére kerülő – aktuális részleghez tartozó – alkalmazottak nevét. A 4. blokkban utólag módosítjuk a faReszleg csomópont megjelenítendő szövegét. Az aktuális részleg létszámát itt sem kell külön megszámolni, mert a faReszleg-től elkérhető (getChildCount()). Az 5. blokkban itt sincs különösebb teendőnk. 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public DefaultTreeModel faKeszit2(ArrayList<Alkalmazott> lista) {
  //1
  DefaultMutableTreeNode faGyoker=
    new DefaultMutableTreeNode("Cég ("+lista.size()+" fő)");
  DefaultTreeModel faModell=new DefaultTreeModel(faGyoker);
  int i=0;
  while(i<lista.size()) {
    //2
    String aktReszleg=lista.get(i).getReszleg();
    DefaultMutableTreeNode faReszleg=new DefaultMutableTreeNode();
    while(i<lista.size() && lista.get(i).getReszleg().equals(aktReszleg)) {
      //3
      faReszleg.add(new DefaultMutableTreeNode(lista.get(i).getNev()));
      i++;
    }
    //4
    faReszleg.setUserObject(
      aktReszleg+" ("+faReszleg.getChildCount()+" fő)");
    faGyoker.add(faReszleg);
  }
  //5
  return faModell;
}

A 2. megoldás eredménye

Fát építünk, képernyőkép

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

Attól függően, hogyan jutunk hozzá a megjelenítéshez szükséges adatokhoz, több tanfolyamunkhoz is kapcsolódik a feladat és a modell rétegben mindig másképpen tervezünk és implementálunk:

  • A Java SE szoftverfejlesztő tanfolyam 45-48. óra: Adatbázis-kezelés JDBC alapon, 1. rész alkalmán hagyományos SQL lekérdező utasítást készítünk JDBC környezetben.
  • A Java EE szoftverfejlesztő tanfolyam 25-32. óra: Adatbázis-kezelés JPA alapon alkalommal a perzisztencia szolgáltatásait vetjük be.
  • A Java adatbázis-kezelő tanfolyam 13-16. óra: Konzolos kliensalkalmazás fejlesztése JDBC alapon, 1. rész, 33-36. óra: Grafikus kliensalkalmazás fejlesztése JDBC alapon, 2. rész alkalmain hierarchikus lekérdezéseket használunk.

Ismerkedjünk Lambda-kifejezésekkel!

Lambda-kifejezésA Java 8-tól használhatunk Lambda-kifejezéseket, amivel hatékonyabban, rövidebben és könnyebben valósíthatunk meg tipikus műveleteket.

Korábban általában az eseménykezelést globálisan (interfészek implementálásával), vagy lokálisan (névtelen interfész implementálásával) oldottuk meg, illetve besegítettek adapterek is. Sok- és többféle eseménynél ez a forráskódunk elaprózódásához vezetett, ami nehézkes olvashatóságot és karbantarthatóságot eredményezett.

A lambda-kifejezés egy olyan kódrészlet, amelyben valamihez hozzárendelünk valamit (x -> y), például egy metódus paraméteréhez a végrehajtandó forráskódot. Ehhez építünk a funkcionális interfészekre és a metódus referenciákra (szintén Java 8-tól), illetve a típus kikövetkeztetés képességére is (Java 7-től).

A kiválogatás programozási tételt valósítjuk meg többféle implementációval, felhasználva a Java nyelv újdonságait, és a fentieken kívül még a Stream API-t is.

Adatforrás

Először is kellenek adatok, hiszen azokat dolgozzuk fel. Egy Termek osztályú egyszerű POJO-val dolgozunk, nev és ar tulajdonságokkal, generált konstruktorral, getter metódusokkal és toString()-gel. Az adatforrást biztosító absztrakt Lista ősosztályban a POJO-kból felépítünk egy termekLista nevű generikus listát (például CSV vagy XML fájlból beolvasva az összetartozó adatokat) – listaFeltolt() eljárás – és implementálunk egy univerzálisan használható listaKiir(String uzenet, List termekLista) eljárást is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
abstract class Lista {
  private File xmlFajl=new File("./files/prod.xml");
  private File csvFajl=new File("./files/prod.csv");
  protected ArrayList termekLista=null;

  public Lista() {
    listaFeltolt();
    listaKiir("Összes termék listája", termekLista);
  }
 
  private void listaFeltolt() {
    …
  }

  public void listaKiir(String uzenet, List termekLista) {
    System.out.print(uzenet+" - ");
    if(termekLista.isEmpty()) {
      System.out.println("Nincs ilyen termék.\n");
      return;
    }
    System.out.println(termekLista.size()+" db:");
    termekLista.forEach(termek -> System.out.println(termek));
    System.out.println();
  }
}

 
Örökítünk három utódosztályt a Lista osztályból, amelyek különbözőképpen dolgozzák fel a termekLista-t, bemutatva a fejlődés útját, illetve a rendelkezésre álló lehetőségeket.

Válogassunk a termékek közül négyféleképpen és adjuk vissza azon termékeket, amelyek:

  • limit alatti áron kaphatók,
  • ára limit1 és limit2 közé esik (zárt intervallumban),
  • neve adott szöveggel kezdődik (kis- és nagybetű különbözik),
  • neve adott szöveget tartalmaz (kis- és nagybetű nem különbözik)!

1. változat

Hagyományos megközelítéssel a fentiek megvalósításához külön egy-egy függvény tartozik, ahogyan az alábbiakban látható:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
class Kivalogatas1 extends Lista {

  private List<Termek> termekListaLimitAr1(int limit) {
    List<Termek> lista=new ArrayList<>();
//    for (int i = 0; i < termekLista.size(); i++) //1
//      if(termekLista.get(i).getAr()<limit)
//        lista.add(lista.get(i));
////    Iterator i=termekLista.iterator(); //2
////    while(i.hasNext()) {
////      Termek termek=i.next();
////      if(termek.getAr()<limit)
////        lista.add(termek);
////    }
//////    for (Termek termek : termekLista) //3
//////      if(termek.getAr()<limit)
//////        lista.add(termek);
////////    termekLista.stream(). //4
////////      filter(termek -> termek.getAr()<limit).
////////      forEach(termek -> lista.add(termek));
    lista=termekLista.stream(). //5
      filter(termek -> termek.getAr()<limit).
      collect(Collectors.toList());
    return lista;
  }

  private List<Termek> termekListaLimitAr2(int limit1, int limit2) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> limit1<=termek.getAr() && termek.getAr()<=limit2).
      forEach(termek -> lista.add(termek));
    return lista;
  }
 
  private List<Termek> termekListaSzoveg1(String szoveg) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> termek.getNev().startsWith(szoveg)).
      forEach(termek -> lista.add(termek));
    return lista;
  }

  private List<Termek> termekListaSzoveg2(String szoveg) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> termek.getNev().toLowerCase().
        contains(szoveg.toLowerCase())).
      forEach(termek -> lista.add(termek));
    return lista;
  }

  public void run() {
    listaKiir("Terméklista - ár<10", termekListaLimitAr1(10));
    listaKiir("Terméklista - 10<=ár<=20", termekListaLimitAr2(10, 20));
    listaKiir("Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik)",
      termekListaSzoveg1("Sir"));
    listaKiir("Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik)",
      termekListaSzoveg2("hot"));
  }
}

 
A termekListaLimitAr1() függvényben látható ötféle lehetőség a kiválogatásra a termekLista-ból:

  • //1: hagyományos, index alapú változat,
  • //2: iterátorra közvetlenül építő változat,
  • //3: bejáró ciklus, iterátorra közvetve építő változat,
  • //4: Stream API-ra építő változat, kiválogatás lambda-kifejezéssel (filter), a megmaradó termékekre végrehajtandó forEach művelet lambda kifejezéssel,
  • //5: Stream API-ra építő változat, kiválogatás lambda-kifejezéssel (filter), a megmaradó termékeket összegyűjtő/leképező collect művelettel.

Jól megfigyelhető, hogy négy függvény vázszerkezete megegyezik, és csupán a filter-ben található lambda-kifejezések különböznek. Ez a megoldás meglehetősen redundáns, nem általánosítható, valamint további műveletek megvalósítása további függvények implementálását igényli.

2. változat

Őrizzük meg a négyféle funkciót, sőt tegyük lehetővé, hogy ez tetszőlegesen bővíthető legyen. Használjunk saját generikus, funkcionális Feltetel interfészt saját döntés megvalósítását biztosítani tudó implementálandó teszt() függvénnyel, az alábbiak szerint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
interface Feltetel<T> {
  boolean teszt(T t);  
}

class Kivalogatas2 extends Lista {
 
  private List<Termek> termekListaFeltetel(Feltetel<Termek> feltetel) {
    List<Termek> lista=new ArrayList<>();
//    for (Termek termek : termekLista)
//      if(feltetel.teszt(termek))
//        lista.add(termek);
    termekLista.stream().
      filter(termek -> feltetel.teszt(termek)).
      forEach(termek -> lista.add(termek));
    return lista;
  }
 
  public void run() {
    listaKiir("Terméklista - ár<10", termekListaFeltetel(new Feltetel<Termek>() {
      @Override
      public boolean teszt(Termek t) {
        return t.getAr()<10;
      }
    }));
//    listaKiir("Terméklista - ár<10", termekListaFeltetel((Termek termek) -> termek.getAr()<10));
    listaKiir("Terméklista - 10<=ár<=20",
      termekListaFeltetel((Termek termek) -> 10<=termek.getAr() && termek.getAr()<=20));
    listaKiir("Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().startsWith("Sir")));
    listaKiir("Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().toLowerCase().contains("hot".toLowerCase())));
  }
}

 
A termekListaFeltetel() függvény paramétere a saját Feltetel interfészünket implementáló névtelen osztály példánya, amely felhasználható:

  • //6: ciklusban egyszerű feltételként,
  • //7: Stream API filter műveletében megadott lambda-kifejezésben,
  • //8: a listaKiir() metódusban paraméterként átadva névtelen osztály példányaként,
  • //9-től: a listaKiir() metódusban paraméterként átadva lambda-kifejezésként.

Látszik, hogy többféle kiválogató művelethez egyetlen implementált függvény szükséges. Ez a megoldás már jóval általánosabb.

3. változat

A saját interfész helyett használjuk fel a beépített Predicate generikus, funkcionális interfészt, építve annak test() függvényére az alábbiak szerint:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Kivalogatas3 extends Lista {
 
  private List<Termek> termekListaFeltetel(Predicate<Termek> feltetel) {
    List<Termek> lista=new ArrayList<>();
    termekLista.stream().
      filter(termek -> feltetel.test(termek)).
      forEach(termek -> lista.add(termek));
    return lista;
  }
 
  public void run() {
    listaKiir("Terméklista - ár<10", termekListaFeltetel(new Predicate<Termek>() {
      @Override
      public boolean test(Termek t) {
        return t.getAr()<10;
      }
    }));
    listaKiir("Terméklista - ár<10", termekListaFeltetel(
      (Termek termek) -> termek.getAr()<10));
    listaKiir("Terméklista - 10<=ár<=20",
      termekListaFeltetel((Termek termek) -> 10<=termek.getAr() && termek.getAr()<=20));
    listaKiir("Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().startsWith("Sir")));
    listaKiir("Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik)",
      termekListaFeltetel((Termek termek) -> termek.getNev().toLowerCase().contains("hot".toLowerCase())));
  }
}

 

Belépési pont

Végül következzen a közös belépési pont, amelyben tetszőlegesen aktiválható és tesztelhető mindhárom változat működése:

1
2
3
4
5
6
7
public class Kivalogatas {
  public static void main(String[] args) {
    //new Kivalogatas1().run();
    //new Kivalogatas2().run();
    new Kivalogatas3().run();
  }
}

 

Mit ír ki a program a konzolra?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
Összes termék listája - 77 db:
Chai, 18.0
Chang, 19.0
Aniseed Syrup, 10.0
Chef Anton's Cajun Seasoning, 22.0
Chef Anton's Gumbo Mix, 21.35
Grandma's Boysenberry Spread, 25.0
Uncle Bob's Organic Dried Pears, 30.0
Northwoods Cranberry Sauce, 40.0
Mishi Kobe Niku, 97.0
Ikura, 31.0
Queso Cabrales, 21.0
Queso Manchego La Pastora, 38.0
Konbu, 6.0
Tofu, 23.25
Genen Shouyu, 15.5
Pavlova, 17.45
Alice Mutton, 39.0
Carnarvon Tigers, 62.5
Teatime Chocolate Biscuits, 9.2
Sir Rodney's Marmalade, 81.0
Sir Rodney's Scones, 10.0
Gustaf's Knäckebröd, 21.0
Tunnbröd, 9.0
Guaraná Fantástica, 4.5
NuNuCa Nuß-Nougat-Creme, 14.0
Gumbär Gummibärchen, 31.23
Schoggi Schokolade, 43.9
Rössle Sauerkraut, 45.6
Thüringer Rostbratwurst, 123.79
Nord-Ost Matjeshering, 25.89
Gorgonzola Telino, 12.5
Mascarpone Fabioli, 32.0
Geitost, 2.5
Sasquatch Ale, 14.0
Steeleye Stout, 18.0
Inlagd Sill, 19.0
Gravad lax, 26.0
Côte de Blaye, 263.5
Chartreuse verte, 18.0
Boston Crab Meat, 18.4
Jack's New England Clam Chowder, 9.65
Singaporean Hokkien Fried Mee, 14.0
Ipoh Coffee, 46.0
Gula Malacca, 19.45
Rogede sild, 9.5
Spegesild, 12.0
Zaanse koeken, 9.5
Chocolade, 12.75
Maxilaku, 20.0
Valkoinen suklaa, 16.25
Manjimup Dried Apples, 53.0
Filo Mix, 7.0
Perth Pasties, 32.8
Tourtiere, 7.45
Pâté chinois, 24.0
Gnocchi di nonna Alice, 38.0
Ravioli Angelo, 19.5
Escargots de Bourgogne, 13.25
Raclette Courdavault, 55.0
Camembert Pierrot, 34.0
Sirop d'érable, 28.5
Tarte au sucre, 49.3
Vegie-spread, 43.9
Wimmers gute Semmelknödel, 33.25
Louisiana Fiery Hot Pepper Sauce, 21.05
Louisiana Hot Spiced Okra, 17.0
Laughing Lumberjack Lager, 14.0
Scottish Longbreads, 12.5
Gudbrandsdalsost, 36.0
Outback Lager, 15.0
Flotemysost, 21.5
Mozzarella di Giovanni, 34.8
Röd Kaviar, 15.0
Longlife Tofu, 10.0
Rhönbräu Klosterbier, 7.75
Lakkalikööri, 18.0
Original Frankfurter grüne Soße, 13.0

Terméklista - ár<10 - 11 db:
Konbu, 6.0
Teatime Chocolate Biscuits, 9.2
Tunnbröd, 9.0
Guaraná Fantástica, 4.5
Geitost, 2.5
Jack's New England Clam Chowder, 9.65
Rogede sild, 9.5
Zaanse koeken, 9.5
Filo Mix, 7.0
Tourtiere, 7.45
Rhönbräu Klosterbier, 7.75

Terméklista - 10<=ár<=20 - 29 db:
Chai, 18.0
Chang, 19.0
Aniseed Syrup, 10.0
Genen Shouyu, 15.5
Pavlova, 17.45
Sir Rodney's Scones, 10.0
NuNuCa Nuß-Nougat-Creme, 14.0
Gorgonzola Telino, 12.5
Sasquatch Ale, 14.0
Steeleye Stout, 18.0
Inlagd Sill, 19.0
Chartreuse verte, 18.0
Boston Crab Meat, 18.4
Singaporean Hokkien Fried Mee, 14.0
Gula Malacca, 19.45
Spegesild, 12.0
Chocolade, 12.75
Maxilaku, 20.0
Valkoinen suklaa, 16.25
Ravioli Angelo, 19.5
Escargots de Bourgogne, 13.25
Louisiana Hot Spiced Okra, 17.0
Laughing Lumberjack Lager, 14.0
Scottish Longbreads, 12.5
Outback Lager, 15.0
Röd Kaviar, 15.0
Longlife Tofu, 10.0
Lakkalikööri, 18.0
Original Frankfurter grüne Soße, 13.0

Terméklista - 'Sir'-rel kezdődő terméknevek (kis- és nagybetű különbözik) - 3 db:
Sir Rodney's Marmalade, 81.0
Sir Rodney's Scones, 10.0
Sirop d'érable, 28.5

Terméklista - 'hot'-ot tartalmazó terméknevek (kis- és nagybetű nem különbözik) - 2 db:
Louisiana Fiery Hot Pepper Sauce, 21.05
Louisiana Hot Spiced Okra, 17.0

 
A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java SE szoftverfejlesztő tanfolyam szakmai moduljának 21-24. óra: Objektumorientált programozás 2. rész, 25-28. óra: Objektumorientált programozás 3. rész, valamint a Java EE szoftverfejlesztő tanfolyam szakmai moduljának 9-12. óra: XML feldolgozás alkalmaihoz kötődik.

Kockadobás kliens-szerver alkalmazás

Fejlesszünk elosztott, hálózatos, datagram alapú üzenetküldéssel megvalósított Java alkalmazást!

A kockadobás kliens egyszerre két szabályos dobókockával dob, amit ezerszer megismétel és a dobott számok összegét datagram típusú üzenetküldéssel folyamatosan elküldi a szervernek. A szerver localhost-on fut és egy megadott porton keresztül várja a klienstől. A szerver és a kliens egyaránt szálkezelést alkalmazó konzolos alkalmazás.

A projektben van egy swing GUI-s alkalmazás, amely JFreeChart oszlopdiagramon – folyamatosan frissítve – megjeleníti az összesített adatokat, mindez a szerver üzenetküldésével irányítva (amint beérkezik egy dobott (2-12-ig) összeg).

A kommunikációnak – a lehetőségekhez képes – biztonságosnak és – a hálózati adatforgalmat tekintve – takarékosnak kell lennie! Ennek részeként szükséges egy azonosító és egy egyszerű szabály (protokoll).

Tekintsük át mondatszerűen a szálkezelést használó kliens és szerver kommunikációhoz kötődő feladatait:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Kliens (háttérszálon):
  ciklus 1-től 1000-ig egyesével
    DatagramSocket létrehozása
    dobás1, dobás2, dobásÖsszeg előállítása
    üzenet előállítása //pl.: "12;kocka2#"
    DatagramPacket létrehozása
    üzenet elküldése a szervernek
    visszhang/log kiírása a konzolra
    DatagramSocket lezárása
    várakoztatás
  ciklus vége

Szerver (háttérszálon):
  ciklus(végtelen)
    DatagramSocket létrehozása
    DatagramPacket létrehozása
    üzenet fogadása a klienstől
    DatagramSocket lezárása
    ha(valid az üzenet) akkor
      dobásÖsszeg feldolgozása
      ablak grafikonjának frissítése (GUI szálon)
    elágazás vége
  ciklus vége

 
Ezek működését összefogja egy központi vezérlőosztály és a programok az alábbiak szerint működnek együtt:

A bejegyzéshez tartozó forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java EE szoftverfejlesztő tanfolyam 1-8. óra: Elosztott alkalmazások, webszolgáltatások, szálkezelés, párhuzamosság alkalmához kapcsolódik.

A képernyőről a videó a FlashBack Express programmal, a videóból az animált gif az aconvert.com weboldalon készült.

Denevérek a barlangban

DenevérekÉvekkel ezelőtt hálózatos Java EE esettanulmányt akartunk készíteni Lengyel Borisz kollégával. Ötleteltünk: milyen technológiával és hogyan kommunikáljon egymással a szerver és a kliens(ek). A távoli metódushívás (Remote Method Invocation) mellett döntöttünk és elkészült a denevérek a barlangban projekt, amely evolúciós projektként azóta több változatot is megélt. A felhasználói felület (barlang) betölt néhány képet (denevér kliensek), amelyek a szerver segítségével mozognak.

Ismertetjük a tervezés folyamatát, a kliens és szerver funkcióit részletesen, végül ötleteket adunk a továbbfejlesztésre.

A főbb feladatokat így határoztuk meg:

  • az RMI kommunikációs módszer megismertetése,
  • az RMI szolgáltatás reprezentálása látványos grafikus/swinges klienssel,
  • alternatíva nyújtása a TCP protokoll közvetlenül csatlakozó socket-jére.

Elkészítettük az alábbi osztálydiagramot (persze ez nem az első változat):

Denevérek a barlangban - Osztálydiagram

A szerver és kliens funkcióit megvalósító osztályok/interfészek feladatait így határoztuk meg:

BarlangDenevérInterfész interfész:

  • véletlen 5 és 10 közötti a denevérek száma,
  • méretek a GUI-hoz.

Denevér osztály:

  • megvalósítja az RMI kliens funkciót,
  • JLabel leszármazott, külső képfájlt tölt be (bat.jpg),
  • egyedi azonosítója van,
  • eldönti mozgásának irányát (4) és léptékét (3), mintha ultrahangot adna,
  • a szerver megadja neki, hogy az új helyre elmozdulhat-e,
  • saját magát képes mozgatni.

Pozíció interfész:

  • öröklődik a java.rmi.Remote interfészből,
  • két távolról hívható metódus fejét tartalmazza.

BarlangSzerver osztály:

  • megvalósítja az RMI szerver funkciót,
  • implementálja a Pozíció interfészt,
  • JFrame leszármazott,
  • figyel arra, hogy a denevérek ne mozogjanak ki a barlangból.

BarlangFelület osztály:

  • JFrame leszármazott,
  • GUI az RMI kliensek megjelenítéséhez.

BarlangSzerverTérkép osztály:

  • JPanel leszármazott,
  • GUI a szerveren a kliensek mozgásának követésére.

Ha futtatjuk az elkészült szerver és kliens programot, akkor ezt láthatjuk:

Denevérek a barlangban - animáció

A fejlesztés és tesztelés közben sok-sok továbbfejlesztési ötletet/javaslatot fogalmaztunk meg:

  • háttérkép a barlangról,
  • a háttérkép megvalósíthat labirintust, koordináta-rendszert,
  • átlátszó illetve egyedi képfájlok a denevéreknek,
  • a denevérek mozgásának tetszőleges iránya (360),
  • a denevérek mozgásának egyedi léptéke,
  • a denevérek figyeljenek egymásra (ne ütközzenek össze),
  • a denevérek figyeljenek a környezetükre (ne ütközzenek bele sziklákba, cseppkövekbe),
  • a szerver követheti a denevérek útvonalát,
  • a szerver archiválhat, szerializálhat, készíthet statisztikát.

A bejegyzéshez tartozó – több lépésben továbbfejlesztett – forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java EE szoftverfejlesztő tanfolyam 21-24. óra: RMI alapú kommunikáció alkalmához kapcsolódik.

A képernyőről a videó a FlashBack Express programmal, a videóból az animált gif az aconvert.com weboldalon készült.

Időjárás Budapesten

Talált időjárás Widget

A Widgets constructor – OpenWeatherMap weblapon nézelődve megtetszett ez a Widget:

aktuális időjárás Budapest

Főleg az volt nagyon szimpatikus, hogy milyen egyszerűen beépíthető mindez egy webes/mobil felületre az alábbi JavaScript forráskóddal:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<div id="openweathermap-widget-15"></div>
  window.myWidgetParam ? window.myWidgetParam : window.myWidgetParam = [];
  window.myWidgetParam.push({
    id: 15,
    cityid: '3054643',
    appid: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    units: 'metric',
    containerid: 'openweathermap-widget-15',
  });
  (function() {
    var script = document.createElement('script');
    script.async = true;
    script.charset = "utf-8";
    script.src = "//openweathermap.org/themes/openweathermap/assets/vendor/owm/js/weather-widget-generator.js";
    var s = document.getElementsByTagName('script')[0];
    s.parentNode.insertBefore(script, s);
  })();

Mindössze egy regisztráció szükséges hozzá a Members OpenWeatherMap weboldalon a fenti forráskódba behelyettesítendő API kódért. Az egy sorba ömlesztve kapott forráskódot a Javascript Viewer, Beautifier and Formatter, Editor weblapon formáztam könnyen olvashatóvá.
 

Saját fejlesztés

Kedvet kaptam ezt a funkcionalitást összerakni úgy, hogy a hálózati kommunikációra helyeztem a hangsúlyt.
A nézet réteg ezért igen egyszerű, Java swing felületen, JFrame form-ként varázsolt az alábbiak szerint, mindössze JPanel és JLabel vizuális komponensekből áll. Egy JLabel osztályú komponens képes szöveg és/vagy kép megjelenítésére is.

aktuális időjárás Budapest
 

1. feladat

A modell rétegben tárolt település nevét és a szolgáltatás igénybevételéhez szükséges API kulcsot összerakva a Current weather data – OpenWeatherMap oldal specifikációját követve, megkapjuk az adatok lekérdezéséhez szükséges URL-t:

időjárás API URL
 

2. feladat

A hálózati kapcsolatot felépítve el kell kérni (GET) az URL-ről kapott JSON formátumú adatot és tárolni kell azt a modellben (jsonPuffer). A kivételkezelést nem részleteztem, mert most nem ezen van a hangsúly.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  private void setJsonPuffer() {
    jsonPuffer=new StringBuilder();
    try {
      HttpURLConnection c=
       (HttpURLConnection)(new URL(API_URL_KEY)).openConnection();
      c.setRequestMethod("GET");
      c.connect();
      InputStream is=c.getInputStream();
      BufferedReader br=new BufferedReader(new InputStreamReader(is));
      String line;
      while((line=br.readLine())!=null)
        jsonPuffer.append(line+"\r\n");
      is.close();
      c.disconnect();
    }
    catch(MalformedURLException e) {
      System.out.println("Hiba: JSON URL...");
    }
    catch(ProtocolException e) {
      System.out.println("Hiba: http, https, ftp...");
    }
    catch(IOException e) {
      System.out.println("Hiba: JSON parser...");
    }
  }

 
A jsonPuffer objektum ezt tartalmazza:

IdojarasJSON

A könnyen átlátható formátumot a JSON FORMATTER & VALIDATOR weblapon állítottam elő.
 

3. feladat

A JSON-t fel kell dolgozni és a különböző adatokat formázni/konvertálni kell, alkalmazkodva a megjelenítés igényeihez (például hőmérséklet Celsius fokban egész számra kerekítve, szélsebesség egytizedes pontossággal, hónap neve angolul, szükségesek a megfelelő mértékegységek). Külön gondoskodni kell arról, hogy az aktuális időjárást szimbolizáló ikonhoz (képként külön letöltve) is hozzájussunk, mert az API csupán az útvonalát jelentő URL-ből csak a fájl nevét (azonosítóját) adja meg. A kivételkezelést itt sem fejtettem ki.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
  private void jsonProcessing() {
    try {
      JSONObject json=new JSONObject(jsonPuffer.toString());
      name=json.getString("name"); //település
      country=json.getJSONObject("sys").getString("country"); //ország
      weatherDescription=json.getJSONArray("weather").
        getJSONObject(0).getString("description"); //leírás
      JSONObject main=json.getJSONObject("main");
      temp=(int)Math.round(main.getDouble("temp")-273.15); //hőmérséklet
      wind=Double.parseDouble(new DecimalFormat("#,#").format(
        json.getJSONObject("wind").getDouble("speed"))); //szélsebesség
      humidity=main.getInt("humidity"); //páratartalom
      pressure=main.getInt("pressure"); //légnyomás
      dateTime=new SimpleDateFormat("hh:mm MMM dd", Locale.ENGLISH).
        format(new Date(json.getLong("dt")*1000)); //dátum és idő
      URL weatherIconURL=new URL("http://openweathermap.org/img/w/"+
        json.getJSONArray("weather").getJSONObject(0).
        getString("icon")+".png"); //ikon      
      weatherIcon = ImageIO.read(weatherIconURL);
    }
    catch(JSONException e) {
      System.out.println("Hiba: JSON elemzése");
    }  
    catch(MalformedURLException e) {
      System.out.println("Hiba: icon URL...");
    }
    catch(IOException e) {
      System.out.println("Hiba: icon...");
    }
  }

 

4. feladat

Végül a modelltől elkért adatokkal frissíteni kell a nézetet.

1
2
3
4
5
6
7
8
9
10
11
  private void revalidateData() {
    lbNameCountry.setText(modell.getOWMNameCountry());
    lbWeatherDescription.setText(modell.getWeatherDescription());
    lbWeatherIdIcon.setText("");
    lbWeatherIdIcon.setIcon(new ImageIcon(modell.getWeatherIcon()));
    lbTemp.setText(modell.getOWMTemp());
    lbWind.setText(modell.getOWMWind());
    lbHumidity.setText(modell.getOWMHumidity());
    lbPressure.setText(modell.getOWMPressure());
    lbDateTime.setText(modell.getOWMDateTime());
  }

 

Az eredmény

IdojarasBudapest
 


IdojarasLondon
 
IdojarasHouston
 
IdojarasTokyo

Aki kedvet kapott, annak többféle API is rendelkezésére áll, dokumentációval és példákkal együtt a https://openweathermap.org/api weboldalon. Kísérletezni bátran szabad, illetve érdemes megnézni és értelmezni azokat az adatokat, amiket JSON formátumban visszakapunk, de ehhez a feladathoz nem volt rájuk szükség.

A bejegyzéshez tartozó teljes forráskódot ILIAS e-learning tananyagban tesszük elérhetővé tanfolyamaink résztvevői számára.

A feladat a Java EE szoftverfejlesztő tanfolyam 9-12. óra: XML feldolgozás és 13-16. óra: JSON feldolgozás alkalmaihoz kapcsolódik.

Hogyan értékeljük az online vizsgafeladatot?

Tanfolyamaink követelményeinek teljesítéséhez több online tesztet kell kitölteni és egy komplex, online vizsgafeladatot kell megoldani.

A feladatspecifikáció mindig részletes, maximum 1 db A4-es oldal terjedelmű, folyó szövegben felsorolásokat is tartalmaz és szándékosan nincsenek benne ábrák. Törekszünk az egyértelmű megfogalmazása, de hagyunk mozgásteret egyéni értelmezésre is, amit – megfelelő indoklással – elfogadhatunk. Az online vizsgafeladat megoldásához bármilyen segédeszközt lehet használni.

Az online vizsgafeladat megoldásának tervezésére, implementálására, tesztelésére és dokumentálására és határidőre való feltöltésére körülbelül egy hét áll rendelkezésre. Közben online konzultációt biztosítunk, ahol megbeszéljük az ezzel kapcsolatos kérdéseket és rávezető (nem konkrét) segítséget biztosítunk.

vizsgafeladat értékelése

Figyelembe vett szempontok az online vizsgafeladat értékelése során

  • Objektumorientált szemléletmód alkalmazása
  • MVC architektúrális tervezési minta alkalmazása
  • Logikus MVC szeparáció
  • Egyértelműen elhatárolódó felelősségi kör: a modell, a nézet és a vezérlő azt és csak azt oldja meg, amit, ahogyan, amikor, ahányszor kell
  • Adatbázis-kapcsolatért felelős rész szeparációja
  • Vezérlésért felelős rész szeparációja
  • Megjelenítésért felelős rész szeparációja
  • MVC kommunikációs irányok betartása, megfelelő adatkonverzió
  • Szükség esetén singleton és factory típusú tervezési minta alkalmazása
  • Adatbázis-kapcsolat megfelelő menedzselése, nyitás, zárás, kivételkezelés
  • Szükséges adatbázis-karbantartó (CRUD) művelek megfelelő megvalósítása
  • Specifikáció pontos értelmezése
  • Specifikáció pontos megvalósítása
  • Specifikáció alapján tesztelés megvalósítása
  • Megfelelő GUI komponensek alkalmazása, elhelyezése, paraméterezése, kommuniká­ciója, eseménykezelése
  • Adatbázis olvasása során a keletkező eredménytábla és/vagy kivételobjektum megfelelően jut el a nézet réteghez
  • Modellvezérelt fejlesztés elveinek alkalmazása
  • Szükség esetén POJO és ezek adatszerkezeteinek konstrukciós és szelekciós műve­letei
  • Eseménykezelés logikus működésének megtervezése és megvalósítása
  • Extrém tesztadatokkal való hibakeresés, tesztelés
  • Felesleges forráskód-részletek nincsenek
  • Szintaktikai és/vagy szemantikai hibák nincsenek (Java, SQL, HQL oldalon egyaránt)
  • Projekt megfelelő elnevezése és szerkezete
  • Logikus és konvencióknak megfelelő elnevezések következetes alkalmazása
  • Algoritmusban, folyamatokban, saját modellekben való eligazodás, alkalmazkodás ké­pessége, ezek szintjei és megvalósulása
  • Szükséges programozási tételek felismerése, megvalósításuk, összeépítésük
  • Logikus gondolkodás és feladatmegoldás szintjei és alkalmazásuk
  • Hatékonysági szempontok ismerete és alkalmazása

Az online vizsgafeladatot – a tanfolyamot záró 53-56. óra: Összefoglalás alkalommal – közösen, részletesen meg is beszéljük: lépések, rétegek, funkciók, ellenőrzési/tesztelési lehetőségek, hibakeresés, tipikus problémák a megoldás során.

vizsgafeladat értékelése