Haladó Java kurzus

Általános linkek

Tesztelés (JUnit)

  1. A témakör feladataihoz általában
  2. Készítsd el a Fibonacci-függvény rekurzív és iteratív implementációját, és teszteld őket néhány konkrét értékre.
  3. Teszteld, hogy az a metódus, amelynek törzse egy végtelen ciklust tartalmaz, elszáll (nem terminál rögzített, hosszú időtartam alatt).
  4. Teszteld, hogy ha egy metódusban a nullával való osztás, illetve tömb túlindexelése szerepel, akkor a megfelelő kivételek tényleg kiváltódnak. (Tehát az az elvárt működés, hogy a kivétel kiváltódik.)
  5. Készíts egy Pont osztályt, amelynek adj néhány jellemző műveletet, pl. eltolás. Mindegyik teszteset elején vedd fel az adott pontot (@Before), és teszteld mindegyik műveletet.
  6. Készíts három osztályt: a Posta osztály kezdetben két Postafiok példányt kap meg. A Posta osztály valogat metódusa egy Level példányt kap, és kézbesíti: a páros címűeket az első, a páratlan címűeket a második postafiókba (fogad metódus). A tesztelő küldjön el néhány levelet, és ellenőrizze, hogy a megfelelő helyre érkeznek-e.
  7. Az előző tesztelőt alakítsd át viselkedési tesztelővé, hogy a Posta osztályt önmagában, a Postafiok használata nélkül tesztelje. Készíts két Postafiok típusú mock-ot és ellenőrizd, hogy a megfelelő leveleket kapják meg. mockito
  8. Készíts egy teljesen egyszerű "Helló világ" programot. Teszteld, hogy tényleg azt írja-e ki a program. Ehhez szükséges a sztenderd kimenet átirányítása.
  9. A tesztelőt Javából is meg lehet hajtani. Tedd ezt meg, és írd ki először a sikeres, majd a sikertelen tesztesetek neveit.
  10. Az egységtesztelésre egy másik lehetőség, ha nem konkrét elvárt értékeket adunk meg, hanem valamilyen feltétel teljesülését próbáljuk ki sok, véletlenszerűen választott adattal. Erre jellemzően nem a JUnit eszközt használjuk.

Hibakeresés, program futásának vizsgálata (debugging)

A hibakereséshez a fenti Posta feladatot módosítjuk. Most már nem egyetlen Posta példányunk van, hanem tetszőlegesen sok állomás. Mindegyik postának van egy szöveges címe (tegyük fel, hogy mindegyik különböző), és tetszőlegesen sok postafiókot tartalmaz. Az állomások eltárolják, hogy adott című állomásra küldött leveleket melyik másik postának kell továbbítani. Ilyen továbbítási bejegyzésből is tetszőleges számú lehet mindegyik postán. Ha egy levél eléri a célállomását, a fenti feladat szerint kézbesítse az állomás, különben pedig dobja el. A levelek jegyezzék meg, hogy melyik állomásokon utaztak át.

Az alábbi lépéseket, bár mind meg lehet őket tenni parancssorból is, a kényelem kedvéért csak fejlesztőkörnyezetben hajtjuk végre.

  1. Olvasd be a fájlok tartalmát, és írd ki egy fájlba mindegyik levélről, milyen utat járt be.
  2. Keress olyan konfigurációt, amelyre rosszul működik a program.
  3. Ha a levél nem továbbítható és nem is kézbesíthető, a program váltson ki egy kivételt. Ha a levél túl sokáig utazik, egy másik fajta kivételt.
  4. Készíts olyan programot, amely ellenőrzi egy konfigurációról, hogy helyes-e (sosem működik rosszul) és teljes-e (mindenhonnan eljuthatnak-e a levelek mindenhová).
  5. Használd a debuggert szükség szerint a tesztelés során is.

Felsorolási típus

  1. Készítsd el a Varos felsorolási típust.
  2. Írd ki a Varos elemeit sorban.
  3. Készítsd el a HetNapja felsorolási típust.
  4. A levél most már ne közvetlenül a címét tartalmazza, hanem a címzett városát. A levélen legyen rajta a nap is: a postára feladáskor paraméterként kelljen megadni, hogy melyik napon történt a feladás. Amikor a levél a következő postaállomásra utazik, lépjen egy napot.

