A következő címkéjű bejegyzések mutatása: Java. Összes bejegyzés megjelenítése
A következő címkéjű bejegyzések mutatása: Java. Összes bejegyzés megjelenítése

2017. március 26., vasárnap

Jenkins 2 pipeline maven alapú verziózással

Felhasznált technológiák: Jenkins 2, Maven 3.3.9, Nexus 2.14, SonarQube 6.3

Az alábbi cikkben egy általam összerakott egyedi megoldást szeretnék bemutatni, ami lehetővé teszi a Jenkins build szám és a maven verziószámok egységes használatát. Remélem mindenki számára hasznos információkat tartalmaz majd ez a bejegyzés. :)

Az erőforrásaimat mostanság eléggé lekötik az ALM és DevOps szemlélet gyakorlatba való átültetésének lehetőségei, ezért a blogolásra kicsit kevesebb időm jutott, de ami késik nem múlik :) Az elmúlt időszakban több Continuous Integration projekt is sikeresen záródott, így kellő tapasztalat halmozódott fel az újabb irányok és igények implementálásához is. A mostani cikkemben arról fogok írni, hogy a Jenkins 2 pipeline-t miként integráltam össze a maven verzió kezelésével, de előtte egy kis visszatekintéssel kezdjünk. 

Jenkins 1 esetén amikor egy pipeline-t akartunk definiálni, akkor a Build Pipeline vagy a Delivery Pipeline pluginra támaszkodhattunk és a pipeline minden lépését egy-egy külön job-ban tudtuk meghatározni és triggerekkel összekötve építettük fel a láncot. Nem beszélve arról, hogy minden lépéshez egy külön job-ot kellett létrehozni, sok helyen könnyen elrontható volt a pipeline és időigényes is volt az összerakása. Egyszóval valóban megérett egy újragondolásra.




Ezekből a tapasztalatokból született meg a Jenkins 2, ami beépítve támogatja a groovy szkripttel létrehozható pipeline mechanizmust, azaz 1 job-on belül a teljes pipeline-t megírhatjuk, ami a gyakorlatban nagyon hatékony implementációt teszt lehetővé. A Blue Ocen UI plugin segítségével pedig egy modern felületet varázsolhatunk a pipeline köré. Létrehoztam egy pipeline-t ami a következő lépéseket tartalmazza: Build, Unit Test, Store Artifact, Code Coverage, Static Code Check, Deploy és Performance Test.


A maven verziózásnál 2 típust különböztetünk meg, a SNAPSHOT (fejlesztés alatti) és a RELEASE (kiadható) állapotokat. Ezért a pipeline indításakor is kiválaszthatjuk, hogy snapshot vagy release pipeline-t kívánunk indítani, továbbá release választása esetén opcionálisan megadhatjuk azt is, hogy mi legyen a következő snapshot verziószám, amennyiben el akarnánk térni az alapértelmezettől.


A build gombra kattinva elindul a pipleline, természetesen a folyamat indítható ütemezetten (pl. minden nap éjfélkor) vagy fejlesztő kommit hatására is, azáltal hogy a Jenkins poll-ozza az SCM-et.

A Build fázisban Maven segítségével buildelődik a forráskód és létrejönnek a telepíthető build termékek (ear, war, jar), majd a Unit Test fázisban lefutnak a JUnit alapú tesztek. Ezután elérkezünk a Store Artifact fázishoz, ahol is egy artifact repositoryban, esetünkben Nexus-ban eltárolódnak a build termékek

Amennyiben a pipeline indításakor a snapshot-ot választottuk ki, a build termékek a snapshot repositoryban fognak eltárolódni, a groupid, artifactid és a verziószámnak megfelelő könyvtár struktúrában. Ahogy az alábbi ábrán is látható a Jenkins build szám megegyezik a snapshot verzióval (0.0.13-SNAPSHOT) és snapshot sorszámával (3), ami esetünkben a 0.0.13-SNAPSHOT-3.


Ha a pipeline indításakor a release-t választjuk ki, a build termékek a release repositoryban fognak eltárolódni és taggelődik a forráskód bázis is, azaz ehhez az állapothoz a későbbiekben bármikor visszatérhetünk. Release készítésekor egyébként a release:prepre és release:perform fog lefutni. Látható, hogy release készítés után a következő snapshot verzió egy inkrementált vagy az általunk megadott sorszámot kapja majd.


A pipeline következő 2 fázisa a kód lefedettség mérés és a statikus kód ellenőrzés párhuzamosan fog futni. A statikus kód ellenőrzéshez a SonarQube eszközt szoktam javasolni, ahova az aktuális verziószám szintén delegálásra kerül, így a mérési eredmények beazonosítása egyértelmű.


A következő lépésben egy szkript automatikusan feltelepíti a tesztkörnyezetre az alkalmazást, majd performancia teszteket futtatunk úgy hogy a dynaTrace a háttérben méri az alkalmazás dinamikus jellemzőit mint például a válaszidőt és a performancia hot spotokat. A mérés lefutása után a dynatrace session és minden eredmény adat (unit teszt, kódlefedettség) a Jenkins eredmény oldalán megtekinthető és bármikor vissza kereshető.

Összefoglalva nézzük meg milyen előnyökkel kecsegtet az így kialakított pipeline:

  • Jenkins 2 segítségével egy szabványos és hatékony pipeline megvalósítás
  • Maven release-elés verziószáma megegyezik a Jenkins build számával
  • Ha parancssorból (Jenkins nélkül) futtatjuk a maven release kiadását, akkor sem csúsznak el a verziószámok egy későbbi Jenkins pipeline indításakor
  • A verziószám végig kíséri az összes lépést
  • A Jenkins eredmény oldalán minden lépés eredmény fájlja és naplói elérhetők

