Java 8 vs Scala Lambdat bytecode tasolle

Java 8 julkaistiin pitkän odottelun jälkeen, ja viimein kieleen saatiin Lambda lausekkeet – sekä niitä hyödyntäviä rajapintoja aikaiseksi. Monessa muussa kielessä Lambdat ovat olleet jo pitkään, ja voidaan ehkä sanoa että Scala popularisoi ne. Jäin miettimään onko Java ja Scala lambdatoteutuksissa eroja bytecodetasolla, molemmat kun ovat käännettäviä kieliä. Törmäsin kiintoisaan artikkeliin:

 

http://www.takipiblog.com/2014/01/16/compiling-lambda-expressions-scala-vs-java-8/

 

Aikanaan kun Scala tehtiin, kieleen lisättiin monenlaisia rakenteita jotka toteutetaan aika rajulla bytecode teurastuksella. Esim. funktio ilman luokkaa ei ole Javassa ollut mahdollinen, joten Scalassa on luotu keinotekoisia luokkia jos johonkin väliin. Lopputulos on kielen osalta hieno ja innovatiivinen mutta bytecode tasolla vähän purkkaviritystä.

Java 8 toteuttaa Lambdat käyttäen Java 7:ssä lisättyä uutta bytecode tason piirrettä nimeltä invokevirtual. Näin vältetään purkkaviritykset väliin, ja funktio voidaan luoda sellaisenaan. Syntyvä bytecode on paljon mutkattomampaa. Suorituskyvystä ei artikkelissa puhuttu mutta voisin arvata että senkin suhteen Java 8 vetää pisteet kotiin.

Eli koska rakastan kovasti Scala kielellä koodailua – se tuntuu raikkaan keväiseltä hengähdykseltä – tässä toivelista Scala piirteille joita pitäisi ottaa kiinni:

– Sama optimointi: Käytetään Java 7 invokedynamic piirteitä

– Samantien voitaisiin toteuttaa vähäisemmät Java 7 Coin piirteet: binääriliteraalit, try-with-resources (AutoCloseable), alaviivat numeroiden ryhmittämiseen (tämä ei ehkä onnistu ollenkaan koska villikortti), jne.

Mainokset

Tykkäätkö erityisesti hitaasta, monimutkaisesta ja vaikeasta? Älä missään nimessä siirry Java 8:aan!

Jep, provosoivaan otsikkoon vähän sisältöä. Olen jo vuoden päivät opiskellut Java 8 version tulevia muutoksia, kyseessä on tosiaan iso pläjäys joka koodaajaa hyödyntävää tavaraa. Ne summautuvat yhteen tehokkuudeksi – monissa paikoissa voi nyt tehdä koodaustyöt siten että koodirivejä syntyy vähemmän – koodi on luettavampaa ja näin ollen kalliita huolimattomuusvirheitä on vähemmän – ja kaiken lisäksi uudella tapaa tehty koodi voi skaalautua moniydinprosessoireissa ihan uudella tapaa. Turha odotella paria vuotta kun kaikesta tästä voi nauttia NYT. Koodaajille tätä ei ole tarpeen paljoa perustella, mutta koko projektin kannalta näkyy jo selvää rahaa kun siirtyy käyttämään fiksumpia uusin piirtein varustettuja alustoja. Jos pyörität vielä Java versioita 5 tai 6, on peiliin katsomisen aika. 😉

Java 1.8 julkaistaan näillä näkymin 18.3. lopullisena versiona. Siitä on jo toimivaa Release Candidate versiota tarjolla. Jokainen kehitysympäristö tukee jo enemmän tai vähemmän sen käyttöä. Ja mikä tärkeintä, Tieturilla on tarjolla heti koulutusta jossa saat kickstartin uusiin piirteisiin ohjatusti ja vinkkejä parhaista käytännöistä ja myös sudenkuopista joita voi tulla vastaan.

Java 8 uudet piirteet (Huhti ja toukokuussa toteutus):
http://www.tieturi.fi/kurssit/kurssi.html?course=85000345

Tärkeimmät tulossa olevat uutuudet ovat tietysti Lambdat, joka toimii kevyenä porttihuumeena, ja sitten Streamit, joissa piilee se kova kama. Sen ohella vietellään kovasti kohden funktionaalista ohjelmointia joka on testattavampaa ja skaalautuvampaa. Vanha kunnon isoisän Javahan alkaa tässä näyttämään ihan Scalalta! Kun soppaan lisätään vielä uusi päivämääräkirjasto, joka on Jodatimen mukaan tehty ja ensimmäistä kertaa alkaa olla kohtuullisen mehukas – on aika painavat syyt päivittää itsensä ja ympäristönsä ASAP.