Lambda függvény

  1. Készíts különböző névtelen függvényeket. Készítsd el őket rövidebb és hosszabb alakjaikban is. Próbáld ki, mennyivel gyorsabb, ha primitív típussal dolgozol. Néhány a legfontosabb lambda-interfészek közül (az első kivételével a java.util.function csomag osztályai):
  2. Készíts olyan lambdát, amely többször meghívva a pozitív egész számokat adja ki.
  3. Készíts olyan lambdát, amely többször kiírja a beérkező számokat.
  4. Készíts faktoriális lambdát.
  5. Készíts Fibonacci lambdát.
  6. Készíts olyan lambdát, amely két Map<String, Integer> paramétert kap, és ilyet is állít elő; az eredményben az adott szöveghez hozzárendelt érték az eredetiek összege legyen.
  7. Készíts olyan lambdát, amely megadja egy számról, hogy prím-e.
  8. Készíts olyan lambdát, amely a prímszámokat állítja elő sorban. (Ennek kell, hogy legyen állapota is.)
  9. Készíts olyan lambdát, amely a sík pontjait állítja elő (valamilyen) sorrend szerint.
  10. Készíts olyan lambdát, amely egy Supplier-t kap, és önmaga is Supplier: először a “belső” Supplier első elemét, aztán az első két elemét, aztán az első három elemét stb. adja ki.
  11. Készíts olyan lambdát, amely paraméterként egy BiFunction-t kap, és olyan BiFunction az eredménye, amely az eredetivel megegyező működésű, de fordítva vannak a paraméterei.
  12. Készíts olyan lambdát, amely két IntUnaryOperator (matematikai értelemben vett) kompozícióját képzi. Azaz, a paraméterei két IntUnaryOperator, és ilyennel is tér vissza: a két paraméter kompozíciójával.
  13. Készíts olyan lambdát, amely két függvényt kap (ezek Integer értéket állítanak elő), és olyan predikátumot ad ki, amely akkor ad igaz értéket, ha a predikátum paraméterére az első függvényt alkalmazva nagyobb szám adódik, mint a második paramétert alkalmazva. A predikátum és a függvények bemenő paramétere legyen valamilyen rögzített típus, pl. String.
  14. Az Arrays.sort művelet második paraméterként kaphat egy lambdát, ami az összehasonlító rendezés feltételét írja le. Rendezd a parancssori paramétereket…

Folyam (stream)

A Stream osztály segítségével sokszor tömör megoldások adhatók. A legtöbbjük lambdákat használ. Cheat sheet.

Három fázis: a folyam elkészül, dolgozunk vele, majd felhasználjuk.

  1. Elkészítés.
  2. Átalakítás: map, mapTo..., filter, limit, skip, sorted
  3. Felhasználás: a Collectors műveletei segítségével. Ezeket a műveleteket (a JUnit assert-jeihez és a Mockito műveleteihez hasonlóan) gyakran import static importáljuk.

A műveletek, ha nem biztos, hogy van eredményük (pl. find), Optional<T>-t adnak vissza. Ez kevésbé veszélyes, mint ha null lenne ilyen esetekben az eredmény.

Az alábbi feladatok megoldása során, ahol csak lehet, használj folyamokat és lambdákat.

  1. Egy fájl mindegyik sorához fűzz hozzá egy rögzített szöveget, és az eredményt írd ki egy másik fájlba.
  2. Készíts lambdát, amely egy Scanner-t használ fel, és olyan folyamot készít, amely a Scanner-ből kiolvasott szavakat adja vissza, illetve, miután elfogytak, null-t.
  3. Egy fájlban mindegyik sor egy pont két Descartes-koordinátáját tartalmazza.
  4. Készíts folyamot a nemnegatív számokból: 0..Integer.MAX_VALUE. Add őket össze.
  5. Egy fájlban egész számok vannak. Azokból, amelyek értéke legalább 2, collect segítségével készíts Map-et, amelyben a legkisebb prímosztóhoz lesznek rendelve a számokból képezett halmazok.
  6. Készíts eszközt, amit az alábbi módon lehet használni (st a Scannerből érkező tokenek folyama, tipp itt):

    withScanner("test.txt", st -> {
        st.forEach(System.out::println);
    });
  7. Próbáld ki a Map-be a Java 8-ban bekerült default metódusokat.
  8. Azokba a postafiókokba, amelyek üresek, tegyél be (közvetlenül) egy levelet.
  9. Csoportosítsd a(z összes posta összes postafiókjába) beérkezett leveleket aszerint, hogy melyik nap érkeztek meg.

Generikus típusok