A végére hagytam egy hasznos lehetőséget, méghozzá egy általam létrehozott Deployer jobot, aminek a segítségével a Nexus repository-ban, egy korábban eltárolt release vagy snapshot app verzió egy gombnyomással telepíthető az általunk kiválaszott környezetre. Ez a kis job nagyon hasznos az élesítések végrehajtásához is, ugyanis a megfelelő jogosultságokkal rendelkező adminisztrátorok gyorsan elvégezhetik ezt a feladatot.




A legközelebbi cikkben a környezetenként eltérő konfiguráció kezelésről és az adatbázis automatizált verziózásáról fogok írni, ami szintén szerves része a kialakított platfromnak.


2016. szeptember 1., csütörtök

dynaTrace - Memória szivárgás beazonosítása

Az előző cikk folytatásaként, egy éles rendszerben előfordult memória szivárgáson keresztül fogom megmutatni, hogy hogyan is kell a dynaTrace-szel a gyökér okokig eljutni. Általában minden azzal kezdődik, hogy a dynaTrace-től kapunk egy riasztást, hogy kevés a szabad memória vagy túl sokat GC-zik az appserver, majd a Process Health dashboardon validáljuk, hogy valóban ez a helyzet. Restart után készítettem néhány Memory Consumption Trending dump-ot és beazonosítottam, hogy mely osztályok példány száma növekszik. Esetünkben a BasifTopup osztályból 669.195 darab volt a heap-en.


Ezután a System Profile/Sensors menüpont alatt a BasifTopup osztályra felhelyeztem egy memória szenzort. A dynaTrace hot sensor placement feature segítségével, külön újraindítás nélkül, futásidőben aktiválódott ez a szenzor szabály.


Selective Memory Dump-ok készítésével látható váltak az objektum allokációk, ahonnan pedig továbbfúrtam azokhoz a PurePath-okhoz, ahol ezek az objektum példányok létrejöttek.

A PurePath hívási láncnál megjelent, hogy a BasifTopup példányai, a registerResultInUnitOfWork() metódus hívásnál keletkeznek nagy számban. Innen két kattintással a bytecode visszafejtésével elém tárult a forráskód, ami alapján megtettem a javaslataimat a probléma elhárítása érdekében.


Hát nem egyszerű és nagyszerű? :))


2016. augusztus 6., szombat

dynaTrace - Memória dump típusok

Gyorsan meg akarod találni a memória szivárgás (memory leak) valódi okát? Használj dynaTrace-t! Attól függően, hogy milyen információra van szükséged, 3 féle heap dump típus készítése közül választhatsz:

1. Deep Memory Leak Analysis:

Ez a hagyományos heap dump-nak feleltethető meg, azaz az objektumok kapcsolatai, a referenciák mentén lekövethető. A heap méretétől függően több percbe is telhet a dump létrehozása, mialatt a JVM mással nem foglalkozik, így napközben való készítése, éles környezetben csak indokolt esetben javasolt. Az elkészített dump utófeldolgozását a dynaTrace Analysis szerver fogja elvégezni. Gyakori kérdés, de a dynaTrace-es dump-ot más eszközzel (Eclipse Memory AnalyzerIBM HA) nem tudjuk beolvasni.


2. Memory Consumption Trending:

Ezzel a dump-pal megkapjuk, hogy melyik osztályból hány példány volt a memóriában. Az előnye, hogy néhány másodperc alatt lefut, azaz nem akasztja meg az alkalmazás futását, így éles környezetben, akár napközben is használható. Ha memória fogyást tapasztalunk, készítünk pl. 10 percenként pár dumpot és összehasonlíthatjuk őket, hogy mely osztályok példányszáma növekszik folyamatosan. 


3. Selective Memory Dump:

Miután a Memory Consumption Trending dump-ok készítésével meghatároztuk azokat az osztályokat, amelyek példányszáma növekszik, ezen az osztályokra memória szenzort tehetünk fel, majd a Selective Memory Dump készítésével megtekinthetjük, hogy a PurePath hívási láncban ezek az objketumok melyik metódusnál jönnek létre. Nagyon hasznos feature!


A folytatásban pedig egy esettanulmányt fogok megmutatni, hogy a gyakorlatban hogyan kell egy memory leak gyökér okát beazonosítani a dynaTrace-szel.

2016. július 24., vasárnap

EMA and Plumbr - Memory leak hunting

Hogy ne mindig csak a dynaTrace-ről legyen szó, most az Eclipse Memory Analyzer-rel és a Plumbr eszközökkel mutatom meg, hogyan lehet megoldni egy memória szivárgást. Egy web-alkalmazásnál tapasztaltam azt a jelenséget, hogy néhány undeploy-deploy művelet hatására java.lang.OutOfMemoryError: PermGen space hiba keletkezett. A szokásostól eltérően, most nem a dynaTrace-t vettem elő (mert az adott szerveren nem volt DT licence) hanem más alternatívákat.

Az OutOfMemory hatására készült egy heap dump, amit betöltöttem az EMA eszközbe. Itt van egy jó tutorial az EMA-val való ClassLoading leak megtaláláshoz. A megoldáshoz nem kellett sokat keresgélni, mert már a beépített Leak Suspect Report is kimutatta, hogy hol lesz a probléma, mindenesetre azért kicsit magam is megnézegettem a dumpot, ahol bebizonyosodott, hogy az oracle jdbc driver miatt, az osztálybetöltő nem tudott felszabadulni.


Már rég kiakartam próbálni a java agnet alapú Plumbr free verzióját, ezért ez a memory leak pont kapóra jött nekem. A Plumbr nem az utólag elkészített heap dump-ot analizálja, hanem real-time figyeli az alkalmazásunkat és ha egy anomáliát talál, azonnal kijelzi a probléma okát és még megoldási javaslatokkal is ellát minket. Ezt már nevezem!


Itt, itt meg itt találtam néhány leírást ami pontosan illeszkedett erre a problémára, röviden tehát a leak-et az okozta, hogy az adatbázis driver nem került sohasem felszabadításra egy WildFly bug miatt, amit az osztálybetöltési policy módosításával sem sikerült orvosolni. Végül, más megoldás hiányában - egy nem szép - de működő programozott megoldást próbáltam ki amivel megoldódott a hibajelenség.