Tein vähän mallikoodia omaan käyttöön testatakseni piirteitä. Lähtötilanteena oli käydä läpi vähän tietokannastamme löytyvää kurssitarjontaa, ja tehdä siihen erilaisia hakuja. Otin tahallisesti laajan otoksen kaikesta mitä löytyi, joten datan laadussa on hyvin tyypillisiä heikkouksia, mm. puuttuvia kenttiä, ja tein siihen liittyvää perinteistä hyvin rumaa mutta myös hyvin tyypillistä imperatiivista koodia. Kuvaavaa on että koodin monimutkaisuuden vuoksi ehdin tehdä parikymmentä kertaa jonkin virheen joka testatessa kävi ilmi. Huolimattomuusvirheet voivat isommassa projektissa tulla äkkiä hyvin kalliiksi etenkin jos testaus ei ole automatisoitua, kattavaa, säännöllistä  ja nopeaa. Lähdin alunperin testailemaan suorituskykyä, mutta matkalla löytyikin muuta. Katsotaanpa mitä tapahtui.

Lähtötilanne: Kannasta on haettu tauludataa, joka on talletettu JSON olioihin. Pääohjelma lataa ne levyltä, ja prosessoi ne läpi, tulostaen lopuksi käsitellyt tiedot. Pääohjelma on tämän näköinen:

public static void main(String[] args)
    throws IOException, JAXBException {
  List<Course> courses = loadCourses("test.json");
  long begin = System.nanoTime();
  List<Course> validPublicTrainings = filterAndFixCourses(courses);
  long end = System.nanoTime();

  // Print values out to control how it works 
  for (Course course : validPublicTrainings) { 
    System.out.println(course);
  }

  System.out.println("Number of filtered courses: " 
    + validPublicTrainings.size()); 
  System.out.println("Time spent: " + (end - begin) / 1000000.0 
    + "ms"); 
}