Hasznos anyag a típusparaméterek megszorításairól. Egy másik jó leírás.

  1. Készítsünk egy BiMap adatszerkezetet, ami kulcs és érték szerint is rendezve tárol kulcs-érték párokat. Két típusparamétere van, a kulcsok típusa és az értékek típusa. Implementációját megvalósíthatjuk két TreeMap-pel.
  2. Legyen egy statikus create metódus, ami akkor használható, ha a létrehozott BiMap mindkét paramétere Comparable. A létrehozott BiMap az alapértelmezett összehasonlítást fogja használni.
  3. Készítsünk egy másik create metódust, ami két Comparator objektumot kap, egyet a kulcsokra, egyet az értékekre. A létrehozott BiMap ezekkel végezze el az összehasonlítást.
  4. Készítsünk “kulcs-érték-pár beszúrás”, “érték keresése kulcs alapján”, “kulcs keresése érték alapján” metódusokat.
  5. Készítsünk egy metódust, ami két listát kap, az egyik paramétere a kulcstípus, vagy annak egy altípusa, a másiké az értéktípus vagy annak egy altípusa. Ha a két lista egyező hosszú, akkor végigmegy rajtuk és berakja őket a BiMap-be.
  6. Teszteljük a programot, adjunk meg olyan tesztesetet, amikor egy altípus listáját akarjuk beszúrni a BiMap-be.
  7. Készítsünk genetikus algoritmust alkalmazó metódust, amely a következőképpen működik. A kiemelten szedett nevek a metódus paraméterei lesznek; az egyedek típusa típusparaméter.
    1. Először populationCount darab egyedet készít a createRandomEntity() többszöri meghívásával.
    2. Ezután két (véletlenszerűen kiválasztott) egyedre meghívja a doCrossover(Entity e1, Entity e2) metódust. Ezt crossoverCount alkalommal ismétli.
    3. Ezután minden egyedet megváltoztat mutationProbability valószínűséggel, a mutateEntity(Entity e) metódus segítségével. (Általában a mutationProbability értéke viszonylag alacsony, pl. 0.1%.)
    4. A létrejött egyedeknek kiszámolja a fitneszértékét: calculateFitness(Entity e). Ezek közül a legjobb pruneCount darab egyedet tartja meg, a többi helyére új egyedeket generál a createRandomEntity() segítségével.
    5. A fenti lépések egy új generációt állítanak elő. Összesen generationCount darab generációváltást tesz meg, majd a populáció legnagyobb fitneszű egyedével tér vissza.
    1. Keressünk közelítő megoldást a hátizsák-problémára genetikus algoritmus segítségével.

Szálkezelés

  1. Készíts olyan metódust, amely egyszámjátékot játszik az alábbiak szerint.
    1. Szinkronizáció nélkül kivétel váltódik ki.
  2. Készíts olyan applyAssoc metódust, amely egy T→T lambdát és T típusú értékek egy tömbjét kapja meg. Feltételezve, hogy a lambda asszociatív függvényt ír le, számítsd ki, milyen eredményt ad az értékekre alkalmazva (pl. [1,2,3,4] a (n,m) -> n + m lambdával a 10 értéket adja). Feltételezzük, hogy van legalább egy elem a tömbben.
  3. Adott n-re készíts az Executors.newFixedThreadPool(n) hívás segítségével egy eszközt, amely egyszerre legfeljebb n tevékenységet hajt végre n szálon.
    1. Adj át sok tevékenységet a pool-nak, mindegyik várjon véletlenszerű ideig. A Future-jeik segítségével írd ki másodpercenként, hogy melyik van készen.
    2. Készíts saját pool implementációt szálak segítségével.
      • Az n szál mellett szükség lesz egy vezérlőre, ami kiosztja a feladatokat, és a szálak ennek jelzik, ha felszabadultak.

Önelemzés (reflection), annotációk

  1. Készíts metódust, amely ellenőrzi, hogy a paraméterben kapott osztály mindegyik adattagja private láthatóságú-e.
  2. Készíts @Author annotációt típusokhoz, amely a típus készítőjének nevét írja le.
    1. Készíts metódust, amely megkapja a típus nevét szövegesen, és kiírja a készítő nevét (ha az annotáció szerepel a típuson).
    2. Az annotáció legyen alkalmazható metódusokra is.
      1. Készíts metódust, amely megkapja a típus nevét szövegesen, és kiírja a típus azon metódusainak nevét, amelyeknek más a készítője, mint a típusé.
    3. Az annotáció legyen többszörözhető (@Repeatable).
      1. A fenti metódusok működjenek akkor is, ha egy szerző szerepel, és akkor is, ha több.
  3. Készíts @Date annotációt, amelyet metódusok kaphatnak meg.
    1. Készíts metódust, amely paraméterként kapott nevű osztály metódusai közül megkeresi azokat, amelyek rendelkeznek az annotációval, és paraméter nélküliek. Ezek közül hívd meg azokat, amelyek egy (paraméterben kapott) dátumnál nem régebbiek.
  4. Készíts @ParameterFor annotációt, amely metódusra alkalmazható. A metódus valamilyen értékek tömbjét adja vissza. Az annotáció kap egy szöveget, ami ugyanabban az osztályban egy metódus neve; ez a metódus olyan típusú paramétert vár, mint amilyen értékek a másik metódus visszatérési értékének tömbjében szerepelnek. Az annotáció legyen többszörözhető.
    1. Készíts metódust, amely paraméterként kapott nevű osztály metódusai közül megkeresi azokat, amelyek rendelkeznek az annotációval. Az annotációban szereplő nevű metódust hívd meg sorban azokkal a paraméterekkel, amelyeket az annotált metódus visszatérési értékének tömbjében szerepelnek.