Jut eszembe, a Java 8-ban már nincs is perm gen space helyette van metaspace. :)

2015. november 19., csütörtök

Jenkins - Pipeline Version Updater plugin

Nemrég blogoltam a Jenkins Delivery Pipeline használatának előnyeiről és arról, hogy a pipeline verziószáma kiemelt szereppel bír, mivel végig kíséri az összes lépést és benne lesz a létrejött telepítési egységek nevében is. A verziószám generálását többféleképpen el lehet végezni, most csak egy lehetséges megoldást szeretnék bemutatni, ami éppen illeszkedett ahhoz környezethez ahol a kialakított CI rendszert használták.

Az ötletem, aminek a megvalósításához fejlesztettem egy Jenkins plugin-t az volt, hogy a pipeline verziószáma két részből álljon, az első rész a legfrissebb SCM TAG értéke legyen (pl.: v2.0.0) a második rész pedig egy 1-ről induló, buildenként növekvő szám, ami egy új TAG érkezésekor mindig 1 értékre inicializálódik. Azért, hogy az SCM tag értékénél a verziószám pattern-t kötelező legyen betartani, szerver oldali GIT HOOK-ot alkalmaztam, hasonlóan ahhoz mint amit ebben a cikkben írtam.

Az elkészült plugin forráskódját megnyitottam és feltöltöttem a github-ra. A használatához csak be kell pipálni a Pipeline Version Updater checkbox-ot a Build Environments szekció alatt, ahogy az alábbi kép is mutatja.


Most pedig nézzük meg gyakorlatban is a plugin verziózásának működését, az alábbi pipeline részlet alapján. A legfrissebb scm tag a v2.0.0 volt így az első build indításakor a v2.0.0.1 lett a pipeline verziószám. A következő build indításakor sem érkezett újabb scm tag, ezért a pipeline verziószám v2.0.0.2 értéket vette fel.


Ezután a vezető fejlesztő meg-tag-geli a forráskódot a v2.0.1 verziószámmal, ahogy az alábbi kép is mutatja.


A következő buildnél a v2.0.1.1 értéket veszi fel a verziószám, a legfrissebb scm tag és az 1-ről induló build number alapján.


Ez lenne az általam kialakított pipeline verziószámozós rendszer. Ha kérdésed lenne ezzel kapcsolatban, esetleg Ti más utat választottatok a verziózáshoz szívesen veszem az ötleteket.

Stay tuned...!

2015. november 2., hétfő

dynaTrace - JDBC batch insert

A teljesítménybeli problémák egy része konfigurációs (jvm, pool) hiányosságokra vezethető vissza, azonban a legtöbb esetben mégis maga az alkalmazás kódja vagy az egyes API-k ill. keretrendszerek nem megfelelő használata okozza a legsúlyosabb veszteségeket. Már az egyetemen is mondogatták, hogy nagy mennyiségű adat beszúrását kötegelve végezzük el, mert a DML műveletek (insert, update, delete) egyenként történő végrehajtása, külön-külön adatbázis feldolgozást és magasabb hálózati overhead-et is jelent.

Az alábbi program 20.000 JDBC insert műveletet fog lefuttatni egyenkénti (tehát nem kötegelt) végrehajtással.


Ahogy a dynaTrace PurePath dashleten is láthatjuk, a válaszidő 13.4 másodperc volt, 97%-ban Input-Output művelettel a lekérdezések végrehajtásánál.


Módosítsuk a programot és nézzük meg, hogy mennyi javulást érünk el, ha az insert műveleteket kötegelve hajtjuk végre.


Az alábbi képen látható eredmény magáért beszél, sikerült a válaszidőt 1.8 másodpercre lecsökkenteni minimális forráskód módosítással. 


Érdemes arra odafigyelni, hogy a nagyméretű kötegek esetén időnként (minden x insert után) futtassunk le egy JDBC batch insertet, különben OutOfMemorError keletkezhet amennyiben a heap memória megtelik!

2015. október 12., hétfő

Jenkins - Delivery Pipeline, Build Pipeline

Pár éve már írtam a Jenkins-ről és a folyamatos integráció (CI = Continuous integration) lényegéről, most pedig a KEKKH-s CI bevezetési projektem kapcsán ennek a "tovább fejlesztéséről" szeretnék pár gondolatot megosztani. 

Kisebb projekteknél talán még elfogadható lehet, ha minden részfeladatot egy Jenkins job-ba sűrítünk, azonban hamar a következő igények merülhetnek fel:

  • Vizuálisan is szeretnénk lekövetni, hogy éppen melyik lépés futtatásánál tart a folyamat
  • Sebesség optimalizálás céljából, szeretnénk beazonosítani a lassú lépéseket
  • Vannak olyan lépések amelyeket párhuzamosan is lehetne futtatni
  • Párhuzamos lépések esetén csak akkor akarunk tovább haladni ha minden párhuzamosan indított lépés sikeres volt (join)
  • Egy adott lépéstől kezdve szeretnénk indítani a pipeline-t. (pl. a terheléses teszteléstől)

Ezen igények megvalósítására két Jenkins plugint is bevethetünk: Build Pipeline és a Delivery Pipeline. Én az utóbbit választottam, mert a build pipeline a párhuzamosan join-olt jobokat nem jeleníti meg jól.