Melko yksinkertaista tavaraa, siis. Kaikki mehukas tapahtuu filterAndFixCourses() funktiossa, sen tehtävä on suodattaa kurssit jotka ovat julkisia, valideja, ja pistää ne päivämäärän mukaan järjestykseen. Ongelmia tuottaa, että osassa dataa arvoja puuttuu ja ne ovat null-arvoj (Kaiken tämän voisi tehdä kannassa mutta tämä ei ole kantakoodauksen artikkeli 😉

Tältä näyttää perinteiseen imperatiiviseen tyyliin kirjoitettu varsin ruma ja optimoimaton (mutta myös varsin tyypillinen) esimerkki:

public static List<Course> filterAndFixCourses(
    List<Course> courseList) {
  // Get all trainings that are public and valid
  List<Course> validPublicTrainings = new ArrayList<>();
  for (Course course : courseList) {
    if (course.getType() != null && course.getType() == 3 
        && course.getStatus() != null && course.getStatus() == 3) {
      if (course.getBeginDate() != null 
          && course.getEndDate() != null) {
        // Fix dates by adding +10 hours, 36000ms
        course.setBeginDate(new Date(
           course.getBeginDate().getTime() + 36000));
        course.setEndDate(new Date(
           course.getEndDate().getTime() + 36000));
      }
     validPublicTrainings.add(course);
    }
  }

  // Sort by begindate and enddate
  // Descending order for dates, meaning that newest dates go first
  // Null values go to bottom of list
  Comparator<Course> c = new Comparator<Course>() {
    @Override
      public int compare(Course o1, Course o2) {
        if (o1.getBeginDate() == null 
            && o2.getBeginDate() == null) {
          return 0;
        } else if (o1.getBeginDate() == null) {
          return 1;
        } else if (o2.getBeginDate() == null) {
          return -1;
        } else if (o1.getEndDate() == null 
            && o2.getEndDate() == null) {
          return 0;
        } else if (o1.getEndDate() == null) {
          return 1;
    } else if (o2.getEndDate() == null) {
      return -1;
    } else if (o1.getBeginDate().before(o2.getBeginDate())) {
      return 1;
    } else if (o1.getBeginDate().after(o2.getBeginDate())) {
      return -1;
    } else if (o1.getEndDate().before(o2.getEndDate())) {
      return 1;
    } else if (o1.getEndDate().after(o2.getEndDate())) {
      return -1;
    } else {
      return 0;
    }
   }
  };
  Collections.sort(validPublicTrainings, c);
  return validPublicTrainings;
}

Rumaa? Jep, elämä on. Tuota voisi stilisoida moninkin tavoin mutta totuus on, että tuollaista koodia löytyy tuotantojärjestelmistä ympäri maailman. Kuten Venkat Subramaniam sanoi luennoissaan, kun tuota kirjoittelee päivän, tuntee olonsa likaiseksi ja kaipaa suihkua.

Miltäpä tämä sitten näyttäisi Java 8 avulla? Kokeillaanpa uudestaan. Ei mikromanageroida ulkopuolelta mitä pitäisi tehdä, vaan käytetään Lambdoja ja Streameja, ja annetaan algoritmit suoraan Streamille, näin:

public static List<Course> filterAndFixCourses(
    List<Course> courseList) {
  return courseList.stream()
    .filter(c -> c.getType() != null)
    .filter(c -> c.getType() == 3)
    .filter(c -> c.getStatus() != null)
    .filter(c -> c.getStatus() == 3)
    .sorted((o1, o2) -> {
       if (o1.getBeginDate() == null 
            && o2.getBeginDate() == null) {
         return 0;
       } else if (o1.getBeginDate() == null) {
         return 1;
       } else if (o2.getBeginDate() == null) {
         return -1;
       } else if (o1.getEndDate() == null 
           && o2.getEndDate() == null) {
         return 0;
       } else if (o1.getEndDate() == null) {
         return 1;
       } else if (o2.getEndDate() == null) {
         return -1;
       } else if (o1.getBeginDate().before(o2.getBeginDate())) {
         return 1;
       } else if (o1.getBeginDate().after(o2.getBeginDate())) {
         return -1;
       } else if (o1.getEndDate().before(o2.getEndDate())) {
         return 1;
       } else if (o1.getEndDate().after(o2.getEndDate())) {
         return -1;
       } else {
         return 0;
       }
     })
     .map((c) -> fixDate(c))
     .collect(Collectors.toList());
}

Parempi? Mmmhmm… Tavallaan, imperatiivisen tyylin mikromanagerointi on poistunut, ja voisi sanoa että koodi on alkuun jopa ymmärrettävämpää. Mutta monimutkaiset asiat ovat edelleen monimutkaisia ja sotkuisia, vaikeasti ymmärrettäviä. Mutta meillä on käsissämme funktionaalinen ohjelmointityyli. Mitäpä jos.. käyttäisimme funktioita hoitamaan likaiset työt? Sama uusiksi funktioreferensseillä Java 8 tapaan:

public static List<Course> filterAndFixCourses(
    List<Course> courseList) {
  return courseList.stream()
    .filter(App::typeThreeIsValid)
    .filter(App::statusThreeIsValid)
    .sorted(App::sortByDate)
    .map(App::fixDate)
    .collect(Collectors.toList());
}

Aaaaaivan! Koodirivien määrä rupsahti 53:sta kuuteen – tai jos tarkkoja ollaan niin yhteen, tuohan on kaikki samaa return lauseketta eli one-lineri. Tietysti osa koodia siirtyi vain funktioihin joka aina kannattaisi tehdä. Kuten yllä näkyy, hyvä idea nimetä funktiot dokumentatiiviseen tapaan, vähän kuin hyvissä yksikkötesteissä.

Mutta luettavuuden voisi väittää parantuneen, kun ylläolevaa koodia lukee, sitä voi jopa ymmärtää. Kenties jopa kuukauden päästä palatessaan asiaan.

Mitäpä tapahtui suorituskyvylle? Kellotin vähän millisekunteja. Imperatiivinen koodiesimerkki rusikoi 100 000 tietueen massaa n. 27ms verran. Funktionaalinen Java 8 versio käytti samaan aikaa n. 54ms. Hetki, miten tässä näin kävi? Eikö tämän pitänyt olla nopeampaa? No ei. Skaalautuvampaa, kyllä. Nopeushävikkiä voi syntyä esim. immutable-olioiden kopioinnista, ja toisaalta tässä ovat operaatiot niin nopeita että ylimääräisten askelien overhead jää suuremmaksi kuin mikään tehosäästö.

Mutta, tämänhän piti skaalautua mukavasti moniydinprosessoreille. Tässä koneessani on niitä 8, ajetaanpa testi uusiksi tällä koodilla:

// Best version of Java 8 features with parallel execution
 public static List<Course> filterAndFixCourses(
    List<Course> courseList) {
    return courseList.parallelStream()
       .filter(App::typeThreeIsValid)
       .filter(App::statusThreeIsValid)
       .sorted(App::sortByDate)
       .map(App::fixDate)
       .collect(Collectors.toList());
}

Kuten huomaat, ainoa mitä piti muuttaa oli korvata stream() parallelStream() kutsulla. Nyt taustalla käytetään Fork&Joinia ja hajoitetaan ja hallitaan operaatioita, ajaen ne rinnakkain. Mitenkäs suuri nopeushyöty saadaan tästä?

Turpiin tuli. Nyt aikaa meni 90ms, eli lähes neljä kertaa hitaampaa. Mikä meni vikaan?

Kuten aiemminkin, overhead iski. Thread poolin pystytys ja töiden skedulointi vie suoritusaikaa. Tässä esimerkissä laskennallinen tehtävä on edelleen triviaalin nopea ja siksi sen tekeminen rinnakkain ei hyödytä tarpeeksi. Eli jos nopeutta hakee, tämä kannattaisi edelleen tehdä mutkattomammin imperatiivisesti. Ytimien lisääminenkään tuskin auttaisi, koska overhead.

Mutta.. entäpä jos työ olisi raskaampaa.. Nykyään dataa haetaan usein verkon yli, tai tietokannasta, useissa erissä. Edellämainitun koodin ongelmana alkoi olemaan muistin riittäminen jos tietueita olisi vaikka miljoona. Looginen ratkaisu olisi hakea sitä sopivammissa viipaleissa kannasta tai verkon yli.

Entä jos siinä menisi esim. 10ms per kierros aikaa? Simuloidaan koodiin pientä verkkoviivettä, näin:

try {
 Thread.sleep(10);
 } catch (InterruptedException ex) { }

Joka kierroksella menee hetki aikaa, joko laskenta on raskasta tai haetaan jotain verkon yli tai kannasta. 18793 tehtävää (viive on vain suodatetuilla alkioilla), 10ms viivettä per tehtävä. Nyt tilanne muuttuu, imperatiiviselta koodilta kestää: 194 045ms tehdä työ. Ei kovin suuri yllätys, 100k kertaa 10 on miljoona. Entä miten suoriutuu parallel Stream Java 8 versio? Suoritusaika on 25272ms, eli imperatiivisen koodin aika jaettuna ytimien määrällä plus overhead. Kahdeksan ytimen kone ajaa operaatiot lähes 8x nopeammin, ja skaalautuu jos ytimiä onkin 16, 32, jne..

Yhteenveto: Java 8 uudet piirteet ovat uutta arsenaalia ohjelmoijan työkalupakissa. Jo selkeyden ja ongelmanratkaisun välineinä niitä ei ole varaa olla käyttämättä. Ne eivät automaattisesti ole ylivoimaisia suoritusnopeudessa – mutta tehtävissä joissa rinnakkaisuudesta voi olla hyötyä, ne tuovat ylivertaista ja ohjelmoijalle helppoa skaalautuvuutta. Ja vielä on yksi syy lisää opiskella tämä NYT: Ellet opiskele, pian voi pamahtaa silmille Java-koodia jota et enää pysty ymmärtämään. Muistatko miten kävi Java 5 kohdalla? 😉

Pari tärppiä vielä loppuun. Streamien debuggaus voi olla hankalaa – ja tuki vielä huteraa kehitysvälineissä. Apuun tulee peek()-funktio, jolla voit kurkata prosessoinnin sisään. Esim. näin:

return courseList.parallelStream()
  .filter(App::typeThreeIsValid)
  .filter(App::statusThreeIsValid)
  .sorted(App::sortByDate)
  .peek(System.out::println)
  .map(App::fixDate)
  .collect(Collectors.toList());

Ei siis hullumpaa uudistusta. Matkaa on vielä Scalan hienouksiin mutta nyt meillä on jotain käyttökelpoista joka taas inspiroi koodaamista vuosiksi eteenpäin.

JFokus 2014 loppuraportti

Osallistuin tänä vuonna JFokukseen sekä kuuntelijana että myös näyttelyn puolella yläkerrassa. Meillä oli täkynä LeapMotion laite ja se herättikin huomiota. Olen jo aiemmin bloggaillut sen koodailusta, joka on erityisen hauskaa Java 8 Lambdoilla ja JavaFX:llä.

With Great Power Comes Great Responsibility

Seminaarissa näytti toistuvan jo viime vuoden San Fransciscon JavaOne seminaarin teemat – osittain jopa samat luennot, slidet ja vitsit. Kuumia pääteemoja näyttää tälläkin vuodella olevan:

  • Tuleva Java 8 ja sen kaikki muutokset,mutta erityisesti Lambdat
  • Internet of Things muodossa: Halpoja laitteita jotka mittaavat/ohjaavat, helposti koodattavissa
  • Reaktiiviset käyttöliittymät ja JavaScriptin uusi tuleminen

Itse innostuin myös uudella tavalla Stream API:sta. Kuten Venkat Subramaniam sanoi jo JavaOne seminaarissa – Lambdat ovat porttihuume, ja Streamit ovat se kova huume johon ne johtavat, ja joista on vaikeampaa päästä eroon. Streameissä manifestoituu kaikki se uusi hyvä (ja huono) mitä Java versio 8 tuo mukanaan, ja ne tuovat valtavasti voimaa koodaajan arkeen.Ja niitä voi alkaa käyttämään heti. Tätä heijastaen Tieturin Java 8 kurssilla Streamien osuutta tullaan syventämään.

Venkat puhuu Lambdojen ja Streamien voimasta

Java 8 julkaisu on 18. Marraskuuta – oletko valmis? Release Candidate 1 on saatavilla heti, ja paljon tietoa. Uusien piirteiden myötä tulee uskomattomasti voimaa – tehokkuuta ja koodauksen selkeyttä – mutta myös vastuuta ja sudenkuoppia. Paras alkaa treenaamaan, montaa vuotta näitä ei voi väistellä enää. Itse ajan RC1 versiota Java 8:sta Raspissani ja muutama muukin päivittäisessä käytössä oleva työkalu on suoraan JDK 8:lla rakennettu.

Java 1.8.0 RC1 in Raspberry Pi

Mielenkiintoista on myös,että koska tämä ei ole Oraclen seminaari, myös Google näkyi täällä, ja osallistuin mielenkiintoiseen esitykseen tänä vuonna julkaistavasta Google Glass-älylaseista. Pär Sikö piti hyvän esityksen niiden ohjelmoinnista. Ne ovat näemmä käytössä jo mm. palomiehellä ja parillakin lääkärillä työssä, ja koodausmahdollisuudet sen kun vain lisääntyvät.Uuden GTK rajapinnan myötä monetkin Android 4:lle kehitetyt sovellukset ovat siirrettävissä uuteen käyttöön – kunhan vain käyttöliittymän rajoitukset huomioidaan.

Google Glass - What's Best In Life?

Tuli itsekin kokeiltua niitä – lasit reagoivat kiitettävän nopeasti komentoihin ja näyttö on kirkas,selkeä ja hieno. Käyttöliittymä saa tottumattoman takeltelemaan, etenkin kun jotain menee pieleen. Mielenkiintoista miten olemassaolevat Android kehitysvälineet – ja myös osaaminen – sopivat suoraan tälle.

JFokus

Ruotsin JFokus on skandinaavisen softakehittäjän kannalta mielenkiintoinen ja helposti saatavilla oleva seminaari jota voin lämpimästi suositella.

Miksi tarvitsemme Lambda Lausekkeita?

Pakko blogata pikkasen, törmäsin erinomaiseen artikkeliin jossa selitettiin miksi Lambda Expressions (lausekkeet) ovat niin hyödyllisiä, miksi niitä tarvitaan ja miksi ne ovat tulossa Javaan. Olen itse niihin ihastunut jo Scalan puolella, ja leikitellyt myös JavaScript koodissa, mutta on ollut vaikeaa pukea sanoiksi mitä hyödyllistä niissä on, sen on vain jollain tasolla itse tiedostanut.

Joka tapauksessa, tässä artikkelissa ei ole mitään omaperäistä ideaa, vain hatunnosto originaalille. Kaveri nimittäin selittää artikkelissaan miten hän käskyttää normaalisti lastaan for-loopin+ehtolauseen tavoin:

  • Pistä lelut pois. Onko maassa leluja?
  • Sofia: On, pallo
  • Ok, laita pallo laatikkoon. Onko maassa vielä jotain?
  • Sofia: On, nukke
  • Ok, laita nukke laatikkoon. Onko maassa vielä jotain?
  • Sofia: Ei ole
  • Ok, valmista tuli sitten.

Tämähän vastaa nykyisellä Java-kielellä esim. tämäntapaista:

for (int number : numbers) {
 System.out.println(number);
}

Eli tiukasti kontrolloitua ulkopuolelta käskytettyä toimintaa. Itse oliolta otetaan kaikki valta ja aivojen käyttö pois. Vähän niinkuin perinteisessä tiukasti johdetussa projektitavassa resursseilta 😉

Miten homma hoituisi sitten lambdalla? Vähän kuten ketteryydessä. Annetaan valta olioille. Kuka on paras ekspertti päättämään miten joku työ tehdään? Työn tekijä itse (yleensä, olettaen että hän on pätevä tehtävään). Jos lapselle annetaankin yksinkertaisesti käsky: Kerää lelut pois lattialta, hän voikin itse päättää miten sen tekee. Hän voi esim. kerätä kaikki lelut kerralla syliin, tai viedä kaksi tai yhden kerrallaan, riippuen niiden koosta, muodosta, painosta. Hän voi priorisoida mistäpäin alottaa tekemisen. Nämä ovat ohjelmointipuolella rinnastettavissa mm. ytimien käyttöön rinnakkaisprosessoinnissa, sekä algoritmien optimointiin automaattisesti.  Ja niitä on kovian paljon vaikeampaa tehdä ulkoapäin hyvin ilman ylenmääräistä kommunikaatiota ja monimutkaista kaksisuuntaista rajapintaa.

Eli em. Java 8 Lambdalla (anastettu allaolevasta artikkelista 😉

numbers.forEach((Integer value) -> System.out.println(value));

Käykö järkeen? Toivottavasti, Lambdat ovat tulossa, halusit tai et. Niitä tullaan käyttämään. Niitä täytyy pystyä ymmärtämään. Toivottavasti olet valmis.

http://java.dzone.com/articles/why-we-need-lambda-expressions

Java 8 ja Lambda Expressions

Ok, nyt oli hetki aikaa alkaa maistelemaan Java 8 uusia piirteitä. Kapistushan tulee julkaisuun vuonna 2013, eli mitään kiirettä ei sen suhteen ole, mutta kun nyt työn puolesta toimenkuvaan vähän niinkuin kuuluu olla etukenossa, niin..  latailin jo developer previewn java 8 työkaluista, ja ei muuta kuin koodailemaan.

Lambda expression on siis uudenlainen syntaksi. Skeptikoiden mielestä tämä on sama kuin anonyymin luokan käyttö, eli kutsutaan jotain metodia, ojennetaan parametrina anonyymi luokka jossa on algoritmi sisässä. Esim. Comparator toteutus. Lambda Expression on kuitenkin syvemmälle menevä muutos joka muuttaa Java-kielen syntaksia. Tällä hetkellä päällimmäisin muutos on uusi operaattori: ->

Tässähän ei ole mitään uutta, Scalassa ja C# kielessä on ollut jo tämä piirre aikansa, mutta hiljaa hyvä tulee.. Tässä esimerkki Lambda Expression käytöstä:

x -> x + 1

Eli mitäpä tuo tarkoittaa? Määritellään tavallaan anonyymi ilmassa roikkuva funktio, joka ei näytä kuuluvan mihinkään luokkaan. Funktio ottaa parametrinaan muuttujan x, ja määrittelee operaattorin perään sille algoritmiksi: palauta x + 1. Tietysti tässä tapauksessa lambda expression olettaa vahvasti että x on arvo jolle +1 on laillinen operaatio, esim. numeroarvo. Tuo ei tietysti yksinään tee mitään, vaan tätä täytyy käyttää jossain. Määritellään rajapinta joka sisältää metodin joka ottaa sisään arvon ja palauttaa toisen:

interface MyCalculator {
  public double operate (double input);
}

Tähän asti tuttua kamaa. Seuraavaksi määritellään lambda expression ja asetetaan muuttuja viittaamaan siihen:

MyCalculator mc = (x) -> x+1;
System.out.println(mc.operate(1));

Eli näillä tulostuu tietysti ruudulle kaksi. Eiköhän tässä ole teaseriksi sopivasti, palataan asiaan joskus kun tiedän näistä enemmän. Aikaisinhan tässä liikkeellä ollaan

Ei mikään suuri salaisuus, että näitä tullaan näkemään eritoten Java 8 Collections-rajapinnoissa. Juuri kun suuri osa koodaajista alkaa olla toipunut Java 5 Generics tyyppiparametrien käytöstä.. 😉