Fejlett fájlkezelés

A java.nio (“New I/O”) csomagban sok érdekes eszköz található meg.

  1. Az első parancssori paraméter egy fájlnév, ezután pozíciók (long) és értékek (byte) párjai jönnek. Ellenőrizd, hogy a fájl megadott pozíciói a megadott értékeket tartalmazzák-e.
  2. A zip fájlok szerkezete ismert (pl. ennek a leírásnak a 4.3.7. fejezete tartalmazza). Tömöríts be tetszőleges fájlokat, majd az előállt zip fájlt megvizsgálva írd ki az alábbiakat. Tipp: a ByteBuffer alapértelmezés szerint MSB bájtsorrendet használ, mint a Java általában; a zipből való megfelelő olvasáshoz meg kell hívni rá a .order(ByteOrder.LITTLE_ENDIAN) műveletet.
  3. Figyeld meg egy megadott nevű könyvtár fájljainak megváltozásait: minden változáskor jegyezd fel a fájl új tartalmát. A felhasználó a sztenderd bemeneten küldhesse el egy fájl nevét és egy verziószámot; a program írja vissza a fájlba a kiválasztott korábbi tartalmat.
  4. Japán ismerősöd küldött egy egysoros üzenetet. Csak annyi ismert az üzenetről, hogy szerepel benne a karakter. Találd ki, milyen kódolást használhatott, és mi az üzenet tartalma.

Reguláris kifejezések

  1. Tüntesd el egy szövegből a bezárójelezett részeket. Feltehető, hogy a zárójelek nem tartalmaznak zárójeleket.
  2. Készíts morzekódra és -ról konvertáló programot. Kódtáblázat itt érhető el.
  3. Adott néhány karakter és két fájl. Add meg azoknak a soroknak a sorszámát a két fájlból, amelyek azonosak; annyi eltérés engedhető meg, hogy a megadott karakterek esetén nem számít, kis- vagy nagybetűsek-e.

Osztálybetöltés (nem része a számonkérésnek)

  1. Készíts olyan programot, amelyik egy osztály több különböző változatát képes betölteni (természetesen különböző osztálybetöltő-példányok segítségével; az osztálybetöltéshez használt interfész ne változzon). Lehessen példányt készíteni bármelyik betöltött változatból.
  2. Készíts olyan programot, amelyik egy könyvtár változásait figyeli, és ha abban megjelenik egy új .class fájl, akkor betölti belőle az osztályt, és készít belőle egy példányt. A program a sztenderd bemenetről sorokat olvas be; minden beolvasott sorral meghívja mindegyik betöltött példány processLine metódusát.
  3. Készíts minimális alkalmazásszervert, amely egy Socket-en keresztül várja a bejövő kapcsolatokat. A szerver legyen képes alkalmazásokat (osztályokat) betölteni. Amikor egy böngésző kapcsolódik, elküldi a szerverünknek az első sorban a kérés típusát (általában GET vagy POST) és a lekért URL-t. A szerver hívja meg a betöltött alkalmazások acceptsURL metódusát az URL-lel; az első alkalmazás, amelyik igaz értéket ad vissza, fogja kezelni a kérést. Hívd meg az alkalmazás doGet vagy doPost metódusát a kérés típusától függően; ez előállítja a kiszolgálandó tartalmat, amelyet a megfelelő fejléccel együtt küldj vissza a böngészőnek. Ha egyik alkalmazás sem kezeli a kérést, akkor küldj vissza egy elutasító választ.
  4. Próbáld ki az URLClassLoader osztályt. Ennek segítségével pl. fájlokból és a netről kényelmesen be lehet tölteni osztályokat.