Az alábbi képernyőképen az NLR alkalmazáshoz kialakított CI Pipeline látható. A dobozkák magukért beszélnek, így csak röviden írnám le mi is történik az egyes lépéseknél. A Build lépés során a forráskód lefordul és létrejönnek a telepíthető modulok, alkalmazások. A Unit Test lépésnél kiderül, hogy vajon funkcionálisan is minden jól működik. A Static Code Analysis lépésnél elindul egy SonarQube alapú kódellenőrzés, amivel párhuzamosan Cobertura alapú kódlefedettség (Code Coverage) és JBoss Tattletale alapú könyvtár függőség elemzés (JAR Analysis) is történik. Amikor ezen lépések mindegyike sikeresen befejeződött a modulokat ki kell tenni (Deploy) az alkalmazás szervere, majd egy gyors Smoke tesztet futtatni a telepítés sikerességének ellenőrzéséhez. Ezután következhet a dynaTrace-szel integrált JMeter alapú teljesítmény tesztek futtatása (Performance Test), majd a dynaTrace-szel integrált Selenium alapú UI tesztek (Browser Test) futtatása. Amennyiben minden lépés sikeres volt, akkor az utolsó, Store Artifact lépésnél a telepített modulok bekerülnek a Nexus repositoryba.


A pipeline verziószáma - esetünkben a v1.0.0.3 - végig kíséri az összes lépést, benne lesz a buildelt telepítési modulok nevében, a SonarQube analizálásnál, a performancia és böngésző tesztek során létrejött dynaTrace-es session-ök nevében és  persze a Nexus repositoryban eltárolt moduloknál is.

Ha valamelyik lépés hibára futna (pl. volt 1 hibás unit tesztünk), akkor az adott doboz piros színnel jelenik meg és a következő lépések már nem futnak le, hibásnak jelölve ezzel a teljes pipeline-t. A hiba fogalmát a SonarQube és a dynaTrace-es Jenkins build breaker funkcionalitással mi magunk határozhatjuk meg. Ha például volt legalább 1 blocker prioritású SonarQube hiba, vagy ha volt olyan JDBC lekérdezés a perf. teszt során ami 8 másodpercnél tovább futott, hibásnak jelölve az adott lépést, megállíthatjuk a pipeline lépéseinek további futását.

Szóval, jó dolgokat lehet ezekkel a pluginokkal csinálni, használjátok és ha van kérdésetek, ne tartsátok magatokban...

2015. október 5., hétfő

dynaTrace Redmine Action Plugin

Már pár hónapja a KEKKH-ban tevékenykedek CIdynaTrace illetve GWT-s tesztelés témákban, ahol is a feladataim között volt a dynaTrace és a Redmine issue kezelő integrálása. Az elkészített plugin lényege, hogy amikor egy dynaTrace-es riasztás történik (pl. egy webservice túl lassan válaszol) akkor a dynaTrace egy új issue-t vesz fel a Redmine-ba, kitöltve az issue-t a hiba körülményeihez tartozó releváns információkkal (system profile, prioritás, riasztás oka). Amennyiben a probléma folyamatosan jelen van természetesen nem szemeteli teli az issue kezelőt ugyanazzal a problémával.


Az elkészült plugin és a konfigurációs dokumentációja szabadon elérhető ezen a linken. A plugin tesztelve lett Redmine 2.x és 3.x-es verziókkal is. Eredményes monitorozást mindenkinek!

2015. június 9., kedd

dynaTrace Nagios Plugin

A KH Bank igényei alapján nemrég fejlesztettem egy dynaTrace plugin-t, ami a dynaTrace által rögzített riasztásokat, a Nagios ill. klónjai számára (OP5, Icinga) képes automatikusan továbbítani. A pluginnál felhasználtam a jsendnsca API-t, ami elvégzi a Passive Check-ek elküldését a Nagios NSCA add-on számára.


Az elkészült plugin-t felraktam a dynaTrace community portálra, ahonnan szabadon letölthető a dynaTrace és a Nagios oldali konfigurációs dokumentációval együtt. Jó monitorozást!

2015. április 2., csütörtök

Java SE 8 - Stream API további példák

A Stream API lehetőségeit érdemes minél jobban kihasználni, ha már Java SE 8-on fejlesztünk, ezért a múltkori cikket folytatva nézzünk meg még néhány használati példát.

Az alábbi kód megjeleníti a legtöbb karaktert tartalmazó String-et a listából. Látható, hogy milyen elegánsan használható a Comparator funkcionális interfész comparing() statikus metódusa mivel nem kell mindkét összehasonlítandó objektumot definiálni, csak azt kell meghatározni egy lambda kifejezéssel, hogy miszerint végezzük el az összehasonlítást. A max() hívás visszatérési értéke egy Optional<T> típusú objektum, amitől lekérdezhetjük az eredményt egy get() hívással vagy akár azt is hogy volt-e egyáltalán valamilyen eredmény: isPresent().
List<string> fruitList = 
 Arrays.asList("Alma","BaNÁN","KÖRTE","szILVA","BaracKok","kiwi");

String max=fruitList.stream()
 .max(Comparator.comparing(s -> s.length())).get()

System.out.println(max); //BaracKok
A következő példa a listában található sztringeket összefűzi az első 3 karakterük felhasználásával. A sztringeket 3 karakteres formára leképező map() már ismerős lehet az előző cikkből, azonban van itt egy újdonság is a reduce(). A reduce segítségével a Collection elemeit egy adott értékre redukálhatjuk le, hasonlóan ahogy a korábban bemutatott max() is működik. A példánál maradva, itt a reduce feladata az lesz, hogy a 3 karakteres sztringeket összefűzze egy String-be.
String concat=fruitList.stream().map(s -> s.substring(0, 3))
 .reduce("",(acc,curr) -> acc+curr);

System.out.println(concat); //AlmBaNKÖRszIBarkiw
A végére hagytam a kedvencemet a flatMap metódust, amivel egy listákat tartalmazó lista elemeit a lehető legelegánsabb módon dolgozhatunk fel. A Java 8 előtti időkben ehhez még 2 for ciklust kellett volna használnunk...
List<List<String>> list = new ArrayList<>();
List<String> sList1 = Arrays.asList("aaa", "bbb", "ccc");
List<String> sList2 = Arrays.asList("ddd", "eee", "fff");
List<String> sList3 = Arrays.asList("ggg", "hhh", "iii");  
list.add(sList1);list.add(sList2);list.add(sList3);

// external iteration
for(List<String> ls: list)
 for(String s: ls)
  System.out.println(s);  

// internal iteration
list.stream().flatMap(l->l.stream())
       .forEach(e->System.out.println(e)); 
Hát ennyi lett volna a Stream API, a folytatás hamarosan következik.

2015. március 3., kedd

Java SE 8 - Stream API bevezető

Legutóbb a Java 8 funkcionális interfészeivel ismerkedtünk meg, most pedig következzen a Stream API. A Java 8 könyvtárakhoz kapcsolódó legnagyobb változások a Collections API-t érintették, többek között megalkották a Stream API-t, amivel a Collection-ben lévő objektumokat - kihasználva a Lambda kifejezések lehetőségeit - most már stream-ként is feldolgozhatunk.

A Stream API előtti időkben ha egy listát akartunk bejárni, akkor külső (external) iterációt alkalmaztunk, ami feleslegesen terjengőssé tette a kódot valamint a párhuzamosított végrehajtásra való átállás esetén az egészet újra kellett írnunk. A Stream API egy olyan fluent megoldást ad a kezünkbe, ahol nem kell külön for ciklusokat definiálnunk, az iteráció (internal) részleteit lekezeli az API.
List<String> fruitList = 
 Arrays.asList("Alma","BaNÁN","KÖRTE","szILVA","BaracKok","kiwi");

// external iteration
long xcount = 0;
for (String str : fruitList) {
 if (str.length() == 4)
  xcount++;
}
System.out.println(xcount); //2

// internal iteration with Stream API
xcount=fruitList.stream().filter(str -> str.length() == 4).count()
System.out.println(xcount); //2
Az fenti kódrészlet megszámolja a 4 karakteres String-eket a Java 8 előtti és a Stream API által bevezetett iteráció használatával. Az internal iterációnál maradva, a stream() hívással elkérjük a listában található objektumok stream-jét, majd a Lambda kifejezést használó intermediate filter() művelettel kiválogatjuk a 4 karaktereseket, végül a count() terminate hívással lekérdezzük ezen elemek darabszámát. Érdemes tudni, hogy az intermediate műveletek csupán konfigurációs (lazy) feladatot látnak el, a tényleges bejárás a terminate (eager) művelet során történik meg.
Set<String> set = fruitList.stream()
 .filter(str -> str.length() != 4)
 .map(str -> str.toUpperCase()).collect(Collectors.toSet());

System.out.println(set); // [BARACKOK, KÖRTE, BANÁN, SZILVA]
A következő példa a nem 4 karakteres Stringeket nagybetűssé alakítja, majd az eredményt egy Set-be teszi. A map() konfigurációs művelet az értékeket egy másik értékre konvertálja át, az esetünkben minden egyes String-et - ami a filter()-ben definiált feltételnek megfelel - nagybetűssé alakít. A collect() hívással megkezdődik a Stream feldolgozása és a megfelelő objektumok bekerülnek az új Set-be.

A folytatásban további példákat fogok mutatni a Stream API használatára. De most legyen egy kis házi feladat: a filter() és a map() milyen funkcionális interfészt használ paraméterként?

2014. december 12., péntek

Java SE 8 - Funkcionális interfészek

Miután megismerkedtünk a Lambda kifejezésekkel, folytassuk az utunkat a funkcionális interfészekkel amiknek a jellemzője, hogy csak egyetlen egy absztrakt metódussal rendelkeznek és a Lambda kifejezések típusát fogják majd meghatározni. A metódus neve bármi lehet, a lényeg hogy a szignatúrája kompatibilis legyen a Lambda kifejezéssel. Funkcionális interfészekkel már korábban is találkozhattunk a Java nyelvben (Runnable, Callable, Comparator), azonban a JDK8-at is kiegészítették jó pár generikus funkcionális interfésszel:
// Predicate<T>
Predicate<Integer> p = x -> x > 5;
System.out.println(p.test(10)); //true

// Consumer<T>
Consumer<Integer> c = i -> System.out.println(i);
c.accept(111); //111

// Function<T,R>
Function<Integer, String> f = i -> i > 10 ? "OK" : "NOT OK";
System.out.println(f.apply(100)); //OK

// Supplier<T>
Supplier<Integer> s = () -> 66;
System.out.println(s.get()); //66

// UnaryOperator<T>
// return használatakor a {}-t ki kell tenni 
UnaryOperator<Boolean> u = (bool) -> {return !bool;};
System.out.println(u.apply(false)); //true

// BinaryOperator<T>
BinaryOperator<Double> b = (Double d1, Double d2) -> d1 + d2;
System.out.println(b.apply(11d, 22d)); //33.0
A fenti kódrészleten látható Predicate funkcionális interfész egyetlen egy absztrakt metódust tartalmaz (test()), ami paraméterként Integer típust vár és a visszatérési értéke boolean. Érdemes megemlíteni, hogy a paraméter típusát nem kötelező definiálni, mivel a Type Inference feature segítségével a java compiler ezt képes kikövetkeztetni.

A beépített funkcionális interfészek mellett akár sajátot is létrehozhatunk és opcionálisan elláthatjuk a @FunctionalInterface annotációval, így a fordító azonnal szól ha esetleg több absztrakt metódust is defniáltunk volna az interface-ben.

A folytatásban a Stream API következik.

2014. december 1., hétfő

Java SE 8 - Lambda kifejezések

A Java SE 7 újdonságai után haladjunk a korral és nézzük meg a Java SE 8 leginkább várt feature-ét, a Lambda kifejezéseket. A Lambda kifejezések és a hozzá kapcsolódó újítások (Stream API, funkcionális interfészek, alapértelmezett metódusok, metódus referenciák) azért is jelentősek, mivel a Java SE 5 verzióban megjelent generikusok és annotációk óta nem volt ilyen nagy változás, ami ennyire átalakította volna a Java nyelv használatát. A lambda kifejezésekkel tisztább, sokkal kifejezőbb és így karbantarthatóbb kódot írhatunk valamint már nyelvi szinten is kihasználhatjuk a több processzoros gépek párhuzamos feldolgozási képességeit. De mik is azok a lambda kifejezések?

A lambda kifejezések olyan névtelen metódusok, amiket ott írunk meg ahol ténylegesen használunk. Egy remek példa erre az anonim osztályokkal megvalósított Swing-es eseménykezelők átírása lambda kifejezések használatával:
Button btn = new Button();

//anonymous inner class
btn.addActionListener(new ActionListener() {
 public void actionPerformed(ActionEvent event) {
  System.out.println("print");
 }
});

//lambda expression
btn.addActionListener(event -> System.out.println("print"));
A lambda kifejezéssel magát a viselkedést, tehát az implementációt adjuk át az addActionListener() metódusnak, nem pedig egy olyan objektumot ami megvalósítja a kívánt interfészt. A fenti példa alapján beazonosítható a Lambda kifejezések szintaktikája, azaz definiáljuk a paramétereket - akár a típus megnevezése nélkül - (event), a nyíl tokent (->) majd az implementációt (System.out.println("print")).

A továbbiakban nézzünk meg néhány mintát a Lambda kifejezésekre. Az első példában egy Comparator-nak adjuk meg az implementációját, majd a 10 és 20 értékre vonatkozólag megjelenítjük az eredményt. A második példa pedig egy argumentum nélküli, több soros implementációval rendelkező Lambda kifejezést mutat.
// sample1
Comparator<Integer> myComparator = (a, b) -> a.compareTo(b) * -1;
System.out.println(myComparator.compare(10, 20));

// sample2
int i = 10;
// i=777; fordítási hiba, mivel az i változó "effectively final"
Runnable r2 = () -> {
 int c = i + 8;
 System.out.println("Adder: " + c);
};
r2.run();
Érdemes megemlíteni, hogy az i változó értékét nem módosíthatjuk annak ellenére, hogy a final módosító nincs kiírva, mivel azt egy lambda kifejezésben is használjuk. (effectively final)

A cikk hamarosan folytatódik, legközelebb a Lambda kifejezésekhez szorosan kapcsolódó funkcionális interfészekről fogok blogolni. Egyébként használja már valaki a Java 8-ast éles fejlesztéshez? :)

2014. november 18., kedd

A Java Standard Edition 7 újdonságai

A munkámból adódóan sok vállalatnál megfordulok és azt látom, hogy a legtöbb helyen még mindig csak a Java 6-os verzióját használják annak ellenére, hogy 2014 március óta már a Java SE 8 is elérhető. Habár a hivatalos roadmap szerint a Java SE 7 publikus frissítései csak 2015 áprilisig várhatóak, érdemes a legfontosabb újdonságokat megismerni, mert azok természetesen a Java SE 8 verzióban is használhatók. Ezekből feature-ökből gyűjtöttem most össze néhányat.

1. Numerikus literáloknál aláhúzásjel használata

Az aláhúzásjelek használatának a célja, hogy megkönnyítse a számok olvashatóságát.
class Numbers {
 public static void main(String args[]) {
  int i = 1_000_000;
  long m = 9_999_999_999_999L;
  double pi = 3.14_15;
 }
}

2. String használata switch szerkezetben

A String equals() metódusával lesz kiértékelve a kifejezés. Érdemes tudni, hogy a Java compiler hatékonyabb bytekódot generál ebben az esetben, mintha az if-else szerkezetet használnánk!
class StringSwitch {
 public static void main(String args[]) {
  String fruit = "apple";
  switch (fruit) {
   case "orange":
    System.out.println("orange");
    break;
   case "apple":
    System.out.println("apple");
    break;
   case "banana":
    System.out.println("banana");
    break;
  }
 }
}

3. Erőforrások automatikus lezárása (try-with-resource)

Az erőforrások automatikusan lezárásra kerülnek, ha a try-with-resource szerkezetben, AutoCloseable vagy a Closeable interfészt implementáló osztályokat használunk.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;

class TryWithResousrce {
 public static void main(String args[]) throws IOException {
  try (BufferedReader br = new BufferedReader(
                           new FileReader("c:/temp/myfile.txt"));
   PrintWriter pw = new PrintWriter(
                    new FileWriter("c:/temp/myfile.txt"))) {
   pw.println("mytext");
   pw.flush();
   System.out.println(br.readLine());
  }
 }
}

4. Multi Catch

A kód duplikáció elkerülése miatt hasznos, amikor ugyanazt a kivétel lekezelő műveletet szeretnénk használni unrelated kivételek esetén, anélkül hogy a közös ősüket kellene elkapnunk.
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

class MultiCatch {
 public static void main(String args[]) {
  try {
   BufferedReader br = 
    new BufferedReader(new FileReader("myfile.txt"));
   Connection con = DriverManager
    .getConnection("jdbc:postgresql://myhost/db","u","p");
  } catch (IOException | SQLException ce) {
   //handle exceptions
  }
 }
}

5. Precise ReThrow

Nem kapunk fordítási hibát annak ellenére, hogy a kivétel lekezelő ágban feldobott e objektum általánosabb, mint a throws-nál deklarált FileNotFoundException, mivel a fordító képes meghatározni, hogy a kivétel aktuális típusa ebben az esetben FileNotFoundException lesz.
import java.io.FileNotFoundException;

public class PreciseReThrow {

 public static void main(String args[]) 
                                throws FileNotFoundException {
  try {
   throw new FileNotFoundException();
  } catch (Exception e) {
   throw e;
  }
 }
}

6. Diamond operátor használata

Az olvashatóság megkönnyítése érdekében, a jobb oldalról elhagyhatjuk a generikus típus paramétereket a <> operátor kiírásával.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class DiamondUsage {
 public static void main(String args[]) {
  List<String> list1 = new ArrayList<>();
  List<Map<Integer,List<Double>>> list2 = new ArrayList<>();
 }
}
Persze a lista koránt sem teljes, érdemes még megismerni a NIO 2, a Concurrency API és a JDBC 4.1 újdonságait is mert hasznosak lehetnek a munkánk során és az 1Z0-804 vizsgán is kérdezhetik! .)

2014. november 7., péntek

Oracle 1Z0-804 vizsga feladatok

Ebben a bejegyzésben összegyűjtöttem néhány feladványt az 1Z0-804 vizsga kérdésekből, amelyek tipikusan jellemzik a feladatokat. A vizsgán 90 feladatot kell megoldani 150 perc alatt, amiből legalább 65%-ot kell teljesíteni! A feladatok egy része a lexikális Java API tudásra épít (pl. milyen konstruktorai vannak a Locale osztálynak) a többi pedig az alábbi példákhoz hasonló mit ír ki program:

1. Enum 
  
public enum Book {
 BEGINNER, INTERMEDIATE;

 static {
  System.out.println("static init block");
 }

 {
  System.out.println("instance initializer");
 }

 Book() {
  System.out.println("constructor");
 }

 public static void main(String... args) {
  System.out.println(Book.BEGINNER);
 }
}

2. Integer
  
public class MyInteger {
 public static void main(String[] args) {
  Integer i1 = 127;
  Integer i2 = 127;
  Integer i3 = 128;
  Integer i4 = 128;
  if (i1 == i2)
   System.out.println("same");
  else
   System.out.println("different");

  if (i3 == i4)
   System.out.println("same");
  else
   System.out.println("different");
 }
}

3. CopyOnWriteArraySet
  
public class MyCopy {
 public static void main(String[] args) {
  Set<String> set = new CopyOnWriteArraySet<String>();
  set.add("2");
  Iterator<String> iter = set.iterator();
  set.add("3");
  while (iter.hasNext()) {
   System.out.print(iter.next() + " ");
  }
 }
}

4. Overload
  
class MyOverLoad {
 private void overload(Object o) {
  System.out.println("Object");
 }

 private void overload(double[] arr) {
  System.out.println("double []");
 }

 private void overload(double arr) {
  System.out.println("double");
 }

 private void overload(int arr) {
  System.out.println("int");
 }

 private void overload() {
  System.out.println("void");
 }

 public static void main(String[] args) {
  new My().overload(null);
 }
}

5. Calendar
  
class MyCalendar {
 public static void main(String[] args) {
  DateFormat df = DateFormat.getDateInstance(DateFormat.LONG, Locale.US);
  Calendar c = Calendar.getInstance();
  c.set(Calendar.YEAR, 2012);
  c.set(Calendar.MONTH, 12);
  c.set(Calendar.DAY_OF_MONTH, 1);
  System.out.println(df.format(c.getTime()));
 }
}

6. TreeSet
  
class MyTreeSet {
 public static void main(String[] args) {
  TreeSet<StringBuilder> treeSetNames = new TreeSet<StringBuilder>();
  treeSetNames.add(new StringBuilder("aaa")); 
  treeSetNames.add(new StringBuilder("bbb"));
  treeSetNames.add(new StringBuilder("ccc"));
  treeSetNames.add(new StringBuilder("ddd"));
  Iterator it = treeSetNames.descendingIterator();
  while (it.hasNext())
   System.out.print(it.next() + ":");
 }
}

és a megoldások:

1. Enum
instance initializer
constructor
instance initializer
constructor
static init block
BEGINNER

2. Integer:
same
different

3. CopyOnWriteArraySet:
2

4. Overload:
double []

5. Calendar:
January 1, 2013

6. TreeSet:
java.lang.ClassCastException

2014. október 30., csütörtök

Oracle Certified Professional, Java SE7 Programmer

Nagyjából fél évvel ezelőtt raktam le az 1Z0-803 vizsgát és akkor úgy döntöttem, hogy még az idén leteszem ennek a folytatását, az 1Z0-804 vizsgát is. Sikerült!


A kérdések jellege nem sokat változott, - ott próbálnak becsapni ahol csak tudnak :) - figyelni kell a részletekre, mivel néha teljesen máshol van a probléma mint ami a feladatból először következne. Viszonylag sok témakört pakoltak ebbe a vizsgába, amivel szerintem sikerült is lefedni a legfontosabb core Java ismereteket: Class Design, Design Patterns, Generics, Collections, String Processing, Exceptions and Assertions, Java IO, Java NIO2, JDBC, Thread, Concurrency, Localization. 

A felkészüléshez Mala Gupta, OCP Java SE 7 Programmer II Certification Guide könyvét olvastam át, ami még nem végleges kiadás, így elég sok hibával találkoztam. Ezenkívül átfutottam Tushar Sharma, OCP Java SE 7 Programmer Exams 1Z0-804 and 1Z0-805 könyvét is, ami szintén tartalmazott jópár elírást. 

Összességében továbbra is azt tudom mondani, hogy bőven megérte a felkészülésre rászánni az időt, mivel helyretett dolgokat és olyan API-kat ismertem meg, amiket eddig nem használtam (pl.:NIO2, Concurrency). Hamarosan belinkelem ide a saját jegyzetemet is hátha hasznos lesz valakinek. Irány a következő Java vizsga! :)

2014. április 22., kedd

Oracle 1Z0-803 vizsga jegyzet

Az Oracle Certified Associate, Java SE 7 Programmer vizsga felkészülés során összeírtam egy rövid jegyzetet és 3 feladványt amit most megosztok veletek is, hátha hasznos lesz valakinek.

- A default package-ben lévő osztályok nem importálhatók
- A main() paramétere vararg is lehet: main(String ...args)
- A hozzáférés és nem hozzáférés módosítók felcserélhetők (pl.: public - static)
- && és || short-circuit operátorok (&, | pedig nem)
- Precedencia táblázat
- A Java Pass By Value-t használ!
- Hozzáférés módosítók (public, protected, default, private)
- Overloading vs Overriding
- Encapsulation, Information hiding
- Polimorfizmus
- A metódus argumentum és a metódus paraméter nem ugyanaz
- A változónevek karakterszáma tetszőleges
- A változónevekben lehetnek számok, de nem a legelső karakter helyen
- A változóneveknél, a speciális karakterek közül csak az aláhúzás és a valuta jel használható, akár a legelső karakter helyen is.
- Lokális változókhoz ill. metódus paraméterekhez nem adható meg hozzáférés módosító
- A vararg az utolsó paraméter lehet és csak egy definiálható metódusonként
- Ha nincs felhasználó által definiált konstruktor, akkor a fordító generál egy paraméter nélkülit
- A String objektum immutable
- A StringBuilder és a StringBuffer mutable
- "ORACLE".substring(2) -> ACLE
- "ORACLE".substring(2,4) -> AC
- A String trim metódusa a kezdeti és a végi white space karaktereket törli csak
- A StringBuilder-nek nincs trim metódusa
- new StringBuilder("0123456").replace(2,3,"ABCD") -> 01ABCD3456
- new StringBuilder("ABC").append("123456",1, 3) -> ABC23
- new StringBuilder("ABC").insert(1,true) -> AtrueBC
- new StringBuilder("ABC").insert(1,"123456",1,3) -> A23BC
- A tömb az egy objektum
- A tömb mérete mindig int típusú és 0 is lehet
- A List<String> list=new ArrayList<>(); is érvényes
- A String class-ban az equals() felül van definiálva, a StringBuilder-ben nincs
- A switch()-ben használható típusok: char, byte, short, int, String, enum
- Az interface-ben minden metódus public és abstract (mégha nincs is kiírva)
- Az interface-ben minden változó public, static, final (mégha nincs is kiírva)
- A finally blokk nem fut le System.exit() hívásnál és fatal error-nál sem
- Ha a catch blokknak van visszatérési értéke a finally blokk akkor is lefut
- Checked vs Unchechked Exceptions, Errors
- invalid ArrayList pozíció esetén -> IndexOutOfBoundsException
- invalid array pozíció esetén -> ArrayIndexOutOfBoundsException
- Ha egy static inicializáló blokkban dobódik bármilyen RuntimeException -> ExceptionInInitializerError
- Rekurzív végtelen hívásánál -> StackOverflowError
- A lokális változók nem kapnak alapértelmezett kezdő értéket
- while (false) {...} vagy for( int i = 0; false; i++) x = 3; -> Fordítási hiba
- A konstruktorok nem öröklődnek
- Short k = 9; Integer i = 9; System.out.println(k == i); // compile error
- 'b'+63+"a" : 161a
- int a = b = c = 100 -> invalid
- byte: -128 to 127

És végül a gyakorláshoz összeírtam három "mit ír ki" jellegű feladványt aminek a megoldását a cikk végén megtaláljátok!

1. Overriding and hiding methods
  
class A {
  int x = 10;
  static int y = 20;

  public void m1() {
    System.out.println("a");
  }

  public static void m2() {
    System.out.println("b");
  }
}

class B extends A {
  int x = 30;
  static int y = 40;

  public void m1() {
    System.out.println("c");
  }

  public static void m2() {
    System.out.println("d");
  }

}

class MyTest {
  public static void main(String[] args) {
    System.out.println(new B().x + ", " + new B().y);
    new B().m1();
    new B().m2();

    A a = new B();
    System.out.println(a.x + ", " + a.y);
    a.m1();
    a.m2();
  }
}

2. Initialization order
  
class AA {

  public AA() {
    System.out.println("AA constructor");
  }

  static int i = 10;
  {
    i = 15;
    System.out.println("AA " + i);
  }
  static {
    System.out.println("AA static " + i);
  }
}

class BB extends AA {

  public BB() {
    System.out.println("BB constructor");
  }

  static {
    i = 45;
    System.out.println("BB static " + i);
  }
  {
    i = 30;
    System.out.println("BB " + i);
  }
}

class MyTest {

  public static void main(String args[]) {
    BB m = new BB();
  }
}

3. Throws
  
import java.io.FileNotFoundException;
import java.io.IOException;

class Super {

  public Super() throws IOException {
  }

  public void m1() throws IOException {

  }
}

class Sub extends Super {

  public Sub()  throws FileNotFoundException{ 
  }

  public void m1() throws Exception {

  }
}

És a válaszok:

1. Overriding and hiding methods

30, 40
c
d
10, 20
c
b

2. Initialization order

AA static 10
BB static 45
AA 15
AA constructor
BB 30
BB constructor


3. Throws

Compile error
throws FileNotFoundException ->  throws Exception
throws Exception ->  throws FileNotFoundException

2014. április 9., szerda

Oracle Certified Associate, Java SE 7 Programmer

A héten sikeresen leraktam az Oracle 1Z0-803 vizsgát, a mai napon pedig megkaptam az Oracle Certified Associate, Java SE 7 Programmer tanúsítványt elektronikus formában. A vizsgán előforduló témakörök az alap Java ismeretekre fókuszáltak, azonban a feladatok többsége tipikusan becsapós volt, mint például ezek a minta feladatok. A vizsgán egyébként 90 kérdésből 150 perc alatt min. 63 %-ot kellett elérni, ami szerintem bőven teljesíthető egy kis készülés után.


A felkészüléshez Mala Gupta - OCA Java SE 7 Programmer I Certification Guide könyvét olvasgattam át és az interneten fellelhető vizsga kérdésekből oldottam meg néhányat. A saját jegyzetemet itt elérhetitek. A felkészülésre fordított idő úgy gondolom megtérült, mivel nemcsak felfrissítettem, de rendszereztem is a Java 7-es ismereteimet. Irány az 1Z0-804!