﻿<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="/index.xsl"?>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>A Java 8 újdonságai</title>
<link href="/_sweb4/main.css" rel="stylesheet" type="text/css" />
</head>
<body>
  <div style="margin: 0px auto; width: 985px;" id="menudiv">
  <!-- Menü -->
  <ul class="menu" id="menu">
    <li>
      <a href="#" class="menulink">Informatika</a>
      <ul>
        <li>
          <a href="#" class="sub">Nyílt forrás</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/informatika/nyilt/lnw.htm">A Linux nem Windows</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/a-katedralis-es-a-bazar/">A katedrális és a bazár</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Zárt forrás</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/what-is-the-matrix/">What is the Matrix?</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Történelem</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/informatika/tortenelem/commodore.htm">Commodore</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/szszgep.htm">Személyi számítógépek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/x86cpu.htm">PC processzorok</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/archi.htm">Számítógép-architektúrák</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tortenelem/geos.htm">GEOS</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Esszék</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Joel on Software</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/informatika/joel/gui.htm">Felhasználói felületek</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/mkernel.htm">Mikrokernel</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/hasznalat.htm">A gép használata</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/linux-vagy-windows/">Linux vagy Windows?</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/javabytecode.htm">Java bájtkód</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/javagc.htm">Java szemétgyűjtő</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/java/java8.htm">A Java 8 újdonságai</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/essze/mbrgpt.htm">Az MBR-től a GPT-ig</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Tesztek</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/informatika/tesztek/winteszt.htm">Melyik a gyorsabb?</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/informatika/tesztek/kindle.htm">Amazon Kindle</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Tudomány</a>
      <ul>
        <li>
          <a href="#" class="sub">Atomerőművek</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/tudomany/atom/baleset.htm">Atomerőmű balesetek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/tudomany/atom/19ev.htm">Csernobil 19 évvel később</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/tudomany/atom/csernobil.htm">Csernobil</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Űrkutatás</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/tudomany/urkutatas/voyager.htm">A Voyager szondák</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/tudomany/urkutatas/voyagercomp.htm">A Voyager-ek számítógépe</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Szárazföld</a>
      <ul>
        <li>
          <a href="#" class="sub">Lamborghini</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/szarazfold/egyeb/lambo/gt.htm">350 GT/400 GT</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/szarazfold/egyeb/lambo/miura.htm">Miura</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/szarazfold/egyeb/lambo/countach.htm">Countach</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Munkagépek</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Dömperek</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/terex-titan/">Terex Titan</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/munkagepek/domper/cat797/cat797.htm">Caterpillar 797</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Kotrógépek</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/munkagepek/kotrogep/muskie.htm">Big Muskie</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Építmények</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Épületek</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/rekord/rekord.htm">Magassági rekordok</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/taipei101/taipei101.htm">Taipei 101</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/petronas/petronas.htm">Petronas tornyok</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/swfc/swfc.htm">SWFC</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/epulet/hins/hins.htm">Home Insurance</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Tornyok</a>
              <ul>
                <li class="topline">
                  <a href="https://new.egalizer.hu/osztyankino-tv-torony/">Osztyankino TV-torony</a>
                </li>
                <li>
                  <a href="https://new.egalizer.hu/eiffel-torony/">Eiffel torony</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Egyéb</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/epitmeny/egyeb/tenna.htm">Antennák</a>
                </li>
                <li>
                  <a href="https://new.egalizer.hu/akashi-kaikyo-hid/">Akashi Kaikyo</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Vasút</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/sinkanzen/">Sinkanzen</a>
            </li>
            <li class="topline">
              <a href="http://www.egalizer.hu/szarazfold/vasut/nagyseb.htm">Nagy sebességű vonatok</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Egyéb</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/szarazfold/egyeb/wind.htm">Big Wind</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Zene</a>
      <ul>
        <li>
          <a href="#" class="sub">Esszék</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/paul-hindemith-zene-es-erzelem/">Zene és érzelem</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/arthur-honegger-a-jelen-es-a-jovo-kilatasai/">A jövőről</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/benjamin-britten-palyamrol/">Pályámról</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Zeneszerzők</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Wolfgang Amadeus Mozart</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/mozart-porosz-vonosnegyesei/">Porosz vonósnégyesek</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/zeneszerzok/mozart/zvers.htm">Fortepiano versenyek</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/utazas-a-jupiterre/">Utazás a Jupiterre</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Joseph Haydn</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/a-szimfonia-szuletese/">A szimfónia születése</a>
                </li>
              </ul>
            </li>			
            <li>
              <a href="#" class="sub">Bartók Béla</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/bartok-bela-oneletrajz/">Önéletrajz</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/szamomra-minden-nap-bartok-evfordulo/">Évforduló</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Gustav Mahler</a>
              <ul>
                <li class="topline">
                  <a href="http://new.egalizer.hu/ket-es-fel-perc-csond/">Két és fél perc csönd</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Ludwig van Beethoven</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/zene/zeneszerzok/beethoven/kilenc.htm">Kilencedik szimfónia</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="http://www.egalizer.hu/zene/zeneszerzok/webern.htm">Anton Webern</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Karmesterek</a>
          <ul>
            <li class="topline">
              <a href="#" class="sub">Wilhelm Furtwängler</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/zene/karmesterek/furtw.htm">Bevezetés</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/karmesterek/furtw2.htm">Furtwängler élete</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/karmesterek/furtw3.htm">Furtwängler lemezen</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/friedrich-schnapp/">Friedrich Schnapp</a>
                </li>
              </ul>
            </li>
            <li>
              <a href="#" class="sub">Willem Mengelberg</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/zene/karmesterek/mengel.htm">Columbia felvételek</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/zene/karmesterek/mengel_opk.htm">Opus Kura lemezek</a>
                </li>
              </ul>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://new.egalizer.hu/cd-velemenyek/">CD vélemény</a>
        </li>
        <li>
          <a href="#" class="sub">Sorozatok, kiadók</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/zene/kiado/greatcon.htm">Great Conductors</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://www.egalizer.hu/zene/librettok/librettok.htm">Szövegkönyvek</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">High Fidelity</a>
      <ul>
        <li>
          <a href="#" class="sub">Esszék</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/esszek/zenehifi.htm">A zene és a hifi</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/esszek/termtorz.htm">A HiFiről</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/a-high-end-rol/">A High End</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/esszek/modszer.htm">Módszerek és csapdák</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/a-digitalis-kihivas/">A digitális kihívás</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/loudness-unit-cd-adatbazis/">Hangosság adatbázis</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://new.egalizer.hu/sajat-rendszerem/">Saját rendszer</a>
        </li>
        <li>
          <a href="#" class="sub">Salvatore</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/highend/bev.htm">Bevezetés</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/tortenet.htm">Történetem</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/kereskedelem.htm">Kereskedelem</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/lemezek.htm">Hanglemezek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/filo.htm">Filozófiám</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/magazin.htm">Magazinok</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/highend/lp12.htm">Linn Sondek</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Hangtechnika</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/hangtechnika/tda1541/tda1541.htm">TDA1541</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/resta/resta.htm">Restaurálás</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/cd-sacd-dvd-audio/">Formátumok</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/zfelv.htm">A zenei felvételekről</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/nagyfel/nagyfel.htm">A nagy felbontásról</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/hangtechnika/futomuvek.htm">CD futóművek tesztje</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Történelem</a>
          <ul>
            <li class="topline">
              <a href="http://www.egalizer.hu/hifi/tortenelem/cd.htm">A Compact Disc</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/pcm-felvetelek/">Az első PCM felvételek</a>
            </li>
            <li>
              <a href="http://www.egalizer.hu/hifi/tortenelem/gramo.htm">Gramofónia</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/solti-ringje-es-a-bitrata/">Solti Ringje és a bitráta</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="#" class="sub">Készülékek</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/magyar-hi-fi-keszulekgyartok-listaja/">Magyar gyártók</a>
            </li>
          </ul>
        </li>
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Irodalom</a>
      <ul>
        <li class="topline">
          <a href="#" class="sub">Írók</a>
          <ul>
            <li class="topline">
              <a href="http://new.egalizer.hu/rejto-jeno-szomoru-elete/">Rejtő Jenő</a>
            </li>
            <li>
              <a href="http://new.egalizer.hu/olaf-stapledon-elete/">Olaf Stapledon</a>
            </li>
          </ul>
        </li>
        <li>
          <a href="http://new.egalizer.hu/a-kepes-kronika/">Képes Krónika</a>
        </li>		
      </ul>
    </li>
    <li>
      <a href="#" class="menulink">Tárgyak</a>
        <ul>
            <li class="topline">
              <a href="#" class="sub">Kockák a négyzeten</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/kockak1.htm">Az őskor</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/kockak2.htm">A hőskor 1.</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/kockak3.htm">A hőskor 2.</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/ajandek.htm">Ajándék- készletek</a>
                </li>
              </ul>                  
            </li>			
            <li>
              <a href="#" class="sub">Karórák</a>
              <ul>
                <li class="topline">
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/eichi2.htm">Eichi 2</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/szarazfold/egyeb/orakar.htm">Rejtett költségek</a>
                </li>
                <li>
                  <a href="https://new.egalizer.hu/udvozlet-japanbol/">Üdvözlet Japánból</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/kings.htm">A King Seiko története</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/king1.htm">Az első King Seiko</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/57gs.htm">Az második Grand Seiko</a>
                </li>
                <li>
                  <a href="http://www.egalizer.hu/targyak/orak/cosc.htm">A COSC röviden</a>
                </li>
                <li>
                  <a href="http://new.egalizer.hu/regi-seiko-arlistak/">Régi Seiko árlisták</a>
                </li>
              </ul>                  
            </li>			
        </ul>
    </li>
    <li>
      <a href="#" class="menulink">Névjegy</a>
      <ul>
        <li>
          <a href="http://www.egalizer.hu/nevjegy.htm">Bemutatkozás</a>
        </li>
        <li>
          <a href="http://www.egalizer.hu/szakdoga.htm">Krónikás</a>
        </li>
        <li>
          <a href="http://www.egalizer.hu/letolt.htm">Letöltés</a>
        </li>
        <li>
          <a href="http://www.egalizer.hu/linkek.htm">Linkek</a>
        </li>
      </ul>
    </li>
    <li>
      <a href="https://new.egalizer.hu" class="menulink" style="border-right: 1px solid #909090;">Kezdőlap</a>
    </li>
  </ul>
  <!-- Menü vége -->
</div>

  <p id="sweb_elements">
    <object id="footer">
      Sipos Róbert (2019)<br /> Az összeállításhoz használt oldalak:<br /> <a onclick="window.open(this.href);return false;"
        href="https://www.hwsw.hu/hirek/51991/oracle-java-8-sdk-javascript-fejlesztes-lambda-nashorn-jigsaw.html">Végre itt a Java 8!</a><br /> <a
        onclick="window.open(this.href);return false;" href="http://www.theregister.co.uk/2012/03/07/oracle_java_9_10_roadmap/">Java won't curl up and die like Cobol, insists Oracle</a><br />
      <a onclick="window.open(this.href);return false;" href="http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html">What's New in JDK 8</a><br /> <a
        onclick="window.open(this.href);return false;" href="http://www.deadcoderising.com/functional-interfaces-in-java-8/">Functional Interfaces in Java 8</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.journaldev.com/2389/java-8-features-with-examples">Java 8 Features with Examples</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.javacodegeeks.com/2014/05/java-8-features-tutorial.html">Java 8 Features Tutorial</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.javacodegeeks.com/2013/03/introduction-to-functional-interfaces-a-concept-recreated-in-java-8.html">Introduction
        to Functional Interfaces</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.infoq.com/articles/Java-7-Features-Which-Enable-Java-8">Java 7 Features Which
        Enable Java 8</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.infoq.com/articles/Invokedynamic-Javas-secret-weapon">Invokedynamic - Java’s Secret
        Weapon</a><br /> <a onclick="window.open(this.href);return false;" href="https://stackoverflow.com/questions/1504633/what-is-the-point-of-invokeinterface">What is the point of
        invokeinterface? </a><br /> <a onclick="window.open(this.href);return false;" href="https://dzone.com/articles/hacking-lambda-expressions-in-java">Hacking Lambda Expressions in
        Java</a><br /> <a onclick="window.open(this.href);return false;" href="https://inf.nyme.hu/~nemes/Software/Java/Exception/Explanation/hierarchy.htm">A Java kivétel-osztályainak
        hierarchiája</a><br /> <a onclick="window.open(this.href);return false;" href="https://inf.nyme.hu/~nemes/Software/Java/Exception/Syntax/rule6.htm">Deklarációkényszer</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://tifyty.wordpress.com/2014/03/26/a-java8-default-metodus-hazugsaga/">A Java8 default metódus hazugsága</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html">Default Methods</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html">Method References</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://vickychijwani.me/java-8-method-references/">What you need to know about Java 8 method references</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/tutorial/java/annotations/repeating.html">Repeating Annotations</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.6.4.2">Java Language Specification - Interfaces</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.7.16">Java Language Specification - The class file
        format</a><br /> <a onclick="window.open(this.href);return false;" href="https://blog.jooq.org/2014/04/04/java-8-friday-the-dark-side-of-java-8/">Java 8 Friday: The Dark Side of
        Java 8</a><br /> <a onclick="window.open(this.href);return false;" href="http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html">Tired of Null Pointer
        Exceptions?</a><br /> <a onclick="window.open(this.href);return false;" href="https://dzone.com/articles/java8-oogways-advise-on-optional">Oogway's Advice on Optional</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.logicbig.com/tutorials/core-java-tutorial/java-util-stream/stream-cheat-sheet.html">Java 8 Stream operations
        cheat sheet</a><br /> <a onclick="window.open(this.href);return false;" href="http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html">When to use parallel streams</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.logicbig.com/tutorials/core-java-tutorial/java-util-stream/ordering.html">Java 8 Streams - Ordering</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://dzone.com/articles/why-theres-interface">Why There's Interface Pollution in Java 8</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://blog.jooq.org/2014/06/13/java-8-friday-10-subtle-mistakes-when-using-the-streams-api/">10 Subtle Mistakes When Using
        the Streams API</a><br /> <a onclick="window.open(this.href);return false;" href="https://stackoverflow.com/questions/23699371/java-8-distinct-by-property">Java 8 Distinct by
        property</a><br /> <a onclick="window.open(this.href);return false;" href="https://marcin-chwedczuk.github.io/java-streams-best-practices">Java streams best practices</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://stackify.com/streams-guide-java-8/">A Guide to Streams in Java 8</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://blog.jooq.org/2017/06/29/a-basic-programming-pattern-filter-first-map-later/">A Basic Programming Pattern: Filter
        First, Map Later</a><br /> <a onclick="window.open(this.href);return false;" href="https://blog.jooq.org/2017/07/03/are-java-8-streams-truly-lazy-not-completely/">Are Java 8
        Streams Truly Lazy? Not Completely!</a><br /> <a onclick="window.open(this.href);return false;" href="http://sebastian-millies.blogspot.com/2015/05/streamflatmap-may-cause-short.html">Stream#flatMap()
        may cause short-circuiting of downstream operations to break</a><br /> <a onclick="window.open(this.href);return false;"
        href="https://stackoverflow.com/questions/30843279/stream-skip-behavior-with-unordered-terminal-operation">Stream.skip behavior with unordered terminal operation</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://jaxenter.com/the-new-java-8-date-and-time-api-an-interview-with-stephen-colebourne-107663.html">The new Java 8 Date
        and Time API: An interview with Stephen Colebourne</a><br /> <a onclick="window.open(this.href);return false;"
        href="https://docs.oracle.com/javase/tutorial/datetime/overview/index.html">Lesson: Date-Time Overview</a><br /> <a onclick="window.open(this.href);return false;"
        href="https://docs.oracle.com/javase/tutorial/datetime/iso/index.html">The Java Tutorials - Standard Calendar</a><br /> Mérésügyi közlemények XLVI. évf. 4. szám: 2005. december<br />
      <a onclick="window.open(this.href);return false;" href="https://www.baeldung.com/java-8-date-time-intro">Introduction to the Java 8 Date/Time API</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://dzone.com/articles/java-8-apis-javautiltime">Java 8 APIs: java.util.time</a><br /> <a
        onclick="window.open(this.href);return false;" href="https://www.javabrahman.com/java-8/working-with-time-zones-in-java-8-zoneddatetime-zoneid-tutorial-with-examples/">Working
        with Time Zones in Java 8</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.javaspecialists.eu/archive/Issue235.html">Checking HashMaps with
        MapClashInspector</a><br /> <a onclick="window.open(this.href);return false;" href="https://dzone.com/articles/concurrenthashmap-isnt-always-enough?fromrel=true">ConcurrentHashMap
        isn't always enough</a><br /> <a onclick="window.open(this.href);return false;" href="https://liviutudor.com/2014/06/26/java-8-accumulators-and-adders/#sthash.8EeV4RHk.dpbs">Java 8
        accumulators and adders</a><br /> <a onclick="window.open(this.href);return false;" href="https://winterbe.com/posts/2015/04/30/java8-concurrency-tutorial-synchronized-locks-examples/">Java
        8 Concurrency Tutorial: Synchronization and Locks</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.javaspecialists.eu/archive/Issue215.html">StampedLock
        Idioms</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.javaspecialists.eu/talks/jfokus13/PhaserAndStampedLock.pdf">Phaser And StampedLock Concurrency
        Synchronizers</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.deadcoderising.com/java8-writing-asynchronous-code-with-completablefuture/">Writing
        asynchronous code with CompletableFuture</a><br /> <a onclick="window.open(this.href);return false;" href="https://www.nurkiewicz.com/2013/05/java-8-definitive-guide-to.html">Java
        8: Definitive guide to CompletableFuture</a><br /> <a onclick="window.open(this.href);return false;" href="https://dzone.com/articles/20-examples-of-using-javas-completablefuture">20
        Examples of Using Java’s CompletableFuture</a><br /> <a onclick="window.open(this.href);return false;" href="https://dempkow.ski/blog/java-completablefuture-exception-handling/">Java's
        CompleteableFuture exception handling: whenComplete vs. handle</a><br />
    </object>
    <object id="buttons">
      <button title="tartalombutton" />
    </object>
  </p>
  <h2>Bevezetés</h2>
  <p class="essze_bevezetes">Hosszú várakozás után 2014. március 18-án jelent meg a Java 8 fejlesztői környezet és programozási nyelv (JDK 1.8.0). A 8-as Java talán a legnagyobb
    előrelépés a 2004-ben megjelent Java 5 óta és óriási mennyiségű újítást hoz a nyelvbe, fordítóba, osztálykönyvtárakba és a JVM-be. Az 1998-as Java 2 (J2SE 1.2) után a nyelv fejlesztése
    évekig 2 éves kiadási ciklussal működött. Ezt az ütemet a Java 7 törte meg, amire a 2006-os Java SE 6 után különböző üzletpolitikai huzavonák miatt majdnem 5 évet kellett várni. A Java
    8 esetén ennél azért már rövidebb volt a fejlesztési idő, a Java 9 után pedig a fejlesztést végző közösség tervei szerint újra visszaáll a két éves kiadási ciklus. Egy 2012-es The
    Register cikkből kiderült, hogy akkoriban a Java 9-et és 10-et még 2015-re és 2017-re tervezték. Végül aztán a Java 9 2017. szeptember 21-én jelent meg. Ez a verzió nyelvi szempontból
    gyakorlatilag nem változott a 8-hoz képest, a Java 10-be viszont újra jelentős módosítást terveznek, mégpedig a teljes objektumorientáltság elérését. Ezzel megszűnnének a primitív
    típusok és immár minden objektum lenne. Ezen kívül pedig a tömböket 64 bites indexeléssel is lehetne használni ami sokkal nagyobb tömbméreteket tenne lehetővé. Emellett pedig némi
    tisztogatást is szeretnének elvégezni. A Java ugyanis már több, mint 20 éve velünk van. Először 200 class fájllal indult, ma már 70 ezernél is több van benne. Néhány osztályban több a
    deprecated metódus, mint a használható.</p>
  <p class="bekezd">
    Ebben a cikkben részletesen áttekintem a Java 8 nyelvi újdonságait. (Bár a Java 8 SE támogatása 2019 januárjában hivatalosan végetért, a cikkben található ismeretek újabb Java esetén is
    hasznosak. A fejlesztők túlnyomó többsége pedig <a onclick="window.open(this.href);return false;" href="https://www.theregister.co.uk/2019/03/07/java_developers_version_8/">még
      mindig</a> ezt a verziót használja.) A cikkben található példaprogramok az alábbi JDK-verzióval készültek:
  </p>
  <ul>
    <li>Java 8u162 x64</li>
  </ul>
  <h2>Sok-sok újdonság</h2>
  <p class="bekezd">Az alábbi lista a Java 8 minden újdonságát felsorolja kategóriákra bontva:</p>
  <p class="bekezd">
    <b>Java programozási nyelv</b>
  </p>
  <ul>
    <li>lambda kifejezések: függvények metódusparaméterként való kezelése, kód adatként való kezelése. A funkcionális interfészek lehetővé teszik egy metódust tartalmazó interface-ek
      példányosítását sokkal tömörebben.</li>
    <li>a metódus referenciák könnyen olvasható lambda kifejezéseket adnak névvel már rendelkező metódusoknak</li>
    <li>az alapértelmezett (default) metódusok lehetővé teszik, hogy osztálykönyvtárakban lévő interface-ekhez új funkcionalitást adjunk úgy, hogy megmarad azon interface-ek régebbi
      verzióihoz írt kóddal való kompatibilitása</li>
    <li>ismétlő annotációk (repeating annotations) lehetővé teszik, hogy ugyanazt az annotációtípust egynél többször hozzáadjuk ugyanahhoz a deklarációhoz vagy típus használathoz</li>
    <li>a típus annotációk (type annotations) lehetővé teszik, hogy ugyanazt az annotációt egy adott típus minden előfordulásához alkalmazzuk, nem csak egyhez. Ezt a becsatolható típus
      rendszerrel használva (pluggable type system) lehetővé válik a kód jobb ellenőrzése.</li>
    <li>továbbfejlesztett típus interface</li>
    <li>metódus paraméter név reflection</li>
  </ul>
  <p class="bekezd">
    <b>Collections</b>
  </p>
  <ul>
    <li>az új java.util.stream package-ben lévő Stream API, ami funkcionális jellegű műveletek elvégzését teszi lehetővé elemek során. A Stream API integrálva van a Collections API-ba,
      ami lehetővé teszi tömeges műveletek elvégzését collection-ökön.</li>
    <li>teljesítménybeli javulás a HashMap-eken a hash ütközés esetén. Nagyobb HashMap-ek esetén hasznos.</li>
  </ul>
  <p class="bekezd">
    <b>Kompakt profilok</b>, amelyek a Java SE előre meghatározott részhalmazait tartalmazzák olyan alkalmazásokhoz, amelyeknek a kisebb eszközökön való futáshoz nincs szükségük a teljes
    platformra.
  </p>
  <p class="bekezd">
    <b>Biztonság</b>
  </p>
  <ul>
    <li>kliensoldali TLS 1.2 alapértelmezetten engedélyezett</li>
    <li>az <span class="programkod">AccessController.doPrivileged</span> új variánsa, ami lehetővé teszi a kódnak, hogy kijelölje a privilégiumainak részhalmazát anélkül, hogy
      megakadályozná a verem teljes elérését más jogosultságok ellenőrzéséhez
    </li>
    <li>erősebb algoritmusok a jelszó-alapú titkosításhoz</li>
    <li>SSL/TLS Server Name Indication (SNI) támogatás a JSSE szerverben</li>
    <li>AEAD algoritmusok támogatása</li>
    <li>KeyStore fejlesztések, mint például új Domain KeyStore típusú <span class="programkod">java.security.DomainLoadStoreParameter</span>, és új parancssori kapcsoló a keytool
      eszközhöz: <span class="programkod">importpassword</span></li>
    <li>SHA-224 Message Digest</li>
    <li>kiterjesztett támogatás az NSA Suite B Cryptography kriptográfiai algoritmusokhoz</li>
    <li>jobb támogatás a High Entropy Random Number Generation-höz</li>
    <li>új <span class="programkod">java.security.cert.PKIXRevocationChecker</span> osztály az X.509 tanúsítványok visszavonás ellenőrzésének konfigurálásához
    </li>
    <li>64 bites PKCS11 Windowshoz</li>
    <li>új rcache típusok a Kerberos 5 Replay Caching-ben</li>
    <li>Kerberos 5 Protocol Transition támogatás és Constrained Delegation</li>
    <li>a Kerberos 5 gyenge enkódolás típusok alapból tiltva vannak</li>
    <li>Unbound SASL a GSS-API/Kerberos 5 mechanizmushoz</li>
    <li>SASL szolgáltatás több kiszolgálónévhez</li>
    <li>JNI bridge a natív JGSS-hez Mac OS X-en</li>
    <li>támogatás az erősebb DH kulcsokhoz a SunJSSE provider-ben</li>
    <li>támogatás a szerveroldali cipher testreszabáshoz a JSSE-ben</li>
  </ul>
  <p class="bekezd">
    <b>JavaFX</b>
  </p>
  <ul>
    <li>az új Modena téma használata</li>
    <li>az új <span class="programkod">SwingNode</span> osztály lehetővé teszi a fejlesztőknek, hogy JavaFX alkalmazásokba ágyazzanak Swing tartalmat
    </li>
    <li>az új UI Controls csomagba bekerültek <span class="programkod">DatePicker</span> és <span class="programkod">TreeTableView</span> vezérlők
    </li>
    <li>a <span class="programkod">javafx.print</span> csomag publikus osztályokat tartalmaz a JavaFX Printing API-hoz
    </li>
    <li>a 3D Graphics most már tartalmaz 3D alakzatokat, kamerát, fényeket, helyszíneket (subscene), anyagokat, élsimítást. Új <span class="programkod">Shape3D</span> (<span
      class="programkod">Box, Cylinder, MeshView</span> és <span class="programkod">Sphere</span> alosztályok), <span class="programkod">SubScene, Material, PickResult, LightBase</span> (<span
      class="programkod">AmbientLight</span> és <span class="programkod">PointLight</span> alosztályok) és <span class="programkod">SceneAntialiasing</span> API osztályok kerültek a JavaFX
      3D grafikus könyvtárba. A <span class="programkod">Camera</span> API osztály is frissítve lett ebben a kiadásban.
    </li>
    <li>a <span class="programkod">WebView</span> osztály új funkciókat és fejlesztéseket tartalmaz a HTML5 támogatásával együtt
    </li>
    <li>kiterjesztett szövegtámogatás kétirányú szövegekkel és olyan összetett szövegekkel mint a Thai és Hindi a vezérlőkben, valamint többsoros, többstílusú szöveg a szöveges
      csomópontokban</li>
    <li>Hi-DPI kijelszők támogatása</li>
    <li>a CSS Styleable osztályok publikus API-vá váltak</li>
    <li>az új <span class="programkod">ScheduledService</span> osztály lehetővé teszi, hogy automatikusan újraindítsák a szolgáltatást
    </li>
    <li>a Java FX most már elérhető ARM platformokra. Az ARM-os JDK tartalmazza a JavaFX alap, grafikus és vezérlő komponeneseit</li>
  </ul>
  <p class="bekezd">
    <b>Eszközök</b>
  </p>
  <ul>
    <li>a <span class="programkod">jjs</span> parancs bevezetése a Nashorn motorhoz
    </li>
    <li>a <span class="programkod">java</span> parancs indítja a JavaFX alkalmazásokat
    </li>
    <li>a java man page újra lett írva</li>
    <li>a <span class="programkod">jdeps</span> parancssori eszköz bevezetése a class fájlok analizásához
    </li>
    <li>a Java Management Extensions (JMX) távoli elérést biztosít diagnosztikai parancsokhoz</li>
    <li>a <span class="programkod">jarsigner</span> eszköznek most már van olyan opciója, ami aláírt időbélyeget kér egy Time Stamping Authority (TSA)-tól
    </li>
    <li>javac
      <ul>
        <li>a <span class="programkod">-parameters</span> opció bevezetése (később lesz róla szó)
        </li>
        <li>az egyenlőséghez szükséges típusszabályokat most már pontosan kikényszeríti a javac</li>
        <li>a javac most már támogatja azt, hogy a javadoc a megjegyzéseket ellenőrizze olyan szempontok szerint, amik problémákhoz vezethetnek, mint érvénytelen HTML vagy elérhetőségi
          problémák a fájlokban, amik a javadoc futásakor létrejönnek. Ezt a funkciót a <span class="programkod">-Xdoclint</span> paraméter engedélyezi. Ezt a funkciót a javadoc-ba is
          beépítették és alapértelmezetten engedélyezve van.
        </li>
        <li>a javac eszköz most már fel van készítve natív header-ek generálására, ha szükséges. Emiatt már nincs szükség a javah eszköz futtatására a build folyamat különálló
          lépéseként. Ezt a funkciót az új <span class="programkod">-h</span> javac opció engedélyezi, amivel meg kell adni egy könyvtárat, ahová a header fájlok majd kerülnek. A header
          fájlok bármely osztályhoz elkészülnek, amelynek vagy natív metódusa van vagy pedig olyan konstans mezői amelyek az új <span class="programkod">java.lang.annotation.Native</span>
          annotációval vannak jelölve.
        </li>
      </ul>
    </li>
    <li>javadoc
      <ul>
        <li>a javadoc most már támogatja az új DocTree API-t ami lehetővé teszi, hogy javadoc megjegyzéseket absztrakt szintaxisfaként bejárhassunk</li>
        <li>a javadoc támogatja az új Javadoc Access API-t, ami lehetővé teszi, hogy meghívjuk a Javadoc eszközt közvetlenül a Java alkalmazásból anélkül, hogy új processzt kellene
          indítani.</li>
        <li>a javadoc most már támogatja a megjegyzések ellenőrzését olyan dolgok szemszögéből, amik problémákhoz vezethetnek, mint érvénytelen HTML vagy elérhetőségi problémák a
          fájlokban, amik a javadoc futásakor létrejönnek. Ez a funkció alapértelmezetten engedélyezve van de az új <span class="programkod">-Xdoclint</span> paraméterrel is vezérelni
          lehet. A funkció a javac eszközbe is beépítésre került, bár ott alapértelmezetten nincs engedélyezve.
        </li>
      </ul>
    </li>
  </ul>
  <p class="bekezd">
    <b>Nemzetközi támogatás</b>
  </p>
  <ul>
    <li>Unicode fejlesztések, az Unicode 6.2.0 támogatása</li>
    <li>az Unicode CLDR használata és a java.locale.providers system property</li>
    <li>új Calendar és Locale API-k</li>
    <li>képesség arra, hogy Custom Resource Bundle telepíthető legyen kiterjesztésként</li>
  </ul>
  <p class="bekezd">
    <b>Deployment</b>
  </p>
  <ul>
    <li>homokozóban futó appletek és Java Web Start alkalmazások esetén már az <span class="programkod">URLPermission</span> használatos az indító szerverhez való kapcsolódáshoz. A <span
      class="programkod">SocketPermission</span> immár nem engedélyezett többé.
    </li>
    <li>minden biztonsági szinten szükséges a Permissions attribútum a fő JAR fájl manifest-ben</li>
  </ul>
  <p class="bekezd">
    <b>Date-Time csomag</b>: új csomagkészlet, ami új és kiterjedt dátum-idő modellt biztosít
  </p>
  <p class="bekezd">
    <b>Scripting</b>
  </p>
  <ul>
    <li>a Rhino javascript motort lecserélte a Nashorn</li>
  </ul>
  <p class="bekezd">
    <b>Pack200</b>
  </p>
  <ul>
    <li>Pack200 támogatás a konstanskészlet bejegyzésekhez és új bájtkódok a JSR 292 szerint</li>
    <li>JDK8 támogatás a class fájlokhoz a JSR-292, JSR-308 és JSR-335 szerint</li>
  </ul>
  <p class="bekezd">
    <b>IO és NIO</b>
  </p>
  <ul>
    <li>új <span class="programkod">SelectorProvider</span> implementáció a Solarishoz. A használatához a <span class="programkod">java.nio.channels.spi.Selector</span> rendszer
      property-t a <span class="programkod">sun.nio.ch.EventPortSelectorProvider</span> értékkel kell beállítani
    </li>
    <li>a <span class="programkod">&lt;JDK_HOME&gt;/jre/lib/charsets.jar</span> fájl méretbeli csökkenése
    </li>
    <li>teljesítménybeli javulás a <span class="programkod">java.lang.String(byte[], *)</span> konstruktornál és a <span class="programkod">java.lang.String.getBytes()</span>
      metódusnál
    </li>
  </ul>
  <p class="bekezd">
    <b>java.lang és java.util csomagok</b>
  </p>
  <ul>
    <li>párhuzamos tömbrendezés</li>
    <li>szabványos Base64 enkódolás és dekódolás</li>
    <li>unsigned aritmetika támogatása</li>
  </ul>
  <p class="bekezd">
    <b>JDBC</b>
  </p>
  <ul>
    <li>a JDBC-ODBC bridge el lett távolítva</li>
    <li>JDBC 4.2 új tulajdonságok</li>
  </ul>
  <p class="bekezd">
    <b>Java DB</b>
  </p>
  <ul>
    <li>Java DB 10.10</li>
  </ul>
  <p class="bekezd">
    <b>Hálózatkezelés</b>
  </p>
  <ul>
    <li>új <span class="programkod">java.net.URLPermission</span> osztály
    </li>
    <li>ha biztonsági manager telepítve van, akkor a <span class="programkod">java.net.HttpURLConnection</span> osztályban olyan hívások esetén, amelyek connection-t kell hogy
      nyissanak, jogosultság szükséges
    </li>
  </ul>
  <p class="bekezd">
    <b>Konkurencia</b>
  </p>
  <ul>
    <li>új osztályok és interfészek kerültek a <span class="programkod">java.util.concurrent</span> csomagba
    </li>
    <li>új metódusok kerültek a <span class="programkod">java.util.concurrent.ConcurrentHashMap</span> osztályba, hogy támogassák az újonnan létrehozott lambda és stream funkciókhoz az
      aggregáló műveletet
    </li>
    <li>új osztályok kerültek a <span class="programkod">java.util.concurrent.atomic</span> csomagba, hogy támogassák a skálázható frissíthető változókat
    </li>
    <li>új metódusok kerültek a <span class="programkod">java.util.concurrent.ForkJoinPool</span> osztályba, hogy támogassák a közös pool-t
    </li>
    <li>új <span class="programkod">java.util.concurrent.locks.StampedLock</span> osztály, hogy hárommódú képesség-alapú zárolást biztosítson vezérlő read/write eléréshez
    </li>
  </ul>
  <p class="bekezd">
    <b>Java XML-JAXP</b>
  </p>
  <p class="bekezd">
    <b>HotSpot</b>
  </p>
  <ul>
    <li>bekerült támogatás a hardveres Advanced Encryption Standard (AES) használatához. Új UseAES és UseAESIntrinsics kapcsolók engedélyezik a hardveres AES használatát Intel
      hardveren (amennyiben az 2010-es vagy újabb Westmere). A hardveres AES bekapcsolása: <span class="programkod">-XX:+UseAES -XX:+UseAESIntrinsics</span> ;a hardveres AES letiltása: <span
      class="programkod">-XX:-UseAES -XX:-UseAESIntrinsics</span>
    </li>
    <li>a PermGen megszűnt és megjelent a metaspace</li>
    <li>a Java programnyelv default metódusai támogatottak a bájtkód utasításokban a metódushívásoknál</li>
  </ul>
  <p class="bekezd">
    <b>Java Mission Control 5.3</b>
  </p>
  <ul>
    <li>a Java 8 bevezeti a Java Mission Control 5.3-at</li>
  </ul>
  <p class="bekezd">Ebből a hatalmas újdonsághalmazból ebben a cikkben az új nyelvi elemeket és változásokat fogom ismertetni. Ugorjunk is egyből a mély vízbe!</p>
  <h2>LambAda</h2>
  <p class="bekezd">
    A lambda kifejezések bevezetése (néha closure néven is emlegetik) a Java 8 egyik legjobban várt és (higgyük el!) legnagyobb újdonsága. Magát a fogalmat többféleképpen magyarázzák el
    különböző bemutatók és tananyagok. Egyesek szerint ez végre egy módszer arra, hogy funkcionális programozási stílust vezessünk be az objektum-orientált Java-ba. Más megközelítés szerint
    lehetővé teszi, hogy függvényeket használjunk metódusparaméterként vagy kódot kezeljünk adatként. Egy harmadik elképzelés szerint - és ezt az utat járja az Oracle oktatási leírása is -
    a megismerés legegyszerűbb első lépése, ha azt mondjuk, hogy anonim belső osztályokat lehet kiváltani lambda kifejezésekkel. (Emellett nem elhanyagolható módon hatékonnyá és egyszerűbbé
    teszi a modern többmagos processzorok kihasználását is.) A lambda kifejezés egyébként a funkcionális nyelvekben használatos fogalom, de több JVM-en futó nyelvben megvolt már régóta
    (Groovy, Scala). Aki matematikai megalapozást szeretne a fogalomhoz, annak könnyed esti olvasmányként tudom ajánlani a Typotex kiadó <a onclick="window.open(this.href);return false;"
      href="https://www.typotex.hu/book/346/csornyei_zoltan_lambda_kalkulus">Lambda kalkulus</a> nevű lektűrjét.
  </p>
  <p class="bekezd">A Java szerint a lambda kifejezés szintaxisa a következő:</p>
  <table cellpadding="0" cellspacing="0" width="30%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Paraméterlista</b></th>
        <th class="normal"><b>Nyíl token</b></th>
        <th class="normal"><b>Törzs</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>(int x, int y)</td>
        <td>-&gt;</td>
        <td>x+y</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    A törzs lehet egyetlen kifejezés vagy pedig egy hosszabb kódblokk. A kifejezés törzse a végrehajtáskor egyszerűen kiértékelődik és visszaadódik az értéke. Kódblokk esetén a blokk egy
    metódustörzshöz hasonlóan lefut és a vezérlés egy <span class="programkod">return</span> kifejezéssel visszatér a hívóhoz. A <span class="programkod">break</span> és <span
      class="programkod">continue</span> kulcsszavak nem használhatók a törzsben a szokványos ciklusbeli használatuktól eltekintve. Amennyiben a törzs visszatérési értéket készít, akkor
    minden vezérlési útnak vissza kell adnia valamit, különben nem fordul le a kódunk. Nézzünk három példát:
  </p>
  <div class="programkod">
    <pre>(int x, int y) -&gt; x + y
() -&gt; 42
(String s) -&gt; { System.<span class="java_constant">out</span>.println(s); }
</pre>
  </div>
  <p class="bekezd">Az első kifejezés vesz két egész paramétert (x és y) és visszaadja az összegüket. A másodiknak nincs paramétere és egy egészet, 42-t ad vissza. A harmadik egy
    sztringet vesz és kiírja a konzolra, majd nem ad vissza semmit (de azt nagyon). Nézzünk egy gyakorlati példát is, mondjuk ezt, ami kiírja egy lista elemeit:</p>
  <div class="programkod">
    <pre>Arrays.asList(<span class="java_string">&quot;a&quot;</span>, <span class="java_string">&quot;b&quot;</span>, <span class="java_string">&quot;d&quot;</span>).forEach((String e) -&gt; System.<span
        class="java_constant">out</span>.println(e));</pre>
  </div>
  <p class="bekezd">
    Ezen még egyszerűsíthetünk is, ugyanis a fordító van olyan okos, hogy kikövetkezteti az <span class="programkod">e</span> típusát:
  </p>
  <div class="programkod">
    <pre>Arrays.asList(<span class="java_string">"a"</span>, <span class="java_string">"b"</span>, <span class="java_string">"d"</span>).forEach(e -&gt; System.<span
        class="java_constant">out</span>.println(e));</pre>
  </div>
  <p class="bekezd">Kódblokkot is fabrikálhatunk, ez így néz ki:</p>
  <div class="programkod">
    <pre>Arrays.asList(<span class="java_string">"a"</span>, <span class="java_string">"b"</span>, <span class="java_string">"d"</span>).forEach(e -&gt; {
        System.<span class="java_constant">out</span>.print(e);
        System.<span class="java_constant">out</span>.print(e);
    }); </pre>
  </div>
  <p class="bekezd">Java 8-ban minden collection kiegészült lambdát fogadó forEach megtódussal. A lambda kifejezések hivatkozhatják az osztály tagváltozóit és helyi változókat. A
    következő két kódrészlet ekvivalens:</p>
  <div class="programkod">
    <pre>String separator = <span class="java_string">","</span>;
Arrays.asList(<span class="java_string">"a"</span>, <span class="java_string">"b"</span>, <span class="java_string">"d"</span>).forEach((String e) -&gt; System.<span class="java_constant">out</span>.print(e + separator)); </pre>
  </div>
  <p class="bekezd">és</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">final</span> String separator = <span class="java_string">","</span>;
Arrays.asList(<span class="java_string">"a"</span>, <span class="java_string">"b"</span>, <span class="java_string">"d"</span>).forEach((String e) -&gt; System.<span class="java_constant">out</span>.print(e + separator)); </pre>
  </div>
  <p class="bekezd">
    A kifejezés típusa <b>non-capturing</b>, ha nem ér el a törzsén kívül definiált változókat és <b>capturing</b> ha igen.
  </p>
  <p class="bekezd">
    Egy lambdán kívüli nem final változó elérése lambda törzséből fordítási hibát fog okozni. Akkor hogy is van ez a fenti példánál? Nos expliciten nem kötelező minden változót final-ként
    jelölni. A Java 8 bevezette az <b>effektíven final</b> fogalmat: a fordító automatikusan minden változót final-nak tekint aminek csak egyszer adnak értéket. Lambda kifejezéseknél csak
    final vagy effektíven final lambdán kívüli változókra lehet hivatkozni. Ez a megközelítés egyszerűsíti a lambda kifejezések szálbiztossá tételét. A lambdák egyik fő célja ugyanis a
    párhuzamos feldolgozásban való használat, a szálbiztosság pedig nagyon alkalmassá teszi őket erre. Az effektíven final elképzelés sokat segít ebben de azért ez sem megoldás mindenre. A
    mutable objektumok belső állapotát továbbra is megváltoztathatják. Tekintsük a következő kódrészletet:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span>[] total = <span class="java_keyword">new</span> <span class="java_keyword">int</span>[1];
Runnable r = () -&gt; total[0]++;
r.run(); </pre>
  </div>
  <p class="bekezd">A kód helyes, a total változó effektíven final, de az általa hivatkozott objektum nem marad ugyanaz a lambda végrehajtása után. Ezt nem árt észben tartani, hogy
    elkerüljük az olyan kód írását ami nem várt módosításokat csinál.</p>
  <p class="bekezd">
    A lambda kifejezések visszatérési értékének típusát (ha van visszatérési érték) szintén kikövetkezteti a fordító. Amennyiben a lambda törzse csak egy sorból áll, akkor a <span
      class="programkod">return</span> kifejezés nem kötelező. A következő két kódrészlet ekvivalens:
  </p>
  <div class="programkod">
    <pre>Arrays.asList(<span class="java_string">"a"</span>, <span class="java_string">"b"</span>, <span class="java_string">"d"</span>).sort((e1, e2) -&gt; e1.compareTo(e2)); </pre>
  </div>
  <p class="bekezd">és</p>
  <div class="programkod">
    <pre>Arrays.asList(<span class="java_string">"a"</span>, <span class="java_string">"b"</span>, <span class="java_string">"d"</span>).sort((e1, e2) -&gt; {
    <span class="java_keyword">int</span> result = e1.compareTo(e2);
    <span class="java_keyword">return</span> result;
}); </pre>
  </div>
  <p class="bekezd">
    Lambda kifejezést persze nem lehet csak úgy ész nélkül írogatni mindenhová. A fenti példákban a <span class="programkod">sort</span> és <span class="programkod">forEach</span> metódusok
    azért használhatók ilyen módon, mert ún. funkcionális interfészeket várnak paraméterként. A nyelv tervezői ugyanis sokat gondolkodtak azon, hogyan tegyék a már létező funkcionalitást
    lambda-baráttá. A végeredmény a funkcionális interfészek elképzelése lett. A két fogalom kéz a kézben jár.
  </p>
  <h3>Funkcionális interfészek</h3>
  <p class="bekezd">
    A Java fejlesztők már korábban is használhattak funkcionális interfészt mégpedig anélkül, hogy egyáltalán tudtak volna róla. Például ezeket: <span class="programkod">java.lang.Runnable</span>,
    <span class="programkod">java.awt.event.ActionListener</span>, <span class="programkod">java.util.Comparator</span>, <span class="programkod">java.util.concurrent.Callable</span>.
    Ezekben több közös vonás is van, azonban itt most az a lényeges, hogy a definíciójuk <b>csak egy metódust</b> tartalmaz. Ezt más néven Single Abstract Method interfészeknek is hívják
    (SAM). Ezeket mindenki előszeretettel használja úgy, hogy csinál hozzájuk egy anonim belső osztályt, mint például:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> AnonymousClassTest {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">new</span> Thread(<span class="java_keyword">new</span> Runnable() {
            <span class="java_annotation">@Override</span>
            <span class="java_keyword">public</span> <span class="java_keyword">void</span> run() {
                System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Most valami remek dolog történt...&quot;</span>);
            }
        }).start();
    }
}</pre>
  </div>
  <p class="bekezd">Anonim belső osztály? Mintha ezt már hallottuk volna a lambda kifejezéseknél is! Igen, itt kapcsolódik össze a két fogalom. A Java 8-cal a SAM interfészeket némileg
    újragondolták, elnevezték funkcionális interfészeknek és immár szépen használhatók lambda kifejezésekkel (sőt metódus és konstruktor referenciákkal is, de azokról majd később). Ahol egy
    metódus funkcionális interfészt vár, ott lambda kifejezést is megadhatunk. A funkcionális interfész metódusát funkcionális metódusnak hívjuk.</p>
  <p class="bekezd">
    A gyakorlatban a funkcionális interfészek elképzelése törékeny dolog, hiszen ha bárki hozzáír még egy metódust, akkor onnantól már sok minden lesz, csak nem funkcionális. Ezért aztán
    bevezettek egy <span class="programkod">@FunctionalInterface</span> annotációt is, amivel a fordítónak meg lehet mondani, hogy amit elkövetni készülünk, az egy funkcionális interfész.
    Ha esetleg mégis megszaladna a ceruzánk és nem ez lesz az eredmény, akkor a fordító majd a kezünkre csap az annotáció hatására. Ezt például lefordítja:
  </p>
  <div class="programkod">
    <pre>
<span class="java_annotation">@FunctionalInterface</span>
<span class="java_keyword">public</span> <span class="java_keyword">interface</span> Buli <span class="java_keyword">extends</span> OsInterface {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> buliVan();
}</pre>
  </div>
  <p class="bekezd">
    De ha hozzáírunk még egy <span class="programkod">public void nagyBuliVan()</span> metódust, akkor már nem. Azt viszont elfogadja, ha a <span class="programkod">java.lang.Object</span>
    osztályból definiálunk benne absztrakt metódusokat (<span class="programkod">toString</span>, <span class="programkod">equals</span>, stb.). Ez tehát szintén szabályos:
  </p>
  <div class="programkod">
    <pre>
<span class="java_annotation">@FunctionalInterface</span>
<span class="java_keyword">public</span> <span class="java_keyword">interface</span> Buli <span class="java_keyword">extends</span> OsInterface {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> buliVan();
    
    <span class="java_keyword">public</span> String toString();

    <span class="java_keyword">public</span> <span class="java_keyword">int</span> hashCode();     
}</pre>
  </div>
  <p class="bekezd">
    Az interfészek öröklődhetnek más interfészekből és ha nem hoznak be új absztrakt metódusokat egy korábban funkcionális interfészbe, akkor továbbra is funkcionálisnak tekinthetők. Ha egy
    funkcionális interfészt bármennyi default vagy statikus metódussal kiegészítünk, még mindig funkcionális marad. (Default metódusokról később lesz szó.) A Java összes &quot;gyári&quot;
    osztálykönyvtárában lévő funkcionális interfészt is megjelölték a <span class="programkod">@FunctionalInterface</span> annotációval. A funkcionális interfészek a lambda kifejezésekkel
    immár egyszerűen kiváltják a korábbi belső osztályos megoldást:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> BuliTest {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        induljonABanzaj(<span class="java_keyword">new</span> Buli() {
            <span class="java_annotation">@Override</span>
            <span class="java_keyword">public</span> <span class="java_keyword">void</span> buliVan() {
                System.<span class="java_constant">out</span>.println(<span class="java_string">"Anonim osztállyal!"</span>);
            }
        });

        induljonABanzaj(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Lambdával könnyebb az élet! :-)&quot;</span>));
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> induljonABanzaj(Buli buli) {
        buli.buliVan();
    }

}</pre>
  </div>
  <div class="keretes">
    <p>
      A Java 8 a <b>viselkedési paraméter (behavioral parameter)</b> kifejezést használja a paraméterként átadott funkcionalitásra. Viselkedési paraméterek használata a Java 8 előtt csak
      interfészekkel és anonim osztályokkal volt megoldható, mint a fenti <span class="programkod">AnonymousClassTest</span> vagy <span class="programkod">BuliTest</span> is bemutatja. A
      Java 8 a lambda kifejezésekkel és a metódusreferenciákkal a viselkedési paraméterek leírását sokkal könnyebbé tette.
    </p>
  </div>
  <p class="bekezd">
    A Java 8 API-ja 40 funkcionális interfészt tartalmaz a <span class="programkod">java.util.function</span> package-ben, de természetesen bárki aki elég bátorságot érez magában, akárhány
    újat is definiálhat magának. Előbb azonban érdemes a <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html">beépítetteket megnézni</a>, hátha van már olyan!
  </p>
  <p class="bekezd">Nézzünk ezek közül néhány nagyon egyszerűt és használatukat!</p>
  <p class="bekezd">
    <u>Predicate&lt;T&gt;</u>: egy paraméter predikátumát reprezentálja. Olyan lambda kifejezést hozhatunk vele létre, ami egy <span class="programkod">boolean</span> értéket ad vissza a
    megadott aktuális paraméter alapján. Például meg tudjuk nézni, hogy egy átadott érték pozitív-e, majd ezt használva el tudjuk tüntetni egy listából a pozitív értékeket:
  </p>
  <div class="programkod">
    <pre>Predicate&lt;Integer&gt; isPositive = number -&gt; number &gt; 0;
Arrays.asList(10, -3, 1).removeIf(isPositive); </pre>
  </div>
  <p class="bekezd">
    Persze a lambda kifejezést egyből a <span class="programkod">removeIf</span> paramétereként is megadhatjuk, hiszen az <span class="programkod">Predicate&lt;T&gt;</span> típusú. A
    Predicate&lt;T&gt; funkcionális metódusát <span class="programkod">test</span>-nek hívják.
  </p>
  <p class="bekezd">
    <u>Consumer&lt;T&gt;</u>: olyan műveletet vár, aminek egy paramétere van és nincs visszatérési értéke. A többi funkcionális interfésztől eltérően ennek valamilyen mellékhatása van. A
    fenti forEach-es példa pont ilyen volt. Most a külön <span class="programkod">Consumer&lt;String&gt;</span> deklarációt már nem írom ki, gondolom ezek után már egyértelmű, hogy úgy is
    lehet:
  </p>
  <div class="programkod">
    <pre>Arrays.asList(<span class="java_string">&quot;a&quot;</span>, <span class="java_string">&quot;b&quot;</span>, <span class="java_string">&quot;d&quot;</span>).forEach(e -&gt; System.<span
        class="java_constant">out</span>.println(e)); </pre>
  </div>
  <p class="bekezd">
    <u>Supplier&lt;T&gt;</u>: valamilyen végeredmény létrehozója. Egyfajta gyártómetódus, ami nem vár paramétert, csak visszaad egy eredményt. Akár egy objektumpéldányt:
  </p>
  <div class="programkod">
    <pre>Supplier&lt;String&gt; domainName = () -&gt; <span class="java_string">&quot;egalizer.hu&quot;</span>;
domainName.get(); </pre>
  </div>
  <p class="bekezd">
    <u>Function&lt;T,R&gt;</u>: olyan függvényt reprezentál, ami egy T típusú paramétert fogad és egy R típusú eredményt ad.
  </p>
  <div class="programkod">
    <pre>Function&lt;Double, Double&gt; multiPi = x -&gt; x * Math.PI;
multiPi.apply(3.0); </pre>
  </div>
  <p class="bekezd">
    A Function interfésznek van egy statikus <span class="programkod">identity()</span> metódusa is amivel egyszerűen lehet olyan implementációt gyártani ami a paramétert változatlanul adja
    vissza (itt például <span class="programkod">Integer</span> típusút):
  </p>
  <div class="programkod">
    <pre>Function&lt;Integer, Integer&gt; funct = Function.&lt;Integer&gt; identity();</pre>
  </div>
  <p class="bekezd">
    A fenti alapvető funkcionális interfészeknek létezik típusos verziója is a <span class="programkod">java.util.function</span> csomagban, de ha megnézzük ezeket, meglepve fogjuk
    tapasztalni, hogy míg <span class="programkod">int</span>, <span class="programkod">long</span>, <span class="programkod">double</span>, <span class="programkod">boolean</span> primitív
    típusokat alkalmazó interfészek léteznek, a többi primitív típus - <span class="programkod">byte</span>, <span class="programkod">short</span>, <span class="programkod">float</span> és
    <span class="programkod">char</span> - mintha nem is létezne. Úgy tűnik, minden típus egyenlő de vannak egyenlőbbek. (Erről a kérdésről később még lesz szó.)
  </p>
  <p class="bekezd">A funkcionális interfészeket természetesen kombinálhatjuk is, például így:</p>
  <div class="programkod">
    <pre>Function&lt;Integer, Predicate&lt;Integer&gt;&gt; numberCheck = value -&gt; other -&gt; value &gt; other;
List&lt;Integer&gt; numbers = Arrays.asList(5, 8, 23, 56, 2);
Long numberCount = numbers.stream().filter(numberCheck.apply(5)).count(); </pre>
  </div>
  <p class="bekezd">
    A fenti kódrészletben a <span class="programkod">numberCount</span> 1 lesz. Bár teljes megértéséhez a stream-ek ismerete is szükséges lesz, most elégedjünk meg annyival, hogy a <span
      class="programkod">stream()</span> visszaadja a numbers elemeit, amiből a <span class="programkod">filter</span> a <span class="programkod">numberCheck</span> interfész által
    visszaadott predikátummal kiszűr bizonyos elemeket és azok számosságát adja vissza a <span class="programkod">count()</span>. Egyébként a leszámolást egyszerűen így is megírhattuk
    volna:
  </p>
  <div class="programkod">
    <pre>numberCount = numbers.stream().filter(p -&gt; 5 &gt; p).count(); </pre>
  </div>
  <p class="bekezd">A fentebb ismertetett előre definiált funkcionális interfészek elnevezéseivel többen is elégedetlenek voltak már a 8-as Java fejlesztése közben is. Mert például van
    ilyen:</p>
  <pre class="programkod">Function&lt;T,R&gt; </pre>
  <p class="bekezd">meg van ilyen:</p>
  <pre class="programkod">BiFunction&lt;T,U,R&gt; </pre>
  <p class="bekezd">
    Felmerül a kérdés, hogy minek kellett a <span class="programkod">BiFunction</span>-nak külön nevet adni, miért nem lett volna elég <span class="programkod">Function&lt;T,U,R&gt;</span>
    típust bevezetni? A Java 8 egyik tervezője, Brian Goetz elárulta, hogy a tervezés korai fázisában voltak akik kardoskodtak a többféle Function bevezetése mellett, de a Java-nak van egy
    olyan tulajdonsága ami miatt fájó szívvel bár de ő is leszavazta ezt a megoldást. Ez a tulajdonság pedig a típustörlés. A Java típustörlése miatt a <span class="programkod">Function&lt;T1,T2&gt;</span>
    és <span class="programkod">Function&lt;T1,T2,T3&gt;</span> vagy <span class="programkod">Function&lt;T1,T2,T3,...Tn&gt;</span> típusok között a bájtkód szintjén semmi különbség nincs.
    Ezért kreatívkodtak inkább különböző nevekkel. Amennyiben a típustörléssel egy jövőbeli verzióban majd tudnak valamit kezdeni, akkor később persze változhat a helyzet és bevezethetik a
    többféle <span class="programkod">Function</span>-t.
  </p>
  <p class="bekezd">A tisztánlátás miatt a következőkben összefoglalom az API által adott különböző funkcionális interfészeket.</p>
  <p class="bekezd">
    <u>Funkcionális interfészek <span class="programkod">void</span> visszatérési típussal.
    </u>
  </p>
  <table cellpadding="0" cellspacing="0" width="65%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Függvény típusa</b></th>
        <th class="normal"><b>Lambda kifejezés</b></th>
        <th class="normal" width="25%"><b>Ismert funkcionális interfészek</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>Nullaszoros</td>
        <td>() -&gt; doSomething()</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/lang/Runnable.html">Runnable</a></td>
      </tr>
      <tr class="tr2">
        <td>Egyszeres</td>
        <td>param -&gt; System.out.println(param)</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html">Consumer</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntConsumer.html">IntConsumer</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongConsumer.html">LongConsumer</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoubleConsumer.html">DoubleConsumer</a></td>
      </tr>
      <tr class="tr1">
        <td>Bináris</td>
        <td>(console,text) -&gt; console.print(text)</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/BiConsumer.html">BiConsumer</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/ObjIntConsumer.html">ObjIntConsumer</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/ObjLongConsumer.html">ObjLongConsumer</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/ObjDoubleConsumer.html">ObjDoubleConsumer</a></td>
      </tr>
      <tr class="tr2">
        <td>n-szeres</td>
        <td>(sender,host,text) -&gt; sender.send(host, text)</td>
        <td>sajátot kell definiálni</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    <u>Funkcionális interfészek <span class="programkod">T</span> visszatérési típussal.
    </u>
  </p>
  <table cellpadding="0" cellspacing="0" width="65%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Függvény típusa</b></th>
        <th class="normal"><b>Lambda kifejezés</b></th>
        <th class="normal" width="25%"><b>Ismert funkcionális interfészek</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>Nullaszoros</td>
        <td>() -&gt; &quot;Visszatérek!&quot;</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Callable.html">Callable</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html">Supplier</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/BooleanSupplier.html">BooleanSupplier</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntSupplier.html">IntSupplier</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongSupplier.html">LongSupplier</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoubleSupplier.html">DoubleSupplier</a></td>
      </tr>
      <tr class="tr2">
        <td>Egyszeres</td>
        <td>n -&gt; n + 1<br /> n -&gt; n &lt;= 0
        </td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/Function.html">Function</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntFunction.html">IntFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongFunction.html">LongFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoubleFunction.html">DoubleFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntToLongFunction.html">IntToLongFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntToDoubleFunction.html">IntToDoubleFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongToIntFunction.html">LongToIntFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongToDoubleFunction.html">LongToDoubleFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoubleToIntFunction.html">DoubleToIntFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoubleToLongFunction.html">DoubleToLongFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/UnaryOperator.html">UnaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntUnaryOperator.html">IntUnaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongUnaryOperator.html">LongUnaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongUnaryOperator.html">LongUnaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/Predicate.html">Predicate</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntPredicate.html">IntPredicate</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongPredicate.html">LongPredicate</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoublePredicate.html">DoublePredicate</a></td>
      </tr>
      <tr class="tr1">
        <td>Bináris</td>
        <td>(a,b) -&gt; a > b ? 1 : 0<br /> (x,y) -&gt; x + y<br /> (x,y) -&gt; x % y == 0
        </td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html">Comparator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/BiFunction.html">BiFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/ToIntBiFunction.html">ToIntBiFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/ToLongBiFunction.html">ToLongBiFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/ToDoubleBiFunction.html">ToDoubleBiFunction</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/BinaryOperator.html">BinaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/IntBinaryOperator.html">IntBinaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/LongBinaryOperator.html">LongBinaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/DoubleBinaryOperator.html">DoubleBinaryOperator</a> <a
          onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/function/BiPredicate.html">BiPredicate</a></td>
      </tr>
      <tr class="tr2">
        <td>n-szeres</td>
        <td>(sender,host,text) -&gt; sender.send(host, text)</td>
        <td>sajátot kell definiálni</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    Ezek után bónusz lambda és funkcionális interfész példaként nézzük a <span class="programkod">Runnable</span> interfész szokványos és újszerű használatát.
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> RunnableTest {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_comment">// Runnable anonim osztállyal</span>
        Runnable r1 = <span class="java_keyword">new</span> Runnable() {
            <span class="java_annotation">@Override</span>
            <span class="java_keyword">public</span> <span class="java_keyword">void</span> run() {
                System.<span class="java_constant">out</span>.println(<span class="java_string">"Hello world 1!"</span>);
            }
        };

        <span class="java_comment">// Runnable lambdával</span>
        Runnable r2 = () -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">"Hello world 2!"</span>);

        <span class="java_comment">// Fussunk!</span>
        r1.run();
        r2.run();
    }
}</pre>
  </div>
  <p class="bekezd">
    <b>Problémák</b>
  </p>
  <p class="bekezd">
    Ahogyan a költő mondja: nincsen funkcionális interfész és rózsa tövis nélkül. A tövisek egy része a metódustúlterhelésben rejlik. Tegyük fel (de tényleg csak tegyük fel) hogy van egy <span
      class="programkod">run()</span> metódusunk ami <span class="programkod">Callable</span> típusú paramétert vár. Bővíteni szeretnénk, hogy most már <span class="programkod">Supplier</span>
    típust is fogadjon el:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">static</span> &lt;T&gt; T run(Callable&lt;T&gt; c) <span class="java_keyword">throws</span> Exception {
    <span class="java_keyword">return</span> c.call();
}

<span class="java_keyword">static</span> &lt;T&gt; T run(Supplier&lt;T&gt; s) <span class="java_keyword">throws</span> Exception {
    <span class="java_keyword">return</span> s.get();
} </pre>
  </div>
  <p class="bekezd">Ez Java 7 szemmel teljesen jónak tűnik, de próbáljuk csak meghívni lambda kifejezéssel:</p>
  <div class="programkod">
    <pre>run(() -&gt; <span class="java_string">&quot;Fordulj!&quot;</span>); </pre>
  </div>
  <p class="bekezd">Ezt a fordítási hibát fogjuk kapni:</p>
  <pre class="programkod">
javac.exe hu/egalizer/java8/Test.java
hu\egalizer\java8\Test.java:57: error: reference to run is ambiguous
                run(() -&gt; "Fordulj!");
                ^
  both method &lt;T#1&gt;run(Callable&lt;T#1&gt;) in Test and method &lt;T#2&gt;run(Supplier&lt;T#2&gt;) in Test match

  where T#1,T#2 are type-variables:
    T#1 extends Object declared in method &lt;T#1&gt;run(Callable&lt;T#1&gt;)
    T#2 extends Object declared in method &lt;T#2&gt;run(Supplier&lt;T#2&gt;)
1 error </pre>
  <p class="bekezd">Ez bizony balszerencse. Ilyen esetben sajnos nem tudunk mást tenni, mint régi jól bevált megoldásokat alkalmazni:</p>
  <div class="programkod">
    <pre>
run((Callable&lt;Object&gt;) (() -&gt; <span class="java_string">&quot;Fordulj!&quot;</span>));
<span class="java_comment">// vagy:</span>
run(<span class="java_keyword">new</span> Callable&lt;Object&gt;() {
    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> Object call() <span class="java_keyword">throws</span> Exception {
        <span class="java_keyword">return</span> <span class="java_string">&quot;Fordulj!&quot;</span>;
    }
}); </pre>
  </div>
  <p class="bekezd">Persze ha a generikus T típus helyett a két metódust két konkrét eltérő típussal definiáljuk, akkor a fordító a lambda kifejezésből már nagy eséllyel kikövetkezteti,
    melyiket kell meghívni.</p>
  <p class="bekezd">Az anonim osztályoknak egyébként egyik hátránya a lambdákhoz képest, hogy a fordító minden anonim belső osztályhoz egy új class fájlt készít. A fájlnév általában
    ClassName$1.class, ahol a ClassName annak az osztálynak a neve, ahol az anonim belső osztályt definiáltuk, ezt követi a dollár jel és egy szám. Ez a megoldás többek között azért sem
    kívánatos, mert használat előtt minden class fájlt betölteni és ellenőrizni kell, ami az alkalmazás induláskori teljesítményére van hatással. Ha a lambdák is anonim belső osztályokká
    fordítódnának, akkor minden lambdához új class fájlunk lenne és minden anonim osztály helyet foglalna a metaspace-en.</p>
  <p class="bekezd">
    <b>Hatókörök</b>
  </p>
  <p class="bekezd">
    A hasonlóságok ellenére azért a lambda és az anonim osztály nem ugyanaz. Többek között abban is különböznek, hogy anonim osztály használatakor új hatókör jön létre. A befoglaló hatókör
    helyi változóit felüldefiniálhatjuk úgy, hogy azonos névvel bevezetünk új változókat. A <span class="programkod">this</span> kulcsszót is használhatjuk az anonim osztályban
    referenciaként a saját példányára, illetve a ClassName.this megoldást pedig a befoglaló osztály példányára. A lambda kifejezések viszont a befoglaló hatókörben dolgoznak és nem tudunk
    felülírni abból a hatókörből változókat a lambda belsejében. Ebben az esetben a this kulcsszó a befoglaló példányt hivatkozza. Például:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> ScopeExample {
    <span class="java_keyword">public</span> String field = <span class="java_string">"ScopeExample public field value"</span>;

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method() {
        <span class="java_keyword">final</span> String variable = <span class="java_string">"ScopeExample variable value"</span>;
        Runnable runinner = <span class="java_keyword">new</span> Runnable() {
            String field = <span class="java_string">"Runnable field value"</span>;

            <span class="java_annotation">@Override</span>
            <span class="java_keyword">public</span> <span class="java_keyword">void</span> run() {
                System.<span class="java_constant">out</span>.println(<span class="java_string">"Inner Class field: "</span> + field);
                System.<span class="java_constant">out</span>.println(<span class="java_string">"Inner Class this.field: "</span> + <span class="java_keyword">this</span>.field);
                System.<span class="java_constant">out</span>.println(<span class="java_string">"Inner Class ScopeExample.this.field: "</span> + ScopeExample.<span class="java_keyword">this</span>.field);
                System.<span class="java_constant">out</span>.println(<span class="java_string">"Inner Class variable: "</span> + variable);
                String variable = <span class="java_string">""</span>;<span class="java_comment">// megtehetjük, de ezzel innentől kezdve felüldefiniáljuk a külső variable változót</span>
            }
        }};
        Runnable runlambda = () -&gt; {
            String field = <span class="java_string">"lambda field value"</span>;
            System.<span class="java_constant">out</span>.println(<span class="java_string">"Lambda field: "</span> + field);
            System.<span class="java_constant">out</span>.println(<span class="java_string">"Lambda this.field: "</span> + <span class="java_keyword">this</span>.field);
            System.<span class="java_constant">out</span>.println(<span class="java_string">"Lambda ScopeExample.this.field: "</span> + ScopeExample.<span class="java_keyword">this</span>.field);
            System.<span class="java_constant">out</span>.println(<span class="java_string">"Lambda variable: "</span> + variable);
            <span class="java_comment">// String variable=""; - nem fordul le</span>
        };

        runinner.run();
        runlambda.run();
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">new</span> ScopeExample().method();
    }

}</pre>
  </div>
  <p class="bekezd">Ha lefuttatjuk a method() metódust akkor a következő eredményt kapjuk:</p>
  <pre class="programkod">Inner Class field: Runnable field value
Inner Class this.field: Runnable field value
Inner Class ScopeExample.this.field: ScopeExample public field value
Inner Class variable: ScopeExample variable value
Lambda field: lambda field value
Lambda this.field: ScopeExample public field value
Lambda ScopeExample.this.field: ScopeExample public field value
Lambda variable: ScopeExample variable value </pre>
  <p class="bekezd">
    Látható, hogy a <span class="programkod">this.field</span> a belső osztályban a saját mezőt éri el, míg lambda esetén a <span class="programkod">this.field</span> a <span
      class="programkod">ScopeExample</span> osztályban lévő <span class="programkod">field</span>-hez biztosít hozzáférést, nem pedig a lambda belsejében definiált <span class="programkod">field</span>
    értékéhez. Egyébként nagy kódblokkok helyett ajánlatos inkább egysoros lambdákat írni, mert a lambda egy kifejezés nem pedig egy elbeszélés kellene hogy legyen. Ez inkább csak az
    érthetőség miatt fontos, teljesítménybeli hatása egyébként nincs.
  </p>
  <h2>Újdonságok a mélyben</h2>
  <p class="bekezd">Most, hogy megismertük a Java 8 talán legfontosabb két nyelvi újítását, érdemes kissé megnézni, mi van a motorháztető alatt. Bár a 7-es verziót kevésbé jelentős
    mérföldkőnek tartják, mint a 8-at, azért nagyon sok olyan fejlesztést tartalmazott, amire szükség volt a 8-as újdonságaihoz. Érdemes megismerni ezeket az újdonságokat is, mert az egyik
    legfontosabb ilyen fejlesztés például a lambda kifejezések alapját jelenti.</p>
  <p class="bekezd">
    <b>Egy gondolat a típusokról</b>
  </p>
  <p class="bekezd">Mielőtt megnéznénk, mi van a lambda kifejezések mögött, vizsgáljuk meg kicsit az értékadások témakörét. A Java-ban az értékadás szintaxisával kapcsolatosan sokszor
    felmerült már a bőbeszédűség vádja. Java 6-ban például így kell értékadást írnunk:</p>
  <div class="programkod">
    <pre>Map&lt;String, String&gt; map = new HashMap&lt;String, String&gt;(); </pre>
  </div>
  <p class="bekezd">Ez a kifejezés redundáns információkat tartalmaz ezért jó lenne, ha a fordító több mindent magától kitalálna és a programozónak nem kellene mindent expliciten
    megfogalmaznia. Az olyan nyelv mint például a Scala a kifejezésekből nagyon sok típuskövetkeztetést meg tud csinálni és egy értékadás például így is leírható:</p>
  <pre class="programkod">val map = Map(&quot;x&quot; -&gt; 24, &quot;y&quot; -&gt; 25, &quot;z&quot; -&gt; 26); </pre>
  <p class="bekezd">
    A <span class="programkod">val</span> azt jelzi, hogy ennek a változónak ezután nem lehet újra értéket adni (mint a <span class="programkod">final</span> kulcsszó a Javában). Ebben a
    formában egyáltalán nem kell típusinformációt megadni a változóhoz, mert a Scala fordító a jobb oldali kifejezésből magától kitalálja. A változó pontos típusát meghatározza az, hogy
    milyen értéket rendelnek hozzá. A Java 7-ben is megjelent egy nagyon egyszerű típuskövetkeztetés és ennek köszönhetően az értékadások immár a következőképpen is leírhatók (az ún.
    gyémánt operátorral):
  </p>
  <div class="programkod">
    <pre>Map&lt;String, String&gt; m = new HashMap&lt;&gt;(); </pre>
  </div>
  <p class="bekezd">A fő különbség a Scala és eközött, hogy míg a Scala-ban az értékeknek van explicit típusuk, ami meghatározza a változók típusát, a Java 7-ben a változók típusa
    explicit és az értékek típusa lesz kikövetkeztetve. Bár voltak akik a Scala-szerű megoldást szerették volna látni a Java 7-ben is, az kevésbé fért volna össza a lambda kifejezésekkel.</p>
  <p class="bekezd">
    Java 8-ban a fentebb már megismert <span class="programkod">Function</span> funkcionális interfésszel így is le tudunk írni egy függvényt ami 2-t és egy egészet ad össze:
  </p>
  <div class="programkod">
    <pre>Function&lt;Integer, Integer&gt; func = x -&gt; x + 2; </pre>
  </div>
  <p class="bekezd">
    Ez a forma itt most azért jó mert hasonló a Scala-beli megfelelőjéhez. Azzal, hogy a <span class="programkod">func</span> típusát expliciten megadjuk a <span class="programkod">Function</span>-nek
    (egész típusú paramétert vár és egy másik egészet ad vissza eredményként), a Java fordító képes kikövetkeztetni az x paraméter típusát: <span class="programkod">Integer</span>. Ezt a
    mintát már láttuk a Java 7 gyémánt szintaxisban: megadjuk a változók típusát és kikövetkeztetődik az érték típusa. Lássuk ennek megfelelőját Scala nyelven:
  </p>
  <pre class="programkod">val func = (x : Int) =&gt; x + 2; </pre>
  <p class="bekezd">
    Itt expliciten meg kell adni az x paraméter típusát, mivel a <span class="programkod">func</span>-nak nincs konkrét típus megadva és enélkül nem lehetne miből következtetni.
  </p>
  <h3>Metódus handle</h3>
  <p class="bekezd">A metódus handle-ök is a Java 7-ben jelentek meg és bár a legtöbb Java fejlesztő szinte sosem találkozik velük élete folyamán, a lambda kifejezések működéséhez
    alapvető fontossággal bírnak. A metódus handle fejlesztői szemszögből tulajdonképpen egy metódusra vagy konstruktorra hivatkozó típusos referencia. Ahhoz, hogy megértsük ezt a fogalmat,
    elevenítsük fel, hogy egy Java metódus mely négy összetevőből épül fel:</p>
  <ul>
    <li>név</li>
    <li>aláírás (beleértve a visszatérési típust)</li>
    <li>az osztály, ahol definiálva lett</li>
    <li>bájtkód, ami implementálja a metódus törzsét</li>
  </ul>
  <p class="bekezd">
    Ez azt jelenti, hogy ha metódusokra akarunk hivatkozni, akkor először szükség van valamire, amivel hatékonyan ábrázolhatjuk a metódus aláírásokat (és nem a szörnyű <span
      class="programkod">Class&lt;?&gt;[]</span> buherálást használni, amire a reflection-nel kényszerítve vagyunk). A Java 7-ben bevezetett Method Handles API-ban ezt a szerepet a <span
      class="programkod">java.lang.invoke.MethodType</span> osztály játssza, aminek immutable példányai használatosak az aláírások ábrázolásához. (A metódus aláírásának throws záradéka
    semmilyen szerepet nem játszik a metódus handle esetén, azzal valójában a Java fordítón kívül más nem is foglalkozik, a bájtkódban már nem jelenik meg.)
  </p>
  <p class="bekezd">
    Egy <span class="programkod">MethodType</span> példány megszerzéséhez a <span class="programkod">methodType</span> gyártómetódust kell használni. Ez egy nem rögzített paraméterszámmal
    meghívható (variadic) metódus, ami paraméterként class objektumokat vár. Az első paraméter a visszatérési típusnak megfelelő class objektum, a többi pedig a metódusparaméterek
    típusainak megfelelő class objektum-felsorolás. Például:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// toString() aláírása</span>
MethodType mtToString = MethodType.methodType(String.<span class="java_keyword">class</span>);
<span class="java_comment">// Setter metódus aláírása</span>
MethodType mtSetter = MethodType.methodType(<span class="java_keyword">void</span>.<span class="java_keyword">class</span>, Object.<span class="java_keyword">class</span>);
<span class="java_comment">// A Comparator&lt;String&gt; compare() metódusának aláírása</span>
MethodType mtStringComparator = MethodType.methodType(<span class="java_keyword">int</span>.<span class="java_keyword">class</span>, String.<span class="java_keyword">class</span>, String.<span
        class="java_keyword">class</span>); </pre>
  </div>
  <p class="bekezd">
    Ahhoz, hogy metódus handle-ünk legyen, egy <span class="programkod">MethodType</span> mellett kell a metódust definiáló név és osztály is. A handle-t ezek birtokában a statikus <span
      class="programkod">MethodHandles.lookup()</span> metódussal kapjuk meg. Ez egy ún. &quot;lookup kontextust&quot; ad vissza (egy <span class="programkod">MethodHandles.Lookup</span>
    példány), ami az aktuálisan futó (vagyis a lookup-ot hívó) metódus elérési jogain alapul. A lookup reprezentálja gyakorlatilag azt a helyet a kódunkban ahol létre akarjuk hozni a
    metódus handle-t. Létezik <span class="programkod">MethodHandles.publicLookup()</span> is, amivel csak a publikus metódusokat tudjuk elérni (privát vagy protected metódusok esetén ennél
    <span class="programkod">java.lang.IllegalAccessException</span> kivételt kapunk). A <span class="programkod">Lookup</span> objektumnak számos olyan metódusa van, aminek a neve
    &quot;find&quot; szóval kezdődik. Ilyen például a <span class="programkod">findVirtual()</span>, <span class="programkod">findConstructor()</span>, <span class="programkod">findStatic()</span>.
    Ezek a metódusok fogják visszaadni a tulajdonképpeni metódus handle-t, de csak ha a lookup kontextust olyan metódusban hozták létre, ami elérheti (meghívhatja) a kért metódust. (A
    find... metódusok nevéből elég egyértelműen kikövetkeztethető, hogy melyik milyen metódus handle megszerzésére való.) A reflection-től eltérően nincs mód rá, hogy kicselezzük ezt a
    hozzáférés-szabályozást (nincs <span class="programkod">setAccessible()</span>-höz hasonló metódusuk), de az objektumot már továbbadhatjuk olyan metódusoknak amiknek egyébként nem lenne
    joguk meghívni azt a metódust. Persze csak ha megbízunk bennük... Ja, és míg el nem felejtem: a <span class="programkod">MethodHandle</span> példányai is immutable tulajdonságúak!
  </p>
  <p class="bekezd">
    Metódus handle-t egyébként meglepő módon nem csak metódusokra, hanem mezőkre is létre tudunk hozni. Ezekre a <span class="programkod">Lookup</span> objektum <span class="programkod">findGetter</span>,
    <span class="programkod">findSetter</span> illetve ezek statikus mezőkre vonatkozó változatai használhatóak. A nevük ne legyen megtévesztő: nem szükséges, hogy a mezőkre legyen konkrét
    getter vagy setter definiálva az osztályban, ezek anélkül is használhatóak (példát lentebb lehet találni). (A Java 9-ben egyébként megjelent a <span class="programkod">java.lang.invoke.VarHandle</span>
    API is, ami bevezeti a változó handle-öket.)
  </p>
  <p class="bekezd">
    Egy példa arra, hogyan tudunk metódus handle-t előállítani (a példa saját osztályának <span class="programkod">toString()</span> metódusára hoz létre egy metódus handle-t):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> MethodHandle getToStringMH() {
    MethodHandle result = <span class="java_keyword">null</span>;
    MethodType mt = MethodType.methodType(String.<span class="java_keyword">class</span>);
    MethodHandles.Lookup lk = MethodHandles.lookup();
    <span class="java_keyword">try</span> {
        result = lk.findVirtual(getClass(), <span class="java_string">"toString"</span>, mt);
    } <span class="java_keyword">catch</span> (NoSuchMethodException | IllegalAccessException ex) {
       <span class="java_keyword">throw</span> (AssertionError) <span class="java_keyword">new</span> AssertionError().initCause(ex);
    }
    <span class="java_keyword">return</span> result;
}</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">MethodHandle</span>-nek két metódusa van, amivel meghívhatjuk a metódus handle-t. Mindkét metódus a fogadó objektumot és a paramétereket várja, az aláírásuk:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">final</span> Object invoke(Object... args) <span class="java_keyword">throws</span> Throwable;
<span class="java_keyword">public</span> <span class="java_keyword">final</span> Object invokeExact(Object... args) <span class="java_keyword">throws</span> Throwable; </pre>
  </div>
  <p class="bekezd">
    A kettő között az a különbség, hogy az <span class="programkod">invokeExact()</span> megpróbálja közvetlenül a pontos argumentumokkal hívni a metódus handle-t. Az <span
      class="programkod">invoke()</span> viszont szükség esetén tudja kissé módosítani a metódus argumentumait. Végez például egy <span class="programkod">asType()</span> konverziót, ami át
    tudja alakítani az argumentumokat az alábbi szabályoknak megfelelően:
  </p>
  <ul>
    <li>a primitív típusok boxing-ja ha szükséges</li>
    <li>a boxing-olt primitívek unboxingja ha szükséges</li>
    <li>a primitív típusok kiterjesztése ha szükséges</li>
    <li>egy <span class="programkod">void</span> visszatérési típus 0-vá konvertálása (primitív visszatérési típusokhoz) vagy <span class="programkod">null</span>-á konvertálása
      referencia típusokhoz
    </li>
    <li>a <span class="programkod">null</span> értékek szabályosnak számítanak és átadhatók a statikus típusoktól függetlenül
    </li>
  </ul>
  <p class="bekezd">
    Lássunk néhány egyszerű meghívási példát! A legelső eset a legegyszerűbb is; egy <span class="programkod">InvokeExample</span> példányon meghívjuk a <span class="programkod">sayHello</span>
    virtuális metódust:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.lang.invoke.MethodHandle;
<span class="java_keyword">import</span> java.lang.invoke.MethodHandles;
<span class="java_keyword">import</span> java.lang.invoke.MethodType;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> InvokeExample {

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> sayHello(String name) {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Hello &quot;</span> + name);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Throwable {
        MethodHandle sayHelloHandle = MethodHandles.lookup().findVirtual(
            InvokeExample.<span class="java_keyword">class</span>, <span class="java_string">&quot;sayHello&quot;</span>, MethodType.methodType(<span class="java_keyword">void</span>.<span
        class="java_keyword">class</span>, String.<span class="java_keyword">class</span>));
        sayHelloHandle.invoke(<span class="java_keyword">new</span> InvokeExample(), <span class="java_string">&quot;Morgan&quot;</span>);
    }
}</pre>
  </div>
  <p class="bekezd">
    A kívánt objektumot &quot;kötni&quot; (bind) is lehet a metódus handle-höz, így megspóroljuk az átadását a többszörös invokációnál: A fenti <span class="programkod">main</span> metódus
    ez esetben így néz ki (a lookup változatlan):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Throwable {
    MethodHandle sayHelloHandle = MethodHandles.lookup().findVirtual(
        InvokeExample.<span class="java_keyword">class</span>, <span class="java_string">&quot;sayHello&quot;</span>, MethodType.methodType(<span class="java_keyword">void</span>.<span
        class="java_keyword">class</span>, String.<span class="java_keyword">class</span>));
    MethodHandle binded = sayHelloHandle.bindTo(<span class="java_keyword">new</span> InvokeExample());
    binded.invokeWithArguments(<span class="java_string">&quot;T-Rex&quot;</span>);
}</pre>
  </div>
  <p class="bekezd">
    Mivel a <span class="programkod">MethodHandle</span> immutable, ezért új példány készül belőle amikor megadjuk neki a használandó objektumot, aztán onnantól kezdve már lehet durvulni az
    <span class="programkod">invokeWithArguments</span> metódussal ahol ezt már nem kell újra megtenni. Ha esetleg ezzel bind nélkül próbálkoznánk, akkor egy szépséges <span
      class="programkod">java.lang.invoke.WrongMethodTypeException</span> lesz a jutalmunk.
  </p>
  <p class="bekezd">
    Következzen egy icipicit trükkösebb példa egy <span class="programkod">String hashCode</span> metódusának meghívásához:
  </p>
  <div class="programkod">
    <pre>
Object rcvr = <span class="java_string">&quot;baromijó&quot;</span>;
MethodType mt = MethodType.methodType(<span class="java_keyword">int</span>.<span class="java_keyword">class</span>);
MethodHandles.Lookup l = MethodHandles.lookup();
MethodHandle mh = l.findVirtual(rcvr.getClass(), <span class="java_string">&quot;hashCode&quot;</span>, mt);
<span class="java_keyword">int</span> ret = (<span class="java_keyword">int</span>) mh.invoke(rcvr);
System.<span class="java_constant">out</span>.println(ret);</pre>
  </div>
  <p class="bekezd">
    Egy példa a getterek, setterek használatára (getterek és setterek esetén <span class="programkod">MethodType</span>-ot nem kell megadni, csak a mező típusát):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.lang.invoke.MethodHandle;
<span class="java_keyword">import</span> java.lang.invoke.MethodHandles;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> InvokeGetterExample {

    String eztKapdKi;

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Throwable {
        InvokeGetterExample iex = <span class="java_keyword">new</span> InvokeGetterExample();
        iex.eztKapdKi = <span class="java_string">"első"</span>;
        MethodHandle getterHandle = MethodHandles.lookup().
            findGetter(InvokeGetterExample.<span class="java_keyword">class</span>, <span class="java_string">"eztKapdKi"</span>, String.<span class="java_keyword">class</span>);
        System.<span class="java_constant">out</span>.println(getterHandle.invoke(iex));<span class="java_comment">// első</span>
        MethodHandle setterHandle = MethodHandles.lookup().
            findSetter(InvokeGetterExample.<span class="java_keyword">class</span>, <span class="java_string">"eztKapdKi"</span>, String.<span class="java_keyword">class</span>);
        setterHandle.invoke(iex, <span class="java_string">"második"</span>);
        System.<span class="java_constant">out</span>.println(getterHandle.invoke(iex));<span class="java_comment">// második</span>
    }
}</pre>
  </div>
  <p class="bekezd">
    A metódus handle-öket tömbökkel is meghívhatjuk, ekkor az <span class="programkod">asSpreader()</span> metódussal készíteni kell belőle egy új példányt, majd azt már bombázhatjuk
    tömbökbe rejtett paraméterekkel. A tömböt mindig a paraméterlista végén kell megadni és a paramétereknek csak egy részét is tartalmazhatja, a paraméterszámot az <span class="programkod">asSpreader()</span>-nek
    kell megadni. Meghíváskor az aktuális paraméterek pozícióinak természetesen igazodni kell a metódus aláírásához. Egy példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.lang.invoke.MethodHandle;
<span class="java_keyword">import</span> java.lang.invoke.MethodHandles;
<span class="java_keyword">import</span> java.lang.invoke.MethodType;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> SpreaderExample {
    String content;

    <span class="java_keyword">public</span> <span class="java_keyword">boolean</span> equals(SpreaderExample a, SpreaderExample b) {
        <span class="java_keyword">return</span> a.content.equals(b.content) &amp;&amp; b.content.equals(content);
    }

    <span class="java_keyword">public</span> SpreaderExample(String content) {
        <span class="java_keyword">super</span>();
        <span class="java_keyword">this</span>.content = content;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Throwable {
        MethodHandle equals = MethodHandles.lookup().findVirtual(SpreaderExample.<span class="java_keyword">class</span>, <span class="java_string">&quot;equals&quot;</span>,
                MethodType.methodType(<span class="java_keyword">boolean</span>.<span class="java_keyword">class</span>, SpreaderExample.<span class="java_keyword">class</span>, SpreaderExample.<span
        class="java_keyword">class</span>));
        <span class="java_comment">// minden paramétert tömbbe teszünk</span>
        MethodHandle methodHandle = equals.asSpreader(Object[].<span class="java_keyword">class</span>, 3);
        System.<span class="java_constant">out</span>.println(
                (<span class="java_keyword">boolean</span>) methodHandle.invokeExact(<span class="java_keyword">new</span> Object[] { <span class="java_keyword">new</span> SpreaderExample(<span
        class="java_string">"java"</span>), <span class="java_keyword">new</span> SpreaderExample(<span class="java_string">"java"</span>), <span class="java_keyword">new</span> SpreaderExample(<span
        class="java_string">"java"</span>) }));
        <span class="java_comment">// true</span>

        <span class="java_comment">// csak a paraméterek egy részét adjuk át tömbként</span>
        methodHandle = equals.asSpreader(Object[].<span class="java_keyword">class</span>, 1);
        System.<span class="java_constant">out</span>.println(
                (<span class="java_keyword">boolean</span>) methodHandle.invokeExact(<span class="java_keyword">new</span> SpreaderExample(<span class="java_string">"java"</span>), <span
        class="java_keyword">new</span> SpreaderExample(<span class="java_string">"java1"</span>), <span class="java_keyword">new</span> Object[] { <span class="java_keyword">new</span> SpreaderExample(<span
        class="java_string">"java"</span>) }));
        <span class="java_comment">// false</span>
    }
}</pre>
  </div>
  <p class="bekezd">Bonyolultabb feladatok esetén a metódus handle-ök sokkal tisztább módszert kínálnak a dinamikus programozási feladatok megoldásához, mint a reflection. Ráadásul a
    metódus handle-öket már a kezdetektől úgy tervezték, hogy jól működjenek a JVM alacsony szintű végrehajtási modelljével és ezért bizony sokkal jobb teljesítményt is nyújthatnak. (Bár a
    teljesítmény kérdése eléggé komplex dolog ez esetben. A teljesítménybeli javulás egyik oka az, hogy a jogosultságellenőrzés a metódus handle-öknél létrehozási időben történik,
    reflection esetén pedig hívási időben.)</p>
  <h3>invokedynamic</h3>
  <p class="bekezd">Szintén még a Java 7-ben jelent meg a lambda kifejezések működéséhez másik alapvető fontosságú összetevő. Ez pedig az első új bájtkód a Java 1.0 óta: az
    invokedynamic. Ez a bájtkód eredetileg arra lett tervezve, hogy dinamikus működést biztosítson olyan JVM-en futó nyelveknek, mint például a JRuby. Java 7-es fejlesztőknek ezt szinte
    lehetetlen kihasználni, mert a 7-es javac semmilyen körülmények között nem generál olyan class fájlt, ami tartalmazná. Az invokedynamic kezdeti munkálatai még 2007-re nyúlnak vissza, az
    első sikeres dinamikus meghívás pedig 2008. augusztus 26-án történt. Ez még a Sun felvásárlását is megelőzte, tehát ezen a tulajdonságon elég régóta dolgoztak, legalábbis informatikai
    mércével mérve. Az volt a szándék, hogy a felhasználói kód határozza meg a vezérlésátadást a metódus handle API-val de úgy, hogy az ne szenvedjen a reflection teljesítménybeli és
    biztonsági problémáitól. Konkrét cél volt, hogy amikor rendesen kiforrott lesz, ugyanolyan gyors legyen mint a szokványos metódushívás (invokevirtual).</p>
  <p class="bekezd">Egy átlagos Java metódushívás olyan bájtkóddá fordul, amit hívási helynek (call site) hívnak. Ez tartalmaz egy vezérlésátadási opkódot (például invokevirtual a
    példánymetódusok híváshoz) és egy konstanst (egy offszet az osztály konstanskészletében), ami megmutatja, hogy melyik metódust kell meghívni. A különböző vezérlésátadási opkódoknak
    különböző szabályai vannak, de a Java 7-ig a konstans mindig egyértelmű útmutatást adott arra nézvést, hogy melyik metódust kell meghívni.</p>
  <p class="bekezd">Az invokedynamic a már létező metódushívást vezérlő bájtkódokhoz csatlakozik. Ez a négy opkód implementált minden metódushívást, amit a Java programozók használtak a
    Java 7-ig, vagyis:</p>
  <ul>
    <li><b>invokevirtual</b>: szabványos hívás a példánymetódusokhoz</li>
    <li><b>invokestatic</b>: szabványos hívás a statikus metódusokhoz</li>
    <li><b>invokeinterface</b>: szabványos hívás az interface-eken keresztüli metódushívásokhoz</li>
    <li><b>invokespecial</b>: amikor nem virtuális, hanem például pontos metódushívás szükséges</li>
  </ul>
  <p class="bekezd">Felmerülhet a kínzó kérdés: miért van szükség négy opkódra? Nézzünk egy egyszerű példát, ami olyan zseniális, hogy a négyből három opkódot tartalmaz:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.ArrayList;
<span class="java_keyword">import</span> java.util.List;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> TestInvoke {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        TestInvoke test = <span class="java_keyword">new</span> TestInvoke();
        test.run();
    }

    <span class="java_keyword">private</span> <span class="java_keyword">void</span> run() {
        List&lt;String&gt; list1 = <span class="java_keyword">new</span> ArrayList&lt;&gt;();
        list1.add(<span class="java_string">"Kirk"</span>);

        ArrayList&lt;String&gt; list2 = <span class="java_keyword">new</span> ArrayList&lt;&gt;();
        list2.add(<span class="java_string">"Spock"</span>);
    }

}</pre>
  </div>
  <p class="bekezd">A javap eszközzel fejtsük vissza a bájtkódot:</p>
  <pre class="programkod">javap.exe -c -private hu.egalizer.java8.TestInvoke
Compiled from &quot;TestInvoke.java&quot;
public class hu.egalizer.java8.TestInvoke {
  public hu.egalizer.java8.TestInvoke();
    Code:
       0: aload_0
       1: invokespecial #8              // Method java/lang/Object.&quot;&lt;init&gt;&quot;:()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: new           #1              // class hu/egalizer/java8/TestInvoke
       3: dup
       4: invokespecial #16             // Method &quot;&lt;init&gt;&quot;:()V
       7: astore_1
       8: aload_1
       9: invokespecial #17             // Method run:()V
      12: return

  private void run();
    Code:
       0: new           #23             // class java/util/ArrayList
       3: dup
       4: invokespecial #25             // Method java/util/ArrayList.&quot;&lt;init&gt;&quot;:()V
       7: astore_1
       8: aload_1
       9: ldc           #26             // String Kirk
      11: invokeinterface #28,  2    // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
      16: pop
      17: new           #23             // class java/util/ArrayList
      20: dup
      21: invokespecial #25             // Method java/util/ArrayList.&quot;&lt;init&gt;&quot;:()V
      24: astore_2
      25: aload_2
      26: ldc           #34             // String Spock
      28: invokevirtual #36             // Method java/util/ArrayList.add:(Ljava/lang/Object;)Z
      31: pop
      32: return </pre>
  <p class="bekezd">Ez a négyből három opkódot bemutat (a kimaradt invokestatic pedig elég triviálisan előhozható). Elsőként nézzük meg a következő két hívást (a run metódus 11. és 28.
    bájtjánál):</p>
  <div class="programkod">
    <pre>list1.add(<span class="java_string">"Kirk"</span>);
list2.add(<span class="java_string">"Spock"</span>); </pre>
  </div>
  <p class="bekezd">
    Ezek a forráskódban hasonlónak tűnnek, de a bájtkódban már első látásra különböznek. A javac számára a <span class="programkod">list1</span>-nek statikus <span class="programkod">List&lt;String&gt;</span>
    típusa van és a <span class="programkod">List</span> egy interface. Ez esetben nem lehet meghatározni fordítási időben a metódustábla indexet. Az invokeinterface a metódus kikeresését
    futási időre halasztja. A <span class="programkod">list2.add(&quot;Spock&quot;);</span> hívást viszont már a <span class="programkod">list2</span>-n kell elvégezni, ami osztály típusú:
    <span class="programkod">ArrayList&lt;String&gt;</span>. Ez azt jelenti, hogy a metódus indexe már fordítási időben ismert. A fordító tehát egy invokevirtual utasítást tud generálni a
    pontos vtable bejegyzéshez. A metódus végleges kiválasztása természetesen most is csak futásidőben fog megtörténni, hiszen ezzel van biztosítva a metódus felüldefiniálás, de a vtable
    bejegyzés indexe már fordítási időben meghatározható. A példa ezeken kívül az invokespecial lehetséges felhasználási eseteit is bemutatja. Ez az opkód olyan esetekben használatos,
    amikor a metódusfelülírás vagy nem kívánatos vagy nem lehetséges. A mintakód által bemutatott két példa a privát metódusokat és a super hívásokat mutatja (ezeken kívül még a
    konstruktorokat is így hívjuk).
  </p>
  <div class="keretes">
    <h3>Intervirtuális metódusinvokáció</h3>
    <p>Nem kell megrémülni a fenti címtől, csak egy őrült hajnalon született meg a billentyűim között amint az alább elmesélendő történeten dolgoztam.</p>
    <p>
      Azt még az egyszeri ember is tudja, hogy Javában a metódusok alapértelmezetten - néhány kivételtől eltekintve - virtuálisak. Ez azt jelenti, hogy egy leszármazott osztályban azonos
      aláírással rendelkező metódus felülírhatja, vagyis megváltoztathatja a metódus &quot;viselkedését&quot;. (Ezért nincs is külön <b>virtual</b> kulcsszó a nyelvben, mint például a
      C++-ban vagy a szép emlékű Delphi/Object Pascalban.) A virtuális metódushívások belső megvalósításához a JVM (legalábbis a HotSpot) sok más nyelvhez hasonlóan egy ún. virtuális
      metódustáblát használ. Ez a metódustábla hivatkozásokat tartalmaz az osztály összes metódusának bájtkódjára és metódushíváskor ezt használja fel a JVM, hogy tudja, hová kell ugrania a
      vezérlésnek. Ezt a táblát minden osztály az ősétől örökli és kiterjeszti saját metódusainak bejegyzéseivel.
    </p>
    <p>Egy példa többet ér ezer szónál ér ezért tekintsük a következő két osztályt:</p>
    <div class="programkod">
      <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">class</span> BaseClass {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method1() {
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method2() {
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method3() {
    }
}

<span class="java_keyword">public</span> <span class="java_keyword">class</span> ChildClass <span class="java_keyword">extends</span> BaseClass {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method2() {
    }<span class="java_comment">// felülírja a BaseClass metódusát</span>

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method4() {
    }
} </pre>
    </div>
    <p>Ebben az esetben a virtuális metódustábla logikailag valahogy így néz ki:</p>
    <p>
      <span class="programkod">BaseClass</span>
    </p>
    <ol>
      <li><span class="programkod">BaseClass/method1()</span></li>
      <li><span class="programkod">BaseClass/method2()</span></li>
      <li><span class="programkod">BaseClass/method3()</span></li>
    </ol>
    <p>
      <span class="programkod">ChildClass</span>
    </p>
    <ol>
      <li><span class="programkod">BaseClass/method1()</span></li>
      <li><span class="programkod">ChildClass/method2()</span></li>
      <li><span class="programkod">BaseClass/method3()</span></li>
      <li><span class="programkod">ChildClass/method4()</span></li>
    </ol>
    <p>
      A <span class="programkod">BaseClass</span> metódustáblájának 1. indexe a <span class="programkod">method1()</span> metódus bájtkódjára hivatkozik, és így tovább. A <span
        class="programkod">ChildClass</span> metódustáblája megtartja (&quot;lemásolja&quot;) ősének a tartalmát és sorrendjét és csak a <span class="programkod">method2()</span>
      hivatkozást írja abban felül (a <span class="programkod">method4()</span>-et pedig hozzáfűzi). Az invokevirtual bájtkód implementációját így optimalizálni lehet azért, mert a <span
        class="programkod">method3(</span>) metódus mindig a 3. bejegyzés a virtuális metódustáblában bármely olyan objektum esetén amelyen ez a metódus valaha meg fog hívódni. A tábla
      indexe már fordítási időben meghatározható, a hívás egyszerű és gyors.
    </p>
    <p>Az invokeinterface esetén viszont ilyen optimalizálás nem lehetséges! Tekintsük a következő példát:</p>
    <div class="programkod">
      <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> MyInterface {
    <span class="java_keyword">void</span> ifaceMethod();
}

<span class="java_keyword">class</span> AnotherClass <span class="java_keyword">extends</span> ChildClass <span class="java_keyword">implements</span> MyInterface {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method4() {
    }<span class="java_comment">// a ChildClass felülírása</span>

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> ifaceMethod() {
    }
}

<span class="java_keyword">class</span> MyClass <span class="java_keyword">implements</span> MyInterface {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> method5() {
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> ifaceMethod() {
    }
} </pre>
    </div>
    <p>Ez esetben a virtuális metódustábla valahogy így fog kinézni:</p>
    <p>
      <span class="programkod">AnotherClass</span>
    </p>
    <ol>
      <li><span class="programkod">BaseClass/method1()</span></li>
      <li><span class="programkod">ChildClass/method2()</span></li>
      <li><span class="programkod">BaseClass/method3()</span></li>
      <li><span class="programkod">AnotherClass/method4()</span></li>
      <li><span class="programkod">MyInterface/ifaceMethod()</span></li>
    </ol>
    <p>
      <span class="programkod">MyClass</span>
    </p>
    <ol>
      <li><span class="programkod">MyClass/method5(</span>)</li>
      <li><span class="programkod">MyInterface/ifaceMethod()</span></li>
    </ol>
    <p>
      Látható, hogy az <span class="programkod">AnotherClass</span> az interfész implementált metódusát az 5. bejegyzésen tárolja, a <span class="programkod">MyClass</span> pedig a
      másodikon. A kívánt interfész metódus meghívásához az invokeinterface implementációjának mindig végig kell néznie a teljes metódustáblát és az invokevirtual-hoz hasonló optimalizálás
      nem lehetséges. Ezen kívül vannak még egyéb különbségek is, amelyek további hátrányt okoznak az invokeinterface esetén a teljesítményben. Az invokeinterface például olyan objektum
      referenciákkal is használható, amelyek valójában nem is implementálják az interface-t, így azt is mindig futásidőben meg kell vizsgálni, hogy egy metódus létezik-e egyáltalán a
      táblában és ha nem akkor kivételt kell dobni. A modern JVM-implementációk persze számos varázslatot bevetnek annak érdekében, hogy az invokeinterface és invokevirtual között ne legyen
      számottevő teljesítménybeli különbség, de mivel nem JVM-kézikönyvet írok, erről a témakörről talán elég is ennyi ízelítő.
    </p>
  </div>
  <p class="bekezd">
    Az invokevirtual bájtkód egy kétbájtos paramétert használ, ami egy index az osztály futásidejű konstanskészletéhez (constant pool). Azon az indexen található egy szimbolikus referencia
    a kívánt metódushoz (ezt a <span class="programkod">javap</span> szépen ki is írja nekünk: <span class="programkod">java/util/ArrayList.add:(Ljava/lang/Object;)Z)</span>. Azt a JVM
    feloldja és azután történik a metódushívás. Az invokestatic és invokespecial is egy konstanskészlet-indexet kap paraméterként. Az invokeinterface-nek három paramétere van. Első ezek
    közül szintén konstanskészlet-index. A második paraméter a paraméterek méretét határozza meg, a negyedik pedig konstansként 0, ennek a két plusz paraméternek ma már nincs jelentősége,
    csak történelmi okokból szerepel és a visszamenőleges kompatibilitás miatt van megtartva.
  </p>
  <p class="bekezd">Az invokedynamic eltér a korábbi metódushívási módszerektől. Ahelyett, hogy egy olyan referenciára hivatkozna a konstanskészletben, ami közvetlenül megmutatja, hogy
    melyik metódust kell meghívni, az invokedynamic egy indirekciós mechanizmust valósít meg, ami lehetővé teszi, hogy a felhasználói kód döntse el futásidőben, melyik metódust kell
    meghívni.</p>
  <p class="bekezd">
    Az invokedynamic utasításoknak nincs fogadó objektumuk, hanem az invokestatic-hoz hasonlóan viselkednek: egy statikus, ún. bootstrap metódust (BSM) hívnak meg, ami egy <span
      class="programkod">CallSite</span> típusú objektumot ad vissza. Ez egy (target-nek hívott) metódus handle-t tartalmaz, ami azt a metódust reprezentálja, amit majd végre kell hajtani
    az invokedynamic utasítás eredményeként. De maga az invokedynamic csak ennek az előállításáért felel a BSM-en keresztül. Amikor egy invokedynamic-ot tartalmazó osztály betöltődik, a
    Java terminológia azt mondja, hogy a hívási helyek még nincsenek befűzve (unlaced állapot), de miután a BSM visszatér, az eredményül kapott <span class="programkod">CallSite</span> és
    metódus handle már a hívási helyre &quot;befűzött&quot; (laced) állapotban van.
  </p>
  <p class="bekezd">A Java 8 fordítója már generál invokedynamic-ot és ez használatos a motorháztető alatt a lambda kifejezések és alapértelmezett metódusok implementálására és ez az
    elsődleges hívási módszer a Nashorn (JavaScript motor) számára. (Egyébként a Java 8 korai prototípusaiban a lambda kifejezések még anonim belső osztályokká fordítódtak.)</p>
  <p class="bekezd">
    A BSM-nek elvileg bármilyen neve lehet, Java 8-ban a lambda kifejezésekhez az osztálykönyvtár biztosít egy ún. lambda metafactory-t BSM-ként. Ezt a <span class="programkod">java.lang.invoke.LambdaMetafactory.altMetafactory()</span>
    metódus valósítja meg. Ennek az aláírása valahogy így néz ki:
  </p>
  <div class="programkod">
    <pre>static CallSite altMetafactory(MethodHandles.Lookup caller, String invokedName,
    MethodType invokedType, Object... args); </pre>
  </div>
  <p class="bekezd">
    Tehát amikor a vezérlés elsőként ér el egy invokedynamic bájtkódot, meghívódik a BSM, ami visszaad egy <span class="programkod">CallSite</span> objektumot. Ez egy metódus handle-t
    tartalmaz, ami már tartalmazza az invokedynamic hívás valódi eredményét, vagyis hogy valójában melyik metódust kell majd meghívni. Így a bootstrap metóduson keresztül tudja a
    felhasználói kód megadni a hívandó metódust.
  </p>
  <p class="bekezd">A fordító a lambda kifejezést átalakítja egy ún. &quot;szintetikus metódussá&quot;, aminek megvan a megfelelő aláírása és tartalmazza a lambda törzsét is. A lambda
    metafactory BSM-jének a paraméterei között kell egy metódus handle erre a szintetikus metódusra és a lambda megfelelő aláírására. Egy kifejezés tehát mint ez:</p>
  <div class="programkod">
    <pre>Function&lt;Integer, Integer&gt; fn = x -&gt; x + 2; </pre>
  </div>
  <p class="bekezd">erre az invokedynamic hívásra fog lefordulni:</p>
  <pre class="programkod">
     0: invokedynamic #19,  0         // InvokeDynamic #0:apply:()Ljava/util/function/Function;
     5: astore_1 </pre>
  <p class="bekezd">
    Amikor egy invokedynamic utasítás befejeződik, egy objektum áll a verem tetején, ami implementálja a <span class="programkod">Function</span> interface-t és amely tartalmazza a lambda
    kifejezést a saját <span class="programkod">apply()</span> metódusának törzseként. A kód további része ezek után szokványosan fut tovább.
  </p>
  <p class="bekezd">A szintetikus metódust a Java 8 fordító privátként gyártja le a fenti kód esetén a következő bájtkóddal (látható, hogy ennek megvan a szükséges metódus aláírása: egy
    egészet vár és azzal tér vissza):</p>
  <pre class="programkod">private static java.lang.Integer lambda$0(java.lang.Integer);
  Code:
     0: aload_0
     1: invokevirtual #40             // Method java/lang/Integer.intValue:()I
     4: iconst_2
     5: iadd
     6: invokestatic  #46             // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
     9: areturn </pre>
  <p class="bekezd">A non-capturing lambdák egyszerű statikus metódussá fordítódnak, aminek pontosan olyan aláírása van mint a lambda kifejezésnek és ugyanabban az osztályban készül el
    amiben a lambda kifejezés használatban van. Az ilyen metódusok nevében a $0, $1, stb... nem belső osztályt jelent, hanem csak azt mutatja, hogy ez egy fordító által generált kód. A
    capturing lambda esete kicsit összetettebb mert a használt változókat a lambda formális paramétereivel együtt át kell valahogyan adni a generált metódusnak. Ez esetben a használt
    változók is paraméterként kerülnek átadásra (ezért is final típusúak logikailag a lambda kifejezésnek) és a metódus aláírásában megelőzik a lambda kifejezés paramétereit. Ez a megoldás
    egyébként nincs kőbe vésve, mert az invokedynamic bájtkód használata révén a jövőben más megoldás is lehetséges.</p>
  <p class="bekezd">Ha esetleg az egész invokedynamic még mindig zavaros lenne, lássuk Java kód szintaxissal is, hogy működik logikailag a dolog. Legyen mondjuk a következő
    kódrészletünk:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">void</span> printElements(List&lt;String&gt; strings) {
    strings.forEach(item -&gt; System.out.printf(<span class="java_string">"Item = %s"</span>, item));
} </pre>
  </div>
  <p class="bekezd">Amikor ezt a Java fordító lefordítja, akkor logikailag valami ennek megfelelő dolog készül belőle:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> lambda_forEach(String item) {
    <span class="java_comment">// ezt a Java fordító generálja</span>
    System.out.printf(<span class="java_string">"Item = %s"</span>, item);
}

<span class="java_keyword">private</span> <span class="java_keyword">static</span> CallSite bootstrapLambda(Lookup lookup, String name, MethodType type
        <span class="java_keyword">throws</span> LambdaConversionException, NoSuchMethodException, IllegalAccessException {
    <span class="java_comment">// lookup : a VM biztosítja</span>
    <span class="java_comment">// name : &quot;lambda_forEach&quot;, a VM biztosítja</span>
    <span class="java_comment">// type : String -&gt; void</span>
    MethodHandle lambdaImplementation = lookup.findStatic(lookup.lookupClass(), name, type);
    <span class="java_keyword">return</span> LambdaMetafactory.metafactory(lookup, <span class="java_string">"accept"</span>,
        MethodType.methodType(Consumer.<span class="java_keyword">class</span>), <span class="java_comment">// a lambda factory aláírása</span>
        MethodType.methodType(<span class="java_keyword">void</span>.<span class="java_keyword">class</span>, Object.<span class="java_keyword">class</span>),
            <span class="java_comment">// a Consumer.accept metódus aláírása a típustörlés után</span>
        lambdaImplementation, <span class="java_comment">// a lambda törzsét tartalmazó metódusra hivatkozó referencia</span>
        type);
}

<span class="java_keyword">void</span> printElements(List&lt;String&gt; strings) {
    Consumer&lt;String&gt; lambda = <span class="java_comment">// invokedynamic# bootstrapLambda, #lambda_forEach</span>
    strings.forEach(lambda);
}</pre>
  </div>
  <p class="bekezd">Az invokedynamic utasítást pedig a következő Java kódrészlet mutatja be:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private</span> <span class="java_keyword">static</span> CallSite cs;

<span class="java_keyword">void</span> printElements(List&lt;String&gt; strings) <span class="java_keyword">throws</span> Throwable {
    Consumer&lt;String&gt; lambda;
    <span class="java_comment">// begin invokedynamic</span>
    <span class="java_keyword">if</span> (cs == <span class="java_keyword">null</span>) {
        cs = bootstrapLambda(MethodHandles.lookup(), <span class="java_string">"lambda_forEach"</span>, 
            MethodType.methodType(<span class="java_keyword">void</span>.<span class="java_keyword">class</span>, String.<span class="java_keyword">class</span>));
    }
    lambda = (Consumer&lt;String&gt;) cs.getTarget().invokeExact();
    <span class="java_comment">// end invokedynamic</span>
    strings.forEach(lambda);
}</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">LambdaMetafactory</span> létrehoz egy call site-ot az abban lévő &quot;target&quot; metódus handle-lel. Az ebben lévő gyártómetódus az <span
      class="programkod">invokeExact</span>-on keresztül visszaad egy funkcionális interfész implementációt. Ha a lambdának vannak változói, akkor az <span class="programkod">invokeExact</span>
    elfogadja ezeket a változókat paraméterként. A <span class="programkod">LambdaMetafactory</span> valódi kódja egyébként JVM-implementációfüggő.
  </p>
  <h3>Korlátozások és hibák</h3>
  <p class="bekezd">Természetesen a Java fordító és a JVM sem tökéletes, ezek is tartalmaznak néhány korlátozást és hibát, mint mindjárt látni is fogjuk. (Ezek a cikk elején megjelölt
    JDK esetén reprodukálhatók.)</p>
  <p class="bekezd">
    <b>Lambdák gyártása metódus handle-ökből</b>
  </p>
  <p class="bekezd">
    Láttuk, hogy a lambdákat a <span class="programkod">LambdaMetafactory</span>-val dinamikusan is előállíthatjuk. Ehhez kell egy olyan <span class="programkod">MethodHandle</span> ami egy
    funkcionális interfész által deklarált metódus implementációjára mutat. Nézzük ezt a példát:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.lang.invoke.CallSite;
<span class="java_keyword">import</span> java.lang.invoke.LambdaMetafactory;
<span class="java_keyword">import</span> java.lang.invoke.MethodHandles;
<span class="java_keyword">import</span> java.lang.invoke.MethodType;
<span class="java_keyword">import</span> java.util.function.Supplier;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> LambdaGetterClass {
    <span class="java_keyword">private</span> String value = <span class="java_string">&quot;</span><span class="java_string">&quot;</span>;

    <span class="java_keyword">public</span> String getValue() {
        <span class="java_keyword">return</span> value;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> setValue(<span class="java_keyword">final</span> String value) {
        <span class="java_keyword">this</span>.value = value;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> Throwable {
        LambdaGetterClass lambdaGetter = <span class="java_keyword">new</span> LambdaGetterClass();
        lambdaGetter.setValue(<span class="java_string">&quot;Hello world!&quot;</span>);
        MethodHandles.Lookup lookup = MethodHandles.lookup();
        CallSite site = LambdaMetafactory.metafactory(lookup, <span class="java_string">&quot;get&quot;</span>, 
                MethodType.methodType(Supplier.<span class="java_keyword">class</span>, LambdaGetterClass.<span class="java_keyword">class</span>),
                MethodType.methodType(Object.<span class="java_keyword">class</span>), 
                lookup.findVirtual(LambdaGetterClass.<span class="java_keyword">class</span>, <span class="java_string">&quot;getValue&quot;</span>, 
                        MethodType.methodType(String.<span class="java_keyword">class</span>)),
                MethodType.methodType(String.<span class="java_keyword">class</span>));
        Supplier&lt;String&gt; getter = (Supplier&lt;String&gt;) site.getTarget().invokeExact(lambdaGetter);
        System.<span class="java_constant">out</span>.println(getter.get());
    }
}</pre>
  </div>
  <p class="bekezd">
    A fenti <span class="programkod">main</span> metódus kódja ezzel ekvivalens:
  </p>
  <div class="programkod">
    <pre>LambdaGetterClass lambdaGetter = <span class="java_keyword">new</span> LambdaGetterClass();
lambdaGetter.setValue(<span class="java_string">&quot;Hello world!&quot;</span>);
<span class="java_keyword">final</span> Supplier&lt;String&gt; elementGetter = () -&gt; lambdaGetter.getValue();
System.<span class="java_constant">out</span>.println(elementGetter.get());</pre>
  </div>
  <p class="bekezd">
    Már láttuk, hogy a <span class="programkod">MethodHandles.Lookup</span>-nak van egy <span class="programkod">findGetter</span> nevű metódusa is, ami egy nemstatikus mezőhöz tartozó
    gettert hoz létre. Nézzük meg mi történik ha lecseréljük a <span class="programkod">getValue</span>-ra mutató metódus handle-t egy olyanra amit ez hozott létre!
  </p>
  <div class="programkod">
    <pre>CallSite site = LambdaMetafactory.metafactory(lookup, <span class="java_string">&quot;get&quot;</span>, 
        MethodType.methodType(Supplier.<span class="java_keyword">class</span>, LambdaGetterClass.<span class="java_keyword">class</span>),
        MethodType.methodType(Object.<span class="java_keyword">class</span>), 
        lookup.findGetter(LambdaGetterClass.<span class="java_keyword">class</span>, <span class="java_string">&quot;value&quot;</span>, String.<span class="java_keyword">class</span>), 
        MethodType.methodType(String.<span class="java_keyword">class</span>)); </pre>
  </div>
  <p class="bekezd">
    Ennek a kódnak működnie kellene mert a <span class="programkod">findGetter</span> probléma nélkül visszaad egy mező getterre mutató metódus handle-t és érvényes aláírása is van. Ha
    viszont lefuttatjuk a kódot akkor a következő kivételnek örülhetünk:
  </p>
  <div class="programkod">
    <pre>Exception in thread &quot;main&quot; java.lang.invoke.LambdaConversionException: Unsupported MethodHandle kind: getField hu.egalizer.java8.LambdaGetterClass.value:()String</pre>
  </div>
  <p class="bekezd">
    Érdekes módon a getter létrehozás viszont egész jól működik ha <span class="programkod">MethodHandleProxies</span>-t használunk (a <span class="programkod">CallSite</span> helyett):
  </p>
  <div class="programkod">
    <pre>Supplier&lt;String&gt; getter = MethodHandleProxies.asInterfaceInstance(Supplier.<span class="java_keyword">class</span>,
        lookup.findGetter(LambdaGetterClass.<span class="java_keyword">class</span>, <span class="java_string">&quot;value&quot;</span>, String.<span class="java_keyword">class</span>)
                .bindTo(lambdaGetter)); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">asInterfaceInstance</span> metódus létrehozza a megadott funkcionális interfész egy példányát, ami redirektálja a hívását a megadott metódus handle-höz. Meg
    kell adni neki a kívánt interfészt és a cél <span class="programkod">MethodHandle</span>-t. A példában a <span class="programkod">Supplier</span>-ből készít egy új példányt, aminek a
    hívásait a <span class="programkod">lookup.findGetter</span> által adott metódus handle-höz továbbítja.
  </p>
  <p class="bekezd">
    A <span class="programkod">MethodHandleProxies</span> egyébként nem a legjobb módja a lambdák dinamikus létrehozásának mert csak egy proxy osztályba csomagolja a <span
      class="programkod">MethodHandle</span>-t és delegálja az <span class="programkod">InvocationHandler.invoke</span> hívást a <span class="programkod">MethodHandle.invokeWithArguments</span>-hez.
    Ez a módszer reflection-t használ és nagyon lassan működik. Egyébként nem minden metódus handle használható futásidőben lambdák létrehozására, csak ezek:
  </p>
  <ul>
    <li><b>REF_invokeInterface</b>: a Lookup.findVirtual segítségével hozható létre interfész metódusokhoz</li>
    <li><b>REF_invokeVirtual</b>: a Lookup.findVirtual segítségével hozható létre egy osztály által biztosított virtuális metódusokhoz</li>
    <li><b>REF_invokeStatic</b>: a Lookup.findStatic segítségével hozható létre statikus metódusokhoz</li>
    <li><b>REF_newInvokeSpecial</b>: a Lookup.findConstructor segítségével hozható létre konstruktorokhoz</li>
    <li><b>REF_invokeSpecial</b>: a Lookup.findSpecial segítségével hozható létre privát metódusokhoz és egy osztály által biztosított virtuális metódusok korai kötéséhez</li>
  </ul>
  <p class="bekezd">
    Egyéb metódus handle-ök LambdaConversionException-t dobnak. A REF_... azonosítót (a JDK dokumentációja referenciafajta - reference kind - néven hivatkozik rá) egyébként a Java belső
    működéshez használja és a <span class="programkod">MethodHandleInfo</span> osztályban vannak deklarálva.
  </p>
  <p class="bekezd">
    <b>Generikus kivételek</b>
  </p>
  <p class="bekezd">Ez a hiba a Java fordítóban van és a throws záradékban definiált generikus kivételekkel kapcsolatos. Legyen a következő generikus interfészünk:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.concurrent.Callable;

<span class="java_keyword">interface</span> ExtendedCallable&lt;V, E <span class="java_keyword">extends</span> Exception&gt; <span class="java_keyword">extends</span> Callable&lt;V&gt; {
    <span class="java_annotation">@Override</span>
    V call() <span class="java_keyword">throws</span> E;
} </pre>
  </div>
  <p class="bekezd">Ezt próbáljuk meg így használni:</p>
  <div class="programkod">
    <pre>
ExtendedCallable&lt;URL, MalformedURLException&gt; urlFactory = () -&gt; <span class="java_keyword">new</span> URL(<span class="java_string">"http://localhost"</span>);
urlFactory.call(); </pre>
  </div>
  <p class="bekezd">Ennek a kódnak sikeresen le kellene fordulnia, mert az URL konstruktor dobja a MalformedURLException-t, de mégsem ez a helyzet. A fordító a következő hibaüzenetet
    köpi:</p>
  <div class="programkod">
    <pre>
hu\egalizer\java8\Test.java:35: error: call() in &lt;anonymous hu.egalizer.java8.Test$&gt; cannot implement call() in ExtendedCallable
                ExtendedCallable&lt;URL, MalformedURLException&gt; urlFactory = () -&gt; new URL(&quot;http://localhost&quot;);
                                                                          ^
  overridden method does not throw Exception
  where V is a type-variable:
    V extends Object declared in interface ExtendedCallable
1 error</pre>
  </div>
  <p class="bekezd">Ha a lambda kifejezést anonim osztályra cseréljük, semmi gond nincs:</p>
  <div class="programkod">
    <pre>ExtendedCallable&lt;URL, MalformedURLException&gt; urlFactory = <span class="java_keyword">new</span> ExtendedCallable&lt;URL, MalformedURLException&gt;() {
    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> URL call() <span class="java_keyword">throws</span> MalformedURLException {
        <span class="java_keyword">return</span> <span class="java_keyword">new</span> URL(<span class="java_string">"http://localhost"</span>);
    }
}};</pre>
  </div>
  <p class="bekezd">A generikus kivételekhez tartozó típuskövetkeztetések tehát nem működnek rendesen amikor lambdákkal együtt használjuk ezeket. (Egyébként ha valaki Eclipse-ben
    próbálkozik akkor nem fog hibát kapni, az Eclipse ugyanis nem a javac fordítót használja, hanem saját megoldást ami a JDT Core pluginben van és ECJ - Eclipse Compiler for Java a
    becsületes neve.)</p>
  <h2>Default</h2>
  <p class="bekezd">A Java 8 az interface-ek deklarációját további két újdonsággal egészíti ki: a default és statikus metódusokkal. Java 8 előtt ha egy új metódus került egy már
    használatban lévő interfészbe, akkor az összes az interfészt implementáló nem absztrakt osztálynak implementálnia kellett az új metódust is. Így ha egy osztálykönyvtárba új metódus
    került, akkor csak úgy lehetett az új verzióra áttérni, ha a library-t használó kódba is átvezettük a változást. Ha tehát lett egy új babarozsa() nevű metódus az interfészben, akkor a
    használó alkalmazásnak is implementálnia kellett ezt még akkor is, ha amúgy nem is használta ki.</p>
  <p class="bekezd">A default metódusok lehetővé teszik új metódusok hozzáadását létező interfészekhez anélkül, hogy megtörné azon interfészek régebbi verzióihoz készült kóddal való
    kompatibilitást. A default és absztrakt metódusok közötti különbség az, hogy az absztrakt metódusokat kötelező implementálni, de a default metódusokat nem. Ehelyett minden interfésznek
    biztosítania kell egy ún. alapértelmezett implementációt és az összes implementáló alapból azt örökli. Igény szerint természetesen felüldefiniálhatja. Nézzünk egy példát:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> DefaultExample {
    <span class="java_keyword">default</span> String notRequired() {
        <span class="java_keyword">return</span> <span class="java_string">&quot;Default implementation&quot;</span>;
    }
} </pre>
  </div>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> DefaultExampleImpl <span class="java_keyword">implements</span> DefaultExample {

} </pre>
  </div>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> DefaultExampleImpl2 <span class="java_keyword">implements</span> DefaultExample {
    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> String notRequired() {
        <span class="java_keyword">return</span> <span class="java_string">"Overridden implementation"</span>;
    }

} </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">DefaultExample</span> interfész deklarál egy <span class="programkod">notRequired()</span> default metódust a metódus definíciójában szereplő <span
      class="programkod">default</span> kulcsszóval. Az egyik leszármazott osztály, a <span class="programkod">DefaultExampleImpl</span> implementálja ezt az interfészt, de meghagyja a
    default metódus alapértelmezett implementációját. Egy másik, a <span class="programkod">DefaultExampleImpl2</span> felülírja a default implementációját a sajátjával. Amikor egy default
    metódust tartalmazó interfészből származtatunk, akkor a következőket tehetjük:
  </p>
  <ul>
    <li>egyáltalán nem is említjük meg a default metódust, így a leszármazott interfész örökli azt</li>
    <li>újradeklaráljuk a default metódust, ami absztrakttá teszi azt</li>
    <li>újradefiniáljuk a default metódust, ami felülírja azt</li>
  </ul>
  <p class="bekezd">
    Egy interfészben definiált absztrakt metódust egy leszármazott interfészben defaulttá is lehet tenni, onnantól kezdve az abból leszármazott interfészek már a default implementációt
    fogják használni. <span class="programkod">Object</span>-ből örökölt metódusokra egyébként nem lehet default implementációt adni. Költői kérdés egyébként, hogy minek kellett ehhez a
    default kulcsszót bevezetni, hiszen ha nem adunk meg törzset, akkor a metódus alapértelmezetten absztrakt lesz egy interfészben, static kulcsszóval statikus, tehát ha van törzse, akkor
    lehetett volna alapértelmezetten default is, kulcsszó nélkül. De lehet, hogy a jobb olvashatóság oltárán áldoztak a Java tervezői az új kulcsszóval. A default metódusok egyébként
    valójában szimpla virtuális metódusok, a default kulcsszó csak a fordító számára jelent valamit, a bájtkódba egyáltalán nem kerül be ez az információ. Látni fogjuk, hogy ez nem jár
    következmények nélkül.
  </p>
  <p class="bekezd">Némi megkötés, hogy a default metódusoknál sajnálatos módon néhány kulcsszó nem használható:</p>
  <ul>
    <li><b>final</b>: nem jelölhetjük őket final-ként, tehát a leszármazott típusok simán felüldefiniálhatják még akkor is ha egyébként nincs értelme</li>
    <li><b>synchronized</b>: nem tudni miért annyira speciálisak a default metódusok, hogy szép magyar kifejezéssel élve nem lehet őket synchronized-dá sem tenni</li>
  </ul>
  <p class="bekezd"></p>
  <p class="bekezd"></p>
  <p class="bekezd">
    A Java 8 a statikus metódusokat is bevezette az interfészeknél (<span class="programkod">static default</span> metódust viszont nem tudunk definiálni). Íme egy példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.function.Supplier;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> DefaultExampleFactory {
    <span class="java_keyword">static</span> DefaultExample create(Supplier&lt;DefaultExample&gt; supplier) {
        <span class="java_keyword">return</span> supplier.get();
    }
} </pre>
  </div>
  <p class="bekezd">Az alábbi kis kódrészlet egyben mutatja meg a fenti példákból a default és a statikus metódusokat:</p>
  <div class="programkod">
    <pre>
DefaultExample defaultExample = DefaultExampleFactory.create(() -&gt; <span class="java_keyword">new</span> DefaultExampleImpl());
System.<span class="java_constant">out</span>.println(defaultExample.notRequired());
defaultExample = DefaultExampleFactory.create(() -&gt; <span class="java_keyword">new</span> DefaultExampleImpl2());
System.<span class="java_constant">out</span>.println(defaultExample.notRequired()); </pre>
  </div>
  <p class="bekezd">A példát egyébként ennél még lehetne szebben is írni, azonban ehhez kelleni fog a következő fejezeben megismerendő metódus referencia fogalma is:</p>
  <div class="programkod">
    <pre>
DefaultExample defaultExample = DefaultExampleFactory.create(DefaultExampleImpl::<span class="java_keyword">new</span>);
System.<span class="java_constant">out</span>.println(defaultExample.notRequired());
defaultExample = DefaultExampleFactory.create(DefaultExampleImpl2::<span class="java_keyword">new</span>);
System.<span class="java_constant">out</span>.println(defaultExample.notRequired()); </pre>
  </div>
  <p class="bekezd">
    A JVM-ben a default metódus implementáció egyébként eléggé hatékony és a metódushívásra szolgáló bájtkódok is támogatják. A default metódusok lehetővé teszik a létező Java
    interfészeknek a továbbfejlesztését anélkül, hogy meggátolnák a fejlesztési folyamatot. Jó példa erre a <span class="programkod">java.util.Collection</span> interfészhez adott sok új
    metódus: <span class="programkod">stream()</span>, <span class="programkod">parallelStream()</span>, <span class="programkod">forEach()</span>, <span class="programkod">removeIf()</span>...
  </p>
  <p class="bekezd">Úgy tűnhet, hogy a default és statikus metódusok bevezetésével az interfészek és az absztrakt osztályok lényegében ugyanazok lettek. Ez azonban tévedés, hiszen az
    absztrakt osztályoknak lehet konstruktoruk, sokkal összetettebbek lehetnek és lehet belső állapotuk. A default metódusokat akár úgy is implementálhatjuk, hogy más metódusokat hívnak meg
    a saját interfészükön, elérhetik saját metódus paramétereiket (ha vannak), viszont az interfész belső állapotához nem férnek hozzá, lévén az interfészeknek nincs is olyanjuk.</p>
  <p class="bekezd">
    <b>Nem minden arany ami default</b>
  </p>
  <p class="bekezd">Bár a default metódusok csábítóak, nem árt az óvatosság: kétszer is gondoljuk meg mielőtt egy metódust defaultnak deklarálunk, mert komplex rendszerekben
    kétértelműséghez és fordítási hibákhoz vezethet. Probléma főként a többszörös öröklődésnél jöhet elő. A Java esetén korábban csak definíciós szinten volt többszörös öröklődés,
    implementáció esetén nem. A default metódusok bevezetése felkavarta az állóvizet. Tegyük fel, hogy egy osztály több interfészt is implementál, amik mind tartalmaznak egy durvasag()
    metódust. Ha mindegyik interfészben absztrakt a metódus, akkor - akárcsak eddig - az osztálynak implementálnia kell azt. De mi van, ha ezek közül egy vagy több interfész ad default
    implementációt? Java 8 esetén az osztálynak ez esetben is implementálnia kell a metódust, különben fordítási hibát kapunk. Ha viszont öröklődésen keresztül az osztályban a metódusnak
    ugyanaz az egy default implementációja jelenik meg örököltként, akkor nincs gond. Tehát ha az A interfész implementálja a durvasag()-ot, majd a B és C interfészek kiterjesztik az A-t és
    a D osztály implementálja B-t és C-t, akkor örökli a durvasag() implementációját is. Tehát egy osztály pontosan egy default implementációt tud örökölni akár több leszármazási úton
    keresztül is de csak akkor ha ugyanezt a metódust nem örökli absztraktként is. Természetesen ha az osztálynak implementálnia kell a metódust, akkor az történhet magában az osztályban,
    de örökölheti az implementációt kiterjesztett osztályból is.</p>
  <p class="bekezd">Mi van azonban abban az esetben, ha a programunk korábban lefordult olyan interfész verziókkal, amik még nem okoztak gondot a fordítónak, majd az interfészből kijön
    egy új verzió amivel már nem fordulna le? Nos a lefordított verzió valószínűleg továbbra is futni fog, de a Java 8 nem túl egyértelmű ebben az esetben.</p>
  <p class="bekezd">Tekintsük a következő példát:</p>
  <ul>
    <li>van két interfészünk és egy osztály, ami implementálja mindkét interfészt</li>
    <li>az egyik interfész implementálja a default durvasag() metódust</li>
    <li>az osztály és az interfészek lefordulnak</li>
    <li>megváltoztatjuk az interfészt, amelyik nem tartalmazta eddig a durvasag() metódust, hogy deklarálja azt absztraktként</li>
    <li>fordítsuk le csak a módosított interfészt</li>
    <li>futtassuk az osztályt</li>
  </ul>
  <p class="bekezd">Ebben az esetben az osztály futni fog, bár a kód már nem fog újra lefordulni. Íme egy példán keresztül:</p>
  <p class="bekezd">
    <u>InterA.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.mess;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> InterA {

}</pre>
  </div>
  <p class="bekezd">
    <u>InterB.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.mess;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> InterB {
    <span class="java_keyword">default</span> <span class="java_keyword">public</span> <span class="java_keyword">void</span> durvasag() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">"Durva dolgok mennek."</span>);
    }
}</pre>
  </div>
  <p class="bekezd">
    <u>InterABImpl.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.mess;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> InterABImpl <span class="java_keyword">implements</span> InterA, InterB {

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> goTrabi() {
        durvasag();
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">new</span> InterABImpl().goTrabi();
    }
}</pre>
  </div>
  <p class="bekezd">Fordítsuk le és futtassuk a kódot:</p>
  <pre class="programkod">javac.exe hu/egalizer/java8/mess/*.java

java.exe hu.egalizer.java8.mess.InterABImpl

Durva dolgok mennek.</pre>
  <p class="bekezd">Ezután módosítsuk az InterA.java-t:</p>
  <p class="bekezd">
    <u>InterA.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.mess;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> InterA {
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> durvasag();
}</pre>
  </div>
  <p class="bekezd">Fordítsuk le majd futtassuk a kódot:</p>
  <pre class="programkod">
javac.exe hu/egalizer/java8/mess/InterA.java

java.exe hu.egalizer.java8.mess.InterABImpl

Durva dolgok mennek.</pre>
  <p class="bekezd">A dolgok működnek, pedig az InterABImpl-ből még az előző lefordított verziót futtattuk. Próbáljuk most lefordítani újra, és így járunk:</p>
  <div class="programkod">
    <pre>javac.exe hu/egalizer/java8/mess/InterABImpl.java
hu\egalizer\java8\mess\InterABImpl.java:3: error: InterABImpl is not abstract and does not override abstract method durvasag() in InterA
public class InterABImpl implements InterA, InterB {
       ^
1 error</pre>
  </div>
  <p class="bekezd">Most pedig</p>
  <ul>
    <li>módosítsuk azt az interfészt amelyikben az absztrakt metódus van és írjunk a durvasag() metódushoz egy default implementációt</li>
    <li>fordítsuk le csak a módosított interfészt</li>
    <li>próbáljuk futtatni az osztályt: nem fog menni, hibát kapunk</li>
  </ul>
  <p class="bekezd">
    <u>InterA.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.mess;

<span class="java_keyword">public</span> <span class="java_keyword">interface</span> InterA {
    <span class="java_keyword">default</span> <span class="java_keyword">public</span> <span class="java_keyword">void</span> durvasag() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Ütközni fogunk!&quot;</span>);
    }
}</pre>
  </div>
  <div class="programkod">
    <pre>javac.exe hu/egalizer/java8/mess/InterA.java

java.exe hu.egalizer.java8.mess.InterABImpl

Exception in thread "main" java.lang.IncompatibleClassChangeError: Conflicting default methods: hu/egalizer/java8/mess/InterA.durvasag hu/egalizer/java8/mess/InterB.durvasag
        at hu.egalizer.java8.mess.InterABImpl.durvasag(InterABImpl.java)
        at hu.egalizer.java8.mess.InterABImpl.goTrabi(InterABImpl.java:6)
        at hu.egalizer.java8.mess.InterABImpl.main(InterABImpl.java:10)</pre>
  </div>
  <p class="bekezd">Ha két olyan interfész is van az öröklődési láncban, amelyik default implementációt ad egy metódusra, akkor az implementáló osztályban nem lehet meghívni a metódust,
    ha az nincs definiálva expliciten az osztályban vagy egy ősosztályban. A lefordított .class továbbra is használható és futni fog egészen addig, míg nem történik hívás arra a metódusra,
    amelyet többszörösen definiálnak az interfészek.</p>
  <h2>Metódus referenciák</h2>
  <p class="bekezd">Láttuk, hogyan jönnek létre a lambda kifejezésekkel anonim metódusok. Néha azonban egy lambda kifejezés semmit nem csinál, csak meghív egy másik, már létező
    metódust. Ilyenkor egyszerűbb lenne a létező metódusra lambda nélkül csak névvel hivatkozni. A metódus referenciák pont ezt teszik lehetővé: java osztályok vagy objektumok létező
    metódusaira vagy konstruktoraira közvetlenül hivatkozó eszközök. Tegyük fel hogy legót gyűjtünk és készítünk egy legónyilvántartó programot. Egy legókészletet az alábbi osztály
    reprezentál:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_keyword">import</span> java.time.LocalDate;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> LegoSet {
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> yearReleased;<span class="java_comment">// megjelenés éve</span>
    <span class="java_keyword">private</span> String name;<span class="java_comment">// név</span>
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> pieceCount;<span class="java_comment">// elemek száma</span>
    <span class="java_keyword">private</span> LocalDate addedToCollection;<span class="java_comment">// gyűjteménybe kerülés dátuma</span>

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">int</span> compareByAdded(LegoSet a, LegoSet b) {
        <span class="java_keyword">return</span> a.addedToCollection.compareTo(b.addedToCollection);
    }

    <span class="java_keyword">public</span> LocalDate getAddedToCollection() {
        <span class="java_keyword">return</span> addedToCollection;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> setAddedToCollection(LocalDate addedToCollection) {
        <span class="java_keyword">this</span>.addedToCollection = addedToCollection;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">int</span> getYearReleased() {
        <span class="java_keyword">return</span> yearReleased;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> setYearReleased(<span class="java_keyword">int</span> yearReleased) {
        <span class="java_keyword">this</span>.yearReleased = yearReleased;
    }

    <span class="java_keyword">public</span> String getName() {
        <span class="java_keyword">return</span> name;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> setName(String name) {
        <span class="java_keyword">this</span>.name = name;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">int</span> getPieceCount() {
        <span class="java_keyword">return</span> pieceCount;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> setPieceCount(<span class="java_keyword">int</span> pieceCount) {
        <span class="java_keyword">this</span>.pieceCount = pieceCount;
    }

} </pre>
  </div>
  <p class="bekezd">Tegyük fel, hogy a legó nyilvántartó alkalmazásunk a készleteket egy tömbben tárolja és azt a tömböt a készlet hozzáadásának dátuma szerint akarjuk rendezni.
    Eljárhatunk így:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> LegoDatabase {
    <span class="java_keyword">private</span> List&lt;LegoSet&gt; mySets;

    <span class="java_keyword">public</span> LegoSet[] orderSets() {
        LegoSet[] setsAsArray = mySets.toArray(<span class="java_keyword">new</span> LegoSet[mySets.size()]);
        Arrays.sort(setsAsArray, <span class="java_keyword">new</span> LegoAddedComparator());
        <span class="java_keyword">return</span> setsAsArray;
    }

    <span class="java_keyword">class</span> LegoAddedComparator <span class="java_keyword">implements</span> Comparator&lt;LegoSet&gt; {
        <span class="java_annotation">@Override</span>
        <span class="java_keyword">public</span> <span class="java_keyword">int</span> compare(LegoSet a, LegoSet b) {
            <span class="java_keyword">return</span> a.getAddedToCollection().compareTo(b.getAddedToCollection());
        }
    }

} </pre>
  </div>
  <p class="bekezd">A fenti sort metódus aláírása:</p>
  <pre class="programkod">
    <span class="java_keyword">static</span> &lt;T&gt; <span class="java_keyword">void</span>  sort(T[] a, Comparator&lt;? <span class="java_keyword">super</span> T&gt; c) </pre>
  <p class="bekezd">
    Vegyük észre, hogy a <span class="programkod">Comparator</span> egy funkcionális interfész! Erről már tudjuk, hogy lambda kifejezésekkel szépen lehet használni, nem kell föltétlenül
    létrehozni egy <span class="programkod">Comparator</span>-t implementáló osztályból új példányt. Tehát:
  </p>
  <div class="programkod">
    <pre>Arrays.sort(setsAsArray, (a, b) -&gt; a.getAddedToCollection().compareTo(b.getAddedToCollection())); </pre>
  </div>
  <p class="bekezd">
    A készlet gyűjteménybe való bekerülésének dátumára ugyanakkor már a <span class="programkod">LegoSet</span> is tartalmaz egy statikus metódust: <span class="programkod">LegoSet.compareByAdded</span>.
    Ezt is meghívhatjuk a lambda kifejezés törzsében:
  </p>
  <div class="programkod">
    <pre>Arrays.sort(setsAsArray, (a, b) -&gt; LegoSet.compareByAdded(a, b)); </pre>
  </div>
  <p class="bekezd">Ez a lambda kifejezés csupán egy létező metódust hív meg, itt jön be a metódus referencia, amit ez esetben használhatunk a lambda helyett (a metódusreferenciát a
    dupla kettőspont hivatkozza az osztálynév és a metódus között):</p>
  <div class="programkod">
    <pre>Arrays.sort(setsAsArray, LegoSet::compareByAdded); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">LegoSet::compareByAdded</span> metódusreferencia szemantikusan ugyanaz, mint a <span class="programkod">(a, b) -&gt; LegoSet.compareByAdded(a, b)</span>
    lambda kifejezés. Mindkettő a következőképpen viselkedik:
  </p>
  <ul>
    <li>a paraméterlista átmásolódik a <span class="programkod">Comparator&lt;LegoSet&gt;.compare</span> metódusból, ami <span class="programkod">(LegoSet, LegoSet)</span></li>
    <li>a törzs meghívja a <span class="programkod">LegoSet.compareByAdded metódust</span></li>
  </ul>
  <p class="bekezd">Gondolom most már mindenki a körmét rágja abbéli izgalmában, hogy milyen metódusreferenciák vannak pontosan. Nem kell tovább várni, a titokra fény derül: négyféle
    metódusreferencia létezik:</p>
  <table cellpadding="0" cellspacing="0" width="65%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Típus</b></th>
        <th class="normal"><b>Formátum</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>referencia statikus metódusra</td>
        <td>TartalmazoOsztaly::statikusMetodusNeve</td>
      </tr>
      <tr class="tr2">
        <td>referencia objektum példánymetódusára</td>
        <td>tartalmazoObjektum::peldanyMetodusNeve</td>
      </tr>
      <tr class="tr1">
        <td>referencia tetszőleges objektum adott típusának példánymetódusára</td>
        <td>TartalmazoTipus::metodusNeve</td>
      </tr>
      <tr class="tr2">
        <td>referencia konstruktorra</td>
        <td>OsztalyNeve::new</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">A nyájas olvasó természetesen nem ússza meg példák nélkül:</p>
  <p class="bekezd">
    <u>Referencia statikus metódusra</u>: a <span class="programkod">LegoSet::compareByAdded</span> referencia egy statikus metódusra
  </p>
  <p class="bekezd">
    <u>Referencia objektum példánymetódusára</u>: az alábbi példa rávilágít:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> ComparisonProvider {
    <span class="java_keyword">public</span> <span class="java_keyword">int</span> compareByName(LegoSet a, LegoSet b) {
        <span class="java_keyword">return</span> a.getName().compareTo(b.getName());
    }

    <span class="java_keyword">public</span> <span class="java_keyword">int</span> compareByAdded(LegoSet a, LegoSet b) {
        <span class="java_keyword">return</span> a.getAddedToCollection().compareTo(b.getAddedToCollection());
    }

} </pre>
  </div>
  <div class="programkod">
    <pre>
ComparisonProvider myComparisonProvider = <span class="java_keyword">new</span> ComparisonProvider();
Arrays.sort(setsAsArray, myComparisonProvider::compareByName); </pre>
  </div>
  <p class="bekezd">
    A példában a <span class="programkod">myComparisonProvider::compareByName</span> metódusreferencia a <span class="programkod">myComparisonProvider</span> objektum <span
      class="programkod">compareByName</span> metódusát hívja meg. A JRE kikövetkezteti a metódus paramétertípusait, ami <span class="programkod">(LegoSet, LegoSet)</span>.
  </p>
  <p class="bekezd">
    <u>Referencia tetszőleges objektum adott típusának példánymetódusára</u>: egy példa többet mond ezer szónál:
  </p>
  <div class="programkod">
    <pre>
String[] stringArray = { <span class="java_string">&quot;Misi&quot;</span>, <span class="java_string">&quot;Joci&quot;</span>, <span class="java_string">&quot;Robert&quot;</span>, <span
        class="java_string">&quot;Pisti&quot;</span>, <span class="java_string">&quot;Morgan&quot;</span>, <span class="java_string">&quot;Viktor&quot;</span>, <span class="java_string">&quot;Zoli&quot;</span> };
Arrays.sort(stringArray, String::compareToIgnoreCase); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">String::compareToIgnoreCase</span> metódusreferenciának megfelelő lambda kifejezés a <span class="programkod">(String a, String b)</span> formális
    paraméterlistával rendelkezne (a és b hasraütéses változónevek). Ez a metódusreferencia az <span class="programkod">a.compareToIgnoreCase(b)</span> hívást eredményezi.
  </p>
  <p class="bekezd">
    <u>Referencia konstruktorra</u>: Ugyanúgy lehet konstruktorra referenciát létrehozni, mint statikus metódusra, csak a <span class="programkod">new</span> kulcsszót kell használni. Az
    alábbi példa elemeket másol egyik kollekcióból egy másikba:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;T, SOURCE <span class="java_keyword">extends</span> Collection&lt;T&gt;, DEST <span
        class="java_keyword">extends</span> Collection&lt;T&gt;&gt; 
    DEST transferElements(SOURCE sourceCollection, Supplier&lt;DEST&gt; collectionFactory) {
    DEST result = collectionFactory.get();
    <span class="java_keyword">for</span> (T t : sourceCollection) {
        result.add(t);
    }
    <span class="java_keyword">return</span> result;
} </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">Supplier</span> funkcionális interfész egy <span class="programkod">get</span> metódust tartalmaz, ami nem kap paramétert és visszaad egy objektumot. Ezért
    meg lehet hívni a <span class="programkod">transferElements</span> metódust lambda kifejezéssel is így:
  </p>
  <div class="programkod">
    <pre>
Set&lt;LegoSet&gt; setsLambda = transferElements(mySets, () -&gt; {
    <span class="java_keyword">return</span> <span class="java_keyword">new</span> HashSet&lt;&gt;();
}); </pre>
  </div>
  <p class="bekezd">A lambda helyett pedig lehet használni konstruktor referenciát is a következőképpen:</p>
  <div class="programkod">
    <pre>Set&lt;LegoSet&gt; setsSet = transferElements(mySets, HashSet::<span class="java_keyword">new</span>); </pre>
  </div>
  <p class="bekezd">
    A Java fordító kikövetkezteti, hogy egy <span class="programkod">HashSet</span>-et akarunk létrehozni ami <span class="programkod">LegoSet</span> típusú elemeket tartalmaz. Más módon
    így is meg lehet egyébként ugyanezt adni:
  </p>
  <div class="programkod">
    <pre>Set&lt;LegoSet&gt; setsSet = transferElements(mySets, HashSet&lt;LegoSet&gt;::<span class="java_keyword">new</span>); </pre>
  </div>
  <p class="bekezd">
    <b>Szivárgó metódusreferenciák</b>
  </p>
  <p class="bekezd">A Java-ban a metódusreferenciáknak van egy érdekes (vagy inkább bosszantó) tulajdonságuk amit nem árt észben tartani, nehogy kellemetlen meglepetés érje az embert.
    Ez röviden így fogalmazható meg:</p>
  <pre class="programkod">    obj::method != obj::method </pre>
  <p class="bekezd">Kétszer meghatározott metódus referencia nem fog megegyezni! A többi gyakran használt programnyelvben a metódus referenciák általában nem ilyenek, de a Java kivétel.
    A szomorú helyzetet bizonyára a Java tervezői is látták, ezért a metódus referenciákat nem lehet közvetlenül összehasonlítani. De közvetetten már igen:</p>
  <div class="programkod">
    <pre>
String s = <span class="java_string">&quot;galiba&quot;</span>;
Supplier&lt;Integer&gt; sup1 = s::length;
Supplier&lt;Integer&gt; sup2 = s::length;
System.<span class="java_constant">out</span>.println(sup1 == sup2);<span class="java_comment">// false</span>
<span class="java_comment">// System.<span class="java_constant">out</span>.println(s::length == s::length); - nem fordul le</span> </pre>
  </div>
  <p class="bekezd">Miért jelenthet ez problémát?</p>
  <p class="bekezd">
    Tegyük fel hogy van egy osztályunk, ami sorba állított feladatokat futtat adott idő elteltével. Ebbe egy <span class="programkod">add</span> metódussal tudjuk betenni a <span
      class="programkod">Runnable</span> feladatokat, <span class="programkod">remove</span> metódussal pedig kiszedni belőle őket.
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">interface</span> RunnableQueue {
  <span class="java_comment">/**</span>
<span class="java_comment">  * Betesz egy Runnable osztályt a sorba, ami a megadott idő elteltével fog lefutni.</span>
<span class="java_comment">  */</span>
    <span class="java_keyword">boolean</span> add(Runnable r, <span class="java_keyword">long</span> delayMillis);

  <span class="java_comment">/**</span>
<span class="java_comment">  * Eltávolít a várakozó Runnable osztályt a sorból.</span>
<span class="java_comment">  */</span>
    <span class="java_keyword">void</span> remove(Runnable r);
} </pre>
  </div>
  <p class="bekezd">
    A probléma abból adódik, hogy minden alkalommal amikor leírom a <span class="programkod">this::doSomething</span> kifejezést, a Java új példányt hoz létre egy anonim osztályból, vagyis
    a <span class="programkod">this::doSomething != this::doSomething</span>, még ha ezt Java-ban nem is lehet így leírni. Ezért aztán például a várakozó <span class="programkod">Runnable</span>-ök
    sosem lesznek kivéve a sorból, a végén pedig memóriaszivárgás lesz. Ez a problémakör tulajdonképpen a Java 8 egyik tervezési hibájának is tekinthető. Bár a <span class="programkod">this::doSomething</span>
    ártatlan referenciának tűnik egy metódusra, valójában a <span class="programkod">Runnable</span> egy új példányát hozza létre, ráadásul a <span class="programkod">Runnable</span> egy
    referenciát tartalmaz az őt befoglaló objektumra is! A megoldás persze egyszerű: meg kell tartani egy referenciát a létrehozott <span class="programkod">Runnable</span>-höz és így
    ugyanazt a referenciát tudjuk átadni a <span class="programkod">remove</span>-nak. De így ezt már mindig észben kell tartani és nem írhatjuk önkéntelenül azt ami eszünkbe jutna. A
    történet tanulsága: a metódusreferenciák helyett gyakran érdemesebb inkább lambdákat használni. (Vagy ha nem, akkor legyünk mindig tisztában azzal, hogy mi történik.) A
    metódusreferenciák használata mellett szól viszont az a tény, hogy ezekből csak egy invokevirtual hívást fog a fordító csinálni, míg a lambdákból minden esetben készül a fent már
    említett láthatatlan privát &quot;lambda$0&quot; metódus.
  </p>
  <h2>Annotációk</h2>
  <h3>Ismétlő annotációk</h3>
  <p class="bekezd">Miután a Java 5 bevezette az annotációk támogatását, ez a nyelvi tulajdonság nagyon népszerű és széles körben használatos lett. Volt viszont ezen annotációk
    használhatóságának egy korlátozása is: ugyanazt az annotációt nem lehetett egynél többször használni ugyanazon a helyen. A Java 8 megszünteti ezt és bevezeti az ismétlő annotációkat.
    Ezzel lehetővé válik, hogy ugyanazt az annotációt a deklaráció helyén többször is kiadjuk.</p>
  <p class="bekezd">
    Tegyük fel, hogy egy időzítő szolgáltatást használó olyan kódot írunk. Az időzítő lehetővé teszi, hogy egy metódus adott időpontban vagy pedig valamilyen időzítéssel fusson. Mondjuk
    szeretnénk, hogy az <span class="programkod">induljonABanzaj</span> metódus minden hónap utolsó napján és minden pénteken este 11-kor fusson. Az időzítő beállításához egy <span
      class="programkod">@Schedule</span> annotáció tartozik és a feladathoz kétszer kell az <span class="programkod">induljonABanzaj</span> metódushoz alkalmazni. Ez Java 8 esetén
    semmilyen problémát nem okoz. Az első használat a hónap utolsó napját adja meg, a második pedig a péntek 11-et:
  </p>
  <div class="programkod">
    <pre>
<span class="java_annotation">@Schedule(dayOfMonth=&quot;last&quot;)
@Schedule(dayOfWeek=&quot;Fri&quot;, hour=&quot;23&quot;)</span>
<span class="java_keyword">public void</span> induljonABanzaj() { ... } </pre>
  </div>
  <p class="bekezd">Annotációkat bárhol lehet többszörözni ahol normál annotációkat egyébként is írhatunk.</p>
  <p class="bekezd">
    <b>Ismétlő annotációk létrehozása</b>
  </p>
  <p class="bekezd">Kompatibilitási okokból az ismétlő annotációk egy konténer annotációban tárolódnak, amit a Java fordító automatikusan legenerál. De ahhoz, hogy ezt meg tudja tenni,
    a kódban két deklaráció szükséges hozzá. (Ez a tulajdonság egyébként valójában nem is igazán nyelvi módosítás, hanem csak egy fordítóprogram-szintű trükk, hiszen a technológia ugyanaz
    maradt.)</p>
  <p class="bekezd">
    A ismétlő annotációk meg kell jelöljék magukat a <span class="programkod">@Repeatable</span> annotációval (különben a kutya se hiszi el róluk, hogy tényleg ismétlőek). Nézzünk egy
    példát:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.lang.annotation.Repeatable;

<span class="java_annotation">@Repeatable(Schedules.<span class="java_keyword">class</span>)</span>
<span class="java_keyword">public</span> @<span class="java_keyword">interface</span> Schedule {
    String dayOfMonth() <span class="java_keyword">default</span> <span class="java_string">&quot;first&quot;</span>;

    String dayOfWeek() <span class="java_keyword">default</span> <span class="java_string">&quot;Mon&quot;</span>;

    <span class="java_keyword">int</span> hour() <span class="java_keyword">default</span> 12;
} </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">@Repeatable</span> meta-annotációnak a zárójelek között megadott érték annak a konténer annotációnak a típusa, amit majd a Java fordító arra fog használni,
    hogy az ismétlődő annotációkat abban tárolja. Ebben a példában a tartalmazó annotáció típusa <span class="programkod">Schedules</span>, tehát az ismétlődő <span class="programkod">@Schedule</span>
    annotációk egy <span class="programkod">@Schedules</span> annotációban fognak tárolódni. Ha egy annotációt anélkül próbálunk többször megadni egy deklarációhoz, hogy elsőként
    ismétlődőként deklarálnánk, rövid úton fordítási hibához fog vezetni. Azt pedig senki se szereti.
  </p>
  <p class="bekezd">
    A tartalmazó annotációban egy tömb típusú value mezőnek kell lenni. A tömb típus komponens típusának az ismétlődő annotáció típusúnak kell lennie. A <span class="programkod">Schedules</span>
    tartalmazó annotáció típus deklarációja a következőképp néz ki:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> @<span class="java_keyword">interface</span> Schedules {
    Schedule[] value();
} </pre>
  </div>
  <h3>Kiterjesztett annotáció támogatás</h3>
  <p class="bekezd">A Java 8 bővíti az anotációk használatának lehetőségeit: most már szinte bármit annotálhatunk, amire csak gusztusunk támad: lokális változókat, generikus típusokat,
    ősosztályokat és implementáló interfészeket, sőt még egy metódus kivétel deklarációját is. Néhány példa:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno;

<span class="java_keyword">import</span> java.lang.annotation.ElementType;
<span class="java_keyword">import</span> java.lang.annotation.Retention;
<span class="java_keyword">import</span> java.lang.annotation.RetentionPolicy;
<span class="java_keyword">import</span> java.lang.annotation.Target;
<span class="java_keyword">import</span> java.util.ArrayList;
<span class="java_keyword">import</span> java.util.Collection;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> SokSokAnnotacio {

    <span class="java_annotation">@Retention(RetentionPolicy.RUNTIME)</span>
    <span class="java_annotation">@Target({ ElementType.TYPE_USE, ElementType.TYPE_PARAMETER })</span>
    <span class="java_keyword">public</span> <span class="java_keyword">@interface</span> NonEmpty {
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">class</span> Holder&lt;<span class="java_annotation">@NonEmpty</span> T&gt; <span
        class="java_keyword">extends</span> <span class="java_annotation">@NonEmpty</span> Object {
        <span class="java_keyword">public</span> <span class="java_keyword">void</span> method() <span class="java_keyword">throws</span> <span class="java_annotation">@NonEmpty</span> Exception {
        }
    }

    <span class="java_annotation">@SuppressWarnings(<span class="java_string">&quot;unused&quot;</span>)</span>
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">final</span> Holder&lt;String&gt; holder = <span class="java_keyword">new</span> <span class="java_annotation">@NonEmpty</span> Holder&lt;String&gt;();
        <span class="java_keyword">@NonEmpty</span>
        Collection&lt;<span class="java_annotation">@NonEmpty</span> String&gt; strings = <span class="java_keyword">new</span> ArrayList&lt;&gt;();
    }
} </pre>
  </div>
  <p class="bekezd">
    Az annotáció definíciójában a <span class="programkod">@Target</span> metaannotáció segítségével lehet megadni azt a kontextust ahol az annotációt használni lehet majd. A Java 8-ban két
    új típus jelent meg:
  </p>
  <ul>
    <li><b>ElementType.TYPE_PARAMETER</b>: típus paraméter deklarációhoz</li>
    <li><b>ElementType.TYPE_USE</b>: típus használatához</li>
  </ul>
  <p class="bekezd">Természetesen az annotációfeldolgozó API-ba is bevezették ezen típusú annotációk kezelését.</p>
  <p class="bekezd">
    <b>Annotációk megszerzése</b>
  </p>
  <div class="keretes">
    <p>
      Amint azt bizonyára mindenki tudja, az annotációk bekerülhetnek egy osztály vagy interfész bináris reprezentációjába is (egyszerűbben szólva: a .class fájlba). Ha pedig mág bekerülnek
      akkor elérhetőek (vagy nem) futásidőben is reflection segítségével. Az annotációk definícióját a <span class="programkod">@Retention</span> metaannotációval meg lehet jelölni és ezen
      belül a <span class="programkod">RetentionPolicy</span> enum konstansaival lehet megadni, hogy az adott annotáció meddig legyen megtartva:
    </p>
    <ul>
      <li><b>RetentionPolicy.SOURCE</b>: az annotációkat a fordító eldobja</li>
      <li><b>RetentionPolicy.CLASS</b>: az annotációkat a fordító beleteszi a .class fájlokba, de a JVM nem szükségszerűen tartja meg futásidőben. Ez az alapértelmezett.</li>
      <li><b>RetentionPolicy.RUNTIME</b>: az annotációkat a fordító beleteszi a .class fájlokba és a JVM is megtartja, tehát futásidőben kiolvasható információk</li>
    </ul>
    <p>A fentiektől függetlenül a helyi változókra vonatkozó annotációkat a fordító mindig eldobja akármit csinálunk is. A .class fájlokba többek között az annotációra vonatkozó
      információk is ún. attribútumok formájában kerülnek be. Ezekről most elég itt annyit tudni, hogy hatféle van belőlük:</p>
    <ul>
      <li><b>RuntimeVisibleAnnotations</b>: osztályhoz, mezőhöz vagy metódushoz tartozhat. Futásidőben is elérhető annotációkat tartalmaz.</li>
      <li><b>RuntimeInvisibleAnnotations</b>: osztályhoz, mezőhöz vagy metódushoz tartozhat. Ezeket az annotációkat a JVM-nek futásidőben nem kell elérhetővé tenni, kivéve ha
        valamilyen módon erre utasítottuk. (Ez lehet implementációspecifikus módszer, mint például egy parancssori kapcsoló.)</li>
      <li><b>RuntimeVisibleParameterAnnotations</b>: adott metódushoz tartozó futásidőben is elérhető annotációkat tartalmaz.</li>
      <li><b>RuntimeInvisibleParameterAnnotations</b>: adott metódushoz tartozó futásidőben nem elérhető annotációkat tartalmaz. (Ez alól kivétel, ha a JVM-et valamilyen módon, például
        implementációspecifikus parancssori kapcsolóval arra utasították hogy mégis tegye elérhetővé.)</li>
      <li><b>RuntimeVisibleTypeAnnotations</b>: osztályhoz, mezőhöz vagy metódushoz tartozhat. Olyan futásidőben is elérhető annotációkat tartalmaz, amiket a megfelelő osztály, mező,
        metódus vagy pedig egy metódustörzsben használt kifejezés deklarációjában használt típusokon adtak meg. Emellett ebben az attribútumban vannak olyan annotációk is, amelyek generikus
        osztályok, interfészek, metódusok és konstruktorok típus paraméter deklarációjánál lettek megadva.</li>
      <li><b>RuntimeInvisibleTypeAnnotations</b>: osztályhoz, mezőhöz vagy metódushoz tartozhat. Olyan futásidőben nem elérhető annotációkat tartalmaz, amiket a megfelelő osztály,
        mező, metódus vagy pedig egy metódustörzsben használt kifejezés deklarációjában használt típusokon adtak meg. Emellett ebben az attribútumban vannak olyan annotációk is, amelyek
        generikus osztályok, interfészek, metódusok és konstruktorok típus paraméter deklarációjánál lettek megadva (és szintén elérhetetlenek futásidőben).</li>
    </ul>
    <p>A RuntimeVisibleTypeAnnotations és RuntimeInvisibleTypeAnnotations attribútumok Java 8 esetén újdonságként kerültek a .class fájl definíciójába.</p>
  </div>
  <p class="bekezd">
    A reflecion API-ban több metódus található arra nézvést, hogy futásidőben is elérhető annotációkat megszerezzünk. Az <span class="programkod">AnnotatedElement</span> interfész ki lett
    bővítve az ismétlő annotációk bevezetése miatt. (Ezt az interfészt implementálják a reflection API azon elemei amelyekhez lehet annotációt társítani, mint például a Method, Class,
    Constructor, Field, stb.) Azon metódusok működése, amelyek egy annotációt adnak vissza, mint például az <span class="programkod">AnnotatedElement.getAnnotation(Class&lt;T&gt;)</span>
    változatlan maradt, de egy megszorítással: továbbra is mindig csak egy annotációt adnak vissza, tehát ismétlő annotáció esetén nem az ismétlő annotációt, hanem annak konténerét kell
    lekérdeznünk (akkor is ha csak egy van belőle). Így lehetett elérni a visszamenőleges kompatibilitást. A Java 8-ban három további metódus került az interfészbe a meglévő négy mellé,
    kettő ezekből már az ismétlő annotációkat is visszaadja. Ha a paraméter ismétlő annotáció, akkor a <span class="programkod">getAnnotationsByType(Class&lt;T&gt;)</span> és a <span
      class="programkod">getDeclaredAnnotationsByType(Class&lt;T&gt;)</span> metódus végigmegy a konténer annotációkon, ha van olyan és visszaadja a konténerek tartalmát is.
  </p>
  <p class="bekezd">
    A Java 8 az AnnotatedElement használatához bevezette a <i>directly present</i>, <i>indirectly present</i>, <i>present</i> és <i>associated</i> fogalmakat. Ezek pontosan meghatározzák,
    mely annotációt mely metódus ad vissza. Vigyázat, definíció következik!
  </p>
  <ul>
    <li>egy A annotáció directly present típusú egy E elemen, ha az E elemnek van RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations vagy RuntimeVisibleTypeAnnotations
      attribútuma és az attribútum tartalmazza az A-t.</li>
    <li>egy A annotáció indirectly present egy E elemen ha az E elemnek van RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations vagy RuntimeVisibleTypeAnnotations
      attribútuma, az A típusa ismétlő és az attribútum tartalmaz pontosan egy annotációt aminek a value eleme tartalmazza az A-t és aminek a típusa A típusának konténer annotáció típusa</li>
    <li>egy A annotáció present típusú egy E elemen ha
      <ul>
        <li>az A directly present az E-n vagy</li>
        <li>nincs A annotáció directly present típusban az E-n és E egy osztály valamint A típusa örökölhető és A present típusú az E ősosztályán</li>
      </ul>
    </li>
    <li>egy A annotáció associated egy E elemen ha
      <ul>
        <li>A directly vagy indirectly present az E-n vagy</li>
        <li>nincs A típusú annotáció directly vagy indirectly present az E-n és E egy osztály valamint A típusa örökölhető és A associated típusú az E ősosztályán</li>
      </ul>
    </li>
  </ul>
  <p class="bekezd">(Vegyük észre, hogy - az öröklődés miatt - a present és az associated rekurzív definíció.)</p>
  <p class="bekezd">Az alábbi táblázat megmutatja hogy mely típusú annotáció jelenlétet mely metódussal lehet megvizsgálni. Bár ez a hivtalos Oracle dokumentációból származik, a
    valóságban a definíciók egymásba ágyazottsága miatt a present típusú annotációkat visszaadó metódus nyilvánvalóan a directly present típust is vissza fogja adni.</p>
  <table cellpadding="0" cellspacing="0" width="80%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal" rowspan="2"><b>Metódus</b></th>
        <th class="normal" colspan="4"><b>Típus</b></th>
      </tr>
      <tr>
        <th class="normal"><b>Directly present</b></th>
        <th class="normal"><b>Indirectly present</b></th>
        <th class="normal"><b>Present</b></th>
        <th class="normal"><b>Associated</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>T getAnnotation(Class&lt;T&gt;)</td>
        <td></td>
        <td></td>
        <td>X</td>
        <td></td>
      </tr>
      <tr class="tr2">
        <td>Annotation[] getAnnotations()</td>
        <td></td>
        <td></td>
        <td>X</td>
        <td></td>
      </tr>
      <tr class="tr1">
        <td>T[] getAnnotationsByType(Class&lt;T&gt;)</td>
        <td></td>
        <td></td>
        <td></td>
        <td>X</td>
      </tr>
      <tr class="tr2">
        <td>T getDeclaredAnnotation(Class&lt;T&gt;)</td>
        <td>X</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr class="tr1">
        <td>Annotation[] getDeclaredAnnotations()</td>
        <td>X</td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr class="tr2">
        <td>T[] getDeclaredAnnotationsByType(Class&lt;T&gt;)</td>
        <td>X</td>
        <td>X</td>
        <td></td>
        <td></td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    Egy <span class="programkod">getAnnotationsByType(Class&lt;T&gt;)</span> vagy <span class="programkod">getDeclaredAnnotationsByType(Class&lt;T&gt;)</span> meghívása esetén az E elemen
    lévő directly vagy indirectly present típusú annotációk sorrendje úgy számolódik mintha az E-n lévő indirectly present annotációk is directly present lennének az E-n (a konténer
    annotáció helyett) és abban a sorrendben ahogyan a konténer annotáció value mezőjében megjelennek.
  </p>
  <p class="bekezd">Az alábbi példa bemutatja a különböző típusokat és lekérdezéseket:</p>
  <p class="bekezd">
    <u>AnnotationRootAncestor.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno;

<span class="java_keyword">import</span> java.lang.annotation.ElementType;
<span class="java_keyword">import</span> java.lang.annotation.Inherited;
<span class="java_keyword">import</span> java.lang.annotation.Repeatable;
<span class="java_keyword">import</span> java.lang.annotation.Retention;
<span class="java_keyword">import</span> java.lang.annotation.RetentionPolicy;
<span class="java_keyword">import</span> java.lang.annotation.Target;

<span class="java_keyword">import</span> hu.egalizer.java8.anno.AnnotationRootAncestor.SimpleAnni;

<span class="java_annotation">@SimpleAnni</span>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> AnnotationRootAncestor {
    <span class="java_annotation">@Inherited</span>
    <span class="java_annotation">@Target(ElementType.TYPE)</span>
    <span class="java_annotation">@Retention(RetentionPolicy.RUNTIME)</span>
    <span class="java_keyword">public</span> <span class="java_keyword">@interface</span> Books {
        Book[] value();
    }

    <span class="java_annotation">@Inherited</span>
    <span class="java_annotation">@Target(ElementType.TYPE)</span>
    <span class="java_annotation">@Retention(RetentionPolicy.RUNTIME)</span>
    <span class="java_annotation">@Repeatable(Books.<span class="java_keyword">class</span>)</span>
    <span class="java_keyword">public</span> <span class="java_keyword">@interface</span> Book {
        String value();
    };

    <span class="java_annotation">@Inherited</span>
    <span class="java_annotation">@Retention(RetentionPolicy.RUNTIME)</span>
    <span class="java_keyword">public</span> <span class="java_keyword">@interface</span> SimpleAnni {
    };

}</pre>
  </div>
  <p class="bekezd">
    <u>AnnotationPresenceAncestor.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno;

<span class="java_keyword">import</span> hu.egalizer.java8.anno.AnnotationRootAncestor.Book;

<span class="java_annotation">@Book(<span class="java_string">"Java 8 for dummies"</span>)</span>
<span class="java_annotation">@Book(<span class="java_string">"C# for experts"</span>)</span>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> AnnotationPresenceAncestor <span class="java_keyword">extends</span> AnnotationRootAncestor {

}</pre>
  </div>
  <p class="bekezd">
    <u>AnnotationPresence.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno;

<span class="java_keyword">import</span> java.lang.annotation.Annotation;
<span class="java_keyword">import</span> java.lang.reflect.AnnotatedElement;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> AnnotationPresence <span class="java_keyword">extends</span> AnnotationPresenceAncestor {

    <span class="java_annotation">@Book(<span class="java_string">"Memories"</span>)</span>
    <span class="java_annotation">@Book(<span class="java_string">"War and peace"</span>)</span>
    <span class="java_keyword">public</span> <span class="java_keyword">interface</span> Bookshelf {
    }

    <span class="java_annotation">@SimpleAnni</span>
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> anniTest() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Hahó&quot;</span>);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> NoSuchMethodException, SecurityException {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;----------- Directly present:&quot;</span>);
        annotatedMethods(AnnotationPresence.<span class="java_keyword">class</span>.getMethod(<span class="java_string">"anniTest"</span>), SimpleAnni.<span class="java_keyword">class</span>);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;----------- Indirectly present:&quot;</span>);
        annotatedMethods(Bookshelf.<span class="java_keyword">class</span>, Book.<span class="java_keyword">class</span>);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;----------- Present:&quot;</span>);
        annotatedMethods(AnnotationPresence.<span class="java_keyword">class</span>, SimpleAnni.<span class="java_keyword">class</span>);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;----------- Associated:&quot;</span>);
        annotatedMethods(AnnotationPresence.<span class="java_keyword">class</span>, Book.<span class="java_keyword">class</span>);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> annotatedMethods(AnnotatedElement element, Class&lt;? <span
        class="java_keyword">extends</span> Annotation&gt; annotation) {
        printAnnotation(<span class="java_string">"getAnnotation(Class&lt;T&gt;)"</span>, element.getAnnotation(annotation));
        printAnnotation(<span class="java_string">"getAnnotations()"</span>, element.getAnnotations());
        printAnnotation(<span class="java_string">"getAnnotationsByType(Class&lt;T&gt;)"</span>, element.getAnnotationsByType(annotation));
        printAnnotation(<span class="java_string">"getDeclaredAnnotation(Class&lt;T&gt;)"</span>, element.getDeclaredAnnotation(annotation));
        printAnnotation(<span class="java_string">"getDeclaredAnnotations()"</span>, element.getDeclaredAnnotations());
        printAnnotation(<span class="java_string">"getDeclaredAnnotationsByType(Class&lt;T&gt;)"</span>, element.getDeclaredAnnotationsByType(annotation));
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> printAnnotation(String methodName, Annotation anno) {
        System.<span class="java_constant">out</span>.print(methodName + <span class="java_string">" invocation "</span>);
        <span class="java_keyword">if</span> (anno != <span class="java_keyword">null</span>) {
            System.<span class="java_constant">out</span>.println(<span class="java_string">"found annotation type "</span> + anno.toString());
        } <span class="java_keyword">else</span> {
            System.<span class="java_constant">out</span>.println(<span class="java_string">"found nothing!"</span>);
        }
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> printAnnotation(String methodName, Annotation[] annoArray) {
        System.<span class="java_constant">out</span>.print(methodName + <span class="java_string">" invocation "</span>);
        <span class="java_keyword">if</span> (annoArray.length &gt; 0) {
            StringBuilder sb = <span class="java_keyword">new</span> StringBuilder(<span class="java_string">"found annotation types: "</span>);
            <span class="java_keyword">for</span> (Annotation an : annoArray) {
                sb.append(an.toString());
                sb.append(<span class="java_string">", "</span>);
            }
            System.<span class="java_constant">out</span>.println(sb.toString());
        } <span class="java_keyword">else</span> {
            System.<span class="java_constant">out</span>.println(<span class="java_string">"found nothing!"</span>);
        }
    }

}</pre>
  </div>
  <p class="bekezd">A program kimenetele:</p>
  <div class="programkod">
    <pre>----------- Directly present:
getAnnotation(Class&lt;T&gt;) invocation found annotation type @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni()
getAnnotations() invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), 
getAnnotationsByType(Class&lt;T&gt;) invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), 
getDeclaredAnnotation(Class&lt;T&gt;) invocation found annotation type @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni()
getDeclaredAnnotations() invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), 
getDeclaredAnnotationsByType(Class&lt;T&gt;) invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), 
----------- Indirectly present:
getAnnotation(Class&lt;T&gt;) invocation found nothing!
getAnnotations() invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$Books(value=[@hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Memories), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=War and peace)]), 
getAnnotationsByType(Class&lt;T&gt;) invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Memories), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=War and peace), 
getDeclaredAnnotation(Class&lt;T&gt;) invocation found nothing!
getDeclaredAnnotations() invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$Books(value=[@hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Memories), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=War and peace)]), 
getDeclaredAnnotationsByType(Class&lt;T&gt;) invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Memories), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=War and peace), 
----------- Present:
getAnnotation(Class&lt;T&gt;) invocation found annotation type @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni()
getAnnotations() invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), @hu.egalizer.java8.anno.AnnotationRootAncestor$Books(value=[@hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Java 8 for dummies), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=C# for experts)]), 
getAnnotationsByType(Class&lt;T&gt;) invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), 
getDeclaredAnnotation(Class&lt;T&gt;) invocation found nothing!
getDeclaredAnnotations() invocation found nothing!
getDeclaredAnnotationsByType(Class&lt;T&gt;) invocation found nothing!
----------- Associated:
getAnnotation(Class&lt;T&gt;) invocation found nothing!
getAnnotations() invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$SimpleAnni(), @hu.egalizer.java8.anno.AnnotationRootAncestor$Books(value=[@hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Java 8 for dummies), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=C# for experts)]), 
getAnnotationsByType(Class&lt;T&gt;) invocation found annotation types: @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=Java 8 for dummies), @hu.egalizer.java8.anno.AnnotationRootAncestor$Book(value=C# for experts), 
getDeclaredAnnotation(Class&lt;T&gt;) invocation found nothing!
getDeclaredAnnotations() invocation found nothing!
getDeclaredAnnotationsByType(Class&lt;T&gt;) invocation found nothing!</pre>
  </div>
  <p class="bekezd">Olyan helyzet is előfordulhat amikor egy korábban nem ismétlő annotációból egyszer csak ismétlőt csinálunk. Ilyenkor kompatibilitási kérdések merülnek fel, amikre a
    Java 8 tervezői az alábbi válaszokat adták. (A leírásban T az annotáció, TC pedig annak a konténerannotációja.)</p>
  <ul>
    <li>T módosítása ismétlővé forrás és bináris szinten kompatibilis a T és TC már létező használataival. A forráskód-kompatibilitás azt jelenti, hogy a T vagy TC-vel jelölt forráskód
      továbbra is lefordul. A bináris kompatibilitás azt jelenti, hogy a T vagy TC-vel rendelkező .class fájlok együttműködnek (linkelődnek) a T módosított verziójával ha együttműködtek a
      korábbi verzióval is. (Egy TC annotációtípus tud informálisan működő konténerannotációként működni mielőtt a T-t formálisan is ismételhetővé módosítanánk. De azt is megtehetjük, hogy
      amikor a T-t ismétlővé módosítjuk akkor a TC-t új típusként vezetjük be.)</li>
    <li>ha egy TC annotáció present típusú egy elemen és a T ismétlőnek lett módosítva TC konténerrel akkor:
      <ul>
        <li>a T módosítása viselkedésben kompatibilis a T vagy TC paraméterrel hívott <span class="programkod">get[Declared]Annotation(Class&lt;T&gt;)</span> és <span
          class="programkod">get[Declared]Annotations()</span> metódusokkal, mert a metódusok visszatérési értéke nem fog megváltozni amiatt, hogy TC a T konténerje lett
        </li>
        <li>a T módosítása megváltoztatja a T paraméterrel hívott <span class="programkod">get[Declared]AnnotationsByType(Class&lt;T&gt;)</span> metódusok eredményét, mert ezek a
          metódusok most már felismerik, hogy egy TC típusú annotáció konténerje a T-nek és &quot;végigmennek&quot; rajta, hogy megkeressék a T típusú annotációkat
        </li>
      </ul>
    </li>
    <li>ha egy T annotáció present típusú az elemen és T-t ismétlővé módosítjuk majd több T típusú annotációt adunk az elemhez, akkor:
      <ul>
        <li>a T típusú annotációk hozzáadása forrásszinten és binárisan kompatibilis</li>
        <li>a T típusú annotációk hozzáadása megváltoztatja a <span class="programkod">get[Declared]Annotation(Class&lt;T&gt;)</span> és a <span class="programkod">get[Declared]Annotations()</span>
          metódusokat mert ezek a metódusok most már csak egy konténer annotációt fognak látni az elemen és nem látnak ott T típusú annotációt
        </li>
        <li>a T típusú annotációk hozzáadása megváltoztatja a <span class="programkod">get[Declared]AnnotationsByType(Class&lt;T&gt;)</span> metódusokat mert ezek visszatérési értéke
          tartalmazni fogja a további hozzáadott T típusú annotációkat, míg korábban csak egy T típusú annotációt találtak ott
        </li>
      </ul>
    </li>
  </ul>
  <p class="bekezd">Az első pontban van egy érdekes kitétel ami talán másoknak is szemet szúrt. A TC már azelőtt konténerként tud viselkedni, hogy a T-ből ismétlőt csinálnánk? Nézzük a
    következő példát:</p>
  <p class="bekezd">
    <u>Oldanno.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno.kompa;

<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">FIELD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">METHOD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">TYPE</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.RetentionPolicy.<span class="java_constant">RUNTIME</span>;

<span class="java_keyword">import</span> java.lang.annotation.Retention;
<span class="java_keyword">import</span> java.lang.annotation.Target;

<span class="java_annotation">@Retention(RUNTIME)</span>
<span class="java_annotation">@Target({ TYPE, FIELD, METHOD })</span>
<span class="java_keyword">public</span> @<span class="java_keyword">interface</span> Oldanno {
    String magic();
} </pre>
  </div>
  <p class="bekezd">
    <u>OldannoContainer.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno.kompa;

<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">FIELD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">METHOD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">TYPE</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.RetentionPolicy.<span class="java_constant">RUNTIME</span>;

<span class="java_keyword">import</span> java.lang.annotation.Retention;
<span class="java_keyword">import</span> java.lang.annotation.Target;

<span class="java_annotation">@Retention(RUNTIME)</span>
<span class="java_annotation">@Target({ TYPE, FIELD, METHOD })</span>
<span class="java_keyword">public</span> @<span class="java_keyword">interface</span> OldannoContainer {
}</pre>
  </div>
  <p class="bekezd">
    <u>Test1.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno.kompa;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> Test1 {

    <span class="java_annotation">@Oldanno</span>(magic = <span class="java_string">&quot;Magic Method&quot;</span>)
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> statikus1() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">"irgum"</span>);
    }

    <span class="java_annotation">@OldannoContainer</span>
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> statikus2() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">"burgum"</span>);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> NoSuchMethodException, SecurityException {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Annotációk lekérdezése&quot;</span>);
        System.<span class="java_constant">out</span>.println(Test1.<span class="java_keyword">class</span>.getMethod(<span class="java_string">&quot;statikus1&quot;</span>).getAnnotation(Oldanno.<span
        class="java_keyword">class</span>).magic());
        System.<span class="java_constant">out</span>.println(Test1.<span class="java_keyword">class</span>.getMethod(<span class="java_string">&quot;statikus2&quot;</span>).getAnnotation(OldannoContainer.<span
        class="java_keyword">class</span>));

        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Metódushívás&quot;</span>);
        statikus1();
        statikus2();
    }

}</pre>
  </div>
  <p class="bekezd">Fordítsuk le a három osztályt:</p>
  <pre class="programkod">javac.exe hu\egalizer\java8\anno\kompa\*.java</pre>
  <p class="bekezd">
    Ha elindítjuk a <span class="programkod">Test1</span>-et akkor szépen le fog futni. Ezután módosítsuk az <span class="programkod">Oldanno</span> annotációt ismétlőre. Ahhoz, hogy
    leforduljon csinálni kell neki konténert is, ez lesz az <span class="programkod">OldannoContainer</span>, ezt is és a forrást is módosítani kell ennek megfelelően. Ezután fordítsuk le
    csak az Oldanno.java-t!
  </p>
  <p class="bekezd">
    <u>Oldanno.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno.kompa;

<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">FIELD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">METHOD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">TYPE</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.RetentionPolicy.<span class="java_constant">RUNTIME</span>;

<span class="java_keyword">import</span> java.lang.annotation.Retention;
<span class="java_keyword">import</span> java.lang.annotation.Target;

<span class="java_annotation">@Retention(RUNTIME)</span>
<span class="java_annotation">@Target({ TYPE, FIELD, METHOD })</span>
<span class="java_annotation">@Repeatable</span>(OldannoContainer.<span class="java_keyword">class</span>)
<span class="java_keyword">public</span> @<span class="java_keyword">interface</span> Oldanno {
    String magic();
} </pre>
  </div>
  <p class="bekezd">
    <u>OldannoContainer.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno.kompa;

<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">FIELD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">METHOD</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.ElementType.<span class="java_constant">TYPE</span>;
<span class="java_keyword">import</span> <span class="java_keyword">static</span> java.lang.annotation.RetentionPolicy.<span class="java_constant">RUNTIME</span>;

<span class="java_keyword">import</span> java.lang.annotation.Retention;
<span class="java_keyword">import</span> java.lang.annotation.Target;

<span class="java_annotation">@Retention(RUNTIME)</span>
<span class="java_annotation">@Target({ TYPE, FIELD, METHOD })</span>
<span class="java_keyword">public</span> @<span class="java_keyword">interface</span> OldannoContainer {
    Oldanno[] value();
}</pre>
  </div>
  <p class="bekezd">
    <u>Test1.java</u>
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.anno.kompa;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> Test1 {

    <span class="java_annotation">@Oldanno</span>(magic = <span class="java_string">&quot;Magic Method&quot;</span>)
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> statikus1() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">"irgum"</span>);
    }

    <span class="java_annotation">@OldannoContainer</span>(value = {})
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> statikus2() {
        System.<span class="java_constant">out</span>.println(<span class="java_string">"burgum"</span>);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> NoSuchMethodException, SecurityException {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Annotációk lekérdezése&quot;</span>);
        System.<span class="java_constant">out</span>.println(Test1.<span class="java_keyword">class</span>.getMethod(<span class="java_string">&quot;statikus1&quot;</span>).getAnnotation(Oldanno.<span
        class="java_keyword">class</span>).magic());
        System.<span class="java_constant">out</span>.println(Test1.<span class="java_keyword">class</span>.getMethod(<span class="java_string">&quot;statikus2&quot;</span>).getAnnotation(OldannoContainer.<span
        class="java_keyword">class</span>));

        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Metódushívás&quot;</span>);
        statikus1();
        statikus2();
    }

}</pre>
  </div>
  <pre class="programkod">javac.exe hu\egalizer\java8\anno\kompa\Oldanno.java</pre>
  <p class="bekezd">
    Látni fogjuk, hogy a <span class="programkod">Test1</span> továbbra is ugyanúgy fut ezután is, pedig azt a régi <span class="programkod">Oldanno</span> annotációval fordítottuk. Vagyis
    a korábban lefutott <span class="programkod">OldannoContainer</span> már azelőtt konténerként tud viselkedni, hogy az <span class="programkod">Oldanno</span>-ból ismétlőt csinálnánk.
  </p>
  <h2>Okosabb típuskezelés</h2>
  <p class="bekezd">A Java 8 fordítója sokat fejlődött a típusok kezelése tekintetében. A típus paramétereket sok esetben ki tudja következtetni, így a kód tisztább lehet. Tekintsük az
    alábbi két osztályt!</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> GenericValue&lt;T&gt; {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;T&gt; T defaultValue() {
        <span class="java_keyword">return</span> <span class="java_keyword">null</span>;
    }

    <span class="java_keyword">public</span> T getOrDefault(T value, T defaultValue) {
        <span class="java_keyword">return</span> (value != <span class="java_keyword">null</span>) ? value : defaultValue;
    }
} </pre>
  </div>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> GenericTest {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">final</span> GenericValue&lt;String&gt; value = <span class="java_keyword">new</span> GenericValue&lt;&gt;();
        value.getOrDefault(<span class="java_string">&quot;Próba&quot;</span>, GenericValue.defaultValue());
    }
} </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">Value.defaultValue()</span> típus paramétere ki lesz következtetve és nem szükséges expliciten megadni. Java 7-ben a fenti példa nem fordulna le csak ha
    átírnánk <span class="programkod">Value.&lt;String&gt;defaultValue()</span> formára.
  </p>
  <h2>Új lehetőség a Java fordítóban</h2>
  <p class="bekezd">
    Régóta próbáltak már kerülőmódszereket találni arra, hogy a metódus paraméter nevek bekerüljenek a bájtkódba és elérhetőek legyenek futásidőben. A Java 8 ezt a problémát is megoldja. A
    <span class="programkod">javac</span> kapott kapott egy <span class="programkod">-parameters</span> kapcsolót amivel elérhetjük, hogy a nevek bekerüljenek a .class fájlba, a reflection
    API-ban pedig megjelent a <span class="programkod">Parameter.getName()</span> metódus amivel lekérdezhetjük futásidőben. (Természetesen a paramétereket a fájlméret és az osztály
    futásidejű memóriafoglalásának kárára tarthatjuk meg.) A reflection-ön keresztül a <span class="programkod">Parameter.isNamePresent()</span> metódussal azt is ellenőrizni lehet, hogy ez
    a kapcsoló fordításkor meg volt-e adva. (A JDK-t enélkül fordították, tehát a gyári API-kban a paraméterneveket hiába keressük.)
  </p>
  <h2>Optional</h2>
  <p class="bekezd">
    A híres <span class="programkod">NullPointerException</span> magasan a legnépszerűbb kivétel a Java nyelvben. Népszerűsége abban áll, hogy a programozók szeretnének vele minél
    kevesebbet találkozni. Ezért aztán folyamatosan nullvizsgálatokat kénytelenek írni a kódba. A Google Guava projekt a <span class="programkod">NullPointerException</span> elkerülésére
    már réges-régen (egy messzi-messzi galaxisban) bevezette az Optional nevű megoldását. Ez lecsökkenti a null ellenőrzések kódot teleszemetelő hatását és lehetővé teszi tisztább kód
    írását. A Guava-ból inspirálva az Optional immár a Java 8 osztálykönyvtárnak is része.
  </p>
  <p class="bekezd">
    Az <span class="programkod">Optional</span> valójában csak egy (generikus) konténer. Bármilyen <span class="programkod">T</span> típusú értéket vagy <span class="programkod">null</span>-t
    tud tárolni. Számos hasznos metódusa van, így az explicit nullvizsgálat már nem föltétlenül szükséges többé.
  </p>
  <p class="bekezd">
    <b style="text-decoration: underline;"><span class="programkod">Optional</span> létrehozása</b>
  </p>
  <p class="bekezd">
    Üres, <span class="programkod">null</span>-t tartalmazó <span class="programkod">Optional</span> létrehozása:
  </p>
  <div class="programkod">
    <pre>Optional&lt;T&gt; str = Optional.empty(); </pre>
  </div>
  <p class="bekezd">
    Az empty szó egyébként kicsit megtévesztő, hiszen általában az üres sztringeket is (&quot;&quot;) empty-nek hívják, sok osztálykönyvtárnak van <span class="programkod">isEmpty()</span>
    vagy hasonló metódusa az üres sztringek vizsgálatára. Ha viszont egy üres sztringet egy <span class="programkod">Optional</span> objektumba teszünk, akkor az <span class="programkod">Optional</span>
    nem lesz &quot;empty&quot;. (Régi magyar programozás szakkönyvek egyébként a sztringeket előszeretettel nevezték &quot;füzér&quot;-nek. Szép szó, de én mégis inkább maradok a
    sztringnél.)
  </p>
  <p class="bekezd">
    Az <span class="programkod">ofNullable()</span> metódussal olyan <span class="programkod">Optional</span>-t hozhatunk létre ami a paraméterétől függően lehet üres vagy tartalmazhat <span
      class="programkod">null</span>-tól eltérő értéket:
  </p>
  <div class="programkod">
    <pre>String s = ... <span class="java_comment">//az s korábban kaphatott értéket de lehet null is</span>
Optional&lt;String&gt; name = Optional.ofNullable(s); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">of()</span> gyártófüggvény ezzel szemben mindenképpen nem null paramétert vár, különben <span class="programkod">NullPointerException</span>-t dob!
  </p>
  <div class="programkod">
    <pre>Optional&lt;String&gt; name = Optional.of(&quot;Jancsi&quot;); </pre>
  </div>
  <p class="bekezd">
    <b style="text-decoration: underline;">Az <span class="programkod">Optional</span> használata
    </b>
  </p>
  <p class="bekezd">
    Ha van <span class="programkod">Optional</span> példányunk akkor a metódusain keresztül közvetlenül tudjuk kezelni érték jelenlétét vagy hiányát. Tekintsük a következő példát:
  </p>
  <div class="programkod">
    <pre>LocalDate date = LocalDate.now();
<span class="java_keyword">if</span> (date != <span class="java_keyword">null</span>) {
	System.<span class="java_constant">out</span>.println(date);
} </pre>
  </div>
  <p class="bekezd">
    Az Optional esetén a fenti feltételvizsgálatot az <span class="programkod">ifPresent()</span> metódussal már így is írhatjuk:
  </p>
  <div class="programkod">
    <pre>Optional&lt;LocalDate&gt; date = Optional.of(LocalDate.now());
date.ifPresent(System.out::println); </pre>
  </div>
  <p class="bekezd">
    Ebben a formában nem kell explicit nullvizsgálat. Ha az <span class="programkod">Optional</span> üres lenne, akkor semmi nem íródna ki. Az <span class="programkod">isPresent()</span>
    metódussal viszont megvizsgálhatjuk, hogy van-e az <span class="programkod">Optional</span> objektumpéldányunkban érték. Sőt, van egy <span class="programkod">get()</span> metódus is,
    ami vissza is adja azt (ha nincs, akkor meg <span class="programkod">NoSuchElementException</span> kivételt dob). Ezzel a két metódussal újra behozhatjuk az ifet ha nagyon hiányozna,
    bár ez a fajta használat kerülendő, mert nem használja ki az <span class="programkod">Optional</span> előnyeit:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">if</span> (date.isPresent()) {
  System.<span class="java_constant">out</span>.println(date.get());
} </pre>
  </div>
  <p class="bekezd">
    Ha viszont jobban megnézzük a fenti kódot, akkor egyből felmerül a kérdés, hogy és mi van, ha az <span class="programkod">Optional&lt;LocalDate&gt;</span> típusú <span
      class="programkod">date</span> lesz null? Ezt semmi sem tiltja és akkor innentől kezdve egyáltalán nem száműztük a <span class="programkod">NullPointerException</span>-t, sőt! Nos az
    <span class="programkod">Optional</span> sem tökéletes megoldás, a hátrányairól később még lesz szó.
  </p>
  <p class="bekezd">
    <b style="text-decoration: underline;">Alapértelmezett értékek</b>
  </p>
  <p class="bekezd">Tipikus programozási eset például hogy adjunk vissza valami alapértelmezett értéket ha azt tapasztaljuk hogy egy művelet eredménye null. Használhatjuk ilyenkor a
    háromoperandusú operátort is:</p>
  <div class="programkod">
    <pre>LocalDate datum = bizonytalanDatum != <span class="java_keyword">nul</span>l ? bizonytalanDatum : LocalDate.now(); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">Optional orElse()</span> metódusa visszaad egy alapértelmezett értéket ha az <span class="programkod">Optional</span> null-t tartalmaz. Ezzel a fenti esetet
    így is átírhatjuk:
  </p>
  <div class="programkod">
    <pre>Optional&lt;LocalDate&gt; bizonytalanDatum = ... <span class="java_comment">//nem tudjuk, mi történt</span>
LocalDate datum = bizonytalanDatum.orElse(LocalDate.now()); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">orElseGet()</span> majdnem ugyanezt csinálja, csak az alapértelmezett értéket ott egy <span class="programkod">Supplier</span> funkcionális interfész állítja
    elő, például:
  </p>
  <div class="programkod">
    <pre>LocalDate datum = bizonytalanDatum.orElseGet(LocalDate::now); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">orElseThrow()</span> metódus pedig nem foglalkozik alapértelmezett értékkel; ha az <span class="programkod">Optional</span> üres, akkor a megadott kivételt
    dobja:
  </p>
  <div class="programkod">
    <pre>LocalDate datum = bizonytalanDatum.orElseThrow(IllegalStateException::<span class="java_keyword">new</span>); </pre>
  </div>
  <p class="bekezd">
    <b style="text-decoration: underline;">Műveletek</b>
  </p>
  <p class="bekezd">Gyakran szükség van egy objektumon valamilyen mezőt metódushívással ellenőrizni. Például megnézzük, hogy a dátum újév napja-e:</p>
  <div class="programkod">
    <pre>LocalDate date = ...
<span class="java_keyword">if</span> (date != <span class="java_keyword">null</span> &amp;&amp; date.getDayOfYear() == 1) {
  System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Újév!&quot;</span>);
} </pre>
  </div>
  <p class="bekezd">
    Ezt az <span class="programkod">Optional filter()</span> metódusával a következőképpen lehet megtenni:
  </p>
  <div class="programkod">
    <pre>lehetsegesDatum.filter(date -&gt; date.getDayOfYear() == 1)
    .ifPresent(date -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Újév!&quot;</span>)); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">filter</span> metódus egy <span class="programkod">Predicate</span> funkcionális interfészt vár. Ha az <span class="programkod">Optional</span> tartalmaz
    valamilyen értéket és az megfelel a predikátumnak, akkor a visszaadott új <span class="programkod">Optional</span> objektumpéldány azt az értéket tartalmazza, egyébként pedig üres lesz
    (hasonló megoldást fogunk látni majd a Stream API-ban is).
  </p>
  <p class="bekezd">Nézzük most egy összetettebb példát! Tegyük fel, hogy van egy internetes boltunk, ahol minden rendeléshez (Order) tartozhat egy szállítási cím (Address), amin van
    egy flag, ami megmondja, hogy egyben ez-e a számlázási cím is (billingAddress). Cím (és számla) viszont nem kötelező, ha személyes átvételt választ a vásárló. Hogyan vizsgáljuk meg hogy
    a számlázási cím megegyezik-e a szállítási címmel?</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">if</span> (order != <span class="java_keyword">null</span>) {
  Address address = order.getAddress();
  if (address != <span class="java_keyword">null</span> &amp;&amp; address.isBillingAddress()) {
    System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Számlázási cím!&quot;</span>);
  }
} </pre>
  </div>
  <p class="bekezd">
    Az Address lekérdezését át tudjuk írni az <span class="programkod">Optional map</span> metódusának használatával így:
  </p>
  <div class="programkod">
    <pre>Optional&lt;Order&gt; possibleOrder = ...
Optional&lt;Address&gt; address = possibleOrder.map(Order::getAddress); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">Optional</span> objektumban tárolt érték egy új <span class="programkod">Optional</span> objektummá &quot;alakul&quot; a paraméterként átadott függvény
    használatával (itt egy metódusreferenciával, ami lekérdezi az address-t). A <span class="programkod">map()</span> tehát megváltoztat(hat)ja az eredeti Optional generikus típusát is. Ha
    az <span class="programkod">Optional</span> üres, akkor egy üres <span class="programkod">Optional</span> a <span class="programkod">map</span> visszatérési értéke is. Kombinálni is
    tudjuk a <span class="programkod">map</span>-et a <span class="programkod">filter</span> metódussal, hogy csak olyan címeket fogadjunk el ahol a cím egyben a számlázási cím is:
  </p>
  <div class="programkod">
    <pre>possibleOrder.map(Order::getAddress)
    .filter(address -&gt; address.isBillingAddress())
    .ifPresent(address -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Mehet!&quot;</span>)); </pre>
  </div>
  <p class="bekezd">A map többszörösen egymásba ágyazott adatszerkezetek esetén is hasznos. Tegyük fel, hogy az Address objektumon van egy címzett (Recipient) és annak egy keresztneve
    (firstName). Hogyan kérdezzük ezt le szokványos megoldással?</p>
  <div class="programkod">
    <pre>String name = order.getAddress().getRecipient().getFirstName(); </pre>
  </div>
  <p class="bekezd">
    Mi a helyzet, ha el szeretnénk kerülni a <span class="programkod">NullPointerException</span> kivételeket (mondjuk lehet bolti átvétel, ilyenkor nincs cím, stb.)?
  </p>
  <div class="programkod">
    <pre>String name = <span class="java_string">&quot;&quot;</span>;
<span class="java_keyword">if</span> (order != <span class="java_keyword">null</span>) {
    Address address = order.getAddress();
    <span class="java_keyword">if</span> (address != <span class="java_keyword">null</span>) {
        Name recipient = address.getRecipient();
        <span class="java_keyword">if</span> (recipient.getFirstName() != <span class="java_keyword">null</span>) {
            name = recipient.getFirstName();
        }
    }
} </pre>
  </div>
  <p class="bekezd">
    A lusta kiértékelést kihasználva ezt persze egyetlen ifbe is meg lehet írni, de így most szemléletesebb. Viszont ha megnézzük az eredeti egysoros kifejezést, akkor azt látjuk, hogy a
    kód csupán egy objektumot kivesz egy másikból és pont erre való a <span class="programkod">map</span> metódus is. Az <span class="programkod">Optional</span> használatával alakítsuk át
    az osztályokat így (a <span class="programkod">Name</span> marad változatlan):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> Order {

    <span class="java_keyword">private</span> Optional&lt;Address&gt; address;

    <span class="java_keyword">public</span> Optional&lt;Address&gt; getAddress() {
        <span class="java_keyword">return</span> address;
    }
    
    ...
}

<span class="java_keyword">public</span> <span class="java_keyword">class</span> Address {

    <span class="java_keyword">private</span> Optional&lt;Name&gt; recipient;
    <span class="java_keyword">private</span> <span class="java_keyword">boolean</span> billingAddress;

    <span class="java_keyword">public</span> Optional&lt;Name&gt; getRecipient() {
        <span class="java_keyword">return</span> recipient;
    }

    ...
}

<span class="java_keyword">public</span> <span class="java_keyword">class</span> Name {

    <span class="java_keyword">private</span> String firstName;

    <span class="java_keyword">public</span> String getFirstName() {
        <span class="java_keyword">return</span> firstName;
    }

    ...
} </pre>
  </div>
  <p class="bekezd">
    A fenti adatszerkezet így már mutatja, hogy egy adott érték akár hiányozhat is (opcionális), bár csak a példa kedvéért készült, mezők típusának nem ajánlatos <span class="programkod">Optional</span>
    típust adni. Az <span class="programkod">Optional</span> fentebb bemutatott metódusaival pedig közvetlenül kezelhetjük az érték hiányzó vagy meglévő eseteit. A null referenciákhoz
    képesti előny, hogy az <span class="programkod">Optional</span> kikényszeríti, hogy elgondolkodjunk rajta, mi van akkor amikor egy érték nincs jelen. Az <span class="programkod">Optional</span>-nak
    nem célja lecserélni minden egyes referenciát, de segítséget adhat sokkal egyértelműbb kód írásához azzal, hogy már egy metódus aláírását elolvasva látni fogjuk, hogy nem kötelező
    értéket ad vissza. A fenti egymásba ágyazott ifeket <span class="programkod">Optional</span> használatával eztán így is írhatnánk:
  </p>
  <div class="programkod">
    <pre>String name = order.map(Order::getAddress)
    .map(Address::getRecipient)
    .map(Recipient::getFirstName)
    .orElse(<span class="java_string">&quot;Ismeretlen&quot;</span>); </pre>
  </div>
  <p class="bekezd">
    Sajnos ez a kód nem fog lefordulni. Az order típusa <span class="programkod">Optional&lt;Order&gt;</span>, tehát azon minden gond nélkül meg lehet hívni a map metódust. A <span
      class="programkod">getAddress()</span> metódus viszont egy <span class="programkod">Optional&lt;Address&gt;</span> típusú objektumot ad vissza. Ez azt jelenti, hogy a map művelet
    eredményének típusa <span class="programkod">Optional&lt;Optional&lt;Address&gt;&gt;</span> lesz. Ezen nem lehet meghívni a <span class="programkod">getRecipient</span> metódust, hiszen
    a külső <span class="programkod">Optional</span> értékként egy másik <span class="programkod">Optional</span>-t tartalmaz aminek persze nincs <span class="programkod">getRecipient</span>
    metódusa.
  </p>
  <p class="bekezd">A probléma megoldásához az Optional bevezette a flatMap metódust. Ez a fenti helyzetek kezelésére szolgál: &quot;kilapítja&quot; a műveletek eredményeként előálló
    kétszintű Optional struktúrákat. A kódot tehát át kell írni a flatMap használatára:</p>
  <div class="programkod">
    <pre>String name = order.flatMap(Order::getAddress)
    .flatMap(Address::getRecipient)
    .map(Name::getFirstName)
    .orElse(<span class="java_string">&quot;Ismeretlen&quot;</span>); </pre>
  </div>
  <p class="bekezd">
    Az első flatMap biztosítja, hogy <span class="programkod">Optional&lt;Address&gt;</span> legyen visszaadva <span class="programkod">Optional&lt;Optional&lt;Address&gt;&gt;</span>
    helyett, a második pedig ugyanezt megteszi az <span class="programkod">Optional&lt;Name&gt;</span> esetén. Harmadszorra viszont már elég egy map is, mert a <span class="programkod">getFirstName
      String</span>-et ad vissza, nem pedig egy <span class="programkod">Optional&lt;String&gt;</span>-et.
  </p>
  <p class="bekezd">
    <b style="text-decoration: underline;">Problémák és tanácsok</b>
  </p>
  <p class="bekezd">Amikor először találkoztam ezzel az Optional-dologgal, felmerült bennem a kérdés, hogy mi szükség volt erre az egészre? Ez csak egy szimpla konténer ami referenciát
    tárol egy másik objektumra. Az a referencia attól még továbbra is lehet null (ha nem az of-fal gyártottuk le az Optional-t). A válasz filozófiai mélységekbe (vagy magasságokba) vezet!</p>
  <p class="bekezd">
    Nos, a <span class="programkod">null</span> érdekes dolog. A Java megpróbál a <span class="programkod">null</span>-al valami hiányzó dolgot reprezentálni, de valójában egy hiány még nem
    feltétlenül kellene, hogy <span class="programkod">null</span>-t jelentsen. Ráadásul a Java erősen típusos nyelv, a null-t viszont bármilyen referencia típusú változónak értékként
    tudjuk adni, tehát típus nélküliként viselkedik. Ha egy fejlesztő nem tudja, hogy a visszatérési érték mi legyen, akkor <span class="programkod">null</span>-t ad vissza. A hívóra hárul
    a null ellenőrzés terhe, különben <span class="programkod">NullPointerException</span> dobódhat. Nézzünk egy egyszerű példát (a rövidítés kedvéért egy osztályba vettem a logikát de
    érthető lesz):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.ArrayList;
<span class="java_keyword">import</span> java.util.List;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> OptionalPhilosophy {
    <span class="java_keyword">private</span> <span class="java_keyword">static</span> List&lt;String&gt; allLegoTheme = <span class="java_keyword">new</span> ArrayList&lt;String&gt;();

    <span class="java_keyword">static</span> {
        allLegoTheme.add(<span class="java_string">&quot;Castle&quot;</span>);
        allLegoTheme.add(<span class="java_string">&quot;Pirates&quot;</span>);
        allLegoTheme.add(<span class="java_string">&quot;Classic Space&quot;</span>);
        allLegoTheme.add(<span class="java_string">&quot;Star Wars&quot;</span>);
    }

    <span class="java_keyword">public</span> String findTheme(String theme) {
        <span class="java_keyword">return</span> allLegoTheme.contains(theme) ? allLegoTheme.get(allLegoTheme.indexOf(theme)) : <span class="java_keyword">null</span>;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        String themeName = <span class="java_keyword">new</span> OptionalPhilosophy().findTheme(<span class="java_string">"Star Trek"</span>);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Theme name is &quot;</span> + themeName.toUpperCase());
    }

}</pre>
  </div>
  <p class="bekezd">
    A legó témakörökből egy adott téma nevének kikereséséhez az API fejlesztője bevezet egy <span class="programkod">findTheme</span> metódust ami suttyomban <span class="programkod">null</span>-t
    ad vissza ha a téma nevét nem találta. A hívó megbízik az API-ban és meghívja a <span class="programkod">findTheme</span>-et majd a visszaadott értéket megpróbálja nagybetűsre
    konvertálva kiíratni. Csakhogy <span class="programkod">NullPointerException</span>-t kap. Ez kinek a hibája? A hívóé vagy az API fejlesztőjéé?
  </p>
  <p class="bekezd">
    Az API fejlesztője így gondolkodik: amikor a hívó megad egy nevet és az nem található a listában, akkor mit kéne csinálni? Visszaadjak egy üres vagy valami speciális sztringet? De ha a
    hívó esetleg nem akar üres sztringet, sem pedig valami meghatározott sztringet, akkor mit tehetnénk? Egyszerűbb ha <span class="programkod">null</span>-t adunk vissza.
  </p>
  <p class="bekezd">
    A hívás fejlesztője így gondolkodik: a <span class="programkod">findTheme</span> egy API metódus és az API felelőssége, hogy kezelje a speciális eseteket amibe az is beletartozik, ha a
    keresett téma nem található. Ilyenkor üres sztringet kellene visszaadnia. Engem nem kell, hogy a null ellenőrzés érdekeljen, nem az én felelősségem.
  </p>
  <p class="bekezd">Na most akkor kinek a hibája?</p>
  <p class="bekezd">
    Saját szemszögéből a hívónak és a hívottnak is igaza lehet. A zavar ott van, hogy az API fejlesztője nem jelzi a hívónak, hogy a visszatérési érték lehet jelenlévő vagy hiányzó. A <span
      class="programkod">null</span> azt jelenti, hogy nincs referencia, de nem eléggé kifejező hozzá, hogy jelezze a hívónak, hogy egy érték jelen van vagy hiányzik. Na itt lép be az <span
      class="programkod">Optional</span>. Ez jelzi, hogy az érték jelen is lehet meg hiányozhat is, tehát a hívó felelőssége leellenőrizni, hogy ott van-e. Null visszaadásakor nincs meg ez
    az információ az érték természetéről.
  </p>
  <p class="bekezd">
    Az <span class="programkod">Optional</span> bevezetésétől egyébként nem volt mindenki elragadtatva. Kifogásolták például azt, hogy ha az <span class="programkod">Optional</span>-ban
    nincs érték akkor az <span class="programkod">Optional.get()</span> <span class="programkod">NoSuchElementException</span>-t dob. Vagyis csak annyi történt, hogy kicseréltek egy nem
    ellenőrzött kivételt egy másikra. És a legtriviálisabb dolog: az <span class="programkod">Optional</span> referencia is lehet <span class="programkod">null</span>! Ha teljesen
    biztonságos kódot akarunk írni, akkor továbbra is írhatunk nullvizsgálatokat - az isPresent mellett. Hiszen nincs rá 100% garancia, hogy az <span class="programkod">Optional</span>
    típusú visszatérési érték sosem lesz null, így szükség van egy kiegészítő feltételvizsgálatra és még egy további indirekció bejött egy érték elérésében. Egyébként ha egy már meglévő
    kódot Optional-osítunk, akkor a fordító nem is fog szólni érte, ha valahol egy metódusban a korábbi <span class="programkod">return null</span>-t elfelejtettük return <span
      class="programkod">Optional.empty()</span>-re kicserélni.
  </p>
  <p class="bekezd">
    A null referencia hibákra egyébként a Sun már 2006-ban megpróbált megoldást keresni, de akkor még statikus kódanalizáló eszközökben gondolkodtak. Ez néhány szabványos annotáció lett
    volna, ami segítené a statikus analizáló eszközöket a nulleferencia hibák kiszűrésében. Ugyanazt a problémát próbálták megoldani mint amit az <span class="programkod">Optional</span>
    típus, de anélkül, hogy nagy változásokat vezettek volna be a típuskezelési rendszerben. Ma már ilyen eszközök csak harmadik féltől származó eszközkészletben találhatóak meg, a
    szabványos Java-ba nem kerültek be.
  </p>
  <p class="bekezd">
    Az <span class="programkod">Optional</span>-t ért kritikákra Brian Goetz, a Java nyelv egyik fentebb is idézett tervezője egy Stack Overflow <a
      onclick="window.open(this.href);return false;" href="https://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type/26328555#26328555">bejegyzésben</a> így
    reagált: <i>&quot;Az emberek persze azt csinálnak vele amit akarnak. De nekünk egyértelmű volt a szándékunk amikor ezt hozzáadtuk a nyelvhez. Ez pedig </i>nem<i> az volt, hogy
      valamiféle általános célú dolgot csináljunk, mint egy Maybe vagy Some típus, amit többen szerettek volna. Az volt a célunk, hogy egy korlátozott lehetőséget biztosítsunk a könyvtári
      metódusok visszatérési értékeinek típusaihoz akkor amikor szükség volt, hogy egyértelműen ábrázoljuk a &#187;nincs visszatérési érték&#171; esetet és amikor ilyen esetekben a null
      használata túlnyomórészt feltehetően hibákat okozott volna. Ezt nem ajánlatos használni olyan esetben amikor valami tömböt vagy listát ad vissza, ilyen esetekben üres tömböt vagy üres
      listát érdemes visszaadni. Ugyanígy nem ajánlatos használni mezőként vagy metódusparaméterként. Szerintem ha ezt rutinszerűen getterek visszatérési értékeként használják, az
      túlhasználat. Semmi baj nincs az Optionallal, ami miatt esetleg el kellene kerülni, ez egyszerűen csak nem az a dolog, aminek az emberek szeretnék. [...]&quot;</i>
  </p>
  <p class="bekezd">Eszerint a nyelv tervezőinek eredeti célja nem általános Optional bevezetése volt, viszont ezt igazából nem dokumentálták. Ez a Java dokumentációjában nincs benne,
    csak egy Stack Overflow válaszban. De már késő, mert általános célokra is elkezdték használni a fejlesztők, ezért itt van néhány jótanács, hogy ha már használjuk akkor hogyan is
    használjuk:</p>
  <ul>
    <li><span class="programkod">Optional</span>-t csak metódusok visszatérési értékeként használjunk és semmilyen körülmények között se adjunk vissza ilyenkor <span class="programkod">null</span>-t.
      Bár a fenti Order-Address-Name példa egy hivatalos Oracle oktató anyag alapján készült, valójában ez is &quot;túlhasználat&quot; Brian Goetz válasza alapján. Szemléltetésként még
      elmegy, de éles kódba már lehetőleg ne írjunk <span class="programkod">Optional</span> típusú mezőket.</li>
    <li>ahol lehetséges, mindig használjuk az <span class="programkod">orElse()</span> metódust a becsomagolt érték elérésére a <span class="programkod">get()</span> helyett. Ezzel
      megspóroljuk az <span class="programkod">isPresent()</span> hívást és nem fogunk ellenőrizetlen kivételeket kapni.
    </li>
    <li>primitív típusok visszaadásához 3 nem-generikus <span class="programkod">Optional</span> változat is létezik: <span class="programkod">OptionalDouble</span>, <span
      class="programkod">OptionalInt</span>, <span class="programkod">OptionalLong</span>. Hacsak nincs kifejezetten szükségünk a boxed primitívekre, akkor az <span class="programkod">Optional&lt;T&gt;</span>
      használata helyett inkább ezeket használjuk ezen típusoknál. Ezeknek a típusoknak van egy getAs... metódusuk (értelemszerűen <span class="programkod">getAsDouble()</span>, <span
      class="programkod">getAsInt()</span> vagy <span class="programkod">getAsLong()</span>) amelyekkel lekérdezhetjük a tárolt primitív értéket vagy egy <span class="programkod">NoSuchElementException</span>-t.
    </li>
    <li>ne használjunk <span class="programkod">Optional</span>-t collection-ök vagy tömbök visszaadására. Ilyen esetekben inkább üres collection-t/tömböt adjunk vissza.
    </li>
  </ul>
  <p class="bekezd">És egy kiegészítő tanács:</p>
  <ul>
    <li>ne használjuk az <span class="programkod">Optional</span>-t ha el akarjuk kerülni a heap memóriafoglalásokat. Ez például az az eset amikor valaki egy kalapácsot kap és utána
      mindent szögnek néz (lekérdezzük egy legókészlet nevét, ami ha null akkor alapértelmezettet állítunk be):
    </li>
  </ul>
  <div class="programkod">
    <pre>String nev = Optional.ofNullable(legoSet.getName()).orElse(DEFAULT_NAME); </pre>
  </div>
  <h2>Stream API</h2>
  <p class="bekezd">
    Az új Stream API (<span class="programkod">java.util.stream</span>) a Java 8 legösszetettebb újdonsága, egyben a lambdák mellett a második legfontosabb újítás és a funkcionális
    programozási stílus bevezetése a Java-ba. A Stream API nagyon dióhéjban egyszerűbbé és hatékonyabbá teszi a Java egyik legfontosabb összetevőjének, a collection-öknek a feldolgozását a
    feladatokat deklaratív módon leírhatóvá téve.
  </p>
  <p class="bekezd">
    A Stream API bamutatására szintén a metódusreferenciáknál már látott legógyűjtemény-nyilvántartó példát fogom használni. Akárcsak korábban, ismét a <span class="programkod">LegoSet</span>
    osztály fogja tárolni az adatbázisunkban egy készlet adatait. A következő ciklus Java 7 módon kiírja az adatbázisban lévő összes készlet nevét:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">for</span> (LegoSet ls : mySets) {
    System.<span class="java_constant">out</span>.println(ls.getName());
} </pre>
  </div>
  <p class="bekezd">Az alábbi sor ugyanezt teszi, de már a Stream API-t, mégpedig a forEach aggregáló műveletet használva:</p>
  <div class="programkod">
    <pre>mySets.stream().forEach(e -&gt; System.<span class="java_constant">out</span>.println(e.getName())); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">forEach</span> paramétere egy lambda kifejezés, ami meghívja az <span class="programkod">e</span> objektum <span class="programkod">getName</span> metódusát
    és kiírja a visszatérési értékét. (A fordító kikövetkezteti, hogy az <span class="programkod">e</span> objektum típusa <span class="programkod">LegoSet</span>.) Bár ebben a példában az
    aggregáló műveletet használó megoldás hosszabb, mint a foreach, látni fogjuk, hogy az összetett adatkezelést igénylő feladatok sokkal tömörebben leírhatóak ezen a módon. A következő
    példa kiírja a collection-ben lévő legókészletek közül azokat amiknek az elemszáma nagyobb 2000-nél:
  </p>
  <div class="programkod">
    <pre>mySets.stream().filter(e -&gt; e.getPieceCount() &gt; 2000).forEach(e -&gt; System.<span class="java_constant">out</span>.println(e.getName())); </pre>
  </div>
  <p class="bekezd">Hasonlítsuk ezt össze azzal ami ciklust használ:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">for</span> (LegoSet set : mySets) {
  <span class="java_keyword">if</span> (set.getPieceCount() &gt; 2000) {
    System.<span class="java_constant">out</span>.println(set.getName());
  }
} </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">filter</span> visszaad egy új stream-et ami olyan elemeket tartalmaz, amik megfelelnek a metódus paramétereként átadott predikátumnak. Ebben a példában a
    predikátum a lambda kifejezés: <span class="programkod">e -&gt; e.getPieceCount() &gt; 2000</span>. Ez boolean típusú értéket ad vissza, ami true, ha az e objektumban a <span
      class="programkod">pieceCount</span> mező értéke 2000-nél nagyobb. A stream műveletei mindig új stream-et gyártanak, így a <span class="programkod">filter</span> is, az új stream
    pedig az összes 2000 elemnél nagyobb készletet tartalmazza.
  </p>
  <h3>Alapfogalmak</h3>
  <p class="bekezd">Az ezer mérföldes út az első lépéssel, a Stream fejezet pedig az alapfogalmakkal kezdődik:</p>
  <p class="bekezd">
    <u>(adat)forrás</u>: a stream-ek adatokat biztosító forráson dolgoznak. Ilyen lehet egy collection, tömb vagy I/O erőforrás.
  </p>
  <p class="bekezd">
    <u>stream</u>: egy steam adott típusú egymás után következő elemek sorozatához biztosít műveletvégző interfészt. A stream-ek nem tárolnak elemeket, csak igény szerint számítanak egy
    forrásból egy pipeline-on keresztül. Egy stream-nek nincs kötött, &quot;véges&quot; mérete, mint egy collection-nek. Olyan műveletek mint például a <span class="programkod">limit(n)</span>
    vagy a <span class="programkod">findFirst()</span> elméletben végtelen stream-eken is véges idő alatt képesek eredményt produkálni. Stream-ek létrehozására a Java 8 a következő
    lehetőségeket nyújtja:
  </p>
  <ul>
    <li>a <span class="programkod">java.util.Collection</span> interfész két metódust tartalmaz, ezekkel bármilyen collection-ből stream-et tudunk gyártani:
      <ul>
        <li><span class="programkod">stream()</span>: soros stream létrehozása</li>
        <li><span class="programkod">parallelStream()</span>: potenciálisan párhuzamos stream létrehozása</li>
      </ul>
    </li>
    <li>az <span class="programkod">Arrays.stream(Object[])</span> metódus tömbökből hoz létre stream-eket
    </li>
    <li>a stream osztályok tartalmaznak statikus gyártófüggvényeket:
      <ul>
        <li><span class="programkod">Stream.of(Object[])</span></li>
        <li><span class="programkod">IntStream.range(int, int)</span></li>
        <li><span class="programkod">Stream.iterate(Object, UnaryOperator)</span></li>
      </ul>
    </li>
    <li>egy fájl soraiból a <span class="programkod">BufferedReader.lines()</span> metódussal tudunk stream-et gyártani
    </li>
    <li>fájl elérési utak stream-je a <span class="programkod">Files</span> osztály metódusaival szerezhető meg
    </li>
    <li>véletlenszám-streamet gyárthatunk a <span class="programkod">Random.ints()</span> metódussal
    </li>
    <li>a fentieken kívül számos egyéb módszer van a JDK-ban stream-ek létrehozására, például:
      <ul>
        <li><span class="programkod">BitSet.stream()</span>: bitek vektorából</li>
        <li><span class="programkod">Pattern.splitAsStream(java.lang.CharSequence)</span>: sztringből, amiben adott reguláris kifejezésnek megfelelő sztringek választanak el
          karaktersorozatokat</li>
        <li><span class="programkod">JarFile.stream()</span>: JAR zip fájl bejegyzéseiből</li>
      </ul>
    </li>
  </ul>
  <p class="bekezd">
    <u>Pipeline (csővezeték)</u>: sok stream művelet maga is stream-eket ad eredményül, ami lehetővé teszi, hogy az aggregáló műveletek sorozatát egy csővezetékké rendezzük. Ez lehetővé
    tesz bizonyos optimalizációkat, amiket később majd látni fogunk. Egy pipeline a következő összetevőket tartalmazza:
  </p>
  <ul>
    <li>forrás: lehet egy collection, egy tömb, generátor függvény vagy egy I/O csatorna. A legós példákban a forrás a mySets.</li>
    <li>nulla vagy több közbülső művelet (intermediate operation)</li>
  </ul>
  <p class="bekezd">
    <u>Aggregáló műveletek</u>: funkcionális programozáshoz hasonló leírást lehetővé tévő műveletek. Két típusuk van:
  </p>
  <ul>
    <li><b>közbülső művelet (intermediate operation)</b>: egy közbülső művelet, mint például a filter egy új stream-et hoz létre. Ezek mindig lusta kiértékelésűek Egy stream csővezeték
      nulla vagy több közbülső műveletet tartalmaz. Két altípusa van:
      <ul>
        <li><b>állapotmentes (stateless)</b>: ilyen például a filter és a map, amelyek nem tartanak meg állapotot az előzőleg megvizsgált elemről amikor új elemet dolgoznak fel; minden
          elem az egyéb elemeken elvégzett műveletektől függetlenül feldolgozható. A kizárólag állapotmentes közbőlsű műveletekből álló csővezetékek egy menetben is feldolgozhatóak
          minimális adatpufferelésel, akár párhuzamos akár soros feldolgozásról van szó.</li>
        <li><b>állapottartó (stateful)</b>: ilyen például a distinct és a sorted, amelyek felhasználhatnak állapotot az előzőleg feldolgozott elemről amikor új elemet dolgoznak fel.
          Elképzelhető, hogy a teljes bemenetet fel kell dolgozniuk mielőtt eredményt adnak. Nem lehet például sorbarendezni egy stream elemeit amíg mindegyik elemet nem láttuk. Ezért aztán
          párhuzamos feldolgozásnál, ha a csővezeték állapottartó közbülső műveleteket tartalmaz, szükség lehet rá, hogy az adatot több menetben dolgozza fel, vagy szükség lehet jelentős
          mennyiségű adat pufferelésére.</li>
      </ul></li>
    <li><b>lezáró műveletet (terminal operation)</b>: egy lezáró művelet, mint amilyen például a forEach, egy nem-stream végeredményt ad. Ez lehet egy primitív érték (például double),
      egy collection, vagy a forEach esetén egyáltalán semmi (void). Egy stream csővezeték egy lezáró műveletet tartalmaz. A lezáró műveletek majdnem mindig mohó kiértékelésűek. Miután a
      lezáró művelet lefutott, a stream feldolgozottnak számít és többé nem használható. Ha ugyanazon a forráson szeretnénk további stream műveleteket használni akkor új stream létrehozás
      szükséges. Ha a stream-en a lezáró művelet után egy másikat szeretnénk futtatni, akkor &quot;<span class="programkod">java.lang.IllegalStateException: stream has already been
        operated upon or closed</span>&quot; kivételt kapunk.</li>
  </ul>
  <p class="bekezd">
    <u>Rövidzár műveletek</u>: néhány műveletet ún. rövidzár műveletnek (short-circuiting) nevezünk. Egy közbülső művelet akkor rövidzár, ha véges stream-et tud produkálni eredményként
    akkor is amikor végtelen bemenettel látják el. Egy lezáró művelet akkor rövidzár, ha végtelen bemenet esetén is befejeződik véges idő alatt. Egy csővezetékben szükséges, de nem
    elégséges, hogy legyen rövidzár művelet ahhoz, hogy egy végtelen stream esetén is véges idő alatt befejeződjön a feldolgozása.
  </p>
  <p class="bekezd">
    <u>Lusta kiértékelés</u>: minden közbülső művelet lusta kiértékelésű vagyis csak akkor hajtódik végre amikor szükség van rá. (Ellenkező esetben mohó.) A közbülső műveletek addig nem
    kezdik el a stream tartalmának a feldolgozását amíg a lezáró művelet végrehajtása el nem indul. A stream-ek lusta feldolgozása lehetővé teszi a Java fordítónak és futtatókörnyezetnek,
    hogy optimalizálja a stream-ek feldolgozását.
  </p>
  <p class="bekezd">
    <u>Escape-hatch műveletek</u>: fentebb azt írtam, a lezáró műveletek <i>majdnem mindig</i> mohó kiértékelésűek. Két kivétel van: az <span class="programkod">iterator()</span> és az <span
      class="programkod">spliterator()</span>, amit escape-hatch műveleteknek hívunk. Ez a két lezáró művelet iterátort biztosít a stream-ekhez, ezzel lehetőséget ad arra, hogy saját magunk
    programozzuk le egy stream bejárását, ha a létező lezáró műveletek nem volnának elegendőek. Ez a két escape-hatch művelet abban is eltér a normál lezáró műveletektől, hogy lusta
    feldolgozású: ezeknél a stream-en lévő műveletek csak a következő elem lekérdezésekor hajtódnak végre.
  </p>
  <p class="bekezd">
    <u>Belső iteráció</u>: a collection-ökön való iterálást expliciten nekünk kell leprogramoznunk, a stream műveletek az iterációkat maguk végzik el a színfalak mögött.
  </p>
  <p class="bekezd">Most pedig, hogy az alapfogalmakat már ismerjük, merüljünk el a különböző típusú stream-ek és a stream-műveletek ingoványában.</p>
  <h3>A primitív típusok esete a stream-mel</h3>
  <p class="bekezd">
    Ha már a funkcionális interfészeknél és az <span class="programkod">Optional</span>-nál is bevezettek a boxing/unboxing elkerülésére primitív típusokat közvetlenül kezelő osztályokat, a
    streamek se maradhattak ki. Három speciális stream típus létezik: <span class="programkod">IntStream</span>, <span class="programkod">LongStream</span> és <span class="programkod">DoubleStream</span>.
    Ezek is a <span class="programkod">BaseStream</span> interfészt implementálják és ezen kívül minden olyan metódust tartalmaznak, amit az általános Stream, néhány speciális aritmetikai
    művelettel kiegészítve.
  </p>
  <p class="bekezd">Nézzük, hogy tudunk ilyen stream-et létrehozni például int tömbből:</p>
  <div class="programkod">
    <pre>IntStream intstream = Arrays.stream(<span class="java_keyword">new int</span>[] { 42, 3, 67, 89, 12 });
intstream.min().getAsInt(); <span class="java_comment">// 3-at ad eredményül</span> </pre>
  </div>
  <p class="bekezd">
    De itt is használhatjuk a Stream létrehozó függvényeit, mint például az <span class="programkod">of()</span> a következő példában (a <span class="programkod">max()</span> itt <span
      class="programkod">OptionalInt</span>-et ad vissza):
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">int</span> maxInt = IntStream.of(42, 68, 3, 89, 99).max().getAsInt(); <span class="java_comment">// 99 lesz az eredmény</span> </pre>
  </div>
  <p class="bekezd">A &quot;primitív&quot; és az általános streamek között oda-vissza is tudunk konvertálni, ha nagyon szeretnénk. Primitív típusok boxingjához például:</p>
  <div class="programkod">
    <pre>List&lt;Integer&gt; parosak = IntStream.rangeClosed(1, 100)
    .filter(i -&gt; i % 2 == 0).boxed().collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">
    A példa <span class="programkod">IntStream</span>-mel indít, a <span class="programkod">boxed()</span> közbülső művelet vált át Stream&lt;Integer&gt; típusra (a <span class="programkod">rangeClosed()</span>-ról
    pár sorral lejjebb lesz szó, a <span class="programkod">collect(Collectors.toList())</span> pedig kigyűjti egy listába az eredményt, a collector-ról külön fejezet később). Az általános
    <span class="programkod">Stream</span> pedig mapTo... és flatMapTo... közbülső műveletekkel teszi lehetővé az unboxingot:
  </p>
  <div class="programkod">
    <pre>DoubleStream dstream = Arrays.asList(49, 53, 78, 34).stream().mapToDouble(i -&gt; i); </pre>
  </div>
  <p class="bekezd">A három primitív típust alkalmazó stream azon lezáró műveletei amelyek mindenképpen adnak visszatérési értéket, már eleve primitív típust adnak vissza, tehát nem
    kell getAs... metódusokat hívni. Például:</p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">int</span> sum = IntStream.of(5, 7, 11, 13, 17).sum(); <span class="java_comment">// eredmény: 53</span> </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">IntStream</span> és <span class="programkod">LongStream</span> két olyan statikus generátor függvényt is tartalmaz, amikkel egészek sorozatát tartalmazó
    stream-et generálhatunk:
  </p>
  <div class="programkod">
    <pre>LongStream stream1 = LongStream.range(1, 100);
LongStream stream2 = LongStream.rangeClosed(1, 100); </pre>
  </div>
  <p class="bekezd">A különbség talán sejthető: a range esetén a záró érték már nem szerepel a stream forrásában, míg a másik esetben igen.</p>
  <p class="bekezd">
    Az a szép ezekben, hogy így akár for ciklusokat is kiválthatunk velük, a párhuzamosítás pedig ezek után csak egy csuklómozdulat. Persze nem érdemes mindenhol ész nélkül használni,
    hiszen a hagyományos for gyakran olvashatóbb és hatékonyabb lehet mint a stream-ekkel megvalósított változat. A <a onclick="window.open(this.href);return false;"
      href="https://stackoverflow.com/questions/38998514/when-should-i-use-intstream-range-in-java">stackoverflow</a>-n fel is merült a kérdés, hogy miért érdemes használni a range
    függvényeket?
  </p>
  <p class="bekezd">Nos több ok miatt is.</p>
  <ul>
    <li>fel tudjuk használni magukat a primitív értékeket, ahogy a fenti páros számokat kiszűrő példában látható</li>
    <li>egyszerű lambda kifejezéseket tudunk N-szer végrehajtani:</li>
  </ul>
  <div class="programkod">
    <pre>IntStream.range(0, N).forEach(<span class="java_keyword">this</span>::dolgozdFel); </pre>
  </div>
  <ul>
    <li>fel tudunk tölteni egészekből álló tömböt (bár erre már régóta ott az <span class="programkod">Arrays.fill()</span> is):
    </li>
  </ul>
  <div class="programkod">
    <pre>
      <span class="java_keyword">int</span>[] tombom = IntStream.range(0, N).toArray(); </pre>
  </div>
  <p class="bekezd">Ez utóbbi esetben is kérdéses, nem hatékonyab-e for ciklust használni? Nos kis tömböknél vélhetően elhanyagolható a stream hátránya, nagy tömböknél pedig
    valószínűleg nem az implementáció lesz a szűk keresztmetszet hanem a memória sávszélesség és a GC esetleges működése.</p>
  <p class="bekezd">Akkor is hasznos a range, ha olyan problémát kell megoldani ahol a tömbindexek és az értékek is részt vesznek a számításban. Például egy tömbben meg akarjuk
    határozni azon indexeket ahol növekvő sorozatok indulnak (persze ezeket a feladatokat is meg lehet oldani ciklussal, de lehet olyan eset amikor a stream célszerűbb):</p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">int</span>[] elemek = { 1, 2, 4, 8, 9, 3, 5, 6, 7, 8, 0 };
<span class="java_keyword">int</span>[] novekvok = IntStream.range(0, elemek.length)
    .filter(i -&gt; i == 0 || elemek[i - 1] > elemek[i]).toArray();
System.<span class="java_constant">out</span>.println(Arrays.toString(novekvok)); </pre>
  </div>
  <p class="bekezd">A program kimenete:</p>
  <pre class="programkod">    [0, 5, 10]</pre>
  <p class="bekezd">Nagy elemszámú bemenő tömbnél pedig az egyszerű párhuzamosíthatóság által kinyerhető teljesítménybeli plusz még további előnyt adhat a stream esetén.</p>
  <p class="bekezd">Bizonyára mindenkinek azonnal szemet szúrt, hogy akárcsak az Optional és a funkcionális interfészek esetén, háromnál több primitív típus mintha itt se létezne. Brian
    Goetz a következőket mondta arról, miért döntöttek a többi kihagyása mellett:</p>
  <p class="bekezd">
    <i>&quot;A specializált primitív típusok (például <span class="programkod">IntStream</span>) megléte mögötti filozófia tele van csúnya kompromisszummal. Rengeteg benne a
      kódduplikáció és az interfész-szennyezés. (Interfész-szennyezés: amikor látszólag fölösleges dolgokra szánt interfészek lepik el az egyébként egyszerűnek és átláthatónak szánt
      API-nkat.) A boxed operandusokon végzett bármiféle aritmetika eleve elég nyűgös, de rossz lett volna ha nem lenne megoldásunk az <span class="programkod">int</span>-ek méretének
      csökkentésére. Úgyhogy sarokba voltunk szorítva és megpróbáltuk a helyzetet nem rontani tovább. Két elv mentén haladtunk:
    </i>
  </p>
  <p class="bekezd">
    <i>1. nem foglalkoztunk mind a nyolc primitív típussal. Csak az <span class="programkod">int</span>, <span class="programkod">long</span> és <span class="programkod">double</span>
      típusokkal, a többi ezekkel már szimulálható. Akár még az <span class="programkod">int</span>-től is megszabadulhattunk volna, de úgy véltük, a Java fejlesztők erre még nincsenek
      felkészülve. Volt igény a <span class="programkod">Character</span>-re is, de erre az a válaszunk, hogy tegyétek bele egy <span class="programkod">int</span>-be. Mindegyik
      specializáció nagyjából 100 KB növekedést okozott volna a JRE memóriafoglalásában.
    </i>
  </p>
  <p class="bekezd">
    <i>2. primitív stream-eket használunk a primitív környezetben legmegfelelőbb dolgok elvégzéséhez (rendezés, redukció), de nem próbálunk meg duplikálni mindent amit egyébként a
      boxing világban is meg lehet csinálni. Tehát például nincs <span class="programkod">IntStream.into()</span>. (Ha lenne akkor a következő kérdés az lenne, hogy hol az <span
      class="programkod">IntCollection</span>, <span class="programkod">IntArrayList</span>, <span class="programkod">IntConcurrentSkipListMap</span>? A szándék az volt, hogy sok stream
      referencia stream-ként indulhat és végül primitív stream-ként végzi, de fordítva nem. Ezzel nincs gond és ez csökkenti a szükséges konverziókat (például nincs map túlterhelés az <span
      class="programkod">int -&gt; T</span> esetre és nincs specializáció a <span class="programkod">Function</span>-ből az <span class="programkod">int -&gt; T</span> esetre, stb.).&quot;
    </i>
  </p>
  <h3>Közbülső műveletek</h3>
  <p class="bekezd">Az összetettebb műveletek izgalmaihoz az egyszerűbb műveleteken keresztül vezet az út, ezeket is át kell tekintenünk. A Stream interfész az alábbi általános közbülső
    műveleteket biztosítja a feldolgozáshoz.</p>
  <p class="bekezd">
    <u>filter(Predicate&lt;? super T&gt; predicate)</u>: egy stream elemeinek szűrése. Egy <span class="programkod">Predicate</span> objektumot vár paraméterként és az eredmény stream-be
    csak azon elemek kerülnek át a forrásból amelyek megfelelnek a predikátumnak. Fentebb már több példa volt rá.
  </p>
  <p class="bekezd">
    <u>distinct()</u>: a forrás stream-ből csak az egyedi elemeket hagyja meg. Az összehasonlításhoz az elemek <span class="programkod">equals()</span> metódusát használja. Például:
  </p>
  <div class="programkod">
    <pre>List&lt;String&gt; lista = Arrays.asList(<span class="java_string">&quot;A&quot;</span>, <span class="java_string">&quot;B&quot;</span>, <span class="java_string">&quot;C&quot;</span>, <span
        class="java_string">&quot;A&quot;</span>, <span class="java_string">&quot;D&quot;</span>, <span class="java_string">&quot;C&quot;</span>);
lista.stream().distinct().forEach(System.out::print); </pre>
  </div>
  <p class="bekezd">
    A példa eredménye az &quot;ABCD&quot;. Sokszor persze pont nem az equals szerint szeretnénk egyediséget vizsgálni, főleg nem saját objektumoknál. Ha tetszőleges mező szerinti distinct
    funkcionalitásra vágyunk akkor azt a <span class="programkod">filter()</span> segítségével és egy kis kódolással lehet megoldani. Mivel a distinct alapvetően állapottartó művelet, nem
    ördögtől való dolog kiegészítő osztályt használni a filterhez:
  </p>
  <div class="programkod">
    <pre>
<span class="java_blockcomment">/**</span>
<span class="java_blockcomment"> * Állapottartó szűrő ahol T a stream elemeinek típusa.</span>
<span class="java_blockcomment"> */</span>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> DistinctByKey&lt;T&gt; <span class="java_keyword">implements</span> Predicate&lt;T&gt; {
    Map&lt;Object, Boolean&gt; seen = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;();
    Function&lt;T, Object&gt; keyExtractor;

    <span class="java_keyword">public</span> DistinctByKey(Function&lt;T, Object&gt; ke) {
        <span class="java_keyword">this</span>.keyExtractor = ke;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">boolean</span> test(T t) {
        <span class="java_keyword">return</span> seen.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == <span class="java_keyword">null</span>;
    }
}</pre>
  </div>
  <p class="bekezd">
    A példában a <span class="programkod">DistinctByKey</span> osztályt egyből a <span class="programkod">Predicate</span>-ből származtattuk, ezért a filter-nek közvetlenül át lehet adni,
    vagyis:
  </p>
  <div class="programkod">
    <pre>mySets.stream().filter(<span class="java_keyword">new</span> DistinctByKey&lt;LegoSet&gt;(LegoSet::getName)); </pre>
  </div>
  <p class="bekezd">Itt név alapján szűrjük le az egyedi készleteket a legó adatbázisunkban, vagyis csak egyet hagyva meg azokból amelyekből azonos néven több is szerepel. Viszont (láss
    csodát!) a feladatot külön osztály nélkül is meg lehet oldani ha kiemeljük a Predicate létrehozást egy metódusba:</p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;T&gt; Predicate&lt;T&gt; distinctByKey(Function&lt;? <span class="java_keyword">super</span> T, ?&gt; keyExtractor) {
    Set&lt;Object&gt; seen = ConcurrentHashMap.newKeySet();
    <span class="java_keyword">return</span> t -&gt; seen.add(keyExtractor.apply(t));
} </pre>
  </div>
  <p class="bekezd">Ez így ugyanazt az eredményt adja mint a fenti megoldás:</p>
  <div class="programkod">
    <pre>mySets.stream().filter(distinctByKey(LegoSet::getName)); </pre>
  </div>
  <p class="bekezd">
    Első látásra (főleg Java 7-es szemmel) kissé érthetetlen a metódus működése. Minden egyes meghívásra új seen objektumot hoz létre? Akkor hol az állapottartó működés és hogyan tudja
    eltárolni a már megvizsgált elemeket? Nos a válasz a metódustörzs második sorában van: a metódus valójában csak egyszer hívódik meg, egyetlen seen objektumot hoz létre és egy <span
      class="programkod">Predicate</span> objektumot ad vissza a lambda kifejezésből. A filter már ennek a <span class="programkod">Predicate</span> objektumnak a <span class="programkod">test()</span>
    metódusát fogja hívogatni, annak a törzse pedig a lambda kifejezés.
  </p>
  <p class="bekezd">
    A megoldásnak persze van néhány apróbb problémája. Mivel a költő a párhuzamos streamekre is gondolt, a <span class="programkod">ConcurrentHashMap</span>-et használja. Ez soros
    stream-eknél jelent némi (bár elhanyagolható) pluszmunkát. Párhuzamos stream-eknél viszont nem garantálható, hogy a megadott azonosnak tekintett objektumok közül mindig az elsőt tartja
    meg, mint a distinct. És nem szabad elfelejteni, hogy a metódus által visszaadott <span class="programkod">Predicate</span> objektum nem újrahasznosítható, tehát értelemszerűen több
    stream-re ugyanaz nem fog működni, mindig újat kell létrehozni a <span class="programkod">distinctByKey</span> meghívásával. A megoldásnak igazából csak egy csúnya tulajdonsága van:
    megsérti a Java 8 API filter-re vonatkozó kitételét, miszerint a <span class="programkod">Predicate</span> paraméter nem lehet állapottartó. Bár Java 8 esetén működik és megfelel a
    célnak, későbbi Java verziók esetén esetleg okozhat kellemetlen meglepetést, ha az API máshogy implementálja a filter-t.
  </p>
  <p class="bekezd">
    <u>peek(Consumer&lt;? super T&gt; action)</u>: az eredeti stream minden egyes elemére elvégzi a paraméterként átadott műveletet, ami egy <span class="programkod">Consumer</span>
    objektum. Főként debug célokra szánt művelet, például kiírathatjuk vele az aktuálisan feldolgozott elemet.
  </p>
  <p class="bekezd">
    <u>limit(long maxSize)</u>: ez a rövidzár művelet olyan streamet ad vissza, ami az eredeti streamből a <span class="programkod">maxSize</span> méretnél nem több elemet tartalmaz.
  </p>
  <p class="bekezd">
    <u>skip(long n)</u>: ez a közbülső művelet olyan streamet ad vissza, emiből elhagyja az eredeti stream első <span class="programkod">n</span> elemét. Ha az eredeti stream <span
      class="programkod">n</span>-nél kevesebb elemet tartalmaz, akkor üres streamet ad vissza.
  </p>
  <p class="bekezd">
    Ha nem vagyunk teljesen tisztában a stream csővezeték működésével, akkor olyan meglepetések érhetnek a skip és a limit kapcsán, mint a <a onclick="window.open(this.href);return false;"
      href="https://stackoverflow.com/questions/32414088/java-8-stream-difference-between-limit-and-skip">stackoverflow kérdezőjét</a>. Mi is történt? Nézzük ezt a streamet:
  </p>
  <div class="programkod">
    <pre>    Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .peek(e -&gt; System.<span class="java_constant">out</span>.print(<span class="java_string">&quot;\nA&quot;</span> + e))
        .limit(3)
        .peek(e -&gt; System.<span class="java_constant">out</span>.print(<span class="java_string">&quot;B&quot;</span> + e))
        .forEach(e -&gt; System.<span class="java_constant">out</span>.print(<span class="java_string">&quot;C&quot;</span> + e)); </pre>
  </div>
  <p class="bekezd">A limit művelettel az elemek száma 3-ra van korlátozva, a fenti kódsor ezt az eredményt adja:</p>
  <pre class="programkod">A1B1C1
A2B2C2
A3B3C3</pre>
  <p class="bekezd">Nézzük most a skip műveletet, itt átugorjuk az első 6 elemet:</p>
  <div class="programkod">
    <pre>    Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
        .peek(e -&gt; System.<span class="java_constant">out</span>.print(<span class="java_string">&quot;\nA&quot;</span> + e))
        .skip(6)
        .peek(e -&gt; System.<span class="java_constant">out</span>.print(<span class="java_string">&quot;B&quot;</span> + e))
        .forEach(e -&gt; System.<span class="java_constant">out</span>.print(<span class="java_string">&quot;C&quot;</span> + e)); </pre>
  </div>
  <p class="bekezd">Ezt az eredményt kapjuk:</p>
  <pre class="programkod">A1
A2
A3
A4
A5
A6
A7B7C7
A8B8C8
A9B9C9
A10B10C10</pre>
  <p class="bekezd">A stackoverflow kérdezője meglepődött, hogy skip esetén miért lesz A1-A6 kiírás is? Mi már tudjuk a választ: a kulcs a lusta kiértékelés. Az elemekre a csővezetékben
    csak akkor van szükség amikor a lezáró művelet elkezd működni. Amint elkezdi feldolgozni a stream-et, a közbülső elemek feladata, hogy minden lépésnél a forrásból újra és újra
    előállítsák a szükséges elemet. Az első példában amikor a forEach lekéri az első elemet, a &quot;B&quot; peek-nek szüksége lesz egy elemre, így lekéri a limit kimeneti streamjéből. A
    limitnek tehát meg kell kérnie az &quot;A&quot; peek-et, ez pedig visszamegy a forráshoz egy elemért. Onnan visszajön egy elem, majd az egész folyamat visszafelé felmegy a forEach-ig és
    megvan az első sor:</p>
  <pre class="programkod">A1B1C1</pre>
  <p class="bekezd">A forEach kérésére minden további alkalommal oda-vissza végigmegy a kérés az egész csővezetéken. Amikor a forEach a negyedik elemnél tartana, amint a kérés eléri a
    limit műveletet, az már tudja, hogy az öszes szükséges elemet átadta amit lehetett. Nem kér tehát újabb elemet az &quot;A&quot; peektől, hanem jelzi, hogy nincs több elem, a feldolgozás
    ezért befejeződik.</p>
  <p class="bekezd">A második példában picit más a helyzet. A forEach itt is elkezdi lekérni az elemeket. A kérés végigfut a csővezetéken visszafelé. Amikor eléri a skip műveletet,
    annak előbb 6 elemet el kell kérni a forrás streamjétől és csak az azokat követő elemeket tudja továbbadni az eredmény steram felé. Tehát hat kérés fut a forrás felé az &quot;A&quot;
    peeken keresztül, de ezeket az elemeket a skip egyenként elnyeli anélkül, hogy továbbadná az eredmény stream felé. A hat kérés miatt 6 kiírás is megtörténik az &quot;A&quot; peek-ben:</p>
  <pre class="programkod">A1
A2
A3
A4
A5
A6</pre>
  <p class="bekezd">A 7. kérésnél a skip már továbbadja az elemet a &quot;B&quot; peek felé és innen továbbmegy a forEach-hez, tehát a teljes kimenet megjelenik:</p>
  <pre class="programkod">A7B7C7</pre>
  <p class="bekezd">A helyzet ezt követően hasonló az előző esethez; a kérések oda-vissza átmennek a csővezetéken amíg a teljes forrás el nem használódik.</p>
  <p class="bekezd">
    Bár a párhuzamos streameket csak egy későbbi fejezetben fogom bemutatni, már most elárulom, hogy a skip és a limit a soros streameknél viszonylag kis költségű művelet, de nem így a
    párhuzamos streameknél. Azoknál elég költséges is lehet, különösen az n és a maxSize nagy értékeire, hiszen ezek a stream első n elemét mindenképpen fel kell hogy dolgozzák. Párhuzamos
    streameknél a rendezettség megszüntetése (például a <span class="programkod">BaseStream.unordered()</span>), ha a feladat engedi jelentős teljesítménybeli növekedést eredményezhet.
  </p>
  <h3>Rendezés, rendezettség</h3>
  <p class="bekezd">A rendezés és rendezettség kérdésköre egy picit összetettebb a streamek világában, ezért megérdemel egy külön fejezetet. A rendezettség alapvetően meghatározza azt,
    hogy milyen sorrendben dolgozza fel a stream az adatokat, de még a feldolgozás teljesítményét is befolyásolja. De még mielőtt bővebben megnéznénk a rendezést, nagyon fontos tisztázni
    két dolgot. A Stream API gyakran használja az ordered fogalmat, ami magyarul rendezettet jelent. Csakúgy, mint a sorted. De a kettő nem ugyanazt jelenti (legalábbis a Java 8 API-ban)!</p>
  <p class="bekezd">
    <u>Ordered</u>: azt jelenti, hogy bizonyos adatstruktúrák elemei között egymásutániság, sorrendiség értelmezett. Vagyis meg lehet határozni, hogy ez a 0., ez az 1., ez pedig az n. elem.
    Ilyen például a tömb vagy a lista de nem ilyen például a halmaz. A Stream API tárgyalásakor az ordered fogalmat &quot;rendezett&quot;-nek fordítottam, de fontos látni, hogy ez nem
    ugyanaz, mint a sorbarendezett:
  </p>
  <p class="bekezd">
    <u>Sort(ed)</u>: sorbarendezés, sorbarendezett. Az elemek sorrendje valamilyen reláció által meghatározott, például névsor szerint rendezett. Ezt a fogalmat sorbarendezésnek és
    sorbarendezettnek fordítottam. A sorbarendezett egyben rendezettet is jelent, de a rendezett még nem biztos, hogy sorbarendezett is.
  </p>
  <p class="bekezd">
    Hacsak másként nem egyértelmű a szövegből hogy mikor melyikről van szó, akkor a rendezettség és a sorbarendezettség fogalmak olvasásakor a fenti két dologra kell gondolni. Ezek után
    pedig lássuk a lényeget. A stream az adatokat a forrásból veszi (<i>és a <a onclick="window.open(this.href);return false;" href="https://youtu.be/0HXeVlijtDc?t=195">vízbűl veszi
        ki a zoxigént</a></i>), itt pedig máris szembejön egy új fogalom:
  </p>
  <p class="bekezd">
    <u>Encounter order</u>: az elemek azon sorrendje (order), ahogyan a stream számára elérhetővé válnak feldolgozásra. Hogy egy stream rendezett-e, az függ a forrástól és az esetleges
    megelőző közbülső műveletektől. Bizonyos források (a List vagy tömbök) természetükből adódóan rendezettek, mások (mint például a Set) nem.
  </p>
  <p class="bekezd">A rendezettséggel két közbülső művelet foglalkozik:</p>
  <ul>
    <li>Stream.sorted
      <ul>
        <li><span class="programkod">Stream.sorted()</span>: az elemek természetes sorrendje szerint sobarendszett stream-et ad vissza</li>
        <li><span class="programkod">Stream.sorted(Comparator&lt;? super T&gt; comparator)</span>: a paraméterként megadott comparator szerint sorbarendezett stream-et ad vissza</li>
      </ul>
    </li>
    <li><span class="programkod">BaseStream.unordered()</span>: rendezetlen stream-et ad vissza amely akár a stream további feldolgozásának a teljesítményét is növelheti. A metódus
      csak eltávolítja a rendezettséget jelző tulajdonságot a stream-ből, semmiféle költséges &quot;összekeverést&quot; nem végez.</li>
  </ul>
  <p class="bekezd">A rendezést az unordered metódussal a csővezeték bármely pontján megszüntethetjük. Az alábbi példában a később tárgyalandó párhuzamos streameket használom, mert
    párhuzamos feldolgozásnál jobban szembetűnő a rendezettség kérdése:</p>
  <div class="programkod">
    <pre>Set&lt;Integer&gt; set = <span class="java_keyword">new</span> TreeSet&lt;&gt;(Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83));
Object[] result = set.stream().parallel().limit(5).toArray(); <span class="java_comment">// A tömbbe ez fog kerülni: 2, 3, 5, 7, 11</span>
Object[] result = set.stream().unordered().parallel().limit(5).toArray(); <span class="java_comment">// A tömb tartalma meghatározatlan</span>
</pre>
  </div>
  <p class="bekezd">
    Ha egyszer már meghívtuk az <span class="programkod">unordered()</span> metódust, akkor nincs mód visszaállítani az eredeti rendezettséget. Egy sorted persze sorbarendezettséget hozhat
    a stream életébe, de az nem feltétlenül fog megegyezni az eredeti sorrenddel.
  </p>
  <p class="bekezd">
    <b>Ha egy stream rendezett, akkor a legtöbb művelet ezt meg is tartja függetlenül attól, hogy a stream típusa párhuzamos vagy soros.</b> Ha egy stream forrása egy List, ami az [1,2,3]
    elemeket tartalmazza, akkor a map(x -&gt; x*2) végrehajtásának eredménye [2, 4, 6] kell, hogy legyen. Ha viszont a forrásnak nincs encounter order-je akkor a [2, 4, 6] bármely
    permutációja is lehetséges eredmény. De ahhoz, hogy a rendezettség egy teljes csővezetéken keresztül fennmaradjon, tudnunk kell, hogy a forrás és minden közbülső valamint a lezáró
    művelet megtartja-e a rendezettséget vagy nem. A forEach például <b>nem</b> tartja meg a rendezettséget. Ha erre mégis szükség van akkor a forEachOrdered lezáró műveletet kell
    használni.
  </p>
  <p class="bekezd">
    Soros stream-eknél az encounter order megléte vagy hiánya nem befolyásolja a teljesítményt, csak a determinizmust. Ha a stream rendezett, akkor azonos stream csővezetékek ismételt
    végrehajtása azonos forráson azonos eredményt ad. Ha nem rendezett, akkor ismételt végrehajtások során különböző eredményeket kaphatunk. Párhuzamos stream-eknél viszont a rendezés
    elhagyása néha sokkal hatékonyabb végrehajtást tesz lehetővé. Bizonyos aggregáló műveletek, mint a duplikátumok kiszűrése (<span class="programkod">distinct()</span>) vagy a
    csoportosított redukciók (<span class="programkod">Collectors.groupingBy()</span>) sokkal hatékonyabban implementálhatók, ha az elemek sorrendje lényegtelen. Azon műveleteknek amelyek
    eredendően kötődnek az encounter orderhez, mint például a <span class="programkod">limit()</span>, szükségük lehet pufferelésre a megfelelő sorrend biztosításához. Ez viszont eltünteti
    a párhuzamosság előnyeit. Ha a stream-nek van encounter orderje, de a felhasználót igazából ez nem érdekli, akkor a rendezettség explicit megszüntetése (de-ordering az <span
      class="programkod">unordered()</span> metódussal) növelheti a párhuzamos teljesítményt bizonyos állapottartó vagy lezáró műveleteknél. De a legtöbb stream csővezeték hatékonyan
    párhuzamosítható még a rendezettségi feltételek megléte esetén is.
  </p>
  <p class="bekezd">A következő példa bemutatja, hogyan is néz ki a (sorba)rendezettség a stream-ek feldolgozásánál:</p>
  <div class="programkod">
    <pre>Integer[] intArray = { 1, 2, 3, 4, 5, 6, 7, 8 };
List&lt;Integer&gt; integerList = Arrays.asList(intArray);

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;integerList:&quot;</span>);
integerList.stream().forEach(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;&quot;</span>);

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;integerList visszafelé rendezve:&quot;</span>);
Comparator&lt;Integer&gt; normal = Integer::compare;
Collections.sort(integerList, normal.reversed());
integerList.stream().forEach(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;&quot;</span>);

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Parallel stream&quot;</span>);
integerList.parallelStream().forEach(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;&quot;</span>);

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Újabb parallel stream:&quot;</span>);
integerList.parallelStream().forEach(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;&quot;</span>);

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;forEachOrdered-del:&quot;</span>);
integerList.parallelStream().forEachOrdered(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;&quot;</span>); </pre>
  </div>
  <p class="bekezd">A példa 5 csővezetéket tartalmaz és a következő eredményt adja:</p>
  <pre class="programkod">integerList:
1 2 3 4 5 6 7 8 
integerList visszafelé rendezve:
8 7 6 5 4 3 2 1 
Parallel stream
3 4 1 2 6 7 5 8 
Újabb parallel stream:
3 7 1 6 2 8 4 5 
forEachOrdered-del:
8 7 6 5 4 3 2 1 </pre>
  <p class="bekezd">Az eredményből a következő megfigyeléseket tehetjük:</p>
  <ol>
    <li>az első csővezeték az <span class="programkod">integerList</span> lista elemeit a listához adás sorrendjében írja ki
    </li>
    <li>a második csővezeték rendezi az <span class="programkod">integerList</span> elemeit a <span class="programkod">Collections.sort</span> metódussal, majd szintén kiírja azokat
    </li>
    <li>a harmadik és negyedik csővezeték a lista elemeit látszólag véletlenszerű sorrendben írja ki. Mégpedig azért mert amikor a stream-et párhuzamos feldolgozással futtatjuk, a Java
      fordító és az osztálykönyvtár fogja meghatározni (hacsak a stream művelet máshogy nem rendelkezik), hogy mi az a sorrend aminél a stream elemeinek párhuzamos feldolgozásából a
      legnagyobb nyereséget lehet kihozni.</li>
    <li>az ötödik csővezeték a <span class="programkod">forEachOrdered</span> metódust használja, ami a stream elemeit a forrás által megadott sorrendben dolgozza fel, függetlenül
      attól, hogy a stream-et sorosan vagy párhuzamosan hajtjuk-e végre. Persze ha a <span class="programkod">forEachOrdered</span> és hasonló műveleteket használjuk a párhuzamos
      stream-ekkel, akkor elveszíthetjük a párhuzamosság előnyeit.
    </li>
  </ol>
  <p class="bekezd">A rendezettség után nézzük a sorbarendezettség kérdését. A stream elemeinek sorbarendezésére két metódus áll rendelkezésre:</p>
  <p class="bekezd">
    <u>sorted()</u>: rendezés az elemek &quot;természetes&quot; sorrendjében (natural ordering). A következő egyszerű példa sztringeket rendez ábécérendbe:
  </p>
  <div class="programkod">
    <pre>Stream&lt;String&gt; rendezetlen = Arrays.asList(<span class="java_string">&quot;C&quot;</span>, <span class="java_string">&quot;A&quot;</span>, <span class="java_string">&quot;D&quot;</span>, <span
        class="java_string">&quot;B&quot;</span>, <span class="java_string">&quot;F&quot;</span>, <span class="java_string">&quot;E&quot;</span>).stream();
rendezetlen.sorted().forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">
    <u>sorted(Comparator&lt;? super T&gt; comparator)</u>: rendezés egy Comparator segítségével. A következő példa a sztringeket a hosszuk szerinti növekvő sorrendbe rendezi:
  </p>
  <div class="programkod">
    <pre>Stream&lt;String&gt; rendezetlen = Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span
        class="java_string">&quot;három&quot;</span>, <span class="java_string">&quot;négy&quot;</span>, <span class="java_string">&quot;öt&quot;</span>,
    <span class="java_string">&quot;hat&quot;</span>, <span class="java_string">&quot;hét&quot;</span>, <span class="java_string">&quot;nyolc&quot;</span>, <span class="java_string">&quot;kilenc&quot;</span>, <span
        class="java_string">&quot;tíz&quot;</span>).stream();
rendezetlen.sorted((s1, s2) -&gt; s1.length() - s2.length()).forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">A lambda kifejezés helyett ebben a példában is használhatunk metódusreferenciát:</p>
  <div class="programkod">
    <pre>rendezetlen.sorted(Comparator.comparingInt(String::length)).forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">A sorted rendezett streameknél stabil, vagyis a rendezés feltételére nézve azonos kulcsú elemek a rendezett kimenet encounter orderjében ugyanolyan sorrendben fognak
    szerepelni mint a forrás encounter orderjében.</p>
  <p class="bekezd">
    Gondolom az előzőekből mindenkinek egyértelmű, hogy a <span class="programkod">sorted()</span> közbülső művelet semmiféle módosítást nem végez a forráson. A stackowerflow-n ugyanis
    felmerült olyan kérdés, ahol a kérdező azt hitte, a <span class="programkod">Stream.sorted()</span> a <span class="programkod">Collection</span>-t rendezi.
  </p>
  <div class="keretes">
    <h3>Comparator újdonságok</h3>
    <p>
      Java 8-ban a <span class="programkod"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/Comparator.html">java.util.Comparator</a></span>
      funkcionális interfész lehetőségei jócskán megszaporodtak default és statikus metódusokkal. Az új metódusok mindig új <span class="programkod">Comparator</span> példányt adnak vissza,
      így a <span class="programkod">Comparator</span>-ok összefűzésével lehetővé válik összetettebb rendezési kifejezések leírása is, a lambdákkal pedig sokkal egyszerűbben is használható,
      mint korábban anonim belső osztállyal.
    </p>
    <p>
      Emellett a <span class="programkod">List</span> interfész is kiegészült egy <span class="programkod">sort(Comparator&lt;? super E&gt; c)</span> metódussal a collection rendezéséhez,
      az <span class="programkod">Arrays</span> osztály pedig a már eddig is meglévő tömb-rendező sort mellé kapott egy sor parallelSort függvényt is különböző típusú tömbök párhuzamos
      rendezéséhez. Ezek egy részében szintén használhatunk komparátort. Érdemes áttekinteni a <span class="programkod">Comparator</span> újdonságait mert ezt a fentiek mellett a stream-ek
      rendezésére is fel tudjuk használni (lásd: <span class="programkod">sorted(Comparator&lt;? super T&gt; comparator)</span>).
    </p>
    <p>A Comparator új metódusai:</p>
    <p>
      <u>comparing(Function&lt;? super T,? extends U&gt; keyExtractor)</u>: egy <span class="programkod">T</span> típusú <span class="programkod">Comparator</span>-t ad vissza ami a <span
        class="programkod">keyExtractor</span> által adott értékeket fogja összehasonlítani (ezeknek értelemszerűen a <span class="programkod">Comparable</span> interfészt implementálniuk
      kell). Ha például a legókészleteinket név szerint rendező comparator-t szeretnénk előállítani, akkor ezt így tehetjük meg:
    </p>
    <div class="programkod">
      <pre>Comparator&lt;LegoSet&gt; byName = Comparator.comparing(LegoSet::getName); </pre>
    </div>
    <p>
      <u>comparing(Function&lt;? super T,? extends U&gt; keyExtractor, Comparator&lt;? super U&gt; keyComparator)</u>: az előző metódus kiegészítve egy <span class="programkod">keyComparator</span>
      paraméterrel, ami a <span class="programkod">keyExtractor</span> által adott értékek összehasonlítását végzi, ha esetleg az nem implementálná a <span class="programkod">Comparable</span>
      interfészt vagy nem a természetes rendezését szeretnénk használni.
    </p>
    <p>
      A természetes rendezés (natural ordering) azon osztályokon végezhető el, amelyek implementálják a <span class="programkod">Comparable</span> interfészt. Ilyenkor a rendezést
      autmatikusan ezen <span class="programkod">Comparable</span> interfész implementációjának megfelelően végzi el a Java osztálykönyvtár. De ha mi nem ezt szeretnénk, akkor jönnek jól a
      <span class="programkod">Comparator</span> interfész <span class="programkod">keyComparator</span> paraméteres metódusai. Így például tudunk rendezni sztringeket névsor helyett akár a
      hosszuk alapján:
    </p>
    <div class="programkod">
      <pre>Comparator&lt;LegoSet&gt; byNameLength = Comparator
    .comparing(LegoSet::getName, (s1, s2) -&gt; s1.length() - s2.length()); </pre>
    </div>
    <p>A már ismerős filozófia alapján természetesen itt is kitüntettt szerepe van az int, double és long primitív típusoknak, ezért létezik comparingDouble, comparingInt és
      comparingLong metódus is, amelyek ugyanúgy comparatort adnak vissza, csak keyExtractor paraméterként olyan lambda kifejezést (metódusreferenciát) várnak, ami az adott primitív
      típussal tér vissza.</p>
    <p>
      <u>naturalOrder()</u>: <span class="programkod">Comparable</span>-t implementáló osztályokhoz természetes sorrendbe rendező <span class="programkod">Comparator</span> előállítása.
    </p>
    <p>
      <u>nullsFirst(Comparator&lt;? super T&gt; comparator)</u>: null-barát <span class="programkod">Comparator</span>, ami a <span class="programkod">null</span>-t <i>kisebbnek</i> tekinti
      a nem-nullnál. (Két <span class="programkod">null</span> azonos.) Ha az összehasonlítandó értékek egyike sem null akkor a paraméterként megadott comparator segítségével lesznek
      összehasonlítva. Paraméterként egyébként trükkös módon <span class="programkod">null</span>-t is megadhatunk, ekkor a létrejövő <span class="programkod">Comparator</span> minden
      nemnull értéket azonosnak fog tekintetni.
    </p>
    <p>
      <u>nullsLast(Comparator&lt;? super T&gt; comparator)</u>: ugyanaz mint a fenti csak annyi különbséggel, hogy a nullokat itt nagyobbnak tekinti a nemnulloknál.
    </p>
    <p>
      <u>reversed()</u>: az aktuális <span class="programkod">Comparator</span> példány visszafelé rendező változata.
    </p>
    <p>
      <u>reverseOrder()</u>: a természetes rendezés visszafelé rendező változata.
    </p>
    <p>
      <u>thenComparing(Comparator&lt;? super T&gt; other)</u>: új <span class="programkod">Comparator</span> példánnyal újabb rendezési kulcs hozzáadása <span class="programkod">Comparator</span>
      láncunkhoz. A thenComparing úgy működik, hogy ha az aktuális Comparator két elemet azonosnak talál (vagyis <span class="programkod">compare(a,b)==0</span>) akkor az <span
        class="programkod">other</span>-t hívja meg a sorrend meghatározásához. Például egy <span class="programkod">String</span> típusú collection-t először hossz majd pedig (azonos
      hosszak esetén) nagybetű-kisbetű független természetes sorrendbe rendező <span class="programkod">Comparator</span> előállításához ezt kell begépelnünk:
    </p>
    <div class="programkod">
      <pre>Comparator&lt;String&gt; comp = Comparator
    .comparingInt(String::length)
    .thenComparing(String.CASE_INSENSITIVE_ORDER); </pre>
    </div>
    <p>
      <u>thenComparing(Function&lt;? super T,? extends U&gt; keyExtractor)</u>: a <span class="programkod">keyExtractor</span>-t használó <span class="programkod">T</span> típusú <span
        class="programkod">Comparator</span> előállítása. Ha az aktuális <span class="programkod">Comparator</span> két elemet azonosnak talál (vagyis <span class="programkod">compare(a,b)==0</span>)
      akkor ez az új lesz felhasználva a sorrend meghatározásához. A legókészleteinket rendezhetjük például kiadás éve, az azonos évben kiadottakat pedig névsor szerint így:
    </p>
    <div class="programkod">
      <pre>Comparator&lt;LegoSet&gt; byYearThenName = Comparator
    .comparingInt(LegoSet::getYearReleased)
    .thenComparing(LegoSet::getName); </pre>
    </div>
    <p>
      Akárcsak a <span class="programkod">comparing(Function&lt;? super T,? extends U&gt; keyExtractor)</span> metódusból, a <span class="programkod">thenComparing(Function&lt;?
        super T,? extends U&gt; keyExtractor)</span> metódusból is létezik thenComparingDouble, thenComparingInt és thenComparingLong változat.
    </p>
    <p>
      <u>thenComparing(Function&lt;? super T,? extends U&gt; keyExtractor, Comparator&lt;? super U&gt; keyComparator)</u>: az előző metódus kiegészítése azzal, hogy azonos elemek esetén a <span
        class="programkod">keyComparator</span> comparatorral rendezünk a <span class="programkod">keyExtractor</span> szerint. A fenti példát ezzel módosíthatjuk úgy is, hogy az azonos
      évben kiadott készleteinket névsor szerint visszafelé rendezzük:
    </p>
    <div class="programkod">
      <pre>Comparator&lt;LegoSet&gt; byYearThenName = Comparator
    .comparingInt(LegoSet::getYearReleased)
    .thenComparing(LegoSet::getName, Comparator.reverseOrder()); </pre>
    </div>
    <p>
      A visszafelé rendezésre egyébként a már megismert <span class="programkod">reversed()</span> is eszünkbe juthatna - mégpedig jogosan. Viszont vigyázat! Új <span class="programkod">Comparator</span>
      láncba fűzése az egész <i>megelőző</i> láncra vonatkozik, tehát a fenti helyett ezt írva:
    </p>
    <div class="programkod">
      <pre>Comparator&lt;LegoSet&gt; byYearThenName = Comparator
    .comparingInt(LegoSet::getYearReleased)
    .thenComparing(LegoSet::getName)
    .reversed(); </pre>
    </div>
    <p>az egész listánk fog visszafelé rendeződni nem pedig az egy évben kiadott készletek listája. Ha ezt írjuk:</p>
    <div class="programkod">
      <pre>Comparator&lt;LegoSet&gt; byYearThenName = Comparator
    .comparingInt(LegoSet::getYearReleased)
    .reversed()
    .thenComparing(LegoSet::getName); </pre>
    </div>
    <p>akkor pedig az évek szerint visszafelé, de egy éven belül névsor szerint növekvő listát kapunk.</p>
  </div>
  <p class="bekezd">
    <b>Közbülső műveletek esete a rendezettséggel</b>
  </p>
  <p class="bekezd">
    <u>skip(long n)</u>: igen költséges művelet a rendezett párhuzamos stream-eknél (pláne nagy n-eknél), mert a szálaknak várniuk kell egymásra. Az eredmény kiszámításához figyelembe kell
    venni az encounter order-t, hiszen ez nem akármelyik, hanem az <b>első n</b> elemet hagyja el a stream-ből. Ha a forrás eleve rendezetlen, vagy a stream-et rendezetlenné tesszük (<span
      class="programkod">unordered()</span>) az jelentős sebességnövekedést okoz a műveletben, bár így már nem lehet <i>első</i> n elemről beszélni.
  </p>
  <p class="bekezd">
    <u>limit(long maxSize)</u>: párhuzamos stream-eknél a fentebb részletezett okokból szintén költséges művelet. A rendezetlenné tétel itt is növelheti a teljesítményt.
  </p>
  <p class="bekezd">
    <u>peek(Consumer&lt;? super T&gt; action)</u>: a <span class="programkod">Stream</span> és leszármazottaiban (<span class="programkod">IntStream</span>, <span class="programkod">LongStream</span>
    és <span class="programkod">DoubleStream</span>) lévő peek művelet nem foglalkozik a rendezettséggel, tehát párhuzamos streamek esetén bármilyen sorrendben végrehajtódhat.
  </p>
  <p class="bekezd">
    <u>distinct()</u>: rendezett steameknél a stabilitás biztosítása miatt a distinct igen költséges. A rendezettség megszüntetése növelheti a párhuzamos végrehajtás sebességét, de onnantól
    kezdve a stabilitás nem biztosított: amelyik duplikátum később jön, az lesz kihagyva függetlenül attól, hogy mi volt az encounter order.
  </p>
  <p class="bekezd">Az alábbi példa szépen bemutatja, mit is jelent a stabilitás a rendezettségnél:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.Arrays;
<span class="java_keyword">import</span> java.util.stream.Stream;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> DistinctExample {

    <span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">int</span> i = 1;

    <span class="java_keyword">private</span> <span class="java_keyword">int</span> id = i++;
    <span class="java_keyword">private</span> String s;

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        Object[] exampleObjects = generateStream().parallel().distinct().toArray();
        System.out.printf(<span class="java_string">"ordered distinct: %s%n"</span>, Arrays.toString(exampleObjects));

        DistinctExample.i = 1;
        exampleObjects = generateStream().unordered().parallel().distinct().toArray();
        System.out.printf(<span class="java_string">"unordered distinct: %s%n"</span>, Arrays.toString(exampleObjects));
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> Stream&lt;DistinctExample&gt; generateStream() {
        DistinctExample[] de = <span class="java_keyword">new</span> DistinctExample[] { <span class="java_keyword">new</span> DistinctExample(<span class="java_string">&quot;a&quot;</span>), <span
        class="java_keyword">new</span> DistinctExample(<span class="java_string">&quot;b&quot;</span>),
            <span class="java_keyword">new</span> DistinctExample(<span class="java_string">&quot;c&quot;</span>), <span class="java_keyword">new</span> DistinctExample(<span
        class="java_string">&quot;b&quot;</span>),
            <span class="java_keyword">new</span> DistinctExample(<span class="java_string">&quot;c&quot;</span>), <span class="java_keyword">new</span> DistinctExample(<span
        class="java_string">&quot;a&quot;</span>) };
        <span class="java_keyword">return</span> Stream.of(de);
    }

    <span class="java_keyword">public</span> DistinctExample(String value) {
        s = value;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">int</span> hashCode() {
        <span class="java_keyword">return</span> s != <span class="java_keyword">null</span> ? s.hashCode() : 0;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">boolean</span> equals(Object obj) {
        <span class="java_keyword">if</span> (<span class="java_keyword">this</span> == obj)
            <span class="java_keyword">return</span> true;
        <span class="java_keyword">if</span> (obj == <span class="java_keyword">null</span> || getClass() != obj.getClass())
            <span class="java_keyword">return</span> false;
        DistinctExample other = (DistinctExample) obj;
        <span class="java_keyword">if</span> (s == <span class="java_keyword">null</span>) {
            <span class="java_keyword">if</span> (other.s != <span class="java_keyword">null</span>)
                <span class="java_keyword">return</span> false;
        } <span class="java_keyword">else</span> <span class="java_keyword">if</span> (!s.equals(other.s))
            <span class="java_keyword">return</span> false;
        <span class="java_keyword">return</span> true;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> String toString() {
        <span class="java_keyword">return</span> <span class="java_string">&quot;DistinctExample [id=&quot;</span> + id + <span class="java_string">&quot;, s=&quot;</span> + s + <span
        class="java_string">&quot;]&quot;</span>;
    }

}</pre>
  </div>
  <p class="bekezd">A futtatás eredménye:</p>
  <div class="programkod">
    <pre>ordered distinct: [DistinctExample [id=1, s=a], DistinctExample [id=2, s=b], DistinctExample [id=3, s=c]]
unordered distinct: [DistinctExample [id=1, s=a], DistinctExample [id=4, s=b], DistinctExample [id=3, s=c]]</pre>
  </div>
  <p class="bekezd">
    Rendezett esetben azok a karakterek kerülnek az eredmény streambe amelyek a forrásban elsőként következtek (az <span class="programkod">id</span> szolgál a sorrend meghatározására).
    Rendezetlen esetben meghatározatlan.
  </p>
  <h3>Mapping</h3>
  <p class="bekezd">
    <u>map(Function&lt;? super T,? extends R&gt; mapper)</u>: a művelet egy új streamet ad vissza ami az eredeti stream elemeire alkalmazott mapper eredményeit tartalmazza. Az <span
      class="programkod">Optional</span>-hoz hasonlóan a map itt is megváltoztat(hat)ja a stream generikus típusát. Az alábbi példában a sztringek stream-jéből a sztringhosszak stream-je
    lesz:
  </p>
  <div class="programkod">
    <pre>Stream&lt;String&gt; source = Arrays.asList(<span class="java_string">&quot;ec&quot;</span>, <span class="java_string">&quot;pec&quot;</span>, <span class="java_string">&quot;kimehetsz&quot;</span>).stream();
Stream&lt;Integer&gt; mapped = source.map(s -&gt; s.length()); </pre>
  </div>
  <p class="bekezd">Három speciális map művelet is van, ezek közvetlenül primitív típusú stream-et adnak vissza:</p>
  <ul>
    <li>IntStream mapToInt(ToIntFunction&lt;? super T&gt; mapper)</li>
    <li>DoubleStream mapToDouble(ToDoubleFunction&lt;? super T&gt; mapper)</li>
    <li>LongStream mapToLong(ToLongFunction&lt;? super T&gt; mapper)</li>
  </ul>
  <p class="bekezd">
    <u>flatMap(Function&lt;? super T,? extends Stream&lt;? extends R&gt;&gt; mapper)</u>: lehetővé teszi összetett adatszerkezetek esetén a <span class="programkod">Stream&lt;Stream&lt;R&gt;&gt;</span>
    szerkezetek elkerülését (hasonlóan mint az <span class="programkod">Optional.flatMap()</span>). A flatMap szintén egy <span class="programkod">Function</span> paramétert vár, csak ennek
    a visszatérési típusa nem az eredeti stream elemeinek a <span class="programkod">T</span> típusa, hanem egy újabb stream, ami <span class="programkod">R</span> típusú elemeket tartalmaz
    (bár <span class="programkod">R</span> lehet azonos a <span class="programkod">T</span>-vel):
  </p>
  <pre class="programkod">&lt;R&gt; Stream&lt;R&gt; flatMap(Function&lt;? super T,? extends Stream&lt;? extends R&gt;&gt; mapper) </pre>
  <p class="bekezd">
    Vagyis a map művelethez képest annyi az eltérés, hogy itt nem <span class="programkod">R</span> hanem <span class="programkod">Stream&lt;? extends R&gt;</span> a mapper visszatérési
    értéke. Amikor ezt a mappert az eredeti stream elemeire alkalmazzuk, minden egyes elemre egy új elemeket tartalmazó stream-et ad eredményül. Ezen új stream-ek elemei aztán tovább
    másolódnak egy újabb stream-be és ez lesz aztán a flatMap tényleges visszatérési értéke.
  </p>
  <p class="bekezd">Ha ez így kissé komplikáltan hangzik, csak semmi pánik, a példák során rögtön meg fogunk világosodni! Tegyül fel, hogy van egy sztringlistákat tartalmazó lista
    forrásunk:</p>
  <pre class="programkod">[[egy, kettő, három], [négy, öt, hat], [hét, nyolc, kilenc]]</pre>
  <p class="bekezd">A sztring elemeket a map művelettel nem célszerű feldolgozni, mert ebben az esetben az sztringlistákat fog megkapni, nem pedig sztringeket. Ha viszont a listák
    listáját &quot;kilapítjuk&quot; így, akkor már egész jól lehet kezelni:</p>
  <pre class="programkod">[egy, kettő, három, négy, öt, hat, hét, nyolc, kilenc]</pre>
  <p class="bekezd">Kódügyileg ez a következőképp néz ki:</p>
  <div class="programkod">
    <pre>List&lt;List&lt;String&gt;&gt; lapitsKi = Arrays.asList(Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span
        class="java_string">&quot;három&quot;</span>),
    Arrays.asList(<span class="java_string">&quot;négy&quot;</span>, <span class="java_string">&quot;öt&quot;</span>, <span class="java_string">&quot;hat&quot;</span>),
    Arrays.asList(<span class="java_string">&quot;hét&quot;</span>, <span class="java_string">&quot;nyolc&quot;</span>, <span class="java_string">&quot;kilenc&quot;</span>));
lapitsKi.stream().flatMap(num -&gt; num.stream()).forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">forEach()</span> már egy <span class="programkod">Stream&lt;String&gt;</span>-en megy végig, nem pedig egy <span class="programkod">Stream&lt;Stream&lt;String&gt;&gt;-en</span>
    (ezt tenné ha a flatMap helyett a map műveletet használnánk). Ha az eredményt el szeretnénk tárolni, akkor használhatjuk a collect lezáró műveletet (és a <span class="programkod">num
      -&gt; num.stream()</span> helyett a flatMap paramétereként ebben a példában ezt is írhatnánk: <span class="programkod">Collection::stream</span>):
  </p>
  <div class="programkod">
    <pre>List&lt;String&gt; eredmeny = lapitsKi.stream().flatMap(num -&gt; num.stream()).collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">A collect műveletről később bővebben lesz szó.</p>
  <p class="bekezd">
    Figyeljük meg, hogy a flatMap paraméterében a <span class="programkod">Collection.stream()</span> állítja elő az új rész-streameket, amelyek alapján a flatMap majd elvégzi a
    &quot;kilapítást&quot;. Egyes példák hibásan a <span class="programkod">num -&gt; Stream.of(num)</span> formát használják ehelyett. Az itt csak akkor működne ha az eredeti adatszerkezet
    <span class="programkod">List&lt;String&gt;</span> lenne (akkor meg nincs szükség a flatMap-re). Mégpedig azért mert a két stream létrehozó függvény eltérő:
  </p>
  <p class="bekezd">
    A <span class="programkod">Collection&lt;E&gt;</span> esetén:
  </p>
  <pre class="programkod">default Stream&lt;E&gt; stream() </pre>
  <p class="bekezd">A Stream esetén:</p>
  <pre class="programkod">static &lt;T&gt; Stream&lt;T&gt; of(T... values) </pre>
  <p class="bekezd">
    Mivel a példában a num típusa <span class="programkod">List&lt;String&gt;</span>, ezért a <span class="programkod">stream()</span> függvény eredményének típusa: <span class="programkod">Stream&lt;String&gt;</span>
  </p>
  <p class="bekezd">
    A <span class="programkod">Stream.of()</span> eredményének típusa pedig: <span class="programkod">Stream&lt;List&lt;String&gt;&gt;</span>
  </p>
  <p class="bekezd">
    Az eddigi példákban az eredmény generikus típusa nem tért el a forrástól, lássunk egy másik példát ahol igen. Egészítsük ki a <span class="programkod">LegoSet</span> osztályt egy
    listával ami a készletben lévő elemeket tartalmazza. Egy elemet a <span class="programkod">LegoBrick</span> osztály tartalmaz, ennek belső szerkezete most itt igazából lényegtelen. A <span
      class="programkod">LegoSet</span> ezzel egészül ki:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_keyword">import</span> java.time.LocalDate;
<span class="java_keyword">import</span> java.util.List;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> LegoSet {
    ...
    <span class="java_keyword">private</span> List&lt;LegoBrick&gt; bricks;<span class="java_comment">// a készlet elemei</span>

    ...
    
    <span class="java_keyword">public</span> List&lt;LegoBrick&gt; getBricks() {
        <span class="java_keyword">return</span> bricks;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> setBricks(List&lt;LegoBrick&gt; bricks) {
        <span class="java_keyword">this</span>.bricks = bricks;
    }

    ...

} </pre>
  </div>
  <p class="bekezd">Ki szeretnénk gyűjteni a teljes gyűjteményünk, vagyis az adatbázisban lévő összes készlet összes elemét egy listába. A flatMap használatával ez már pofonegyszerű:</p>
  <div class="programkod">
    <pre>List&lt;LegoBrick&gt; myBricks = mySets.stream()
    .flatMap(legoset -&gt; legoset.getBricks()
    .stream())
    .collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">List&lt;LegoSet&gt;</span> adatszerkezetből egy <span class="programkod">List&lt;LegoBrick&gt;</span> lett.
  </p>
  <p class="bekezd">Levezetésként pedig még egy utolsó flatMap példa, többdimenziós tömbbel, ami kigyűjti a forrásból a páratlan számokat:</p>
  <div class="programkod">
    <pre>Integer[][] ints = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
Arrays.stream(ints)
    .flatMap(row -&gt; Arrays.stream(row))
    .filter(num -&gt; num % 2 == 1)
    .forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">
    Itt - mivel tömbökről van szó és a Stream.of paramétere is tömb - a <span class="programkod">flatMap(row -&gt; Stream.of(row))</span> forma is megfelelő lenne az átalakításhoz.
  </p>
  <p class="bekezd">Ja, és el ne felejtsem: flatMap-ből is létezik értelemszerűen flatMapToDouble, flatMapToInt és flatMapToLong, ahol a Function paraméternek a visszatérési típusa a
    megfelelő primitív típusú stream kell legyen.</p>
  <p class="bekezd">Végül pedig egy érdekességet mutatok be a filter és map műveletekkel. Metódusreferenciaként ugyanis akár a class osztály metódusait is használhatjuk! Legyen egy jó
    kis vegyes stream-ünk:</p>
  <div class="programkod">
    <pre>Stream&lt;Object&gt; csalamade = Stream.of(<span class="java_string">&quot;hurkapiszka&quot;</span>, 123, <span class="java_keyword">new</span> LegoSet(), <span
        class="java_string">&quot;Java 8&quot;</span>);
List&lt;String&gt; stringjeink = csalamade
    .filter(String.<span class="java_keyword">class</span>::isInstance)
    .map(String.<span class="java_keyword">class</span>::cast)
    .collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">
    A filter metódusban a sztringeket szűrjük ki, majd a map metódusban saját magukra castoljuk, hogy <span class="programkod">Stream&lt;String&gt;</span>-ünk legyen. Azt nem árt azért
    tudni, hogy a <span class="programkod">Class&lt;T&gt;::isInstance</span> csak azt nézi, hogy a paraméterként átadott érték adható-e <span class="programkod">T</span> típusú változónak,
    vagyis <span class="programkod">Object.class.isInstance(&quot;valami&quot;)</span> is true-t fog visszaadni, mert <span class="programkod">Object</span> típusú változónak is adhatjuk a
    <span class="programkod">&quot;valami&quot;</span> értéket. Ha a filterben pontosan egy kívánt típusra (mondjuk épp <span class="programkod">String</span>-re) szeretnénk szűrni, akkor
    azt így tehetjük meg (esetleg egy nullvizsgálattal is kiegészíthetjük ha a streamünk tartalmazhat <span class="programkod">null</span>-t):
  </p>
  <div class="programkod">
    <pre>filter(e -&gt; e.getClass().equals(String.<span class="java_keyword">class</span>)) </pre>
  </div>
  <h3>A végtelenbe, és tovább!</h3>
  <p class="bekezd">Eddigi példáinkban a forrás mindig ismert volt már a stream létrehozása előtt. Mi a helyzet, ha úgy szeretnénk stream csővezetéket végrehajtani, hogy a forrás elemek
    és azok száma csak végrehajtás közben válik ismertté? Java 8-cal ez is lehetséges! Ebben az esetben ún. végtelen (más néven nem korlátozott - unbounded) stream-eket tudunk használni. A
    végtelen stream-ek használata a lusta feldolgozás miatt lehetséges, hiszen a forrás elemeit így elég csak a lezáró művelet végrehajtásakor ismerni. A Stream interfész két módszert ad
    végtelen stream-ek létrehozására:</p>
  <p class="bekezd">
    <u>generate(Supplier&lt;T&gt; s)</u>: soros, rendezetlen stream-et állít elő. Paramétereként egy <span class="programkod">Supplier</span>-t kell megadnunk. Ez minden egyes alkalommal
    meghívódik amikor új stream elemre van szükség. Egy faék egyszerűségű példa:
  </p>
  <div class="programkod">
    <pre>Stream&lt;Double&gt; infinite = Stream.generate(Math::random); </pre>
  </div>
  <p class="bekezd">
    A példában a <span class="programkod">Math::random</span> a <span class="programkod">Supplier</span>, ami az <span class="programkod">infinite</span> stream használatakor majd mindig
    visszaad egy új véletlenszámot. Végtelen streamek használatakor alkalmaznunk kell egy olyan rövidzár műveletet ami egyszer majd lezárja a feldolgozást, különben a stream feldolgozása
    valóban az idők végezetéig tartana. Leggyakoribb módszer a limit metódus, ami a streamből legföljebb a megadott számú elemet hagyja meg. Ennek a segítségével a következő példában 10
    véletlenszámból álló tömböt állítunk elő:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">double</span>[] randoms = infinite.limit(10).mapToDouble(i -&gt; i).toArray(); </pre>
  </div>
  <p class="bekezd">Állapottartó művelettel generálhatunk például Fibonacci-számokat:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private static class</span> FibonacciSupplier <span class="java_keyword">implements</span> IntSupplier {
    <span class="java_keyword">int</span> previous = 1;
    <span class="java_keyword">int</span> current = 0;

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public int</span> getAsInt() {
        <span class="java_keyword">int</span> result = current;
        current = previous + current;
        previous = result;
        <span class="java_keyword">return</span> result;
    }
}

...

    IntStream stream = IntStream.generate(<span class="java_keyword">new</span> FibonacciSupplier());
    stream.parallel().limit(30).forEach(System.out::println);</pre>
  </div>
  <p class="bekezd">
    (Ha a <span class="programkod">previous</span> és a <span class="programkod">current</span> kezdő értékeit megcseréljük 1,0 helyett 0,1-re akkor a régebben elfogadott 1,1,2,... sor
    kapjuk a 0,1,1,2 helyett.)
  </p>
  <p class="bekezd">Ha állapottartó műveletet használunk Supplier-ként akkor párhuzamos stream-ek használatakor meghatározatlan eredményt kaphatunk.</p>
  <p class="bekezd">
    A generate függvénnyel tetszőleges típusú elemeket állíthatunk elő. Például ha a legókészlet adatbázisunkat tesztelés miatt generált készletekkel szeretnénk feltölteni és már van egy <span
      class="programkod">constructLegoSet()</span> metódusunk ami véletlenszerűen előállít egy új készletet, akkor azt használhatjuk a következőképpen:
  </p>
  <div class="programkod">
    <pre>Supplier&lt;LegoSet&gt; legoSetSupplier = StreamExample1::constructLegoSet;
List&lt;LegoSet&gt; randomSets = Stream.generate(legoSetSupplier)
    .skip(10)
    .limit(10)
    .collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">A skip metódussal kihagyjuk az első 10 generált készletet majd a következő 10-et berakjuk egy listába.</p>
  <p class="bekezd">
    <u>iterate(T seed, UnaryOperator&lt;T&gt; f)</u>: soros, rendezett stream-et állít elő. Két paramétert vár: egy kezdeti, <span class="programkod">seed</span>-nek nevezett elemet és egy
    függvényt, ami mindig legenerálja az előző elem alapján a következőt. Egy újabb faék egyszerűségű példa:
  </p>
  <div class="programkod">
    <pre>Stream&lt;Integer&gt; parosSzamok = Stream.iterate(2, i -&gt; i + 2); </pre>
  </div>
  <p class="bekezd">Az iterate generáló függvényt se érdemes egyébként párhuzamos streameknél használni.</p>
  <p class="bekezd">Mint mondottam, végtelen stream-ek csővezetékében valóban legyen valahol egy rövidzár művelet (lezáró vagy közbülső), különben nem csak elméletben hanem a
    gyakorlatban is végtelen lesz. És bizony nem árt az éberség, mert teljesen észrevétlenül is írhatunk végtelen stream-eket, mint például:</p>
  <div class="programkod">
    <pre>IntStream.iterate(0, i -&gt; (i + 1) % 2).distinct().limit(10).forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">A példa váltakozó nullákat és egyeket generál, aztán csak az egyedi értékeket, vagyis egy 0-t és 1-et tart meg. Eztán korlátozzuk a stream-et 10 elemre, majd
    felhasználjuk az elemeket. És a kód végtelenségig akar majd futni, ugyanis a distinct nem tudja, hogy az iterate metódusnak átadott függvény soha nem generál kettőnél több különböző
    elemet, hanem többet vár. És csak vár. (Ha a limit és distinct műveleteket megcseréljük, akkor ugyan szemantikailag más kódot kapunk, viszont már nem fog végtelenségig futni.) A kódot
    még szebbé tehetjük ha párhuzamosítjuk:</p>
  <div class="programkod">
    <pre>IntStream.iterate(0, i -&gt; (i + 1) % 2)
    .parallel()
    .distinct()
    .limit(10)
    .forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">Így az összes elérhető processzormagot a végtelen futás szolgálatába lehet állítani!</p>
  <p class="bekezd">
    A <span class="programkod">Stream</span> interfészen kívül a <span class="programkod">Random</span> osztály metódusaival is lehet végtelen stream-eket előállítani:
  </p>
  <ul>
    <li><span class="programkod">ints()</span></li>
    <li><span class="programkod">ints(int randomNumberOrigin, int randomNumberBound)</span>: <span class="programkod">randomNumberOrigin</span> és <span class="programkod">randomNumberBound</span>
      között</li>
  </ul>
  <p class="bekezd">
    A két ints metódusnak megvan a doubles és longs párja is. A <span class="programkod">SplittableRandom</span> osztály a <span class="programkod">Random</span> párhuzamos feldolgozáshoz
    optimalizált változata, szintén rendelkezik ezekkel a végtelen stream előállító metódusokkal. A <span class="programkod">Random</span> használatára egy egyszerű példa (megállunk
    10-nél):
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">new</span> Random().ints().limit(10).forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">
    Az ints-nek van paraméterezhető változata is aminek megadhatjuk a kívánt számú véletlemszám mennyiségét, ha nem végtelent szeretnénk. Többszálú programokban a <span class="programkod">ThreadLocalRandom.current()</span>
    hívással érdemes <span class="programkod">Random</span> objektumhoz jutni.
  </p>
  <p class="bekezd">A stream API-ban nagyon csábító, hogy a közbülső műveleteket bármilyen sorrendben írhatjuk, de ha jót akarunk, akkor érdemes átgondolni egy csővezeték létrehozását.
    Tekintsük a következő példát:</p>
  <div class="programkod">
    <pre>valamilyenCollection.stream()
    .map(e -&gt; valamilyenFunction(e))
    .collect(Collectors.toList())
    .subList(0, 10); </pre>
  </div>
  <p class="bekezd">Első ránézésre nincs vele semmi gond; valamilyen függvénnyel leképezzük a collectiont, majd listába szervezzük az eredményt, abból pedig vesszük az első 10 elemet.
    De adjunk csak annak a map-ben használt függvénynek egy kicsit beszédesebb nevet:</p>
  <div class="programkod">
    <pre>valamilyenCollection.stream()
    .map(e -&gt; marhaKoltsegesFunction(e))
    .collect(Collectors.toList())
    .subList(0, 10); </pre>
  </div>
  <p class="bekezd">Minek is végezzük el a collection minden egyes elemére a költséges műveletet, ha aztán csak az első 10 eleme kell az eredménynek? Ha feltesszük, hogy az explicit
    rendezés nem lényeges, akkor a fenti sort sokkal hatékonyabbra is írhatjuk:</p>
  <div class="programkod">
    <pre>valamilyenCollection.stream()
    .limit(10)
    .map(e -&gt; marhaKoltsegesFunction(e))
    .collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">A tanulság: érdemes előbb mindig lecsökkenteni a stream által használt elemek számát és csak aztán mappelni!</p>
  <p class="bekezd">
    A példa <a onclick="window.open(this.href);return false;" href="https://blog.jooq.org/2017/06/29/a-basic-programming-pattern-filter-first-map-later/">kitalálója</a> persze kicsit
    trükkös volt, mert a subList nem stream művelet (de az elv attól még helyes). De várjunk csak! Nem arról volt szó, hogy a stream lusta kiértékelésű? Akkor meg nem mindegy, milyen
    sorrendben írjuk a közbülső műveleteket? Íme egy példa kétféleképpen írva:
  </p>
  <div class="programkod">
    <pre>Stream.iterate(0, i -&gt; i + 1)
    .map(i -&gt; i + 1)
    .peek(i -&gt; System.<span class="java_constant">out</span>.println(i + &quot;. map&quot;))
    .limit(5)
    .forEach(i -&gt; { });
        
Stream.iterate(0, i -&gt; i + 1)
    .limit(5)
    .map(i -&gt; i + 1)
    .peek(i -&gt; System.<span class="java_constant">out</span>.println(i + &quot;. map&quot;))
    .forEach(i -&gt; { }); </pre>
  </div>
  <p class="bekezd">Mindkét sor kimenete:</p>
  <pre class="programkod">1. map
2. map
3. map
4. map
5. map</pre>
  <p class="bekezd">Csakhogy nem mindig ez a helyzet! Az optimalizáció ugyanis implementációfüggő és általánosságban mégiscsak bölcsebb ha előbb szűrünk és aztán mappelünk, nem hagyunk
    mindent az implementációra. Java 8 esetén például a flatMap éppenséggel nem is lusta kiértékelésű. Tekintsük a következő példát:</p>
  <div class="programkod">
    <pre>System.<span class="java_constant">out</span>.println(&quot;Limit a végén:&quot;);
Stream.iterate(0, i -&gt; i + 1)
    .flatMap(i -&gt; Stream.of(i, i))
    .peek(i -&gt; System.<span class="java_constant">out</span>.println(i + &quot;. map&quot;))
    .limit(5)
    .forEach(i -&gt; { });
System.<span class="java_constant">out</span>.println(&quot;Limit a közepén:&quot;);
Stream.iterate(0, i -&gt; i + 1)
    .flatMap(i -&gt; Stream.of(i, i))
    .limit(5)
    .peek(i -&gt; System.<span class="java_constant">out</span>.println(i + &quot;. map&quot;))
    .forEach(i -&gt; { }); </pre>
  </div>
  <p class="bekezd">A két végtelen stream minden elemét kettővé &quot;lapítjuk&quot;, majd kiíratjuk. A két példának elvben ugyanolyan kimenete kellene legyen, mégsem ez a helyzet:</p>
  <pre class="programkod">Limit a végén:
0. map
0. map
1. map
1. map
2. map
2. map
Limit a közepén:
0. map
0. map
1. map
1. map
2. map</pre>
  <p class="bekezd">Az első csővezeték rá se hederít a limitre és mind a hat elemet kiírja, a második viszont már csak ötöt. A flatMap a lezáró művelet végrehajtásakor ugyanis mohón
    feldolgozza a forrást és mindig legenerálja az összes értéket a következő műveletnek. Vagyis ha nem (mondjuk a fenti példában) két elemű hanem végtelen stream-et generálunk benne, akkor
    kellemetlen meglepetést okoz. Ez még lefut:</p>
  <div class="programkod">
    <pre>Stream.of(&quot;&quot;).map(x -&gt; Stream.iterate(0, i -&gt; i + 1)).findFirst(); </pre>
  </div>
  <p class="bekezd">Ez viszont már végtelen ciklusba kerül:</p>
  <div class="programkod">
    <pre>Stream.of(&quot;&quot;).flatMap(x -&gt; Stream.iterate(0, i -&gt; i + 1)).findFirst(); </pre>
  </div>
  <p class="bekezd">
    Ezt a viselkedést egyébként végül <a onclick="window.open(this.href);return false;" href="https://bugs.openjdk.java.net/browse/JDK-8075939">hibának</a> találták és a Java 10-ben már
    kijavították.
  </p>
  <h2>Összefűzés</h2>
  <p class="bekezd">Stream-eket össze is lehet fűzni. Persze nem gyöngyöt fűzünk belőlük, hanem újabb streameket. Erre szolgál a Stream osztály statikus concat metódusa:</p>
  <div class="programkod">
    <pre>Stream&lt;String&gt; stream1 = Stream.of(<span class="java_string">&quot;sárga &quot;</span>, <span class="java_string">&quot;bögre &quot;</span>);
Stream&lt;String&gt; stream2 = Stream.of(<span class="java_string">&quot;görbe &quot;</span>, <span class="java_string">&quot;bögre &quot;</span>);
Stream.concat(stream1, stream2).forEachOrdered(System.out::print); </pre>
  </div>
  <p class="bekezd">A kód kimenete:</p>
  <pre class="programkod">    sárga bögre görbe bögre </pre>
  <p class="bekezd">A művelet a primitív típusú stream-ek esetén is rendelkezésre áll:</p>
  <div class="programkod">
    <pre>IntStream stream1 = IntStream.of(10, 9, 8, 7, 6, 5);
IntStream stream2 = IntStream.of(4, 3, 2, 1, 0);
IntStream fuzer = IntStream.concat(stream1, stream2); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">fuzer</span> elemei: [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
  </p>
  <p class="bekezd">A concat egy lustán összefűzött streamet állít elő a paraméterként megadott két azonos típusú stream-ből. Az új streamben az első paraméter stream elemei vannak,
    ezeket követik a második stream elemei. Az eredmény stream rendezett, ha mindkét bemeneti stream rendezett és párhuzamos ha bármely bemeneti stream párhuzamos. Amikor a concat eredmény
    stream-jét lezárják, mindkét bemeneti stream close handler-jei is meghívódnak. A művelet asszociatív és egymásba ágyazható:</p>
  <div class="programkod">
    <pre>Stream&lt;String&gt; fuzer1 = Stream.concat(stream1, Stream.concat(stream2, stream3));
Stream&lt;String&gt; fuzer2 = Stream.concat(Stream.concat(stream1, stream2), stream3); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">fuzer1</span> és <span class="programkod">fuzer2</span> eredménye ugyanaz lesz. Vigyázzunk azonban a stream-ek ismételt összefűzésével. Mélyen összefűzött
    stream egy elemének elérése mély hívási láncon keresztül történhet és akár <span class="programkod">StackOverflowException</span>-t is kaphatunk.
  </p>
  <p class="bekezd">Természetesen a stream műveleteket már egyszerűen a concat paraméterlistájában is megadhatjuk, hiszen azoknak is stream a visszatérési értékük. Az alábbi példa
    összefűzi a CASTLE és a STARWARS témájú készleteinket egy stream-be:</p>
  <div class="programkod">
    <pre>Stream&lt;LegoSet&gt; sets = Stream.concat(
    mySets
    .stream()
    .filter(set -&gt; set.getTheme() == Theme.CASTLE), 
    mySets
    .stream()
    .filter(set -&gt; set.getTheme() == Theme.STARWARS)); </pre>
  </div>
  <p class="bekezd">
    Az összefűzést egyébként a <span class="programkod">Stream.of()</span> metódussal is megtehetjük, de ebben az esetben a végeredmény stream-ek stream-je, tehát használnunk kell még a
    flatMap műveletet:
  </p>
  <div class="programkod">
    <pre>Stream&lt;LegoSet&gt; sets = Stream.of(
        mySets
        .stream()
        .filter(set -&gt; set.getTheme() == Theme.CASTLE), 
        mySets
        .stream()
        .filter(set -&gt; set.getTheme() == Theme.STARWARS))
    .flatMap(x -&gt; x); </pre>
  </div>
  <p class="bekezd">A lusta kiértékelésnek köszönhetően akár végtelen stream-eket is összefűzhetünk, csak itt is arra kell figyelni, hogy a létrejövő pipeline-ban valahol legyen egy
    rövidzár művelet.</p>
  <h3>I/O stream-ek és más nyalánkságok</h3>
  <p class="bekezd">
    A tipikus I/O műveletek, mint például szöveges állomány soronkénti olvasása vagy írása is jó pályázó a stream feldolgozás kihasználására. (Ne keverjük össze a Java 8 új stream fogalmát
    a már korábban létező stream fogalommal, pl. <span class="programkod">InputStream</span>, <span class="programkod">OutputStream</span>! A kettőnek nincs köze egymáshoz.) A Java 8-ban a
    fájlfeldolgozás kiegészült stream-támogatással és innentől kezdve bármely stream-bűvészkedésünket fájlokkal is megcsinálhatjuk.
  </p>
  <p class="bekezd">
    De még mielőtt bűvészkednénk! Talán eddig még fel se tűnt (nekem legalábbis nem), hogy a <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html"><span class="programkod">java.util.stream.Stream</span></a> őse, a <a
      onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html"><span class="programkod">java.util.stream.BaseStream</span></a>
    interfész az <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html"><span class="programkod">AutoCloseable</span></a>
    leszármazottja. Ez már a Java 7-ből is ismerős fogalom, a try-with-resource vezérlési szerkezettel jelent meg. A <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/7/docs/api/java/lang/AutoCloseable.html">Java 7 API</a> ezt írja róla: <i>&quot;Olyan erőforrás amit be kell zárni miután már nincs rá
      szükség.&quot;</i> Ezt teszi meg automatikusan a try-with-resource. De ha ebből származik a <span class="programkod">Stream</span>, akkor ez csak nem azt akarja jelenteni, hogy eddig az
    összes példánál rosszul használtuk és minden esetben minimum try-with-resource szerkezetbe kellene írni (vagy külön lezárni)? Szerencsére nem ilyen rossz a helyzet. Az <span
      class="programkod">AutoCloseable</span> Java 8-as <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/lang/AutoCloseable.html">dokumentációja</a>
    ugyanis már sokkal megengedőbb: <i>&quot;Egy objektum, ami foglalhat erőforrásokat (mint például fájl vagy socket), amíg be nem zárják.&quot;</i> Tehát nem be <b>kell</b> zárni hanem
    csak <b>foglalhat</b> is akár. A dokumentáció nem sokkal ezt követően egy érdekes megjegyzést is hozzáfűz: <i>&quot;Elképzelhető, sőt ami azt illeti eléggé általános, hogy egy
      ősosztály implementálja az <span class="programkod">AutoCloseable</span> interfészt még akkor is ha nem is minden leszármazottja vagy példánya fog tárolni felszabadítható
      erőforrásokat. Ha olyan kódról van szó aminek teljes általánosságban kell működnie, vagy amikor ismert, hogy az <span class="programkod">AutoCloseable</span> példánynak fel kell
      szabadítania erőforrást, akkor ajánlatos a try-with-resource használata. Amikor viszont olyan dolgot használunk mint a <span class="programkod">java.util.stream.Stream</span>, ami I/O
      alapú és nem I/O alapú műveleteket is támogat, akkor a try-with-resource blokkok a nem-I/O alapú esetekben általában szükségtelenek.&quot;
    </i>
  </p>
  <p class="bekezd">
    Megváltoztatták tehát az <span class="programkod">AutoCloseable</span> specifikációját, mégpedig pont azért, hogy illeszkedjen a streamek használati eseteihez. A döntést egyébként
    kiterjedt viták előzték meg a Java 8 fejlesztése közben. Brian Goetz az OpenJDK levelező listájára küldött egyik <a onclick="window.open(this.href);return false;"
      href="http://mail.openjdk.java.net/pipermail/lambda-libs-spec-experts/2013-August/002195.html">levelében</a> ki is fejtette hogy milyen megoldási kísérleteik voltak és miért ezt
    választották végül. (Régebbi Eclipse verziók még warningot is jeleztek minden egyes Stream használatkor, ezért az újabb Eclipse-ekben módosítani kelett a kódellenőrzőt az új
    specifikációnak megfelelően.)
  </p>
  <p class="bekezd">
    Szóval a specifikáció alapján általánosságban elmondható, hogy az I/O stream-eket expliciten le kell zárni a használat után, a többit meg nem. Persze ez a döntés sem oldott meg mindent.
    Mert - teszem azt - mi van ha olyan metódust írunk ami <span class="programkod">Stream</span> paramétert fogad vagy ami <span class="programkod">Stream</span> típusú értékkel tér
    vissza? Kinek a felelőssége a stream lezárása, ha egyáltalán le kell zárni? Nos erre a Java 8 nem ad általános érvényű választ. Jelezhetjük külön a javadocban, hogy olyan I/O streamet
    adunk vissza amit le kell zárni használat után. Vagy hogy a paraméterként kapott stream lezárása a hívó felelőssége. Ha mi gyártjuk a streamet, akkor lehet akár már a metódus nevében is
    jelezni, hogy mi a helyzet. Lezárandó stream-eket létrehozó metódus legyen például <span class="programkod">openStream()</span>, lezárást nem igénylőt pedig <span class="programkod">createStream()</span>.
    Bárhogy is, a fejlesztő felelőssége, hogy valamilyen konzisztens megoldást eszeljen ki.
  </p>
  <p class="bekezd">
    <b>Lezárás</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">java.util.stream.BaseStream</span> foglalkozik a lezárás definíciójával, a <span class="programkod">java.util.stream.Stream</span> ettől örökli az alábbi két
    metódust:
  </p>
  <p class="bekezd">
    <u>close()</u>: lezárja a stream-et és minden hozzáadott <span class="programkod">closeHandler</span>-t meghív. Általánosságban csak az I/O csatorna forrással rendelkező streameknek van
    szüksége lezárásra (például amit a <span class="programkod">Files.lines(Path path, Charset cs)</span> ad vissza). A legtöbb stream forrása collection, tömb vagy generáló függvény,
    ezeknek nincs szükségük speciális erőforráskezelésre. Ha a streamnek szüksége van lezárásra, akkor azt egy try-with-resource kifejezésben is megadhatjuk. A flatMap esetén láttuk, hogy a
    mapper <span class="programkod">Function</span> különálló stream-eket állít elő, de ezeket a flatMap automatikusan egyenként lezárja miután összefűzte kimeneti streammé.
  </p>
  <p class="bekezd">
    <u>onClose(Runnable closeHandler)</u>: visszaad egy új streamet amihez hozzáadta a paraméter <span class="programkod">closeHandler</span>-t. A <span class="programkod">closeHandler</span>-ek
    akkor futnak le amikor a <span class="programkod">close()</span> meghívódik a streamen és abban a sorrendben, ahogyan a streamhez lettek adva. Ha a sorban valamelyik kivételt dob, a
    többi attól még lefut. Ha több is dob kivételt, azok az első kivételhez lesznek hozzáfűzve. Példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.stream.IntStream;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> CloseTest {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">try</span> (IntStream is = IntStream.range(0, 10)) {
            is.onClose(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Close 1!&quot;</span>))
                .onClose(() -&gt; generateException(<span class="java_string">&quot;Exception 1&quot;</span>))
                .onClose(() -&gt; generateException(<span class="java_string">&quot;Exception 2&quot;</span>))
                .onClose(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Close 2!&quot;</span>))
                .forEach(System.out::println);
        } <span class="java_keyword">catch</span> (TestException ex) {
            ex.printStackTrace();
        }
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> generateException(String num) <span class="java_keyword">throws</span> TestException {
        <span class="java_keyword">throw</span> <span class="java_keyword">new</span> TestException(num);
    }

    <span class="java_keyword">static</span> <span class="java_keyword">class</span> TestException <span class="java_keyword">extends</span> RuntimeException {
        <span class="java_keyword">public</span> TestException(String message) {
            <span class="java_keyword">super</span>(message);
        }
    }
}</pre>
  </div>
  <p class="bekezd">
    A példa mind a négy <span class="programkod">closeHandler</span>-t meghívja, a sikeresek eredménye megjelenik, majd a két összefűzött kivétel is a kimeneten a kivételkezelő <span
      class="programkod">ex.printStackTrace();</span> sorának köszönhetően:
  </p>
  <pre class="programkod">0
1
...
9
Close 1!
Close 2!
hu.egalizer.java8.CloseTest$TestException: Exception 1
  at hu.egalizer.java8.CloseTest.generateException(CloseTest.java:17)
  ...
  Suppressed: hu.egalizer.java8.CloseTest$TestException: Exception 2
    at hu.egalizer.java8.CloseTest.generateException(CloseTest.java:17)
    ...</pre>
  <p class="bekezd">
    <b>Files</b><img alt="dirtree" class="jobb" src="/informatika/essze/java/java8/dirtree.png" />
  </p>
  <p class="bekezd">
    Az I/O streamek kiindulópontja a <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html">java.nio.file.Files</a>
    osztály, ami 4 stream-létrehozó metódust kapott.
  </p>
  <p class="bekezd">
    <u>walk(Path start, int maxDepth, FileVisitOption... options)</u>: visszaad egy <span class="programkod">Path</span> streamet ami mélységi bejárással (depth-first) végiglépeget a <span
      class="programkod">start</span> elérési útjából kiinduló könyvtárakon. A visszaadott streamnek legalább egy eleme mindenképpen lesz: a startként megadott fájl/könyvtár. A stream
    minden egyes bejárt fájlnál megpróbálja beolvasni a hozzá tartozó <span class="programkod">BasicFileAttributes</span> objektumot. Ha egy könyvtárról van szó és sikeresen megnyitható,
    akkor a könyvtár bejegyzései és azok leszármazottai következnek a bejárás során. Amikor minden bejegyzés meg lett vizsgálva, a könyvtár bezáródik. A szimbolikus linkeket a metódus csak
    akkor járja be, ha az <span class="programkod">options</span> paraméter tartalmazza a <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/FileVisitOption.html#FOLLOW_LINKS"><span class="programkod">FileVisitOption.FOLLOW_LINKS</span></a> opciót. Ha a linkek
    során hurkot talál akkor egy <span class="programkod">FileSystemLoopException</span> kivétel dobódik. A <span class="programkod">maxDepth</span> paraméter a bejárandó könyvtárszintek
    maximális mélységét adja meg. 0 azt mutatja, hogy csak a kiindulási fájlt szeretnénk bejárni. Minden szint bejárásához hívjuk meg <span class="programkod">Integer.MAX_VALUE</span>-val
    (a walk-nak létezik egy másik változata is ahol a <span class="programkod">maxDepth</span> paramétert nem kell megadni, automatikusan így hívódik). A jogosultság miatt nem olvasható
    fájlokat a stream átugorja.
  </p>
  <p class="bekezd">A példákhoz a 8-as JDK forrásának könyvtárszerkezetét használtam fel, abból csak a jobb oldali ábrán lévőket meghagyva. A könyvtárszerkezet bejárása:</p>
  <div class="programkod">
    <pre>Path path = Paths.get(<span class="java_string">&quot;F:&quot;</span>, <span class="java_string">&quot;jdk_8_05_src&quot;</span>);
<span class="java_keyword">try</span> (Stream&lt;Path&gt; stream = Files.walk(path)) {
    stream.forEach(System.out::println);
} <span class="java_keyword">catch</span> (IOException | UncheckedIOException ioe) {
    <span class="java_comment">// <span class="java_todo">TODO</span> kivétel feldolgozása</span>
} </pre>
  </div>
  <p class="bekezd">Ha csak a könyvtárakat szeretnénk bejárni a fájlok nélkül, akkor:</p>
  <div class="programkod">
    <pre>stream.filter(path1 -&gt; path1.toFile().isDirectory()).forEach(System.out::println); </pre>
  </div>
  <p class="kozep">
    <u>find(Path start, int maxDepth, BiPredicate&lt;Path,BasicFileAttributes&gt; matcher, FileVisitOption... options)</u>: visszaad egy streamet ami a start paraméter által megadott fájl
    könyvtárában kezd el keresni és onnan kiindulva bejárja a teljes könyvtárszerkezetet a kívánt fájlok után kutatva. A metódus pont úgy járja be a könyvtárszerkezetet mint a walk. Minden
    megtalált fájlra lefut a megadott <span class="programkod">BiPredicate</span> matcher annak a <span class="programkod">Path</span> és <span class="programkod">BasicFileAttributes</span>
    objektumával. A <span class="programkod">Path</span> objektum az eredmény streamben az abszolút elérési utat tartalmazza és csak akkor szerepel, ha a <span class="programkod">BiPredicate</span>
    true értékkel tér vissza. A működését a walk metódus és a filter közbülső művelettel is lehet reprodukálni, de a find hatékonyabb lehet, mert elkerülhető vele a <span class="programkod">BasicFileAttributes</span>
    redundáns megszerzése.
  </p>
  <p class="bekezd">Az alábbi stream csak azokat a fájlokat dolgozza fel amelyek neve &quot;Exception.java&quot;-ra végződik:</p>
  <div class="programkod">
    <pre>Path path = Paths.get(<span class="java_string">&quot;F:&quot;</span>, <span class="java_string">&quot;jdk_8_05_src&quot;</span>);
<span class="java_keyword">try</span> (Stream&lt;Path&gt; stream = Files.find(path, Integer.MAX_VALUE,
        (actualPath, attributes) -&gt; actualPath.toFile().isFile()
            &amp;&amp; actualPath.toFile().getName().endsWith(<span class="java_string">&quot;Exception.java&quot;</span>))) {
    stream.forEach(System.out::println);
} <span class="java_keyword">catch</span> (IOException | UncheckedIOException ioe) {
    <span class="java_comment">// <span class="java_todo">TODO</span> kivétel feldolgozása</span>
} </pre>
  </div>
  <p class="bekezd">A példa kimenete:</p>
  <pre class="programkod">F:\jdk_8_05_src\java\lang\annotation\AnnotationTypeMismatchException.java
F:\jdk_8_05_src\java\lang\NullPointerException.java</pre>
  <p class="bekezd">
    <u>list(Path dir)</u>: a paraméterként megadott könyvtár elemeinek streamjét adja vissza. A metódus nem rekurzív, tehát csak abban az egy könyvtárban dolgozik (értelemszerűen a
    &quot;.&quot; és &quot;..&quot; könyvtárakat sem dolgozza fel). Ha csak egy könyvtárat szeretnénk feldolgozni akkor a fenti két metódus helyett ezt célszerű használni.
  </p>
  <p class="bekezd">Az alábbi példa a list.txt fájlba kiírja az F:\jdk_8_05_src\java\lang könyvtárban lévő összes fájl nevét:</p>
  <div class="programkod">
    <pre>Path path = Paths.get(<span class="java_string">&quot;F:&quot;</span>, <span class="java_string">&quot;jdk_8_05_src\\java\\lang&quot;</span>);
<span class="java_keyword">try</span> (PrintWriter pw = <span class="java_keyword">new</span> PrintWriter(
        Files.newBufferedWriter(Paths.get(<span class="java_string">&quot;F:&quot;</span>, <span class="java_string">&quot;jdk_8_05_src\\list.txt&quot;</span>)))) {
    Stream&lt;Path&gt; stream = Files.list(path);
    stream.filter(path1 -&gt; path1.toFile().isFile()).forEach(pw::println);
    stream.close();
} <span class="java_keyword">catch</span> (IOException | UncheckedIOException ioe) {
    <span class="java_comment">// <span class="java_todo">TODO</span> kivétel feldolgozása</span>
} </pre>
  </div>
  <p class="bekezd">Mindhárom fenti metódusra vonatkozik, hogy egyik sem foglalja le a könyvtárszerkezetet a bejárás során, tehát visszaadhatják a metódusból való visszatérés (vagyis a
    stream visszaadása) után bekövetkezett fájlrendszer módosításokat is (a JDK ezt gyengén konzisztensnek - weakly consistent - hívja).</p>
  <p class="bekezd">
    <u>lines(Path path, Charset cs)</u>: soronként beolvasssa a megadott fájlt a megadott karakterkészlettel. A beolvasás itt is lusta feldolgozással működik, tehát mindig csak a következő
    sor olvasódik be, nem egyszerre az egész fájl. A metódusnak egyszerűsített <span class="programkod">lines(Path path)</span> változata ugyanezt csinálja, csak dedikáltan UTF-8 kódolást
    használva.
  </p>
  <p class="bekezd">Az alábbi példa kiírja a Long.java tartalmát majd amikor végzett, akkor üzenetet is küld erről:</p>
  <div class="programkod">
    <pre>Path path = Paths.get(<span class="java_string">&quot;F:&quot;</span>, <span class="java_string">&quot;jdk_8_05_src\\java\\lang\\Long.java&quot;</span>);
<span class="java_keyword">try</span> (Stream&lt;String&gt; stream = Files.lines(path)) {
    stream.onClose(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Kész vagyok!&quot;</span>)).forEach(System.out::println);
} <span class="java_keyword">catch</span> (IOException | UncheckedIOException ioe) {
    <span class="java_comment">// <span class="java_todo">TODO</span> kivétel feldolgozása</span>
} </pre>
  </div>
  <p class="bekezd">A lines lusta feldolgozása azért is hasznos, mert ha például egy nagy szöveges fájlnak csak az első 10 sorát szeretnénk beolvasni, akkor nem kell az egész fájlt
    beolvasni előtte, hanem a már ismerős stream műveletekkel egyszerűen és hatékonyan korlátozhatjuk a beolvasást az első 10 sorra.</p>
  <p class="bekezd">
    Minden fenti metódusnál ha <span class="programkod">IOException</span> dobódik a metódus által visszaadott csővezeték feldolgozása közben akkor az egy <a
      onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/io/UncheckedIOException.html"><span class="programkod">UncheckedIOException</span></a>
    kivételbe burkolva dobódik tovább a használó felé (ennek okáról később). Ha pedig a fájlrendszer erőforrásainak felszabadítása szükséges, akkor minden esetben a close meghívása vagy a
    try-with-resources szerkezet használata ajánlatos.
  </p>
  <p class="bekezd">
    <b>JarFile, ZipFile</b>
  </p>
  <p class="bekezd">
    A <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/jar/JarFile.html"><span class="programkod">java.util.jar.JarFile</span></a>
    jar fájlok feldolgozására szolgáló osztály is kapott egy <span class="programkod">stream()</span> metódust ami a jar fájl bejegyzéseit adja vissza:
  </p>
  <div class="programkod">
    <pre>JarFile jarFile = <span class="java_keyword">new</span> JarFile(<span class="java_keyword">new</span> File(<span class="java_string">&quot;C:\\Program Files\\Java\\jre1.8.0_162\\lib\\rt.jar&quot;</span>));
jarFile.stream().forEach((entry) -&gt; System.<span class="java_constant">out</span>.println(entry.getName()));
jarFile.close(); </pre>
  </div>
  <p class="bekezd">
    Majdnem pont ugyanezt tudja a <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipFile.html"><span class="programkod">java.util.zip.ZipFile</span></a>
    csak dedikáltan tömörített ZIP fájlokra.
  </p>
  <h3>Redukció</h3>
  <p class="bekezd">
    Az eddigi példákban szinte minden közbülső műveletet megismertünk, a stream-ek eredménye viszont majdnem mindig a konzolra került. A stream-ek így még nem lennének túl használhatók, de
    szerencsére nem csak forEach lezáró műveletből áll a világ. A lezáró műveletek nagyfejezetéhez bevezetésül egy példa: az alábbi kifejezés kiszámolja az összes, <span class="programkod">mySets</span>
    collection-ben tárolt 2000 előtt kiadott legókészlet átlagos elemszámát egy filter, mapToInt közbülső és average aggregáló műveletet tartalmazó pipeline-al:
  </p>
  <div class="programkod">
    <pre>mySets.stream()
    .filter(set -&gt; set.getYearReleased() &lt; 2000)
    .mapToInt(LegoSet::getPieceCount)
    .average()
    .getAsDouble(); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">filter</span> visszaad egy stream-et ami az összes 2000 előtti legókészletet tartalmazza a <span class="programkod">mySets</span> collection-ből. A <span
      class="programkod">mapToInt</span> műveletet már ismerjük, ez visszaad egy új <span class="programkod">IntSteam</span>-et. Ez a metódus a bemenő stream minden egyes elemére alkalmazza
    a paraméterként átadott, int-et előállító műveletet. A példában ez a <span class="programkod">LegoSet::getPieceCount</span> metódusreferencia, ami visszaadja az adott legókészlet
    elemszámát. (Persze ehelyett akár az <span class="programkod">e -&gt; e.getPieceCount()</span> lambda kifejezést is használhatnánk.) Az average művelet kiszámolja az <span
      class="programkod">IntStream</span> típusú stream-ben lévő elemek átlagértékét és egy <span class="programkod">OptionalDouble</span>-t ad vissza. Az <span class="programkod">Optional</span>
    fejezetben megismerteknek megfelelően amennyiben a stream-ben nincsenek elemek, akkor az average művelet egy <span class="programkod">&quot;empty&quot; OptionalDouble</span>-lel tér
    vissza (ilyenkor a <span class="programkod">getAsDouble</span> meghívása <span class="programkod">NoSuchElementException</span>-t dob). Az API sok ilyen lezáró műveletet tartalmaz, ami
    a stream elemeiből kiszámít egy értéket és visszaadja.
  </p>
  <p class="bekezd">
    Azokat a lezáró műveleteket, amelyek valamilyen értéket vagy collection-t adnak vissza (average, sum, min, max, count) <b>redukciós műveleteknek (reduction operation)</b> nevezzük. Ezek
    valamilyen egyszerű műveletet valósítanak meg, ami már a nevükből is kikövetkeztethető:
  </p>
  <ul>
    <li><b>IntStream.average(), LongStream.average(), DoubleStream.average()</b>: a stream elemeinek átlaga</li>
    <li><b>IntStream.sum(), LongStream.sum(), DoubleStream.sum()</b>: a stream elemeinek összege</li>
    <li><b>IntStream.max(), LongStream.max(), DoubleStream.max()</b>: a stream elemei közül a legnagyobb</li>
    <li><b>IntStream.min(), LongStream.min(), DoubleStream.min()</b>: a stream elemei közül a legkisebb</li>
    <li><b>Stream.max(Comparator&lt;? super T&gt; comparator)</b>: a stream elemei közül a legnagyobb az adott <span class="programkod">comparator</span> szerint</li>
    <li><b>Stream.min(Comparator&lt;? super T&gt; comparator)</b>: a stream elemei közül a legkisebb az adott <span class="programkod">comparator</span> szerint</li>
    <li><b>Stream.count()</b>: a stream elemeinek a száma (ekvivalens a <span class="programkod">mapToLong(e -&gt; 1L).sum();</span> művelettel)</li>
  </ul>
  <p class="bekezd">További egyszerű redukciós műveletek:</p>
  <p class="bekezd">
    <u>findAny()</u>: egy <span class="programkod">Optional</span> objektumot ad vissza ami a stream valamely elemét tartalmazza vagy üres ha a stream is. A művelet viselkedése
    nemdeterminisztikus; mind soros mind párhuzamos stream bármely elemét visszaadhatja. Ez egy rövidzár művelet tehát végtelen stream esetén is véges idő alatt végez a csővezeték
    feldolgozásával. A következő példa visszaadja a legóadatbázisunk bármely 2000 elemnél nagyobb készletét, ha van olyan:
  </p>
  <div class="programkod">
    <pre>Optional&lt;LegoSet&gt; result = mySets.stream().filter(e -&gt; e.getPieceCount() &gt; 2000).findAny();
result.ifPresent(System.out::println); </pre>
  </div>
  <p class="bekezd">
    <u>findFirst()</u>: egy <span class="programkod">Optional</span> objektumot ad vissza, ami a stream első elemét tartalmazza vagy üres ha a stream is. Ha a streamnek nincs encounter
    orderje akkor bármely elemét visszaadhatja. Rövidzár művelet.
  </p>
  <p class="bekezd">
    <u>anyMatch(Predicate&lt;? super T&gt; predicate)</u>: megmondja, hogy a stream valamely eleme megfelel-e a megadott predikátumnak (üres stream esetén false a visszatérési érték). Nem
    elemzi végig a stream összes elemét ha nem szükséges az eredmény megállapításához, éppen ezért ez is rövidzár művelet.
  </p>
  <p class="bekezd">
    <u>allMatch(Predicate&lt;? super T&gt; predicate)</u>: megmondja, hogy a stream összes eleme megfelel-e a megadott predikátumnak (üres stream esetén <span class="programkod">true</span>).
    Nem feltétlenül vizsgálja meg a stream összes elemét, ha nem szükséges az eredmény meghatározásához. Rövidzár művelet.
  </p>
  <p class="bekezd">
    <u>noneMatch(Predicate&lt;? super T&gt; predicate)</u>: true, ha a stream egyetlen eleme sem felel-e meg a megadott predikátumnak (üres stream esetén <span class="programkod">true</span>).
    Nem feltétlenül vizsgálja végig a stream összes elemét, ha nem szükséges az eredmény meghatározásához. Rövidzár művelet.
  </p>
  <p class="bekezd">Ez már mind nagyon szép és jó, de még nincs vége az izgalmaknak! Léteznek általános célú redukciós műveletek is: a reduce és a collect.</p>
  <p class="bekezd">
    <b>Stream.reduce</b>
  </p>
  <p class="bekezd">A következő pipeline a Stream.sum redukciós művelettel kiszámolja az összes készlet elemszámának összegét:</p>
  <div class="programkod">
    <pre>mySets.stream()
    .mapToInt(LegoSet::getPieceCount)
    .sum() </pre>
  </div>
  <p class="bekezd">Ez viszont a Stream.reduce redukciós műveletet használja ugyanerre:</p>
  <div class="programkod">
    <pre>mySets.stream()
    .map(LegoSet::getPieceCount)
    .reduce(0, (a, b) -&gt; a + b) </pre>
  </div>
  <p class="bekezd">A fenti példában a redukciós művelet két paramétert vár:</p>
  <pre class="programkod">T reduce(T identity, BinaryOperator&lt;T&gt; accumulator)</pre>
  <ul>
    <li><b>identity</b>: az identity paraméter egyrészt a redukció kezdeti értéke másrészt az alapértelmezett visszatérési érték, ha nincsenek elemek a stream-ben. A példában az
      identity elem 0; ez a kezdeti elem az elemszámok összegéhez és egyben az alapértelmezett visszatérési érték ha nincsenek elemek a <span class="programkod">mySets</span>
      collection-ben.</li>
    <li><b>accumulator</b>: az accumulator két paramétert vár: a redukció részeredményét (ebben a példában az eddig feldolgozott <span class="programkod">int</span> értékek összege) és
      a stream következő elemét (ebben a példában egy <span class="programkod">int</span>). Visszatérési értéke pedig egy új részeredmény. Az accumulatornak asszociatívnak, interferencia-
      és állapotmentesnek kell lennie. A fenti példában az akkumulátor függvény egy lambda kifejezés, ami két <span class="programkod">int</span> értéket ad össze és visszaad egy új <span
      class="programkod">int</span>-et.</li>
  </ul>
  <p class="bekezd">A reduce-nak létezik három paraméteres változata is:</p>
  <pre class="programkod">&lt;U&gt; U reduce(U identity, BiFunction&lt;U,? super T,U&gt; accumulator, BinaryOperator&lt;U&gt; combiner)</pre>
  <p class="bekezd">Ez egy combiner paraméterrel egészült ki, ami két részeredményből állít elő egy újat. Ez különösen párhuzamos redukció esetén fontos. Ott ugyanis a forrást a
    keretrendszer particionálja, minden partícióra elvégződik egy részleges akkumuláció majd a részeredményeket végeredménnyé kell egyesíteni. Ezt végzi a combiner. A combinernek
    asszociatívnak, interferencia- és állapotmentesnek kell lennie. Az identity elem a combiner számára mindig kezdeti értéket kell jelentsen, vagyis minden u-ra:</p>
  <p class="bekezd">combiner(identity, u)=u</p>
  <p class="bekezd">Persze a combiner függvénynek kompatibilisnek kell lenni az accumulatorral is vagyis minden u-ra és t-re:</p>
  <p class="bekezd">combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)</p>
  <p class="bekezd">A három paraméteres általános reduce művelettel a készletek elemszám-összegző műveletét általánosabb formában így is írhatjuk:</p>
  <div class="programkod">
    <pre>mySets.stream().reduce(0, (sum, b) -&gt; sum + b.getPieceCount(), Integer::sum) </pre>
  </div>
  <p class="bekezd">Ebben a formában a map nem szükséges, bár a map-et használó forma általában jobban olvasható, ezért azt ajánlatos használni, kivéve ha a map és a reduce művelet
    összefűzésével jelentős optimalizáció érhető el.</p>
  <p class="bekezd">A reduce művelet végeredményként mindig egy új értéket ad vissza. Ugyanezt teszi az accumulator függvény is: minden egyes alkalommal, amikor a stream egy elemét
    feldolgozza, új értéket ad vissza. Tegyük fel, hogy redukálni szeretnénk egy stream elemeit egy összetettebb objektummá, mint például egy collection. Ha a reduce műveletünk egy
    collection-höz való hozzáadást végez, akkor minden egyes alkalommal amikor az accumulator függvény feldolgoz egy elemet, egy új collection-t hozna létre ami nem valami hatékony. Sokkal
    hatékonyabb lenne egy már létező collection-t bővíteni. Erre való a Stream.collect metódus.</p>
  <div class="keretes">
    <h3>Asszociativitás</h3>
    <p>
      Matematikából bizonyára ismerős fogalom. Egy <i>op</i> operátor vagy függvény asszociatív ha igaz rá a következő állítás:
    </p>
    <p>
      (a <i>op</i> b) <i>op</i> c == a <i>op</i> (b <i>op</i> c)
    </p>
    <p>Ennek a jelentősége különösen párhuzamos végrehajtásnál jelentkezik, amit akkor láthatunk, ha a fenti állítást négy operandusra kiterjesztjük:</p>
    <p>
      a <i>op</i> b <i>op</i> c <i>op</i> d == (a <i>op</i> b) <i>op</i> (c <i>op</i> d)
    </p>
    <p>
      Így az (a <i>op</i> b) és (c <i>op</i> d) kifejezést párhuzamosan is kiértékelhetjük, aztán az eredményeken újra meghívjuk az <i>op</i> operátort. Asszociatív operátor például a
      numerikus összeadás, min, max és a sztring összefűzés.
    </p>
    <h3>Interferencia-mentesség (non-interference)</h3>
    <p>
      A stream-ek lehetővé teszik párhuzamos végrehajtású aggregáló műveletek futtatását még olyan nem szálbiztos collection-ök esetén is mint az <span class="programkod">ArrayList</span>.
      De ez csak úgy lehetséges, ha a csővezeték futtatása során meg tudjuk gátolni az interferenciát, vagyis azt, hogy a szálak zavarják egymást az adatforráson keresztül. Az <span
        class="programkod">iterator()</span> és <span class="programkod">spliterator()</span> műveletek kivételével a végrehajtás akkor kezdődik amikor a lezáró műveletet meghívják és akkor
      fejeződik be amikor a lezáró művelet befejeződik. A legtöbb adatforrásnál az interferencia meggátolása azt jelenti, hogy biztosítani kell, hogy az adatforrás nem változik a stream
      csővezeték végrehajtása során. Egyetlen fontos kivétel az olyan stream-ek amelyek forrásai a konkurens collection-ök, mert ezeket kifejezetten arra tervezték, hogy a konkurens
      módosítást kezeljék. A konkurens stream forrásoknál az spliterator CONCURRENT karakterisztikát jelez.
    </p>
    <p>
      A következő kód megpróbálja egy redukciós lezáró művelettel összefűzni a <span class="programkod">stringList List</span>-ben lévő sztringeket egy <span class="programkod">Optional&lt;String&gt;</span>
      értékké, de ehelyett <span class="programkod">ConcurrentModificationException</span>-t dob (a <span class="programkod">peek</span> közbülső művelet egy olyan stream-et állít elő, ami
      a forás elemeit tartalmazza úgy, hogy a stream kiértékelése során minden egyes elemre végrehajtja a paraméterként megadott függvényt):
    </p>
    <div class="programkod">
      <pre>List&lt;String&gt; stringList = <span class="java_keyword">new</span> ArrayList&lt;&gt;(Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span
          class="java_string">&quot;kettő&quot;</span>));
String concatenated = stringList.stream()
    .peek(s -&gt; stringList.add(<span class="java_string">&quot;három&quot;</span>))
    .reduce((a, b) -&gt; a + <span class="java_string">&quot; &quot;</span> + b)
    .get();
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Összefűzött sztring: &quot;</span> + concatenated); </pre>
    </div>
    <p>
      A csővezeték meghívja a <span class="programkod">peek</span> közbülső műveletet, ami megpróbál új elemet hozzáadni a <span class="programkod">stringList</span>-hez. De mi már tudjuk,
      hogy a <span class="programkod">peek</span> - mint minden közbülső művelet - lusta kiértékelésű. A példában lévő csővezeték akkor kezdi a végrehajtást, amikor a <span
        class="programkod">get</span> művelet meghívódik és akkor fejezi be amikor a <span class="programkod">get</span> végetér. A <span class="programkod">peek</span> paramétere
      megpróbálja módosítani a stream forrását végrehajtás közben, így a futtatókörnyezet <span class="programkod">ConcurrentModificationException</span>-t dob. Olyan viselkedési
      paraméterek tehát, amelyek forrása feltehetően nem konkurens, stream csővezetékben soha ne módosítsák a stream adatforrását! Egy viselkedési paraméter akkor okoz zavart nemkonkurens
      stream-forrásban, ha közvetlenül vagy közvetetten módosítja azt. Az interferencia-mentesség követelménye nem csak a párhuzamos, hanem minden csővezetékre igaz. Hacsak nem konkurens a
      stream forrása, akkor annak a csővezeték végrehajtása közbeni módosítása kivételeket, helytelen eredményt vagy nem megfelelő működést okozhat.
    </p>
    <p>Megfelelő tervezésnél a forrás még a lezáró művelet megkezdése előtt módosul és ezek a módosítások meg is jelennek a feldolgozandó elemekben. Tekintsük a következő kódot:</p>
    <div class="programkod">
      <pre>List&lt;String&gt; list = <span class="java_keyword">new</span> ArrayList(Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>));
Stream&lt;String&gt; str = list.stream();
list.add(<span class="java_string">&quot;három&quot;</span>);
String result = str.collect(Collectors.joining(<span class="java_string">&quot;, &quot;</span>)); </pre>
    </div>
    <p>Először létrejön egy két sztringből álló lista, amiből aztán egy stream képződik, majd a listához hozzáadódik egy harmadik sztring. Ezután a stream elemeire meghívódik a collect
      lezáró művelet. Mivel a lista még a collect művelet előtt módosult, az eredmény nem egy kivétel, hanem &quot;egy, kettő, három&quot; lesz.</p>
    <h3>Állapotmentes (stateless) viselkedés</h3>
    <p>
      A stream csővezetékek eredménye nemdeterminisztikus vagy hibás lehet, ha a stream műveleteknek megadott viselkedési paraméterek állapottárolók. Egy állapottároló lambda (vagy a
      megfelelő funkcionális interfészt implementáló objektum) eredménye függ bármely belső állapottól ami megváltozhat a stream csővezeték végrehajtása során. Egy állapottároló lambda a <span
        class="programkod">map()</span> paramétereként:
    </p>
    <div class="programkod">
      <pre>Set&lt;Integer&gt; seen = Collections.synchronizedSet(<span class="java_keyword">new</span> HashSet&lt;&gt;());
stream.parallel().map(e -&gt; {
  <span class="java_keyword">if</span> (seen.add(e))
    <span class="java_keyword">return</span> 0;
  <span class="java_keyword">else</span>
    <span class="java_keyword">return</span> e;
}); </pre>
    </div>
    <p>
      Ha itt a <span class="programkod">map</span> művelet párhuzamosan fut, akkor az eredmény futásról futásra változhat ugyanarra a bemenetre szálütemezési eltérések miatt. Állapotmentes
      lambda kifejezéssel az eredmény mindig ugyanaz marad. Biztonság és teljesítmény szempontjából elég rossz módszer viselkedési paraméterekből valamely módosítható belső állapot elérése.
      Ha az adott állatpottároló mezőhöz nem szinkronizáljuk az elérést, akkor hibás működést kaphatunk, ha viszont szinkronizáljuk, az rossz hatással lehet a teljesítményre. A legjobb, ha
      stream műveleteknél teljesen elkerüljük az állapottároló viselkedési paramétereket.
    </p>
  </div>
  <p class="bekezd">
    <b>Stream.collect</b>
  </p>
  <p class="bekezd">
    A reduce metódustól eltérően, ami egy elem feldolgozásakor mindig új értéket hoz létre, a collect egy már létező változót módosít, ezért is hívják <b>mutable reduction</b>-nek. Nézzük
    például, hogy tudjuk egy stream-ben értékek átlagát meghatározni! Két adatra van szükség: az értékek összegére és az értékek számára. De akárcsak a reduce és az összes többi redukciós
    metódus, a collect is csak egy értéket ad vissza. Segítségként létre lehet hozni egy új adattípust ami tartalmaz tagváltozókat az értékek számához és összegéhez. Ilyen például a
    következő <span class="programkod">AverageCollector</span> osztály:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_keyword">import</span> java.util.function.IntConsumer;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> AverageCollector <span class="java_keyword">implements</span> IntConsumer {
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> total = 0;
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> count = 0;

    <span class="java_keyword">public</span> <span class="java_keyword">double</span> average() {
        <span class="java_keyword">return</span> count &gt; 0 ? ((<span class="java_keyword">double</span>) total) / count : 0;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> accept(<span class="java_keyword">int</span> i) {
        total += i;
        count++;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> combine(AverageCollector other) {
        total += other.total;
        count += other.count;
    }
} </pre>
  </div>
  <p class="bekezd">Az alábbi pipeline ezt az osztályt használva a collect metódussal kiszámolja az összes 2000 előtt kiadott készlet átlagát:</p>
  <div class="programkod">
    <pre>AverageCollector averageCollect = mySets.stream()
    .filter(p -&gt; p.getYearReleased() &lt; 2000)
    .map(LegoSet::getPieceCount)
    .collect(AverageCollector::<span class="java_keyword">new</span>, AverageCollector::accept, AverageCollector::combine);
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;2000 előtt kiadott készletek átlagos elemszáma: &quot;</span> + 
    averageCollect.average()); </pre>
  </div>
  <p class="bekezd">A collect három paraméteres változata:</p>
  <pre class="programkod">&lt;R&gt; R collect(Supplier&lt;R&gt; supplier, BiConsumer&lt;R, ? super T&gt; accumulator,
    BiConsumer&lt;R, R&gt; combiner);</pre>
  <ul>
    <li><b>supplier</b>: gyártófüggvény, ami a collect művelethez az eredmény konténerek példányait hozza létre. A fenti példában ez az <span class="programkod">AverageCollector</span>
      osztály új példánya. A supplier egy lambda kifejezés (vagy egy metódusreferencia), nem pedig egy érték, mint az identity elem a reduce műveletben.</li>
    <li><b>accumulator</b>: az accumulator függvény a stream egy elemét dolgozza fel az eredmény konténerbe. A példában módosítja az <span class="programkod">AverageCollector</span>
      eredmény konténert azzal, hogy eggyel növeli a <span class="programkod">count</span> mezőt és hozzáadja a <span class="programkod">total</span> mezőhöz a stream elem értékét, ami egy
      <span class="programkod">int</span> és a készlet elemszámát tartalmazza. Nincs visszatérési értéke, valamint asszociatívnak, interferencia- és állapotmentesnek kell lennie.</li>
    <li><b>combiner</b>: a combiner függvény összefűzi két eredmény konténer tartalmát. A példában módosítja az <span class="programkod">AverageCollector</span> eredmény konténert úgy,
      hogy hozzáadja a <span class="programkod">count</span> mezőhöz az <span class="programkod">AverageCollector</span> példány <span class="programkod">count</span> mezőjét és hozzáadja a
      <span class="programkod">total</span> mezőhöz a másik <span class="programkod">AverageCollector</span> példány <span class="programkod">total</span> mezőjének az értékét. Nincs
      visszatérési értéke, valamint asszociatívnak, interferencia- és állapotmentesnek kell lennie.</li>
  </ul>
  <p class="bekezd">
    Akárcsak a reduce, a collect metódus absztrakt szerkezete is előnyös a párhuzamosítás során: párhuzamosan össze lehet gyűjteni a részeredményeket, aztán egyesíteni lehet ezeket,
    legalábbis ha a accumulator és combiner függvények megfelelnek a követelményeknek. Egy stream forrás elemeinek sztring reprezentációit így is összegyűjthetjük egy <span
      class="programkod">ArrayList</span>-be:
  </p>
  <div class="programkod">
    <pre>List&lt;String&gt; strings = new ArrayList&lt;&gt;();
<span class="java_keyword">for</span> (T element : sourceCollection) {
    strings.add(element.toString());
} </pre>
  </div>
  <p class="bekezd">De használhatjuk a párhuzamosítható collect metódust is:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; strings = sourceCollection.stream()
    .collect(() -&gt; <span class="java_keyword">new</span> ArrayList&lt;&gt;(), (c, e) -&gt; c.add(e.toString()), (c1, c2) -&gt; c1.addAll(c2)); </pre>
  </div>
  <p class="bekezd">De ha még menőbbek akarunk lenni, kivehetjük az accumulator függvényből a leképező műveletet és sokkal tömörebben így is kifejezhetjük:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; strings = sourceCollection.stream()
    .map(Object::toString)
    .collect(ArrayList::<span class="java_keyword">new</span>, ArrayList::add, ArrayList::addAll); </pre>
  </div>
  <p class="bekezd">
    Itt a supplier egyszerűen az <span class="programkod">ArrayList</span> konstruktora, az akkumulátor hozzáadja a sztringesített elemet az <span class="programkod">ArrayList</span>-hez, a
    combiner pedig egyszerűen az addAll-t használja, hogy átmásoljon sztringeket egyik konténerből a másikba.
  </p>
  <p class="bekezd">
    Vegyük észre, hogy a collect három függvénye szorosan kapcsolódik egymáshoz. Ezt a Java tervezői is észrevették, ezért a három aspektus egységbe zárására kitalálták a Collector
    absztrakciót. A collect-ből pedig írtak egy másik változatot is, ami egyetlen <span class="programkod">Collector</span> paramétert vár:
  </p>
  <pre class="programkod">&lt;R,A&gt; R collect(Collector&lt;? super T,A,R&gt; collector)</pre>
  <p class="bekezd">
    Egy <span class="programkod">Collector</span> lehetővé teszi, hogy újrahasznosítsunk collection stratégiákat és hogy összeállítsunk olyan collect műveleteket, mint többszintű
    csoportosítás vagy particionálás. A JDK sok előre megírt hasznos Collector-t tartalmaz a <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html">java.util.stream.Collectors</a> osztályban, érdemes ezeket áttanulmányozni, hátha pont olyan is van
    bennük amire épp szükségünk van.
  </p>
  <p class="bekezd">Egy Collector példányra referenciát a következőképp tárolhatunk:</p>
  <div class="programkod">
    <pre>Collector&lt;LegoSet, ?, Integer&gt; totalPieceCount = Collectors.summingInt(LegoSet::getPieceCount); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">?</span> a második típusparaméter helyén azt jelenti, hogy nem érdekel minket a collector által használt közbülső reprezentáció. Néhány <span
      class="programkod">Collectors</span>-t használó példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// A gyűjteményben lévő készletek neveinek kigyűjtése listába</span> 
List&lt;String&gt; setNames = mySets.stream().map(LegoSet::getName).collect(Collectors.toList());
<span class="java_comment">// A gyűjteményben lévő készletek neveiből egy sztring összeállítása vesszővel elválasztva</span>
String allSetNames = mySets.stream().map(LegoSet::getName).collect(Collectors.joining(<span class="java_string">&quot;, &quot;</span>));
<span class="java_comment">// A gyűjtemény összes elemszámának meghatározása</span>
<span class="java_keyword">int</span> total = mySets.stream().collect(Collectors.summingInt(LegoSet::getPieceCount)); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">groupingBy</span> művelet visszaad egy map-et, aminek a kulcsai a metódus paramétereként átadott kifejezés kiértékeléséből származó értékek lesznek (ezt
    osztályozási függvénynek - classification function - hívják). A legónyilvántartó programunk tartalmaz egy <span class="programkod">Theme</span> enumot, ami azt tartalmazza, a készletek
    milyen témakörökhöz tartozhatnak:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_keyword">public</span> <span class="java_keyword">enum</span> Theme {
    <span class="java_constant">CASTLE</span>, <span class="java_constant">TOWN</span>, <span class="java_constant">TECHNIC</span>, <span class="java_constant">STARWARS</span>, <span
        class="java_constant">PIRATES</span>, <span class="java_constant">ADVANCED_MODELS</span>, 
} </pre>
  </div>
  <p class="bekezd">Az alábbi példában a visszaadott map az enum 7 értékét tartalmazza és ezt ki is írja:</p>
  <div class="programkod">
    <pre>Map&lt;Theme, List&lt;LegoSet&gt;&gt; byTheme = mySets.stream()
    .collect(Collectors.groupingBy(LegoSet::getTheme));
byTheme.keySet().forEach(System.out::println); </pre>
  </div>
  <p class="bekezd">A kód eredménye:</p>
  <pre class="programkod">TOWN
ADVANCED_MODELS
IDEAS
STARWARS
CASTLE</pre>
  <p class="bekezd">
    A kulcshoz tartozó értékek <span class="programkod">List</span> példányok, amelyek a stream azon elemeit tartalmazzák, amelyek az osztályozási függvény végrehajtása során megfeleltek az
    adott kulcs értéknek. További, csoportosítást használó példák:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// A gyűjtemény csoportosítása téma szerint</span>
Map&lt;Theme, List&lt;LegoSet&gt;&gt; byTheme = mySets.stream()
    .collect(Collectors.groupingBy(LegoSet::getTheme));
    
<span class="java_comment">// Téma szerinti összes elemszám meghatározása</span>
Map&lt;Theme, Integer&gt; sumByTheme = mySets.stream()
    .collect(Collectors.groupingBy(LegoSet::getTheme,
        Collectors.summingInt(LegoSet::getPieceCount)));
    
<span class="java_comment">// Particionális: a 2000 elemnél nagyobb és 2000 elemes vagy annál kisebb elemszámú készletek 
// szétválasztása. Az eredmény Map kulcsa a feltétel teljesülése vagy nem teljesülése.</span>
Map&lt;Boolean, List&lt;LegoSet&gt;&gt; partitionBy2000Pieces = mySets
    .stream().collect(Collectors.partitioningBy(set -&gt; set.getPieceCount() &gt; 2000)); </pre>
  </div>
  <p class="bekezd">A következő példa a gyűjtemény minden elemének nevét kigyűjti és téma szerint csoportosítja:</p>
  <div class="programkod">
    <pre>Map&lt;Theme, List&lt;String&gt;&gt; namesByTheme = mySets.stream()
    .collect(Collectors.groupingBy(LegoSet::getTheme,
        Collectors.mapping(LegoSet::getName, Collectors.toList()))); </pre>
  </div>
  <p class="bekezd">
    Ez a <span class="programkod">groupingBy</span> művelet két paramétert vár: egy osztályozási függvényt és egy Collector példányt.
  </p>
  <div class="programkod">
    <pre>
static &lt;T,K,A,D&gt; Collector&lt;T,?,Map&lt;K,D&gt;&gt;  groupingBy(Function&lt;? super T,? extends K&gt; classifier, Collector&lt;? super T,A,D&gt; downstream)</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">Collector</span> paramétert <b>downstream collector</b>-nak hívják. Ez egy olyan collector amit a JVM egy másik collector eredményére alkalmaz. Ennek
    eredményeképp a <span class="programkod">groupingBy</span> művelet lehetővé teszi, hogy egy collect metódust alkalmazzunk a <span class="programkod">groupingBy</span> operátor
    eredményeként előállt <span class="programkod">List</span> értékeire. Ez a példa a mapping collector-t használja, ami a <span class="programkod">LegoSet::getName</span> mapping
    függvényt alkalmazza a stream minden egyes elemére. Ennek megfelelően az eredmény stream csak a tagok nevét tartalmazza. Egy olyan csővezetéket ami egy vagy több downstream collector-t
    használ, mint a példában is, <b>multilevel reduction</b>-nek hívnak.
  </p>
  <p class="bekezd">
    A következő példa minden egyes téma tagjainak összesített elemszámát adja vissza a fentebbi, <span class="programkod">Collectors.summingInt</span> megoldástól eltérő módszert használva:
  </p>
  <div class="programkod">
    <pre>
Map&lt;Theme, Integer&gt; sumByTheme2 = mySets.stream()
    .collect(Collectors.groupingBy(LegoSet::getTheme,
        Collectors.reducing(0, LegoSet::getPieceCount, Integer::sum))); </pre>
  </div>
  <p class="bekezd">A reducing művelet három paramétert vár:</p>
  <div class="programkod">
    <pre>static &lt;T,U&gt; Collector&lt;T,?,U&gt; reducing(U identity, Function&lt;? super T,? extends U&gt; mapper, BinaryOperator&lt;U&gt; op)</pre>
  </div>
  <ul>
    <li><b>identity</b>: akárcsak a Stream.reduce műveletnél, az identity elem egyrészt a redukció kezdeti értéke és az alapértelmezett visszatérési érték ha nincsenek elemek a
      stream-ben. A fenti példában az identity elem 0.</li>
    <li><b>mapper</b>: a reducing művelet a mapper függvényt alkalmazza a stream mindegyik elemére. A példában a mapper a tagok elemszámát veszi ki.</li>
    <li><b>operation</b>: az operation függvény a map által leképezett értékek redukciójára használatos. A példában az operation függvény Integer értékeket ad össze.</li>
  </ul>
  <p class="bekezd">Láthatjuk, hogy bizonyos feladatokat a collect és a reduction metódusokkal egyaránt meg lehet oldani, de az egyes megoldások nem ekvivalensek teljesítmény
    tekintetében. Egy sztring-stream elemeit egyetlen hosszú sztringgé a reduce művelettel is össze tudjuk fűzni:</p>
  <div class="programkod">
    <pre>String concatenated = strings.reduce(&quot;&quot;, String::concat) </pre>
  </div>
  <p class="bekezd">
    Ez is a kívánt eredményt adja, sőt még párhuzamosan is futtatható, de a teljesítményében nem fog sok örömünk telni! Ez az implementáció rengeteg sztringmásolást csinál, sokkal
    hatékonyabb a fentebb ismertetett, <span class="programkod">Collectors.joining()</span>-et használó megoldás.
  </p>
  <p class="bekezd">
    A <span class="programkod">Collectors.toMap</span> segítségével a collection elemeit Map-pé képezhetjük le. Ennek van egyébként egy furcsasága. Két <span class="programkod">Function</span>
    funkcionális interfészt vár, az első a kulcs, a második az érték mapper-e. Ez önmagában még nem furcsaság, azonban ha az eredeti collection-ünk olyan értékeket tartalmaz, amelyekből a
    kulcs mapper kétszer képezi le ugyanazt, a collect metódus <span class="programkod">IllegalStateException</span>-t fog dobni. Nyilvánvaló, hogy az eredmény map kétszer nem
    tartalmazhatja ugyanazt a kulcsot. A kivétel azonban az üzenetben nem a kulcsot, hanem az értéket fogja megjeleníteni, bár a szövege a kulcsra hivatkozik. Az alábbi példában a legó
    adatbázisunkhoz hozzáadunk egy készletet még egyszer (legalábbis ami a nevét és a témáját illeti) majd map-pé szeretnénk leképezni, ahol a kulcs a készlet neve, az érték pedig a témája:
  </p>
  <div class="programkod">
    <pre>LegoSet set = <span class="java_keyword">new</span> LegoSet();
set.setName(mySets.get(0).getName());<span class="java_comment">// a neve ugyanaz lesz mint a nulladik készletünké</span>
set.setTheme(Theme.<span class="java_constant">ADVANCED_MODELS</span>);<span class="java_comment">// a témája lényegtelen, ez lesz az érték. A többi adata is lényegtelen a példa szempontjából.</span>
database.mySets.add(set);

<span class="java_comment">// Map-et képezünk, ahol a kulcs a név, az érték pedig a téma lesz:</span>
Map&lt;String, Theme&gt; eredmeny = mySets.stream().collect(Collectors.toMap(LegoSet::getName, LegoSet::getTheme));</pre>
  </div>
  <p class="bekezd">Ha ezt lefuttatjuk, a következő kivételt kapjuk (azt mondja, hogy duplikálva van a kulcs, ami igaz is, csak aztán nem a kulcsot adja meg hanem az értéket ami
    lényegtelen):</p>
  <pre class="programkod">
Exception in thread "main" java.lang.IllegalStateException: Duplicate key ADVANCED_MODELS</pre>


  <p class="bekezd">
    A <span class="programkod">java.util.LongSummaryStatistics</span> további egyszerű eszköz a collector használatához: számosságot, minimum, maximum és átlag értéket tudunk vele
    egyszerűen számítani. (Bár ezek a funkciók a primitív stream-ekben is benne vannak.)
  </p>
  <div class="programkod">
    <pre>Stream&gt;Long&lt; longStream;
...
LongSummaryStatistics statistics = longStream.collect(LongSummaryStatistics::<span class="java_keyword">new</span>, LongSummaryStatistics::accept, LongSummaryStatistics::combine);</pre>
  </div>
  <p class="bekezd">
    Ezután a <span class="programkod">statistics</span> objektumból már lekérdezhetjük a kívánt eredményt a következő metódusokkal:
  </p>
  <ul>
    <li>getSum()</li>
    <li>getAverage()</li>
    <li>getCount()</li>
    <li>getMax()</li>
    <li>getMin()</li>
  </ul>
  <p class="bekezd">
    A <span class="programkod">LongSummaryStatistics</span> redukciós eredményként is használható. A következő kódsor egy menetben összegzi legókészleteink elemszámát, minimális és
    maximális értékét, öszegét és átlagát.
  </p>
  <div class="programkod">
    <pre>LongSummaryStatistics statistics = mySets.stream().collect(Collectors.summarizingLong(LegoSet::getPieceCount));</pre>
  </div>
  <p class="bekezd">
    Természetesen az osztálynak létezik <span class="programkod">DoubleSummaryStatistics</span> és <span class="programkod">IntSummaryStatistics</span> megfelelője is. (Ahogy a <span
      class="programkod">Collectors</span> osztálnyak summarizingLong és summarizingDouble metódusa.)
  </p>
  <p class="bekezd">
    Ezeket az osztályokat elsősorban stream-ekkel használjuk, de természtesen saját célra is használhatók, hiszen az <span class="programkod">accept(int value)</span> metódusaal saját
    kezűleg is adhatunk neki értékeket. A combine(other) metódussal pedig azonos típusú ...SummaryStatistics objektumot tudunk hozzáfűzni.
  </p>
  <h3>Párhuzamosság</h3>
  <p class="bekezd">A párhuzamos programozás - amint azt bizonyára mindenki tudja - a problémát részekre bontja, az alproblémákat pedig külön szálakon egymással párhuzamosan oldja meg,
    aztán az eredményeket összesíti. A Stream API aggregáló műveleteinél az osztálykönyvtár elvégzi nekünk ezt a részekre bontást (particionálást vagy másnéven felszeletelést), majd az
    aggreagáló műveletek az al-streameket párhuzamosan feldolgozzák és összesítik az eredményeket. A párhuzamosság implementálásánál az egyik nehézség, hogy a collection-ök általában nem
    szálbiztosak, tehát több szál büntetlenül nem módosíthatja egyszerre ugyanazt a collection-t. Az aggregáló műveletek és a párhuzamos stream-ek lehetővé teszik, hogy nem szálbiztos
    collection-ökkel is implementálhassunk párhuzamos feldolgozást, feltéve hogy nem módosítjuk a collection-t a feldolgozás közben. A párhuzamosság persze nem varázsszer, a párhuzamos
    feldolgozás nem lesz automatikusan gyorsabb mint egy soros. Az aggregáló műveletekkel egyszerűen kihasználhatjuk a párhuzamosságot, de továbbra is a kódoló felelőssége, hogy
    megvizsgálja, a feladat alkalmas-e párhuzamos feldolgozásra.</p>
  <p class="bekezd">Streamek létrehozásához az API négyféle lehetőséget ad, ebből kettő soros, kettő pedig párhuzamos feldolgozású streamhez való:</p>
  <ul>
    <li><b>Collection.parallelStream</b>: potenciálisan párhuzamos stream létrehozása a Collection-t forrásként használva</li>
    <li><b>Collection.stream</b>: soros stream létrehozása a Collection-t forrásként használva</li>
    <li><b>BaseStream.parallel</b>: párhuzamos stream létrehozása a forrás stream-ből. Ha a forrás már eleve párhuzamos akkor önmagát adja visza.</li>
    <li><b>BaseStream.sequential</b>: soros stream létrehozása a forrás stream-ből. Ha a forrás már eleve soros akkor önmagát adja visza.</li>
  </ul>
  <p class="bekezd">
    Az <span class="programkod">isParallel()</span> metódussal bármely stream-ről lekérdezhetjük, hogy lezáró művelet végrehajtásakor sorosan vagy párhuzamosan dolgozódna-e fel (de lezáró
    művelet végrehajtása után már eszünkbe ne jusson ezt megtenni, különben az API szerint megjósolhatatlan eredményt kapunk).
  </p>
  <p class="bekezd">
    A fentiek alapján tehát streamből egy csővezetéken belül is csinálhatunk párhuzamosat vagy sorosat. Soros streamre már több példát is láttunk, a következő kifejezés az összes <span
      class="programkod">Castle</span> készletünk átlagos elemszámát párhuzamos feldolgozással számolja ki:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">double</span> atlag = mySets.parallelStream()
    .filter(s -&gt; s.getTheme() == Theme.CASTLE)
    .mapToInt(LegoSet::getPieceCount)
    .average()
    .getAsDouble(); </pre>
  </div>
  <p class="bekezd">
    A soros stream létrehozásban lényegében nincs különbség a fenti két metódus között, a <span class="programkod">Collection.parallelStream</span> esetén azonban a Java 8 dokumentációja
    érdekesen fogalmaz: ez a metódus visszaadhat párhuzamos, de soros streamet is. A default implementáció párhuzamos stream-et kreál, akárcsak a <span class="programkod">Collection</span>
    osztály JDK-beli leszármazottai is. A kitétel valószínűleg azért került bele a dokumentációba, mert technikailag legalábbis lehetséges olyan <span class="programkod">Collection</span>
    leszármazottat gyártani, aminél a parallelStream soros steam-et hoz létre.
  </p>
  <p class="bekezd">Nagyon sok redukciós művelet egyszerű ciklussal is implementálható, például:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span> sum = 0;
<span class="java_keyword">for</span> (<span class="java_keyword">int</span> x : numbers) {
    sum += x;
} </pre>
  </div>
  <p class="bekezd">Ilyen esetekben mégis célszerűbb redukciós műveletet használni, mert az általában jól párhuzamosítható, legalábbis amíg az elemek feldolgozására használt művelet
    asszociatív és állapotmentes. Számok stream-jének összegét a fenti helyett így is írhatjuk:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span> sum = numbers.stream().reduce(0, (x, y) -&gt; x + y); </pre>
  </div>
  <p class="bekezd">vagy</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span> sum = numbers.stream().reduce(0, Integer::sum); </pre>
  </div>
  <p class="bekezd">Ez pedig egy csuklómozdulattal párhuzamossá alakítható:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span> sum = numbers.parallelStream().reduce(0, Integer::sum); </pre>
  </div>
  <p class="bekezd">
    <b>Konkurens redukció</b>
  </p>
  <p class="bekezd">
    Tekintsük a következő példát, ami a készleteinket témakör szerint csoportosítja (már a redukció fejezetben is láttuk). Ez a példa a collect művelettel <span class="programkod">Map</span>-pé
    redukálja a legógyűjtemény collection-t:
  </p>
  <div class="programkod">
    <pre>Map&lt;Theme, List&lt;LegoSet&gt;&gt; byTheme = mySets.stream()
    .collect(Collectors.groupingBy(LegoSet::getTheme)); </pre>
  </div>
  <p class="bekezd">A fenti példával ekvivalens párhuzamos feldolgozású változat:</p>
  <div class="programkod">
    <pre>Map&lt;Theme, List&lt;LegoSet&gt;&gt; byTheme = mySets.parallelStream()
    .collect(Collectors.groupingByConcurrent(LegoSet::getTheme)); </pre>
  </div>
  <p class="bekezd">Ezt a népnyelv konkurens redukciónak (concurrent reduction) hívja. Az osztálykönyvtár akkor végez konkurens redukciót, ha egy collect műveletet tartalmazó
    csővezetéknél a következő feltételek mindegyike igaz:</p>
  <ul>
    <li>a stream párhuzamos</li>
    <li>a collect művelet collector paraméterének <span class="programkod">Collector.Characteristics.CONCURRENT</span> karakterisztikája van. (A collector karakterisztikáját a <span
      class="programkod">Collector.characteristics()</span> metódus adja vissza.)
    </li>
    <li>a stream rendezetlen (unordered) vagy a collector-nak <span class="programkod">Collector.Characteristics.UNORDERED</span> karakterisztikája van. A stream rendezetlenségének
      biztosításához a <span class="programkod">BaseStream.unordered()</span> metódust kell meghívni.
    </li>
  </ul>
  <p class="bekezd">
    Bizonyára mindenkinek feltűnt, hogy a fenti példa valójában egy <span class="programkod">ConcurrentMap</span> példányt ad vissza, bár ha ez talán nem is, az biztosan, hogy a
    groupingByConcurrent műveletet hívja meg a groupingBy helyett. Nos, a groupingByConcurrent-től eltérően a groupingBy elég gyengén muzsikál a párhuzamos stream-ekkel. (Mert úgy működik,
    hogy két map-et a kulcsok alapján fésül össze, ami eléggé számításigényes.) A <span class="programkod">Collectors.toConcurrentMap</span> is jobban működik párhuzamos stream-ekkel, mint
    a <span class="programkod">Collectors.toMap</span>.
  </p>
  <p class="bekezd">
    Van néhány összetett redukciós művelet, amit akár még hátrányosabb is lehet párhuzamosan futtatni. Ilyen például egy <span class="programkod">collect()</span> ami <span
      class="programkod">Map</span>-et hoz létre, mint például ez:
  </p>
  <div class="programkod">
    <pre>Map&lt;Theme, List&lt;LegoSet&gt;&gt; setsByTheme = mySets.parallelStream()
    .collect(Collectors.groupingBy(LegoSet::getTheme)); </pre>
  </div>
  <p class="bekezd">
    Ez azért van mert az összesítő lépés (amikor két <span class="programkod">Map</span>-et kulcs alapján összefűzünk) eléggé költséges lehet egyes <span class="programkod">Map</span>
    implementációknál. Itt segít a konkurens redukció. Ha a redukcióban használt eredménykonténer konkurensen módosítható collection, mint például a <span class="programkod">ConcurrentHashMap</span>,
    az akkumulátor párhuzamos meghívásai valóban betölthetik az eredményüket egyidejűleg ugyanabba a megosztott eredménykonténerbe. Így már nem lesz szükség rá, hogy a combiner különböző
    eredménykonténereket fűzzön össze és ez valóban felgyorsíthatja a párhuzamos végrehajtás teljesítményét. A konkurens redukciót támogató <span class="programkod">Collector
      Collector.Characteristics.CONCURRENT</span> karakterisztikával van megjelölve (a karakterisztikákról később bővebben lesz szó).
  </p>
  <p class="bekezd">A konkurens collection-nek is van hátránya: ha több szál rakosgatja eredményeit egyszerre ugyanabba a megosztott eredménykonténerbe, akkor az eredmények sorrendje
    nemdeterminisztikus lesz. Konkurens redukció tehát csak akkor használható, ha a feldolgozott stream sorrendje nem lényeges.</p>
  <p class="bekezd">
    Összefoglalva: a <span class="programkod">Stream.collect(Collector&lt;? super T,A,R&gt; collector)</span> csak akkor hajt végre konkurens redukciót, ha
  </p>
  <ul>
    <li>a stream párhuzamos</li>
    <li>a collectornak <span class="programkod">Collector.Characteristics.CONCURRENT</span> karakterisztikája van
    </li>
    <li>a stream rendezetlen vagy a collectornak <span class="programkod">Collector.Characteristics.UNORDERED</span> karakterisztikája van
    </li>
  </ul>
  <p class="bekezd">
    A stream rendezetlenségét a már megismert <span class="programkod">BaseStream.unordered()</span> metódussal tudjuk biztosítani. Például:
  </p>
  <div class="programkod">
    <pre>Map&lt;Theme, List&lt;LegoSet&gt;&gt; setsByTheme = mySets.parallelStream()
    .unordered()
    .collect(Collectors.groupingByConcurrent(LegoSet::getTheme)); </pre>
  </div>
  <p class="bekezd">ahol a groupingByConcurrent a groupingBy konkurens megfelelője.</p>
  <p class="bekezd">Ha lényeges, hogy egy adott kulcshoz tartozó elemek ugyanabban a sorrendben jelenjenek meg, mint ahogyan a forrásban voltak, akkor nem használhatjuk a konkurens
    redukciót.</p>
  <p class="bekezd">
    <b>Párhuzamos problémák</b>
  </p>
  <p class="bekezd">Azért nem érdemes ám mindent ész nélkül párhuzamosítani még akkor sem, ha a Stream API segítségével a párhuzamosítás alig több egy csuklómozdulatnál. Tekintsük az
    alábbi példát:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private static long</span> countPrimes(<span class="java_keyword">int</span> max) {
    <span class="java_keyword">return</span> IntStream.rangeClosed(1, max).parallel().filter(ThisClass::isPrime).count();
}
<span class="java_keyword">private static boolean</span> isPrime(<span class="java_keyword">long</span> n) {
    <span class="java_keyword">return</span> n &gt; 1 &amp;&amp; IntStream.rangeClosed(2, (<span class="java_keyword">int</span>) Math.sqrt(n))
        .noneMatch(divisor -&gt; n % divisor == 0);
} </pre>
  </div>
  <p class="bekezd">
    A countPrimes megszámolja a prímek számát 1 és a max között. A stream-et az <span class="programkod">IntStream.rangeClosed</span> metódus hozza létre, aztán átváltunk párhuzamos
    feldolgozásra majd pedig kiszűrjük a stream-ből a nem prímszámokat és a maradékot megszámoljuk. A Stream API-val a megoldás nagyon szépen leírható, a párhuzamosítás egyszerűbb nem is
    lehetne. Mivel az isPrime metódusunk CPU intenzív és nem valami hatékony, kihasználhatjuk a párhuzamosítás előnyeit és az összes rendelkezésre álló processzormagot. Nézzük egy másik
    példát:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private</span> List&lt;LegoSet&gt; getLegoInfo(Stream&lt;String&gt; legoNumber) {
    <span class="java_keyword">return</span> legoNumber.parallel().map(<span class="java_keyword">this</span>::getLegoSet).collect(Collectors.toList());
} </pre>
  </div>
  <p class="bekezd">Megkapunk egy sztringlistát a lekérdezni kívánt legókészletek azonosítóival. Tegyük fel, hogy a getLegoSet metódus hálózaton keresztül kérdezi le valahonnan az adott
    készlet információit, majd visszaadja egy LegoSet példányban. Itt igazából nem sokáig tartó CPU intenzív művelet miatt párhuzamosítunk, de a párhuzamos hálózati kérések miatt itt is
    előnyösnek véljük a párhuzamosítást. Ez a példa viszont tartalmaz egy nagy hibát. A párhuzamos stream-ek a ForkJoinPool API-t használják a párhuzamosításhoz és ha ide hosszú ideig futó
    taszkokat veszünk fel, akkor könnyen blokkolhatjuk a pool-ban lévő többi szál futását. Vagyis az összes taszkot ami párhuzamos stream-eket használ.</p>
  <p class="bekezd">Képzeljük el, hogy szerveres környezetben valaki meghívja a countPrimes metódust, egy másik meg a getLegoInfo-t. Az egyik blokkolni fogja a másikat még akkor is ha
    mindkettőnek más erőforrásokra van szüksége. A párhuzamos stream-eknek nem adhatjuk meg kézzel, hogy milyen thread pool-t használjanak, az osztálybetöltő mindig ugyanazt fogja
    használni. A VisualVM eszközzel meg is jeleníthetjük a szálakat, a prímszám számoló példánál látható, hogy az én négymagos processzoromnál négy szál fogja a munkát végezni: a main és
    három worker szál:</p>
  <p class="kozep">
    <img alt="vvm1" src="/informatika/essze/java/java8/vvm1.jpg" />
  </p>
  <p class="bekezd">A következő példa azt szemléltei, hogy mi történik ha egy folyamat megfogja a szálakat:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.concurrent.ExecutorService;
<span class="java_keyword">import</span> java.util.concurrent.Executors;
<span class="java_keyword">import</span> java.util.concurrent.TimeUnit;
<span class="java_keyword">import</span> java.util.stream.IntStream;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> ParallelPrimes {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) <span class="java_keyword">throws</span> InterruptedException {
        <span class="java_keyword">try</span> {
            Thread.sleep(6000);
        } <span class="java_keyword">catch</span> (InterruptedException e) {
            <span class="java_comment">// do nothing</span>
        }
        <span class="java_keyword">long</span> kezd = System.currentTimeMillis();
        ExecutorService es = Executors.newCachedThreadPool();
        <span class="java_keyword">int</span> MAX = 1000000;
        es.execute(() -&gt; countPrimes(MAX, 100)); <span class="java_comment">// sokáig futó</span>
        es.execute(() -&gt; countPrimes(MAX, 0));
        es.execute(() -&gt; countPrimes(MAX, 0));
        es.execute(() -&gt; countPrimes(MAX, 0));
        es.execute(() -&gt; countPrimes(MAX, 0));
        es.execute(() -&gt; countPrimes(MAX, 0));
        es.shutdown();
        es.awaitTermination(60, TimeUnit.SECONDS);
        System.<span class="java_constant">out</span>.println((System.currentTimeMillis() - kezd) / 1000);
    }

    <span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> countPrimes(<span class="java_keyword">int</span> max, <span
        class="java_keyword">int</span> delay) {
        System.<span class="java_constant">out</span>.println(IntStream.range(1, max).parallel().filter(ParallelPrimes::isPrime).peek(i -&gt; {
            <span class="java_keyword">try</span> {
                Thread.sleep(delay);
            } <span class="java_keyword">catch</span> (InterruptedException e) {
                <span class="java_comment">// do nothing</span>
            }
        }).count());
    }

    <span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">boolean</span> isPrime(<span class="java_keyword">long</span> n) {
        <span class="java_keyword">return</span> n &gt; 1 &amp;&amp; IntStream.rangeClosed(2, (<span class="java_keyword">int</span>) Math.sqrt(n)).noneMatch(divisor -&gt; n % divisor == 0);
    }

}
</pre>
  </div>
  <p class="bekezd">Hat folyamatot szimulálunk, mindegyik CPU igényes feladatot végez, az első viszont fogja magát és szépen elalszik egy tizedmásodpercre, miután megtalált egy
    prímszámot. Ez persze csak egy mesterséges példa, de talán nem nehéz elképzelni egy olyan szálat ami beragad vagy egy blokkoló műveletet végez. A kérdés az, hogy mi fog történni, amikor
    lefuttatjuk ezt a kódot? Hat taszkunk van, egyiküknek egy egész napig tart hogy befejeződjön, míg a többi sokkal hamarább is befejeződne. Nem meglepő módon a példakód minden egyes
    futásakor más-más eredményt kapunk. Néha minden egészséges taszk befejeződik, néha több beragad a lassú mögé. Az alábbi VisualVM diagramon egy ilyen esetet látunk:</p>
  <p class="kozep">
    <img alt="vvm2" src="/informatika/essze/java/java8/vvm2.jpg" />
  </p>
  <p class="bekezd">
    A pool-1-thread-1-től 6-ig az <span class="programkod">ExecutorService</span>-nek átadott hat taszk, viszont a közös ForkJoinPool továbbra is négy szálon dolgozik a négymagos
    processzoron. Négy taszk szerencsés volt és még lefutott (pool-1-thread-2, 4, 5 és 6), de közben a pool-1-thread-1-ben lévő beragadó taszk megkaparintotta mind a három worker szálat és
    nem eresztette többé. A pool-1-thread-3 pedig némi számolás után már csak várakozik arra, hogy felszabaduljon valamely worker szál.
  </p>
  <p class="bekezd">Nem valószínű, hogy egy éles rendszerben ilyen működést szeretnénk. Két lehetőség van, hogy elkerüljük az ilyen helyzeteket. Az első, hogy nem írunk beragadó
    taszkokat... Ezt persze könnyebb mondani mint megcsinálni, pláne összetett alkalmazásokban. A párhuzamos taszkoknak viszont meg is adhatunk specifikus thread pool-t amivel függetlenné
    tehetjük őket egymástól. Saját ForkJoinPool használata:</p>
  <div class="programkod">
    <pre>ForkJoinPool forkJoinPool = <span class="java_keyword">new</span> ForkJoinPool(2);
System.<span class="java_constant">out</span>.println(forkJoinPool.submit(() -&gt; IntStream.range(1, max).parallel().filter(ParallelPrimesFixed::isPrime).peek(i -&gt; {
    <span class="java_keyword">try</span> {
        Thread.sleep(delay);
    } <span class="java_keyword">catch</span> (InterruptedException e) {
        <span class="java_comment">// itt semmi teendő</span>
    }
}).count()).get());</pre>
  </div>
  <p class="bekezd">
    A példa 2 szálat használó <span class="programkod">ForkJoinPool</span>-t mutat be. Így minden egyes folyamatunk külön thread pool-t kap és nem fogják egymást zavarni. Ha ennek
    megfelelően módosítjuk a fenti 6 taszkot futtató példaprogramot akkor már sokkal szebb eredményt kapunk: 5 taszk annak rendje és módja szerint lefut és csak a hatodik várakozik
    szorgalmasan:
  </p>
  <p class="kozep">
    <img alt="vvm3" src="/informatika/essze/java/java8/vvm3.jpg" />
  </p>
  <p class="bekezd">
    Ráadásul a <span class="programkod">get()</span> metódusnak van olyan változata is aminek timeout-ot adhatunk át, így bebiztosíthatjuk magunkat, hogy egy idő után mindegyik taszk
    befejeződjön. De talán nem véletlenül nem lehet közvetlenül megadni a stream-eknek saját thread poolt: a Java 8 tervezői feltehetően <a onclick="window.open(this.href);return false;"
      href="https://stackoverflow.com/questions/33694908/is-forkjoinpool-commonpool-equivalent-to-no-pool">el akarták kerülni</a> azt az esetet amikor mindenki elkezdi ész nélkül használni
    a thread poolokat és a végén egy JVM-en belül is kismillió lesz belőle, így a többszálú teljesítmény végül nem javulni hanem romlani fog. Szóval ezt a megoldást nem árt körültekintően
    használni.
  </p>
  <p class="bekezd">Másik probléma a párhuzamos streamekkel, hogy óvatlan használattal könnyen holtpontot okozhatunk vele a JVM-ben. A legegyszerűbb példa:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.stream.IntStream;

<span class="java_keyword">public class</span> DeadlyLock {
    <span class="java_keyword">public static void</span> main(String... args) {
        <span class="java_keyword">synchronized</span> (System.out) {
            System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Hello World&quot;</span>);
            IntStream.range(0, 4).parallel().forEach(System.out::println);
        }
    }
}</pre>
  </div>
  <p class="bekezd">
    Ha a kódot <span class="programkod">parallel()</span> nélkül futtatjuk, akkor semmi probléma nincs: megkapjuk, hogy Hello World, aztán pedig 0,1,2,3. A parallel-es változat viszont
    szemrebbenés nélkül képes megakasztani a JVM-et. A fő szál megszerezte a zárolást a <span class="programkod">System.out</span>-ra, ami azt jelenti, hogy a thread pool-ból a már föntebb
    is látott worker szálak blokkolódtak (a println metódus ugyanis egy <span class="programkod">synchronized(this)</span> utasítással kezdődik):
  </p>
  <p class="kozep">
    <img alt="vvm4" src="/informatika/essze/java/java8/vvm4.jpg" />
  </p>
  <p class="bekezd">A fő szál nem tud folytatódni amíg a worker szálak nem végeztek azok viszont a fő szálra várnak. Klasszikus holtponti helyzet. A vicces az, hogy az eredmény sem
    mindig lesz ugyanaz, a legviccesebb pedig hogy egy thread dump-ból sem fogjuk tudni kideríteni, hogy ez egy Java holtpont. Ilyen egyszerű példákat számolatlanul lehet gyártani, például:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> DeadlyBlocker {
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> sum;

    <span class="java_keyword">public</span> DeadlyBlocker(<span class="java_keyword">int</span> defaultSum) {
        sum = defaultSum;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> addToSum(<span class="java_keyword">int</span> i) {
        <span class="java_keyword">synchronized</span> (<span class="java_keyword">this</span>) {
            sum += i;
        }
    }

    <span class="java_keyword">public</span> <span class="java_keyword">int</span> getSum() {
        <span class="java_keyword">return</span> sum;
    }

}</pre>
  </div>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.stream.IntStream;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> DeadLockMe {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        DeadlyBlocker blocker = <span class="java_keyword">new</span> DeadlyBlocker(0);

        <span class="java_keyword">synchronized</span> (blocker) {
            System.<span class="java_constant">out</span>.println(blocker.getSum());
            IntStream.range(0, 4).parallel().forEach(blocker::addToSum);
        }
    }

}</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">System.out::println</span> azért is jó példa mert szinte minden párhuzamos Stream-példa tartalmazza valahol. Persze nem árt elkerülni a blokkoló hívásokat a
    párhuzamos stream-ekben de ezt néha nem olyan egyszerű megcsinálni mint mondani.
  </p>
  <p class="bekezd">
    <b>Mikor használjunk párhuzamos stream-eket?</b>
  </p>
  <p class="bekezd">
    Láttuk, hogy a Stream API-nál nekünk kell megadni, mikor szeretnénk párhuzamos és mikor soros feldolgozást. A New York-i Egyetem számítástudományi professzora, Douglas Lea írt egy kis <a
      onclick="window.open(this.href);return false;" href="http://gee.cs.oswego.edu/dl/html/StreamParallelGuidance.html">útmutatót</a> a döntés megkönnnyítéséhez. Ezt ismertetem az
    alábbiakban, mégpedig azért, mert a párhuzamosítás nem varázsszer, bizonyos esetekben még lassabb is lehet mint a soros feldolgozás. Ha az alábbi tanácsokat megfogadjuk, akkor
    elkerülhetjük az aknamezőt. Vagy legalábbis néhány aknát.
  </p>
  <p class="bekezd">
    Tegyük fel, hogy az <span class="programkod">S.parallelStream().operation(F)</span> kifejezés műveletei függetlenek és vagy számításigényesek vagy pedig hatékonyan felszeletelhető
    adatszerkezet sok elemén dolgoznak. A kifejezésben:
  </p>
  <ul>
    <li><b>F</b>: interferencia- és állapotmentes elemenkénti függvény (általában lambda)</li>
    <li><b>S</b>: hatékonyan felszeletelhető forrás collection. A Collection leszármazottain kívül ilyen például a java.util.SplittableRandom. A legtöbb I/O forrás viszont nem, azok
      soros feldolgozásra lettek kitalálva.</li>
  </ul>
  <p class="bekezd">
    Párhuzamos végrehajtást akkor érdemes választani, ha a soros végrehajtással elérhető futásidő túllép egy adott küszöbértéket. Nincs is szükség a futásidő pontos ismeretére, elég jól meg
    lehet becsülni. Szorozzuk meg az <span class="programkod">N</span>-et (elemek száma) <span class="programkod">Q</span>-val (<span class="programkod">F</span> költsége elemenként). Ha az
    <span class="programkod">N*Q</span> legalább 10000 (gyávábbak hozzáírhatnak még egy-két nullát), akkor érdemes párhuzamos feldolgozást választani. (<span class="programkod">Q</span>-nak
    egyszerűen a műveletek vagy kódsorok számát is vehetjük.)
  </p>
  <p class="bekezd">
    Ha például az <span class="programkod">F</span> egy apró függvény (mondjuk <span class="programkod">x -&gt; x+1</span>), akkor az <span class="programkod">N &gt;= 10000</span> kell
    legyen, hogy megérje párhuzamos feldolgozást alkalmazni. Ha az <span class="programkod">F</span> egy óriási számítás (például meghatározni a következő legoptimálisabb lépést egy
    sakkjátszmában), akkor a <span class="programkod">Q</span> tényező már olyan nagy, hogy az <span class="programkod">N</span> nem is számít (feltéve, hogy a collection teljesen
    feldarabolható). Ha viszont a számítás nem interferenciamentes, akkor a párhuzamos feldolgozásnak nem sok értelme van.
  </p>
  <p class="bekezd">A fenti elveken kívül még három további feltétel is befolyásolja a párhuzamos futtatást:</p>
  <ul>
    <li><b>indítás</b>: a modern processzorokba az egyre több maggal együtt energiahatékonysági funkciók is bekerültek. Ezek különféle áttételeken keresztül lassíthatják az inaktív
      magok elindítását, ha szükség van rájuk. Ez is hozzáadódik a küszöbértékhez: mikor áll rendelkezésre elegendő mag a párhuzamos szálak feldolgozásához.</li>
    <li><b>szemcsézettség</b>: nincs sok értelme már eleve kis számítások további darabolásának. A keretrendszer általában úgy darabolja fel a problémákat, hogy az elérhető összes
      processzormagot ki lehessen használni. Ha az indítás után semmi hasznos tennivalója nincs a magoknak, akkor a (főként sorosan végrehajtott) feldarabolásra fordított erőforrás el lett
      pazarolva.</li>
    <li><b>felszeletelhetőség</b>: a leghatékonyabban felszeletelhető collection-ök az <span class="programkod">ArrayList</span>-ek és a (Concurrent)HashMap-ek, de a sima tömbök is
      ilyenek. A legkevésbé hatékonyan felszeletelhető a <span class="programkod">LinkedList</span>, <span class="programkod">BlockingQueue</span> és a legtöbb I/O alapú forrás. A többi
      valahol ezek között helyezkedik el. (Az adatszerkezetek általában akkor hatékonyan felszeletelhetők, ha felépítésükből adódóan támogatják a véletlenszerű elérést, a hatékony keresést
      vagy mindkettőt.) Ha az adatok particionálása tovább tart mint a feldolgozásuk, akkor az egész mókának semmi értelme nem volt. De ha a számítások <span class="programkod">Q</span>
      tényezője elég nagy, akár még egy <span class="programkod">LinkedList</span> esetén is nyerhetünk gyorsulást a párhuzamos feldolgozással, bár ez nem általános. (Néhány forrást pedig
      nem is lehet teljesen feldarabolni egyes elemekre.)</li>
  </ul>
  <p class="bekezd">
    Persze nem egyszerű minden fenti összetevő pontos mérése, de az átlagos hatásokat azért könnyű látni és némi gyakorlattal már jól meg tudjuk ítélni, hogy érdemes-e párhuzamosítani. Egy
    32 magos tesztgépen például a <span class="programkod">max()</span> vagy <span class="programkod">sum()</span> függvények <span class="programkod">ArrayList</span>-en való futtatásakor
    a párhuzamosítás optimális határa 10 ezer elem körül van. Ennél kisebb elemszám esetén a futásidők már nem sokkal rövidülnek és esetenként akár lassabbak mint a soros feldolgozás. A
    legdurvább lassulás akkor következik be, ha 100 elemnél is kevesebbünk van. Ilyenkor egy csomó olyan szál indul el amik úgy végzik be, hogy nem is csinálnak semmit, mert a számítás
    végetér mire elindulnak. Érdekesség, hogy nem okoz speciális kezelésmódot az sem, ha a programunkat olyan rendszeren fogjuk futtatni, ahol a magoknak általában nagy a kihasználtságuk
    (feltéve persze ha a párhuzamos feldolgozáshoz szükséges feltételek egyébként teljesülnek). Párhuzamos taszkjaink versengeni fognak a CPU időért a többivel, tehát kisebb gyorsulást
    tapasztalunk, de ez a legtöbb esetben még mindig jobb lesz mint a soros feldolgozás. Ha már nincsenek elérhető magok, akkor csak egy kis lassulást fogunk tapasztalni a soros
    feldolgozáshoz képest, kivéve persze ha a rendszer már annyira leterhelt, hogy a CPU idő legnagyobb részét a kontextuskapcsolás veszi el vagy a rendszer alapvetően soros feldolgozáshoz
    van beállítva (például a rendszergazda valamiért csak egy magot hagyott bekapcsolva a JVM számára).
  </p>
  <p class="bekezd">A mai modern hardvereknél és operációs rendszereknél egyébként szinte lehetetlen előre megmondani, hogy pontosan mennyi gyorsulást fogunk kapni a párhuzamosítástól.
    Az eredményt befolyásolja a cache jelenlét, szemétgyűjtési teljesítmény, JIT fordítás, memóriaelrendezés, adatelrendezés, operációs rendszer ütemezési beállításai, virtuális gépek, stb.
    Épphogy csak a Hold állása nem. Persze ezek a tényezők a soros feldolgozást is befolyásolják, de a párhuzamosat még inkább. Egy olyan tényező, ami soros végrehajtásban 10%-os eltérést
    ad, párhuzamos feldolgozásban ennek a tízszeresét is jelentheti.</p>
  <p class="bekezd">
    A soros feldolgozásnál alkalmazott fogások általában párhuzamos feldolgozásnál is működnek. Többnyire jó módszer ha a <span class="programkod">Collection</span>-t használó komponens
    fejlesztője a technikai döntéseket a komponensen belül eldönti és csak az ezeken alapuló műveleteket exportálja a felhasználónak. Legyen például egy komponens, aminek van egy belső
    counts collection-je. A komponens a párhuzamos/soros döntéshez használatos méretküszöböt belsőleg kezeli (hacsak nem túl költséges az elemenkénti számítás) és a külvilágnak már a
    megfelelő stream-et adja át:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public long</span> getMaxCount() {
    <span class="java_keyword">return</span> countStream().max();
}

<span class="java_keyword">private</span> Stream countStream() {
    <span class="java_keyword">return</span> (counts.size() &lt; MIN_PAR) ? counts.stream() : counts.parallelStream();
}</pre>
  </div>
  <p class="bekezd">
    <b>I/O és párhuzamosítás</b>
  </p>
  <p class="bekezd">
    A JDK-ban az I/O-alapú Stream-ek (például a <span class="programkod">BufferedReader.lines()</span>) főként soros feldolgozásra lettek kitalálva. Léteznek lehetőségek pufferelt I/O nagy
    hatékonyságú kötegelt feldolgozására, de ezek egyedi fejlesztést igényelnek. Az általános felhasználású, de I/O-t vagy szinkronizálást használó stream-ek közül egyik végletet azok a
    függvények jelentik, amelyek belsőleg soros I/O elérést végeznek vagy zárolt szinkronizált erőforrásokat érnek el és nem felelnek meg az interferencia-mentesség kritériumainak. Ezek
    párhuzamosításának nincs sok értelme. A másik végletet azok a számítások jelentik, amik alkalmankénti átlátszó I/O műveleteket végeznek vagy olyan szinkronizációt ami ritkán blokkoló
    (ilyen például a legtöbb logolás és a konkurens collection-ök mint például a <span class="programkod">ConcurrentHashMap</span> használata). Ezek ártalmatlanok. A kettő közötti esetek
    egyedi elbírálást igényelnek. Ha minden altaszk feltehetően jelentős ideig blokkolódik I/O-ra vagy más erőforrás elérésére várva, akkor a CPU erőforrások kihasználatlanul maradnak.
    Ezekben az esetekben a párhuzamos stream-ek általában nem jelentenek jó választást.
  </p>
  <div class="keretes">
    <h3>Mellékhatások</h3>
    <p>
      Egy metódusnak vagy kifejezésnek akkor van mellékhatása, ha az érték visszaadásán vagy létrehozásán kívül a virtuális gép állapotát is módosítja. Ilyen például a mutable reduction, de
      akár egy <span class="programkod">System.<span class="java_constant">out</span>.println
      </span> is. A JDK a csővezeték bizonyos mellékhatásait elég jól kezeli. A collect metódus a legtöbb, mellékhatással rendelkező stream műveletet szálbiztos módon hajtja végre. A forEach és a
      peek eleve a mellékhatás figyelembe vételével lett tervezve; egy olyan lambda kifejezés pedig, ami void-dal tér vissza (például csak meghívja a <span class="programkod">System.<span
        class="java_constant">out</span>.println
      </span> metódust), gyakorlatilag mást se csinál csak mellékhatást. De azért nem árt, ha az ember a forEach és peek metódust óvatos duhajként használja. Ha ezen műveletek valamelyikét
      párhuzamos stream-mel használjuk, akkor a futtatókörnyezet konkurensen több szálból is meghívhatja a paraméterként megadott lambda kifejezést. A filter és a map műveletek
      paramétereiként pedig soha ne adjunk át mellékhatásokat okozó lambda kifejezéseket.
    </p>
    <p>
      Ha nincs jelezve az ellenkezője, akkor semmi garancia nincs rá, hogy mellékhatásokat okozó viselkedési paraméterek mellékhatásai a többi szál számára is láthatóak, sem pedig arra,
      hogy különböző műveleteket "ugyanazon" az elemen ugyanaz a szál fogja végrehajtani. Még ha ki is van kényszerítve, hogy a legyártott eredmény sorrendje megfeleljen a stream forrásának
      encounter order-jével (például az <span class="programkod">IntStream.range(0, 5).parallel().map(x -&gt; x * 2).toArray()</span> streamnek ezt kell produkálnia: <span
        class="programkod">[0, 2, 4, 6, 8]</span>), akkor sincs meghatározva, hogy az egyes elemekre alkalmazott leképezési függvény milyen sorrendben fog végrehajtódni. És az sem, hogy
      melyik szál hajtja végre egy adott elemhez a viselkedési paramétert. Jobb tehát elkerülni a mellékhatást okozó kifejezéseket, talán csak a debug célokra alkalmazott println kivétel. A
      csak mellékhatással működő stream műveleteket (<span class="programkod">forEach()</span> és a <span class="programkod">peek()</span>) pedig óvatosan kell használni.
    </p>
    <p>Íme egy példa, hogyan tudunk átalakítani egy mellékhatásokat okozó csővezetéket olyanná ami nem csinál ilyet. Ez egy sztring stream-ben olyan mintákat keres, amik megfelelnek egy
      adott reguláris kifejezésnek és ezeket kigyűjti egy listába.</p>
    <div class="programkod">
      <pre>ArrayList&lt;String&gt; results = new ArrayList&lt;&gt;();
stream
    .filter(s -&gt; pattern.matcher(s).matches())
    .forEach(s -&gt; results.add(s)); </pre>
    </div>
    <p>
      Párhuzamos végrehajtással a nem szálbiztos (szálbizonytalan...) <span class="programkod">ArrayList</span> rossz eredményeket adna, a szinkronizálással való kiegészítés pedig
      versengést okozna. Ráadásul a mellékhatás alkalmazása itt teljesen fölösleges. A <span class="programkod">forEach()</span> metódust egyszerűen le lehet cserélni egy redukcióra, ami
      sokkal alkalmasabb párhuzamos feldolgozáshoz:
    </p>
    <div class="programkod">
      <pre>List&lt;String&gt; results = stream
    .filter(s -&gt; pattern.matcher(s).matches())
    .collect(Collectors.toList()); </pre>
    </div>
    <h3>Állapottartó (stateful) lambda kifejezések</h3>
    <p>
      Az állapotottartó (stateful) lambda kifejezések is okozhatnak mellékhatásokat és inkonzisztens vagy megjósolhatatlan eredményeket, különösen a párhuzamos stream-ekben. A lelki béke
      megőrzése érdekében ezeket is jobb elkerülni a stream műveletek paramétereiben. Egy lambda akkor állapottartó ha az eredménye függ bármely olyan állapottól, ami megváltozhat a
      csővezeték végrehajtása közben. A következő példa összead elemeket a <span class="programkod">listOfIntegers List</span>-ből egy új <span class="programkod">List</span> példányba a
      map művelettel. Ezt kétszer teszi meg; először egy soros streammel, aztán egy párhuzamossal:
    </p>
    <div class="programkod">
      <pre>List&lt;Integer&gt; parallelStorage = Collections
    .synchronizedList(new ArrayList&lt;&gt;());
integerList.parallelStream()

    <span class="java_comment">// Elkerülendő állapottartó lambda kifejezés</span>
    .map(e -&gt; {
        parallelStorage.add(e);
        <span class="java_keyword">return</span> e;
    })

.forEachOrdered(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;&quot;</span>);
parallelStorage.stream().forEachOrdered(e -&gt; System.out.print(e + <span class="java_string">&quot; &quot;</span>)); </pre>
    </div>
    <p>
      Az <span class="programkod">e -&gt; { parallelStorage.add(e); return e; }</span> egy állapottartó lambda kifejezés. Ennek eredménye a kód minden egyes futásakor változhat. A példa a
      következőt írja ki:
    </p>
    <pre class="programkod">8 7 6 5 4 3 2 1 
3 4 7 1 6 8 5 2 </pre>
    <p>
      A forEachOrdered művelet az elemeket a stream által adott sorrendben dolgozza fel, függetlenül attól, hogy a stream végrehajtása soros vagy párhuzamos. Viszont amikor egy stream
      párhuzamosan hajtódik végre, akkor a map művelet a stream elemeit a futtatókörnyezet és a fordító által megadott rendben dolgozza fel. Ezért a kód minden egyes futásakor különböző
      lehet annak sorrendje, ahogy az <span class="programkod">e -&gt; { parallelStorage.add(e); return e; }</span> kifejezés beleteszi az elemeket a parallelStorage List-be.
      Determinisztikus és megjósolható eredményhez érdemes biztosítani, hogy a stream műveletek lambda kifejezés paraméterei ne legyenek állapottartóak.
    </p>
    <p>
      Megjegyzés: ez a példa meghívja a synchronizedList metódust, tehát a <span class="programkod">parallelStorage List</span> szálbiztos. Emlékezzünk rá, hogy a collection-ök nem
      szálbiztosak. Ez azt jelenti, hogy több szál nem érhet el egy adott collection-t ugyanabban az időben. Tegyük fel, hogy nem hívjuk meg a synchronizedList metódust a <span
        class="programkod">parallelStorage</span> létrehozásakor:
    </p>
    <div class="programkod">
      <pre>List&lt;Integer&gt; parallelStorage = new ArrayList&lt;&gt;(); </pre>
    </div>
    <p>Ekkor a példa még kiszámíthatatlanabbul viselkedik mert a parallelStorage-t ütemezést biztosító szinkronizációs mechanizmus nélkül éri el és módosítja több szál.</p>
  </div>
  <h3>Iteráció educationis</h3>
  <p class="bekezd">
    Az <span class="programkod">Iterable</span> interfészről bizonyára mindenki hallott már: ez alkotja minden collection-ök ős interfészét és eddig csupán egy metódusa volt ami annyit
    tett, hogy visszaadott egy <span class="programkod">Iterator</span>-t. A Java 8 ezt két plusz metódussal háromra egészítette ki:
  </p>
  <p class="bekezd">
    <u>default void forEach(Consumer&lt;? super T&gt; action)</u>
  </p>
  <p class="bekezd">
    <u>Iterator&lt;T&gt; iterator()</u>
  </p>
  <p class="bekezd">
    <u>default Spliterator&lt;T&gt; spliterator()</u>
  </p>
  <p class="bekezd">A forEach egyszerű bejárást biztosít a lambdákkal azokhoz az interfészekhez amelyek implementálják:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; stringList = Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span class="java_string">&quot;három&quot;</span>);
stringList.forEach(s -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Aktuális elem: &quot;</span> + s)); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">Iterator</span> interfész is kiegészült egy hasonló, lambdát fogadó metódussal (forEachRemaining), aminek a neve magáért beszél:
  </p>
  <div class="programkod">
    <pre>List&lt;String&gt; stringList = Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span class="java_string">&quot;három&quot;</span>);
Iterator&lt;String&gt; iter = stringList.iterator();
<span class="java_keyword">if</span> (iter.hasNext()) {
    iter.forEachRemaining(System.out::println);
} </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">Iterator</span>-ról eddig is tudtuk, hogy micsoda, de mi az az spliterator?
  </p>
  <p class="bekezd">
    Nos a <span class="programkod">java.util.Spliterator</span> alapvetően arra készült, hogy elemek halmazát több részre szétszedjünk (split, innen jön a split iterator - spliterator), így
    bizonyos műveleteket, számításokat párhuzamosan külön szálakban is feldolgozhassunk. Az <span class="programkod">Spliterator</span> lényegében az <span class="programkod">Iterator</span>
    párhuzamos megfelelője, leír egy (potenciálisan végtelen) elemekből álló collection-t, ami támogatja az elemeken a szekvenciális előrelépegetést, kötegelt bejárást és azt, hogy a forrás
    egy részét át lehessen adni másik spliterator-nak, akár párhuzamos feldolgozást is létrehozva. <b>Alacsony szinten minden stream lelke egy spliterator.</b> A collection-ök mellett az <span
      class="programkod">Spliterator</span> által feldolgozott források lehetnek például tömb, I/O csatorna vagy generátor függvény. Bár én ebben a cikkben a <span class="programkod">Spliterator</span>
    használatát is bemutatom, normál esetben egy Java 8 fejlesztőnek szinte sosem kell ezzel foglalkoznia, csak ha esetleg saját <span class="programkod">Collection</span> osztályt szeretne
    fejleszteni.
  </p>
  <p class="bekezd">Az interfész 8 metódust biztosít.</p>
  <p class="bekezd">
    <u>boolean tryAdvance(Consumer&lt;? super T&gt; action)</u>
  </p>
  <p class="bekezd">
    <u>default void forEachRemaining(Consumer&lt;? super T&gt; action)</u>
  </p>
  <p class="bekezd">
    A tryAdvance használatával be tudjuk járni az elemeket <b>sorban</b> egyesével. Ha létezik további elem, akkor a metódus végrehajtja rajta az <span class="programkod">action</span>
    paramétert és <span class="programkod">true</span> értékkel tér vissza, egyébként nem csinál semmit, csak <span class="programkod">false</span> értéket ad vissza. A tryAdvance tehát
    hasonló az <span class="programkod">Iterator hasNext()</span> -<span class="programkod"> next()</span> párosához, csak egyben valósítja meg azt a funkcionalitást. A forEachRemaining
    metódust a soros kötegelt bejáráshoz használhatjuk.
  </p>
  <p class="bekezd">
    <u>Spliterator&lt;T&gt; trySplit()</u>
  </p>
  <p class="bekezd">A trySplit metódus kettévágja az aktuális spliteratort és visszaad egy újat a szétvágás másik részével:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; stringList = Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span class="java_string">&quot;három&quot;</span>);
Spliterator&lt;String&gt; iter = stringList.spliterator();
Spliterator&lt;String&gt; iter2 = iter.trySplit();

iter.forEachRemaining(System.out::println);
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Iter2:&quot;</span>);
iter2.forEachRemaining(System.out::println); </pre>
  </div>
  <p class="bekezd">
    A kettévágás műveletet &quot;particionálisnak&quot; vagy &quot;dekompozíciónak&quot; hívják. Egy ideális trySplit művelet az elemeket pont fele-fele arányban osztja szét. Ha az aktuális
    <span class="programkod">Spliterator</span> példányon már nem lehet további particionálást végezni (a fenti példában ilyen az <span class="programkod">iter2</span>), akkor a
    visszatérési értéke <span class="programkod">null</span>. Ha az <span class="programkod">Spliterator</span> példányunk a split műveletet nem vagy nem hatékonyan, esetleg aránytalanul
    valósítja meg, akkor az azt használó műveletek semmi előnyt nem fognak kapni a párhuzamosságból. Bár az <span class="programkod">Spliterator</span> segít a párhuzamos algoritmusokban,
    nem elvárás tőlük, hogy szálbiztosak legyenek. A spliterátorokat használó párhuzamos algoritmusoknak kell biztosítani, hogy egy spliterator-t egyszerre csak egy szál használjon. A <span
      class="programkod">trySplit()</span>-et hívó szál átadhatja a visszakapott <span class="programkod">Spliterator</span>-t egy (de csak egy) másik szálnak ami viszont bejárhatja vagy
    tovább hasíthatja (split) azt az spliterator-t. A split művelet és a bejárás viselkedése meghatározatlan, ha két vagy több szál használja párhuzamosan ugyanazt az spliterator-t. Ha az
    eredeti szál átadja az spliterator-t feldolgozásra egy másik szálnak, akkor a legjobb, ha az átadás még azelőtt megtörténik mielőtt a <span class="programkod">tryAdvance()</span>
    metódussal bármilyen elem feldolgozásába belekezdenénk, mivel bizonyos biztosítékok (mint például az <span class="programkod">estimateSize()</span> pontossága a <span class="programkod">SIZED</span>
    tulajdonságú spliterator-ok esetén) csak a bejárás elkezdése előtt érvényesek. Látható, hogy maga az <span class="programkod">Spliterator</span> még semmit nem dolgoz fel
    &quot;párhuzamosan&quot; (nincs köze a ForkJoinPool API-hoz sem), csupán alapvető műveleteket biztosít párhuzamosság implementálásához.
  </p>
  <p class="bekezd">
    <u>int characteristics()</u>
  </p>
  <p class="bekezd">
    <u>default boolean hasCharacteristics(int characteristics)</u>
  </p>
  <p class="bekezd">
    Egy spliterator egy bitmaszkban meg tudja mondani a saját implementációjának jellemzőit, vagy más néven karakterisztikáit az interfészben definiált konstansok képében. A
    karakterisztikák (néha stream flag-ként is hívják) bevezetésének az volt a célja, hogy lehetővé tegye a stream műveletek számára a fölösleges munka elhagyását. Ha például tudjuk, hogy
    egy stream már sorbarendezett, akkor a <span class="programkod">sorted()</span> művelet nem csinál semmit. Ha tudjuk a stream elemeinek pontos számát, akkor a <span class="programkod">toArray()</span>
    rögtön a megfelelő méretű tömböt tudja lefoglalni és elkerülhetők a fölösleges tömbmásolások. Vagy ha tudjuk, hogy a forrásnak nincs encounter order-je akkor nem is kell azt
    megtartanunk; erről a párhuzamos stream-ek esetén volt is szó. Egy stream csővezeték minden egyes fokozatának van adott karakterisztikája. A közbülső műveletek beszúrhatnak vagy
    törölhetnek karakterisztikát. 8 különböző karakterisztika létezik:
  </p>
  <ul>
    <li><b>CONCURRENT</b>: a forrás konkurensen több szálból külső szinkronizálás nélkül is biztonságosan módosítható (fogad hozzáadásokat, cseréket és/vagy törléseket). Ebben az
      esetben a <span class="programkod">Spliterator</span>-tól elvárt, hogy dokumentálva legyen, a bejárás közbeni módosítások milyen hatással vannak az eredményre. Felső szintű <span
      class="programkod">Spliterator</span>-oknak nem ajánlott egyszerre CONCURRENT és SIZED karakterisztikát is jelezni magukról, mivel a véges méret - ha ismert - megváltozhat ha a
      bejárás során a forrást konkurensen módosítják. Ilyen <span class="programkod">Spliterator</span> inkonzisztens és semmiféle garancia nem lehet a számításaira. Az al-spliterator-ok
      mondhatnak magukról SIZED karakterisztikát ha az al-méret ismert és a forrás hozzáadásai és eltávolításai ezt nem befolyásolják a bejárás közben.</li>
    <li><b>DISTINCT</b>: ha a bejárt elemekre páronként igaz, hogy bármely <span class="programkod">x</span>, <span class="programkod">y</span> esetén <span class="programkod">!x.equals(y)</span></li>
    <li><b>IMMUTABLE</b>: ha a forrást nem lehet szerkezetileg módosítani, vagyis nem lehet hozzáadni, kicserélni vagy eltávolítani elemeket, tehát ilyen módosítások a bejárás során
      nem fognak megtörténni.</li>
    <li><b>NONNULL</b>: a forrás becsszóra megígéri, hogy a bejárt elemek nem tartalmaznak null-okat</li>
    <li><b>ORDERED</b>: az elemekre definiált valamilyen encounter order</li>
    <li><b>SIZED</b>: azt jelzi, hogy a bejárás előtt meghívot <span class="programkod">estimateSize()</span> metódus véges méretet jelez majd, ami a forrás szerkezeti módosítása
      nélkül pontos számot ad a teljes bejárással bejárható elemek számáról</li>
    <li><b>SORTED</b>: az encounter order egy definiált rendezési sorrendet követ. Ebben az esetben a <span class="programkod">getComparator()</span> visszaadja hozzá a megfelelő <span
      class="programkod">Comparator</span>-t vagy <span class="programkod">null</span>-t ha az elemek <span class="programkod">Comparable</span> típusúak és természetes sorrendben vannak
      rendezve. Ha a <span class="programkod">Spliterator</span> SORTED-et jelez magáról, akkor ORDERED is kell legyen.</li>
    <li><b>SUBSIZED</b>: ha kettévágjuk a példányt a <span class="programkod">trySplit()</span> metódussal, akkor olyan <span class="programkod">Spliterator</span>-okat kapunk amik
      szintén SIZED és SUBSIZED tulajdonságúak lesznek</li>
  </ul>
  <p class="bekezd">
    Ha például egy <span class="programkod">ArrayList</span>-ből csinálunk <span class="programkod">Spliteratort</span>, az ORDERED, SIZED és SUBSIZED jellemzőkkel fog rendelkezni. Ha
    ugyanezt egy <span class="programkod">HashSet</span>-ből tesszük, az DISTINCT és SIZED jellemzőkkel.
  </p>
  <p class="bekezd">
    Az <span class="programkod">Spliterator</span> kötése (bind) az a pillanat amikor az lényegében a forráshoz rendelődik. Egy késői kötésű (late-binding) <span class="programkod">Spliterator</span>
    nem a példány létrehozásakor, hanem az első bejárás, első split vagy a becsült méret első lekérdezésének pontján lesz kötve az elemek forrásához. Egy nem késői kötésű <span
      class="programkod">Spliterator</span> az elemek forrását a létrehozás pillanatában vagy pedig bármely metódus meghívásakor köti. A forrást érintő bármely kötés előtti módosítás
    megjelenik amikor az <span class="programkod">Spliterator</span> bejárja a forrást.
  </p>
  <p class="bekezd">
    A kötés után az <span class="programkod">Spliterator</span>-nak lehetőség szerint <span class="programkod">ConcurrentModificationException</span>-t kell dobnia ha szerkezeti
    interferenciát észlelt. Fail-fast-nak hívjuk az olyan <span class="programkod">Spliterator</span>-okat amik ezt megteszik. A tömeges bejárási metódus (<span class="programkod">forEachRemaining()</span>)
    viszont optimalizálhatja is a bejárást. Ez azt jelenti, hogy nem elemenként ellenőriz és nem dob azonnal hibát, hanem csak miután minden elemet bejárt. Egy olyan <span
      class="programkod">Spliterator</span> aminek nincs IMMUTABLE vagy CONCURRENT tulajdonsága, speciális eljárásmódot igényel: a forrás mindenféle szerkezeti interferenciája a kötés után
    lesz észlelve.
  </p>
  <p class="bekezd">A szerkezeti interferencia a következő módokon kezelhető (nagyjából a csökkenő kívánatosság sorrendjében):</p>
  <ul>
    <li>a forráson nem lehet szerkezeti interferencia. Egy <span class="programkod">CopyOnWriteArrayList</span> például immutable forrás. Egy ebből létrehozott <span class="programkod">Spliterator</span>
      IMMUTABLE karakterisztikát jelez.
    </li>
    <li>a forrás kezeli a konkurens módosításokat. Egy <span class="programkod">ConcurrentHashMap</span> kulcskészlete például konkurens forrás. Ebből létrehozott <span
      class="programkod">Spliterator</span> CONCURRENT karakterisztikát jelez.
    </li>
    <li>a mutable forrás egy késői kötésű és fail-fast Spliterator-t gyárt. A késői kötés leszűkíti azt az időablakot amikor az interferencia hatással lehet a számításra, a fail-fast
      best effort alapon detektálja, hogy a szerkezeti interferencia már a bejárás megkezdése után történt-e és <span class="programkod">ConcurrentModificationException</span>-t dob. Az <span
      class="programkod">ArrayList</span> és a JDK sok egyéb nemkonkurens collection-je is ilyen késői kötésű fail-fast spliterator-t gyárt.
    </li>
    <li>a mutable forrás nem késői kötésű de fail-fast <span class="programkod">Spliterator</span>-t gyárt. A forrás növeli a <span class="programkod">ConcurrentModificationException</span>
      dobásának valószínűségét, mivel a potenciális interferencia időablaka növekedett.
    </li>
    <li>a mutable forrás késői kötésű, de nem fail-fast <span class="programkod">Spliterator</span>-t gyárt. Megvan a kockázata a meghatározatlan, nemdeterminisztikus viselkedésnek a
      bejárás elkezdése után, mivel az interferenciát a forrás nem detektálja.
    </li>
    <li>a mutable forrás nem késői kötésű és nem fail-fast <span class="programkod">Spliterator</span>-t gyárt. A bejárás elkezdése után megnövekedik a meghatározatlan,
      nemdeterminisztikus viselkedés kockázata, mivel az interferencia a létrehozás után is bekövetkezhet.
    </li>
  </ul>
  <p class="bekezd">
    <u>long estimateSize()</u>
  </p>
  <p class="bekezd">
    <u>default long getExactSizeIfKnown()</u>
  </p>
  <p class="bekezd">Az estimateSize megadja a hátralévő elemek becsült számát, amiket egy adott pontnál még be lehet járni. Ha a tulajdonság SIZED akkor a bejárással elérhető pontos
    elemszámot adja vissza, de ha az elemek száma végtelen, ismeretlen vagy túl költséges kiszámítani akkor akár Long.MAX_VALUE értéket is visszaadhat (mindenesetre ha felülírjuk akkor
    lehetőség szerint akkor is érdemes becsülni, ha nem tudunk pontos értéket).</p>
  <p class="bekezd">
    A <span class="programkod">getExactSizeIfKnown()</span> default metódus csak kényelmi funkciókat lát el, az alapértelmezett implementációja:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">return</span> (characteristics() &amp; SIZED) == 0 ? -1L : estimateSize(); </pre>
  </div>
  <p class="bekezd">Az estimateSize visszatérési értékét adja vissza ha az spliterator jellemzője SIZED, egyébként pedig -1-et. Példaként nézzük a következő kódot:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; stringList = Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span class="java_string">&quot;három&quot;</span>);
Spliterator&lt;String&gt; iter = stringList.spliterator();
iter.tryAdvance(System.out::println);
System.<span class="java_constant">out</span>.println(iter.getExactSizeIfKnown()); </pre>
  </div>
  <p class="bekezd">A kimenete:</p>
  <pre class="programkod">    egy
    2</pre>
  <p class="bekezd">
    <u>default Comparator&lt;? super T&gt; getComparator()</u>
  </p>
  <p class="bekezd">
    Amennyiben az <span class="programkod">Spliterator</span> példány SORTED jellemzőjű, ez a metódus visszaadja a megfelelő <span class="programkod">Comparator</span>-t. Ha a forrás
    természetes sorrendben rendezett, akkor <span class="programkod">null</span>-t ad vissza, egyébként pedig ha a forrás nem SORTED, akkor <span class="programkod">IllegalStateException</span>-t
    dob.
  </p>
  <p class="bekezd">
    Az <span class="programkod">Spliterator</span> háromféle primitív típushoz tartozó belső interfészt is definiál:
  </p>
  <ul>
    <li>Spliterator.OfDouble</li>
    <li>Spliterator.OfInt</li>
    <li>Spliterator.OfLong</li>
    <li>Spliterator.OfPrimitive&lt;T,T_CONS,T_SPLITR extends Spliterator.OfPrimitive&lt;T,T_CONS,T_SPLITR&gt;&gt;</li>
  </ul>
  <p class="bekezd">
    Ezen alapértelmezett implementációk tryAdvance és forEachRemaining implementációi boxingolnak primitív értékeket a nekik megfelelő wrapper osztályokba. Az OfPrimitive a szülőosztály a
    primitív típusokat használó speciális osztályokhoz. Ez a boxing alááshatja a primitív specializált osztályok használatából adódó teljesítménybeli előnyöket. A boxing elkerüléséhez a
    megfelelő primitív-alapú metódusokat ajánlatos használni. Tehát az <span class="programkod">Spliterator.OfInt.tryAdvance(java.util.function.IntConsumer)</span> és <span
      class="programkod">Spliterator.OfInt.forEachRemaining(java.util.function.IntConsumer)</span> ajánlatos az <span class="programkod">Spliterator.OfInt.tryAdvance(java.util.function.Consumer)</span>
    és <span class="programkod">Spliterator.OfInt.forEachRemaining(java.util.function.Consumer)</span> helyett. Egy példa:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">int</span>[] ints = { 1, 3, 5, 7 };
Spliterator.OfInt s = Arrays.spliterator(ints);
s.forEachRemaining((IntConsumer) System.out::println); </pre>
  </div>
  <p class="bekezd">
    <b>Stream létrehozása iterátorokból</b>
  </p>
  <p class="bekezd">
    Az <span class="programkod">Iterable</span> interfészt az általánosságot szem előtt tartva tervezték meg, ezért nincs <span class="programkod">stream()</span> metódusa, de nem kell
    kétségbeesni, mert a <span class="programkod">StreamSupport</span> osztállyal bármikor tudunk belőle streamet csinálni! Mégpedig így:
  </p>
  <div class="programkod">
    <pre>Iterable&lt;String&gt; iterable = Arrays.asList(<span class="java_string">&quot;egy&quot;</span>, <span class="java_string">&quot;kettő&quot;</span>, <span
        class="java_string">&quot;érik&quot;</span>, <span class="java_string">&quot;a&quot;</span>, <span class="java_string">&quot;vessző&quot;</span>);
StreamSupport.stream(iterable.spliterator(), false); </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">StreamSupport.stream()</span> második paramétere mondja meg, hogy a létrehozott stream párhuzamos legyen-e. A <span class="programkod">StreamSupport</span>
    alacsony szintű műveleteket biztosít stream-ek létrehozásához és módosításához, a JDK kifejezetten osztálykönyvtár-íróknak ajánlja a használatát. Az általános célú programoknak a Stream
    és leszármazottai elegendő lehetőséget biztosítanak. A fenti stream metódus másik változata <span class="programkod">Spliterator</span> típussal paraméterezett <span class="programkod">Supplier</span>-t
    vár paraméterként és a karakterisztikát is meg kell adni neki:
  </p>
  <p class="bekezd">
    <u>stream(Supplier&lt;? extends Spliterator&lt;T&gt;&gt; supplier, int characteristics, boolean parallel)</u>
  </p>
  <p class="bekezd">
    A <span class="programkod">Spliterator</span>-t itt tehát a supplier adja meg, de a <span class="programkod">Supplier.get()</span> metódus csak egyszer lesz meghívva, mégpedig akkor
    amikor a stream csővezeték lezáró művelete elkezdi a végrehajtást. A két külön stream metódus tehát arra jó, hogy az egyikkel késői, a másikkal pedig korai kötésű stream-eket tudjunk
    létrehozni. A Java 8 dokumentációja szerint az IMMUTABLE vagy CONCURRENT karakterisztikájú spliterator-oknak hatékonyabb, ha az egyszerűbb paraméterezésű változatot használják. A
    második megoldásban a <span class="programkod">Supplier</span> egyfajta indirekciót biztosít, ami csökkenti a forrással való interferencia valószínűségét. Mivel a supplier csak a lezáró
    művelet végrehajtásakor hívódik meg, a forrást érintő bármely, a lezáró művelet előtti módosítás megjelenik a stream eredményében. A <span class="programkod">characteristics</span>
    paraméternek ebben a második változatban meg kell egyeznie a <span class="programkod">supplier.get().characteristics()</span> eredményével, különben nem várt eredményt kaphatunk. Egy
    példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.ArrayList;
<span class="java_keyword">import</span> java.util.List;
<span class="java_keyword">import</span> java.util.Spliterator;
<span class="java_keyword">import</span> java.util.stream.Stream;
<span class="java_keyword">import</span> java.util.stream.StreamSupport;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> StreamSupportExample {

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        List&lt;String&gt; koraiKotesu = <span class="java_keyword">new</span> ArrayList&lt;String&gt;() {
            <span class="java_annotation">@Override</span>
            <span class="java_keyword">public</span> Spliterator&lt;String&gt; spliterator() {
                System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Early binding!&quot;</span>);
                <span class="java_keyword">return</span> <span class="java_keyword">super</span>.spliterator();
            }
        };
        koraiKotesu.add(<span class="java_string">&quot;Első&quot;</span>);
        koraiKotesu.add(<span class="java_string">&quot;Második&quot;</span>);
        koraiKotesu.add(<span class="java_string">&quot;Harmadik&quot;</span>);
        Stream&lt;String&gt; str = StreamSupport.stream(koraiKotesu.spliterator(), <span class="java_keyword">false</span>);

        List&lt;String&gt; kesoiKotesu = <span class="java_keyword">new</span> ArrayList&lt;String&gt;() {
            <span class="java_annotation">@Override</span>
            <span class="java_keyword">public</span> Spliterator&lt;String&gt; spliterator() {
                System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Late binding!&quot;</span>);
                <span class="java_keyword">return</span> <span class="java_keyword">super</span>.spliterator();
            }
        }};
        kesoiKotesu.add(<span class="java_string">&quot;Első&quot;</span>);
        kesoiKotesu.add(<span class="java_string">&quot;Második&quot;</span>);
        kesoiKotesu.add(<span class="java_string">&quot;Harmadik&quot;</span>);
        Stream&lt;String&gt; str2 = StreamSupport.stream(() -&gt; kesoiKotesu.spliterator(), Spliterator.ORDERED, <span class="java_keyword">false</span>);
        str2 = str2.filter(s -&gt; s.length() &gt; 4);

    }

}</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">koraiKotesu</span> listából létrehozott str stream esetén már a létrehozáskor megtörténik a kötés, kiíródik, hogy <span class="programkod">Early
      binding!</span>, míg a <span class="programkod">kesoiKotesu</span> listából létrehozott <span class="programkod">str2</span> stream esetén még a filter közbülső műveletnél sem.
  </p>
  <p class="bekezd">
    A <span class="programkod">StreamSupport</span> a kétféle stream létrehozó metódust természetesen doubleStream, intStream és longStream formában is tartalmazza. Ezek működése az
    eddigiek alapján szerintem már könnyen kitalálható.
  </p>
  <p class="bekezd">
    <b>Spliterators</b>
  </p>
  <p class="bekezd">
    Ha nincs kéznél megfelelő <span class="programkod">Spliterator</span>, az <span class="programkod">Spliterators</span> osztály statikus gyártófüggvényeivel csinálhatunk önálló
    Spliterator példányokat is (vagyis amiket nem collection-ből generálunk):
  </p>
  <p class="bekezd">
    <u>Spliterator&lt;T&gt; emptySpliterator()</u>: üres SIZED és SUBSIZED karakterisztikájú Spliterator létrehozása. Ezeknél a trySplit mindig null-t fog visszaadni. Természetesen itt sem
    maradt ki az emptyIntSpliterator, emptyLongSpliterator és emptyDoubleSpliterator változat.
  </p>
  <p class="bekezd">
    <u>Spliterator&lt;T&gt; spliterator(Object[] array, int additionalCharacteristics)</u>: Spliterator létrehozása, ami az array paraméter elemeit fogja bejárni megadott egyedi
    karakterisztikával. Kényelmi metódus olyan Spliterator-okhoz, amik tömbökben tárolják az elemeiket és szükség van rá, hogy a karakterisztikát expliciten meg lehessen nekik adni. Ez a
    metódus mindig beállít SIZED és SUBSIZED karakterisztikát a visszaadott Spliterator-nak, a hívó a paraméterekben ezeket bővítheti (általában IMMUTABLE és ORDERED karakterisztikával).
    Normál esetben ha tömbhöz való Spliterator-ra van szükségünk, akkor az <span class="programkod">Arrays.spliterator(Object[])</span> metódust érdemes használni.
  </p>
  <p class="bekezd">
    <u>Spliterator&lt;T&gt; spliterator(Object[] array, int fromIndex, int toIndex, int additionalCharacteristics)</u>: a megadott tömb elemeinek adott intervallumából csinál Spliterator-t
    adott karakterisztikával. Ez a metódus is mindig beállít SIZED és SUBSIZED karakterisztikát a visszaadott Spliterator-nak, a hívó a paraméterekben ezeket bővítheti.
  </p>
  <p class="bekezd">
    A fenti két spliterator() metódusnak természetesen van <span class="programkod">int[]</span>, <span class="programkod">long[]</span> és <span class="programkod">double[]</span>
    tömbökből gyártó változata is, amelyek a fentebb már említett háromféle primitív típusú Spliterator-t gyártanak (<span class="programkod">Spliterator.OfDouble</span>, <span
      class="programkod">Spliterator.OfInt</span>, <span class="programkod">Spliterator.OfLong</span>).
  </p>
  <p class="bekezd">
    <u>Spliterator&lt;T&gt; spliterator(Collection&lt;? extends T&gt; c, int characteristics)</u>: a megadott <span class="programkod">c</span> collection <span class="programkod">Collection.iterator()</span>-jából
    gyárt Spliterator-t a <span class="programkod">Collection.size()</span> visszatérési értékével alapértelmezett méretként. Ez kései kötésű lesz és örökli a collection iterátorának
    fail-fast tulajdonságait, valamint implementálja a trySplit metódust korlátozott párhuzamosság biztosítása céljából. A metódus beállít SIZED és SUBSIZED karakterisztikát is (kivéve ha a
    hívó CONCURRENT karakterisztikát ad meg), a hívó a paraméterekben ezeket bővítheti.
  </p>
  <p class="bekezd">
    <u>Spliterator&lt;T&gt; spliterator(Iterator&lt;? extends T&gt; iterator, long size, int characteristics)</u>: a paraméterként megadott <span class="programkod">iterator</span>-ból
    gyárt spliterator-t <span class="programkod">size</span> alapértelmezett mérettel. Ez korai kötésű lesz és örökli a forrás iterator fail-fast tulajdonságait, valamint implementálja a
    trySplit metódust. Fontos észben tartani, hogy ezután az elemek bejárása már a visszaadott spliterator-on keresztül történjen. Ha mégis a paraméter iterátort piszkáljuk, akkor ne
    csodálkozzunk ha valami zagyvaság lesz a végeredmény. Ajánlatos a <span class="programkod">size</span> paramétert is pontosan, az iterator elemeinek tényleges méretére megadni. A
    metódus beállít SIZED és SUBSIZED karakterisztikát is (kivéve ha a hívó CONCURRENT karakterisztikát ad meg), a hívó a paraméterekben ezeket bővítheti.
  </p>
  <p class="bekezd">
    <u>spliteratorUnknownSize(Iterator&lt;? extends T&gt; iterator, int characteristics)</u>: a paraméterként megadott <span class="programkod">iterator</span>-ból gyárt spliterator-t ami
    korai kötésű, örökli a forrás iterator fail-fast tulajdonságait és implementálja a trySplit metódust. Itt sem ajánlatos az <span class="programkod">iterator</span> használata miután
    visszakaptunk belőle egy Spliterator-t. Mivel ez a metódus méret megadását nem igényel, figyelmen kívül hagyja a paraméterként megadott SIZED és SUBSIZED karakterisztikát.
  </p>
  <p class="bekezd">
    Egyébként a default <span class="programkod">Iterable.spliterator()</span> metódus is egy <span class="programkod">Spliterators.spliteratorUnknownSize(iterator(), 0)</span> eredméynét
    adja vissza. A fenti két metódusnak van olyan változata is, aminél az iterator paraméter <span class="programkod">PrimitiveIterator.OfInt</span>, <span class="programkod">PrimitiveIterator.OfLong</span>
    vagy <span class="programkod">PrimitiveIterator.OfDouble</span> lehet, ezek primitív típusokat kezelő <span class="programkod">Iterator</span> interfészek.
  </p>
  <p class="bekezd">
    Az <span class="programkod">Spliterators</span> osztály további négy metódusa a <span class="programkod">Spliterator</span> -&gt; <span class="programkod">Iterator</span> konverziót
    végzi el általános, <span class="programkod">Spliterator.OfInt</span>, <span class="programkod">Spliterator.OfLong</span> vagy <span class="programkod">Spliterator.OfDouble</span>
    típusú spliterator-ból. A <span class="programkod">Spliterators</span> osztállyal előállított <span class="programkod">Spliterator</span>-okat a <span class="programkod">StreamSupport</span>
    osztállyal pedig már egy csuklómozdulattal stream-ekké alakíthatjuk. De ahogy fentebb is írtam, a <span class="programkod">Spliterators</span> használatát ajánlatos elkerülni, hacsak
    nincs rá jó okunk, hogy használjuk. Az <span class="programkod">Iterable</span> implementációk (mint például a collection-ök) rendelkeznek <span class="programkod">spliterator()</span>
    metódussal ami szinte mindig célszerűbb megoldást és esetenként jobb implementációt is biztosít.
  </p>
  <p class="bekezd">
    Láthattuk, hogy sokféle lehetőség van <span class="programkod">Spliterator</span> létrehozására, bár szinte mindegyik valamilyen kompromisszum az implementáció egyszerűsége és az <span
      class="programkod">Spliterator</span>-t használó stream teljesítménye között. A legegyszerűbb, de legrosszabb teljesítményű megoldás a <span class="programkod">Spliterators.spliteratorUnknownSize</span>.
    Ez azért muzsikál gyengén párhuzamos feldolgozásnál, mert elvész a méretezési információ (mekkora az alatta lévő adathalmaz) és egyszerű particionálási algoritmusra lesz korlátozva. Egy
    nagyobb teljesítményű spliterator kiegyensúlyozott és ismert méretű szeleteket (split) ad pontos méretezési információval és számos egyéb jellemzővel, hogy optimalizálni lehessen a
    végrehajtást.
  </p>
  <p class="bekezd">
    A módosítható adatforrásokhoz való <span class="programkod">Spliterator</span>-oknak további kihívás a kötés időzítése. Az az ideális, amikor a stream-hez tartozó spliterator IMMUTABLE
    vagy CONCURRENT karakterisztikájú. Ha nem így van, akkor kései kötésűnek kell lennie. Ha egy forrás közvetlenül nem támogatja az ajánlott spliterator-t, akkor indirekt módon esetleg
    tudunk csinálni egyet a <span class="programkod">StreamSupport Supplier</span>-t elfogadó metódusaival. Az spliterator csak a lezáró művelet indulása után szerezhető meg a supplier-től.
  </p>
  <p class="bekezd">
    Ezek a követelmények (feltéve persze ha a közbülső viselkedési műveletek követelményeit is betartjuk) csökkentik a lehetséges interferenciát a forrás módosulásai és a stream csővezeték
    végrehajtása között. Az spliterator-okon alapuló stream-ek a kívánt karakterisztikával (vagy amelyek használják a <span class="programkod">Supplier</span>-alapú gyártófüggvényeket)
    immunisak a forrásnak a lezáró művelet indulását megelőző módosításaira.
  </p>
  <p class="bekezd">
    <b>Karakterisztikus problémák</b>
  </p>
  <p class="bekezd">
    A karakterisztikával kapcsolatos működés szépnek és kereknek tűnhet, de ha megnézzük az egyes stream műveletek által generált stream-ek tényleges karakterisztikáját, érdekes
    megfigyeléseket tehetünk. (A karakterisztikát úgy kapjuk meg, hogy a streamen meghívjuk a <span class="programkod">.spliterator().characteristics()</span> műveletet.)
  </p>
  <p class="bekezd">
    1. A <span class="programkod">Stream.empty()</span> és a paraméterek nélkül meghívott <span class="programkod">Stream.of()</span> is üres streamet adnak eredményül, de nem ugyanazzal a
    karakterisztikával:
  </p>
  <ul>
    <li><span class="programkod">Stream.empty()</span>: SIZED, SUBSIZED</li>
    <li><span class="programkod">Stream.of()</span>: IMMUTABLE, ORDERED, SIZED, SUBSIZED</li>
  </ul>
  <p class="bekezd">
    És miért nincs például NONNULL karakterisztikája egyiknek sem? Az lenne a logikus, ha a <span class="programkod">Stream.empty()</span> nem csak pluszban IMMUTABLE és ORDERED lenne hanem
    még DISTINCT és NONNULL is. És annak is lenne értelme ha mondjuk a <span class="programkod">Stream.of()</span> metódust egy paraméterrel meghívva DISTINCT lenne az eredmény.
  </p>
  <p class="bekezd">
    2. A paraméterekkel vagy azok nélkül meghívott <span class="programkod">IntStream.of()</span> (de ugyanúgy a <span class="programkod">LongStream</span> és <span class="programkod">DoubleStream</span>)
    által visszaadott stream-nek nincs NONNULL karakterisztikája. Miért nincs ha természete alapján eleve úgyse tud referencia típust tárolni?
  </p>
  <p class="bekezd">
    3. A <span class="programkod">boxed()</span> művelettel elvesznek karakterisztikák a primitív streamekből, pedig semmi ok nem lenne rá:
  </p>
  <ul>
    <li><span class="programkod">IntStream.range(1, 300)</span>: DISTINCT, IMMUTABLE, NONNULL, ORDERED, SIZED, SORTED, SUBSIZED</li>
    <li><span class="programkod">IntStream.range(1, 300).boxed()</span>: ORDERED, SIZED, SUBSIZED</li>
  </ul>
  <p class="bekezd">
    4. Az üres <span class="programkod">peek()</span> művelettel elvész a NONNULL és IMMUTABLE karakterisztika, pedig semmi ok nem lenne rá.
  </p>
  <p class="bekezd">
    5. A <span class="programkod">skip()</span>, <span class="programkod">limit()</span> elveszti a SUBSIZED, IMMUTABLE, NONNULL, SIZED karakterisztikákat, pedig a méret meghatározható.
  </p>
  <p class="bekezd">
    6. a <span class="programkod">filter()</span> művelet elveszti a SUBSIZED, IMMUTABLE, NONNULL, SIZED karakterisztikát. A másik ketttőnek még van is értelme, de miért veszti el az
    IMMUTABLE és NONNULL karakterisztikát is?
  </p>
  <p class="bekezd">
    Sajnos a Java 8 fejlesztői részéről ezekre a felvetésekre nem érkezett válasz. De azért nem maradtunk teljesen magyarázat nélkül, a stackoverflow egyik, a tűzhöz közelebb álló
    felhasználója <a onclick="window.open(this.href);return false;" href="https://stackoverflow.com/questions/46644595/understanding-deeply-spliterator-characteristics">segített kissé
      megvilágítani</a> a feltételezhető okokat és kiderült néhány turpisság.
  </p>
  <p class="bekezd">
    Ha megnézzük a karakterisztikák leírását (én is a Java 8 API <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html">dokumentációját</a> vettem alapul a fordításhoz), úgy tűnhet, hogy a pontos jelentésük még nem volt
    teljesen letisztázva a Java 8 implementációs fázisában és emiatt aztán következetlenül használták őket. Nézzük például mit mond a dokumentáció az IMMUTABLE karakterisztikáról: <i>&quot;azt
      jelzi, hogy az elemek forrását nem lehet szerkezetileg módosítani, vagyis nem lehet elemeket hozzáadni, kicserélni vagy eltávolítani, tehát ilyen módosítások nem történhetnek a
      bejárás során.&quot;</i>
  </p>
  <p class="bekezd">
    Itt eleve fura a kicserélni szó, hiszen az általában nem jelent szerkezeti módosítást amikor List-ről vagy tömbről beszélünk. A tömböt is elfogadó stream és spliterator gyárak (amelyek
    nem klónozzák) adnak is IMMUTABLE karakterisztikát. Ilyen a <span class="programkod">LongStream.of()</span> vagy az <span class="programkod">Arrays.spliterator(long[])</span>. Ha ezt
    általánosabban értelmezzük úgy, hogy &quot;amíg a kliens által nem észrevehető&quot;, akkor nincs jelentős különbség a CONCURRENT karakterisztikához képest. Mindkét esetben látható lesz
    néhány elem a kliens számára de nincs mód rá, hogy megtudjuk, ezeket a bejárás alatt adták-e hozzá és arra sem, hogy voltak-e olyan elemek amiket nem sikerült már bejárni mert közben
    eltávolították őket. A specifikáció viszont nem ér véget: <i>&quot;Olyan Spliterator aminek nincs IMMUTABLE vagy CONCURRENT tulajdonsága, speciális eljárásmódot igényel (például
      ConcurrentModificationException-t kell dobnia) amikor a bejárás során szerkezeti interferenciát észlelt.&quot;</i>
  </p>
  <p class="bekezd">Ez a lényeg: egy spliterator ami vagy IMMUTABLE vagy CONCURRENT karakterisztikát jelzett, garantáltan soha nem dob ConcurrentModificationException-t. A CONCURRENT
    kizárja a SIZED karakterisztikát is, de ennek nincs következménye a kliens kódra nézve.</p>
  <p class="bekezd">
    És itt jön a turpisság: ezek a karakterisztikák <b>semmire</b> nincsenek használva a Stream API-ban, vagyis inkonzisztens használatuk soha sehol nem fog kibukni! Ez a magyarázat arra,
    hogy miért törli ki az összes közbülső művelet a CONCURRENT, IMMUTABLE és NONNULL karakterisztikát: a Stream implementáció nem használja ezeket és a stream belső állapotát reprezentáló
    belső osztályok sem tárolják ezeket. A NONNULL sincs sehol sem használva, tehát a hiányának semmiféle hatása nincs.
  </p>
  <p class="bekezd">
    Az <span class="programkod">IntStream.of()</span> problémát (második) egészen az <span class="programkod">Arrays.spliterator(long[], int, int)</span> hívásig vissza lehet követni. Ez
    tovább hív a föntebb már megismert <span class="programkod">Spliterators.spliterator​(Object[] array array, int fromIndex, int toIndex, int additionalCharacteristics)</span>-be. Ennek a
    dokumentációja az fentebb leírtakon kívül még ezt is mondja: <i>&quot;A hívó hozzáadhat további karakterisztikákat a spliterator-hoz. Például ha ismert, hogy a tömböt nem fogják
      tovább módosítani, akkor megadhat IMMUTABLE-t.&quot;</i>
  </p>
  <p class="bekezd">
    Itt újra feltűnik az IMMUTABLE következetlen használata: úgy beszél a specifikáció, mintha ennek bármilyen módosítás hiányát kellene jeleznie. Az <span class="programkod">Arrays.spliterator</span>,
    <span class="programkod">Arrays.stream</span> és a <span class="programkod">LongStream.of()</span> viszont specifikáció szerint jelezni fog IMMUTABLE karakterisztikát, pedig ezek sem
    képesek garantálni, hogy a hívó nem fogja módosítani a tömbjét. Hacsak azt nem mondjuk, hogy egy elem beállítása nem szerkezeti módosítás, de akkor meg az egész megkülönböztetés
    értelmetlen, mivel a tömböket nem lehet szerkezetileg módosítani. A fenti dokumentáció továbbá egyértelműen nem mond NONNULL karakterisztikát, pedig nyilvánvaló, hogy a primitív értékek
    nem lehetnek null-ok.
  </p>
  <p class="bekezd">Ha figyelmen kívül hagyjuk az összes problémát a CONCURRENT, IMMUTABLE vagy NONNULL esetén (ezeknek úgysincs következménye), akkor a felsorolásból marad az ötödik
    probléma: a SIZED és a skip/limit. Ez ismert probléma és Stream API skip és limit implementációjából adódik. Ez igaz végtelen stream és a limit kombinációjára is, hiszen a limit után
    ott is véges és ismert a méret, de a jelenlegi implementáció mégsem ad ilyen karakterisztikát vissza.</p>
  <p class="bekezd">
    A <span class="programkod">boxed()</span> viselkedését (3. eset) könnyű megmagyarázni. Ezt naivan így implementálták: <span class="programkod">.mapToObj(Long::valueOf)</span>, tehát
    egyszerűen elveszt minden korábbi információt, mivel a mapToObj nem feltételezheti, hogy az eredmény továbbra is rendezett vagy distinct lesz. A Java 9-ben ezt egyébként már
    kijavították: ott a <span class="programkod">LongStream.range(0,10).boxed()</span>-nak már SUBSIZED|SIZED|ORDERED|SORTED|DISTINCT karakterisztikája van. (Egyébként néhány korán kiderült
    Java 8 problémát a későbbi frissítések is megoldottak, ezekről ezért nem is szólok ebben a cikkben. Ha el szeretnénk kerülni minél több JDK bugot, akkor érdemes minél frissebb verziót
    használni a Java 8-ból is.)
  </p>
  <h3>Stream műveletek összefoglalása</h3>
  <table cellpadding="0" cellspacing="0" width="98%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal" width="32%"><b>Stream létrehozása</b></th>
        <th class="normal" width="33%"><b>Közbülső művelet</b></th>
        <th class="normal"><b>Lezáró művelet</b></th>
      </tr>
    </thead>
    <tbody style="font-family: Courier New; font-size: 16px; text-align: justify; text-indent: 0px;">
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html">Collection</a></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html">BaseStream</a></td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/BaseStream.html">BaseStream</a></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">stream()</td>
        <td style="border-right: 1px solid white">sequential()</td>
        <td>iterator()</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">parallelStream()</td>
        <td style="border-right: 1px solid white">parallel()</td>
        <td>spliterator()</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white">unordered()</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html">Stream</a></td>
        <td style="border-right: 1px solid white">onClose(...)</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html">Stream</a></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a></td>
        <td style="border-right: 1px solid white"></td>
        <td>forEach(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html">LongStream</a></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html">Stream</a></td>
        <td>forEachOrdered(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/DoubleStream.html">DoubleStream</a></td>
        <td style="border-right: 1px solid white">filter(...)</td>
        <td>toArray(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static generate(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rendezetlen]</span></td>
        <td style="border-right: 1px solid white">map(..)</td>
        <td>reduce(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static of(...)</td>
        <td style="border-right: 1px solid white">mapToInt(...)</td>
        <td>collect(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static empty()</td>
        <td style="border-right: 1px solid white">mapToLong(...)</td>
        <td>min(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static iterate(...)</td>
        <td style="border-right: 1px solid white">mapToDouble(...)</td>
        <td>max(...)</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static concat(...)</td>
        <td style="border-right: 1px solid white">flatMap(...)</td>
        <td>count()</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static builder()</td>
        <td style="border-right: 1px solid white">flatMapToInt(...)</td>
        <td>anyMatch(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rövidzár]</span></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white">flatMapToLong(...)</td>
        <td>allMatch(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rövidzár]</span></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a></td>
        <td style="border-right: 1px solid white">flatMapToDouble(...)</td>
        <td>noneMatch(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rövidzár]</span></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html">LongStream</a></td>
        <td style="border-right: 1px solid white">distinct() <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[állapottartó]</span></td>
        <td>findFirst() <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rövidzár]</span></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static range(...)</td>
        <td style="border-right: 1px solid white">sorted() <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[állapottartó]</span></td>
        <td>findAny() <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rövidzár, nemdeterminisztikus]</span></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static rangeClosed(...)</td>
        <td style="border-right: 1px solid white">peek(...)</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white">limit(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[állapottartó,
            rövidzár]</span></td>
        <td rowspan="3" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">Az IntStream, LongStream és DoubleStream rendelkezik a Stream lezáró műveleteivel,
          de eltérő paraméterekkel. Alább a plusz metódusok.</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html">Arrays</a></td>
        <td style="border-right: 1px solid white">skip(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[állapottartó]</span></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static stream(...)</td>
        <td style="border-right: 1px solid white"></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;" rowspan="3">Az IntStream, LongStream és DoubleStream rendelkezik
          a Stream metódusaival, de eltérő paraméterekkel. Alább a plusz metódusok.</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/io/BufferedReader.html">BufferedReader</a></td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html">LongStream</a></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">lines()</td>
        <td><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/DoubleStream.html">DoubleStream</a></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a></td>
        <td>sum()</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/nio/file/Files.html">Files</a></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html">LongStream</a></td>
        <td>average()</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static lines(...)</td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/DoubleStream.html">DoubleStream</a></td>
        <td>summaryStatistics()</td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static list(...)</td>
        <td style="border-right: 1px solid white">boxed()</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static walk(...)</td>
        <td style="border-right: 1px solid white">mapToObj(...)</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static find(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/jar/JarFile.html">JarFile</a></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/LongStream.html">LongStream</a></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">stream()</td>
        <td style="border-right: 1px solid white">asDoubleStream()</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white">range(...)</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipFile.html">ZipFile</a></td>
        <td style="border-right: 1px solid white">rangeClosed(...)</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">stream()</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/IntStream.html">IntStream</a></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/regex/Pattern.html">Pattern</a></td>
        <td style="border-right: 1px solid white">asLongStream()</td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">splitAsStream(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/SplittableRandom.html">SplittableRandom</a></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">ints(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rendezetlen]</span></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">longs(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rendezetlen]</span></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">doubles(...) <span class="piros_kiemeles" style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">[rendezetlen]</span></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/Random.html">Random</a></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;"
          href="https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ThreadLocalRandom.html">ThreadLocalRandom</a></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">ints(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">longs(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">doubles(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/BitSet.html">BitSet</a></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">stream()</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/lang/CharSequence.html">CharSequence</a>
          (String)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">chars()</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">codePoints()</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white"><a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/util/stream/StreamSupport.html">StreamSupport</a>
          <span style="font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 14px;">(alacsony szintű)</span></td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static doubleStream(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static intStream(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static longStream(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
      <tr>
        <td style="border-right: 1px solid white">static stream(...)</td>
        <td style="border-right: 1px solid white"></td>
        <td></td>
      </tr>
    </tbody>
  </table>
  <h2>Lambdatrükk</h2>
  <p class="bekezd">Ebben a részben néhány olyan lambdákkal és stream-ekkel kapcsolatos érdekességet fogok bemutatni amelyek egy Java programozó életében mindennap felmerülhetnek.</p>
  <div class="keretes">
    <h3>Egy gondolat a kivételekről</h3>
    <p>
      <img alt="exception" class="jobb" src="/informatika/essze/java/java8/exception.jpg" /> Bár a Java 8 témájához nem kapcsolódik közvetlenül, de ezen a ponton érdemes felidézni egy kis
      érdekességet. Amint azt bizonyára mindenki tudja, a Java-ban minden eldobható kivétel a <span class="programkod">Throwable</span> osztályból származik. Először is két alcsoport:
    </p>
    <ul>
      <li><span class="programkod">Error</span> (és alosztályai): a fatális hibák jelzésére szolgál</li>
      <li><span class="programkod">Exception</span>: majdnem minden ami nem <span class="programkod">Error</span></li>
    </ul>
    <p>
      Az <span class="programkod">Exception</span> osztályból származnak a futásidejű kivételek (<span class="programkod">RuntimeException</span>). Ezekre némileg más szabályok vonatkoznak
      mint a többire. És itt lép be a Java egyik sajátossága, a kivételtípusok két csoportra osztása:
    </p>
    <p>
      <u>Ellenőrzött kivételek (checked exception)</u>: <span class="programkod">Exception</span> osztályból származó kivétel, de nem <span class="programkod">RuntimeException</span>
      leszármazott. Olyan hibákat reprezentálnak, amik a program aktuális végrehajtásán kívül eső érvénytelen állapotok (felhasználótól érkező hibás adat, adatbázis problémák, hálózati
      kiesések, hiányzó fájlok). A metódusoknak kötelező valahogyan kezelniük ezt a típust: jelezniük kell a throws záradékban vagy pedig helyileg kell kezelniük. Az ábrán zölddel jelölt.
    </p>
    <p>
      <u>Nem ellenőrzött kivételek (unchecked exception)</u>: <span class="programkod">Error</span> osztályból vagy pedig a <span class="programkod">RuntimeException</span> osztályból
      származik. Programhibákat jelez. A Java kitalálói szerint: &quot;nem ellenőrzött kivételek olyan állapotokat reprezentálnak, amik általánosságban véve a programunkban lévő hibákra
      utalnak és futásidőben ésszerűen nem kezelhetőek.&quot; A metódusoknak nem kötelező az aláírásukban a throws záradékban jelezni ezeket. (De megtehetik.) Az ábrán pirossal jelölt.
    </p>
    <p>A kivételek ilyen felosztása Java specialitás, más széles körben elterjedt nyelvben (C++, JavaScript, C#, Python, ObjectPascal) nincs ilyen csoportosítás. A Java-ban már régóta
      fennálló vallásháború, hogy szükség van-e erre a felosztásra vagy sem. A szakirodalomban a nem ellenőrzött kivételeket egyébként néha &quot;futásidejű kivételeknek&quot; is szokták
      nevezni (ami eléggé félrevezető).</p>
    <p>
      Mivel a <span class="programkod">Throwable</span> és az <span class="programkod">Exception</span> osztályok mindkét típusú osztálynak ősei, ezért ezek mindkét csoportba beletartoznak,
      vagyis egyszerre ellenőrzött és nem ellenőrzött kivételek is! Ez a tény nem tartozik a Java legkedveltebb tulajdonságai közé. Ha ugyanis <span class="programkod">Exception</span>-t
      vagy <span class="programkod">Throwable</span>-t
    </p>
    <ul>
      <li>dobok akkor biztosan nem lehet nem ellenőrzött</li>
      <li>elkapok akkor lehet, hogy nem ellenőrzött</li>
    </ul>
    <p>
      Ez azért van, mert amikor dobom, nem lehet nem ellenőrzött kivétel, mert az <span class="programkod">Exception</span> nem egyfajta <span class="programkod">RuntimeException</span>,
      tehát ellenőrzött. Amikor elkapok valamit Exception-ként akkor viszont lehet, hogy az objektum tényleges típusa <span class="programkod">RuntimeException</span> volt, mert az is <span
        class="programkod">Exception</span>. Ilyenkor tehát lehet, hogy nem ellenőrzött. De az is lehet, hogy az.
    </p>
    <p>
      Álljon itt mementóként egy rövidke példa ennek demonstrálására! Amikor egy <span class="programkod">Exception</span> típusú kivételt dobunk és a függvényben nem kapjuk el, akkor
      kötelező deklarálni a záradékban:
    </p>
    <div class="programkod">
      <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> NagyBuli {

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> buliVan(<span class="java_keyword">boolean</span> statusz) <span class="java_keyword">throws</span> Exception {
        <span class="java_keyword">if</span> (statusz) {
            System.<span class="java_constant">out</span>.println(<span class="java_string">"Buli van!"</span>);
        } <span class="java_keyword">else</span> {
            <span class="java_keyword">throw</span> <span class="java_keyword">new</span> Exception(<span class="java_string">"Baj van, nem buli!"</span>);
        }
    }

} </pre>
    </div>
    <p>
      Ha pedig deklarálunk <span class="programkod">Exception</span> típusú kivételt a függvény aláírásában, akkor az is ellenőrzött. A következő program nem fog lefordulni:
    </p>
    <div class="programkod">
      <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> NagyBuli {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">new</span> NagyBuli().buliVan(true);
    }

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> buliVan(<span class="java_keyword">boolean</span> statusz) <span class="java_keyword">throws</span> Exception {
        <span class="java_keyword">if</span> (statusz) {
            System.<span class="java_constant">out</span>.println(<span class="java_string">"Buli van!"</span>);
        } <span class="java_keyword">else</span> {
            <span class="java_keyword">throw</span> <span class="java_keyword">new</span> Exception(<span class="java_string">"Baj van, nem buli!"</span>);
        }
    }

} </pre>
    </div>
    <p>
      A <span class="programkod">main</span>-ben el kellene kapni az <span class="programkod">Exception</span> kivételt, vagy pedig itt is deklarálnunk kellene az aláírásban.
    </p>
    <p>Végül pedig egy Java API-val kapcsolatos bónusz kérdés! Az alábbi két kódsor két eltérő típusú kivételt dob. Mindkettő nem ellenőrzött, de itt most nem ez a lényeg, hanem az:
      vajon miért dob a két kódsor két különböző kivételt? Hiszen lényegében ugyanarra szolgálnak!</p>
    <div class="programkod">
      <pre>
Integer.parseInt(null); 
// throws java.lang.NumberFormatException: null
Double.parseDouble(null); 
// throws java.lang.NullPointerException </pre>
    </div>
    <p>Nos a válasz az, hogy azért, mert a JDK sem tökéletes. A két metódust két különböző fejlesztő csinálta és úgy látszik más elképzelésük volt róla, hogy mit kell ilyenkor dobni. (A
      nyájas olvasó nyugodtan kipróbálhatja, nem fog csalódni, valóban így viselkednek!)</p>
  </div>
  <h3>Ellenőrzött kivételek a funkcionális interfészekben</h3>
  <p class="bekezd">Talán eddig nem is tűnt fel, de a JDK-ban lévő funkcionális interfészek bizony nem támogatják az ellenőrzött kivételeket. Ez hamar kiderül, ha egy funkcionális
    interfésznek (vagy stream művelet viselkedési paraméterének) olyan lambdát akarunk megadni ami ellenőrzött kivételt dobhat.</p>
  <p class="bekezd">
    Tegyük fel, hogy egy CSV fájlban tároljuk a legóadatbázisunk adatait. A <span class="programkod">LegoSet</span> osztály kiegészül két metódussal (ezek törzsét most nem részletezem, a
    példához lényegtelen):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

[...]

<span class="java_keyword">public</span> <span class="java_keyword">class</span> LegoSet {

[...]

    <span class="java_blockcomment">/**</span>
    <span class="java_blockcomment">  * A LegoSet-et átalakítja CSV-ben tárolható sztringgé.</span>
    <span class="java_blockcomment">  * </span>
    <span class="java_blockcomment">  * </span><span class="java_blockcomment_param">@return</span><span class="java_blockcomment"> CSV-sor</span>
    <span class="java_blockcomment">  */</span>
    <span class="java_keyword">public</span> String toCsvString() {
        [...]
    }

    <span class="java_blockcomment">/**</span>
    <span class="java_blockcomment">  * Egy CSV-ben lévő sort alakít át LegoSet osztállyá</span>
    <span class="java_blockcomment">  * </span>
    <span class="java_blockcomment">  * </span><span class="java_blockcomment_param">@param</span><span class="java_blockcomment"> line</span>
    <span class="java_blockcomment">  *            CSV-ből beolvasott egy sor</span>
    <span class="java_blockcomment">  * </span><span class="java_blockcomment_param">@return</span><span class="java_blockcomment"> A CSV sornak megfelelő LegoSet osztály</span>
    <span class="java_blockcomment">  * </span><span class="java_blockcomment_param">@throws</span><span class="java_blockcomment"> LegoParseException:</span>
    <span class="java_blockcomment">  *             ellenőrzött kivételt dobunk, ha a paraméterként megadott sztring feldolgozása nem lehetséges</span>
    <span class="java_blockcomment">  */</span>
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> LegoSet parseFromCsvString(String line) <span class="java_keyword">throws</span> LegoParseException {
        [...]
    }
    
[...]

}</pre>
  </div>
  <p class="bekezd">
    A CSV fájl írása során rögtön problémába ütközünk: a <span class="programkod">BufferedWriter</span> nem használható! Ez a kód nem fordul le:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> saveToCSW(LegoDatabase database) {
    Path path = Paths.get(<span class="java_constant">FILENAME</span>);
    <span class="java_keyword">try</span> (BufferedWriter writer = Files.newBufferedWriter(path)) {
        Stream&lt;String&gt; stream = database.mySets.stream().map(set -&gt; set.toCsvString());
        stream.forEachOrdered(writer::write);
    } <span class="java_keyword">catch</span> (IOException ex) {
        <span class="java_comment">// <span class="java_todo">TODO</span> kivételkezelés</span>
        ex.printStackTrace();
    }
}</pre>
  </div>
  <p class="bekezd">
    Hiába van try-catch szerkezetben, a <span class="programkod">stream.forEachOrdered(writer::write)</span> sor hibás, mert a <span class="programkod">writer::write</span> metódus is <span
      class="programkod">IOException</span>-t szeretne dobni, de mivel a forEachOrdered paramétereként várt <span class="programkod">Consumer</span> funkcionális interfész accept metódusa
    nem deklarál semmilyen ellenőrzött kivételt, ezért a fordító nem tud metódusreferenciát gyártani <span class="programkod">Consumer</span> interfészhez. Ezt a problémát ugyan még meg
    tudjuk kerülni: már régóta létezik a <span class="programkod">PrintWriter</span> osztály, amivel szintén tudunk szöveges fájlt írni, de nem dob kivételt, hanem utólag tudjuk a
    sikerességet ellenőrizni. Ez a kód már lefordul (sőt, le is fut):
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> saveToCSW(LegoDatabase database) {
    Stream&lt;String&gt; stream = database.mySets.stream().map(set -&gt; set.toCsvString());
    <span class="java_keyword">try</span> (PrintWriter pw = <span class="java_keyword">new</span> PrintWriter(<span class="java_constant">FILENAME</span>, <span class="java_string">&quot;UTF-8&quot;</span>)) {
        stream.forEachOrdered(pw::println);
        <span class="java_keyword">if</span> (pw.checkError()) {
            <span class="java_comment">// <span class="java_todo">TODO</span> hibakezelés</span>
        }
    } <span class="java_keyword">catch</span> (FileNotFoundException | UnsupportedEncodingException e) {
        <span class="java_comment">// <span class="java_todo">TODO</span> kivételkezelés</span>
        e.printStackTrace();
    }
}</pre>
  </div>
  <p class="bekezd">Most már van CSV fájlunk. A problémát persze nem oldottuk meg, csak megkerültük, de meszire így se jutunk. A beolvasáshoz a parseFromCsvString metódust és a már
    ismerős I/O stream-et szeretnénk használni:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> loadFromCSW(LegoDatabase database) {
    Path path = Paths.get(<span class="java_constant">FILENAME</span>);
    <span class="java_keyword">try</span> (Stream&lt;String&gt; stream = Files.lines(path)) {
        database.mySets = stream.onClose(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Beolvasás kész!&quot;</span>)).map(LegoSet::parseFromCsvString).collect(Collectors.toList());
    } <span class="java_keyword">catch</span> (IOException ex) {
        ex.printStackTrace();
    }
}</pre>
  </div>
  <p class="bekezd">
    Ez a kód szintén nem fordul le, mégpedig ugyanazon okból amiért az előbbi <span class="programkod">BufferedWriter</span>-es sem (csak annyi a különbség hogy a map <span
      class="programkod">Function</span> funkcionális interfészt vár). A <span class="programkod">LegoSet::parseFromCsvString</span> a throws záradékban definiál <span class="programkod">LegoParseException</span>
    ellenőrzött kivételt, ezt viszont már nem tudjuk (és nem akarjuk) megkerülni. Az Eclipse Oxygen a map paraméterét kérésre ilyen szépséggé alakítja át (hibásan, mert a return-t meg
    kifelejti a kivétel után, tehát ez szintén nem fog lefordulni):
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> loadFromCSW(LegoDatabase database) {
    Path path = Paths.get(<span class="java_constant">FILENAME</span>);
    <span class="java_keyword">try</span> (Stream&lt;String&gt; stream = Files.lines(path)) {
        database.mySets = stream.onClose(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Beolvasás kész!&quot;</span>)).map(t -&gt; {
            <span class="java_keyword">try</span> {
                <span class="java_keyword">return</span> LegoSet.parseFromCsvString(t);
            } <span class="java_keyword">catch</span> (LegoParseException e) {
                e.printStackTrace();
            }
        }).collect(Collectors.toList());
    } <span class="java_keyword">catch</span> (IOException ex) {
        ex.printStackTrace();
    }
}</pre>
  </div>
  <p class="bekezd">Persze átalakíthatjuk a catch törzsét erre:</p>
  <div class="programkod">
    <pre>throw new RuntimeException(e); </pre>
  </div>
  <p class="bekezd">és így már nem ellenőrzött kivételt fog dobni, de ettől se lesz sokkal szebb a kód. Most már azt hiszem érthető, mi a probléma azzal, hogy a funkcionális interfészek
    nem támogatják az ellenőrzött kivételeket.</p>
  <p class="bekezd">Sajnos a problémára nincs kézenfekvő megoldás. Mindenképpen be kell vezetnünk egy olyan funkcionális interfészt ami tud kivételt dobni, viszont annak nem ellenőrzött
    kivételnek kell lennie, hogy a stream műveletek is elfogadják. A nyakatekertebbnél nyakatekertebb lehetőségek közül talán még az alábbi a leginkább átlátható.</p>
  <p class="bekezd">
    Csináljunk egy funkcionális interfészt, ami lényegében megfelel a <span class="programkod">Function</span>-nek, de tud bármilyen (ellenőrzött és nem ellenőrzött) kivételt dobni:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_annotation">@FunctionalInterface</span>
<span class="java_keyword">public</span> <span class="java_keyword">interface</span> FunctionWithThrows&lt;T, R&gt; {
    R apply(T t) <span class="java_keyword">throws</span> Exception;
} </pre>
  </div>
  <p class="bekezd">
    Ezután bevezetünk egy &quot;leképező&quot; függvényt ami ezt az új funkcionális interfészünket beteszi a JDK-beli <span class="programkod">Function</span>-be úgy, hogy az esetlegesen
    dobott kivételt <span class="programkod">RuntimeException</span> kivételként, vagyis nem ellenőrzöttként dobja tovább. Így a <span class="programkod">Function</span>-nál nem lesz baj a
    throws záradék hiányából:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;T, R&gt; Function&lt;T, R&gt; mapCheckedToUnchecked(FunctionWithThrows&lt;T, R&gt; wrappable) {
    <span class="java_keyword">return</span> t -&gt; {
        <span class="java_keyword">try</span> {
            <span class="java_keyword">return</span> wrappable.apply(t);
        } <span class="java_keyword">catch</span> (Exception exception) {
            <span class="java_keyword">throw</span> <span class="java_keyword">new</span> RuntimeException(exception);
        }
    };
}</pre>
  </div>
  <p class="bekezd">Így pedig már használhatjuk a stream-et:</p>
  <div class="programkod">
    <pre>database.mySets = stream
    .onClose(() -&gt; System.<span class="java_constant">out</span>.println("Beolvasás kész!"))
    .map(mapCheckedToUnchecked(LegoSet::parseFromCsvString))
    .collect(Collectors.toList()); </pre>
  </div>
  <p class="bekezd">
    Ha történetesen egy feladatban olyan műveletet kellene használnunk, ami <span class="programkod">Supplier</span> interfészt vár, de ott is ellenőrzött kivételt akarunk dobni, akkor még
    plusz interfész bevezetése sem szükséges, mert a JDK régóta meglévő <span class="programkod">Callable</span> funkcionális interfésze ugyanarra való, csak az már &quot;gyárilag&quot;
    rendelkezik <span class="programkod">throws Exception</span> záradékkal, tehát csak a leképező függvényre van szükségünk.
  </p>
  <p class="bekezd">
    Ez a megoldás - bár széles körben elterjedt - egyszerűsége ellenére elég csúnya. Az ellenőrzött kivételek épp arra valók, hogy a figyelmeztessék a kódolót olyan feltétel lehetséges
    bekövetkeztére, amit kezelnie kell. Ezzel a megoldással szépen kitöröljük ezt a figyelmeztetést és innentől kezdve nem ellenőrzött kivételekbe csomagolt ellenőrzött kivételek
    bukkanhatnak fel a kód bármely részén. Ráadásul a catch ágban most már nem is tudunk közvetlenül például <span class="programkod">LegoParseException</span> kivételre hivatkozni, hiszen
    olyat a mapper nem dob! (Legfeljebb várhatunk <span class="programkod">RuntimeException</span>-t és nézegethetjük, hogy mi van benne...) Tehát ez nem fordul le:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">try</span> {
    mapCheckedToUnchecked(s -&gt; {
        <span class="java_keyword">throw</span> <span class="java_keyword">new</span> LegoParseException();
    });
} <span class="java_keyword">catch</span> (LegoParseException lpe) { <span class="java_comment">// ilyen kivétel definíció szerint sosem dobódik
                                   // a try blokkból - legalábbis a fordító szerint</span>
    lpe.printStackTrace();
} </pre>
  </div>
  <p class="bekezd">Nos ez utóbbi problémán lehet valamennyire segíteni, ha például a stream-et átvisszük egy olyan metóduson ami nem csinál mást, mint visszaadja a paraméterét és ezen
    kívül még van throws záradéka. Vagyis lényegében az egyik helyről eltüntetett throws záradékot átrakjuk máshová... Az ötlet alapja tehát egy ilyen metódus:</p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;R, E <span class="java_keyword">extends</span> Exception&gt; Stream&lt;R&gt; of(<span
        class="java_keyword">final</span> Stream&lt;R&gt; stream) <span class="java_keyword">throws</span> E {
    <span class="java_keyword">return</span> stream;
} </pre>
  </div>
  <p class="bekezd">
    Ezután pedig csak azt kellene megoldani, hogy a mapper most már ne a <span class="programkod">RuntimeException</span>-be csomagolt kivételt dobja tovább. Java 8-cal ez is lehetséges!
  </p>
  <p class="bekezd">
    A Java 8 a kivételek típuskövetkeztetésében is bevezetett ugyanis némi változtatást. Ez a változás <a onclick="window.open(this.href);return false;"
      href="https://stackoverflow.com/questions/31316581/a-peculiar-feature-of-exception-type-inference-in-java-8">másoknak</a> is feltűnt. A stackoverflow bejegyzést író kolléga ugyanis a
    következő furcsaságot tapasztalta:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">static</span> <span class="java_keyword">void</span> sneakyTest() {
    Exception e = <span class="java_keyword">new</span> Exception();
    sneakyThrow(e);<span class="java_comment"> // itt nincs gond</span>
    nonSneakyThrow(e);<span class="java_comment"> // nem fog lefordulni, mert: 
                       // &quot;unreported exception Exception; must be caught or declared to be thrown&quot;</span>
}

<span class="java_keyword">static</span> &lt;T <span class="java_keyword">extends</span> Throwable&gt; <span class="java_keyword">void</span> sneakyThrow(Throwable t) <span
        class="java_keyword">throws</span> T {
    <span class="java_keyword">throw</span> (T) t;
}

<span class="java_keyword">static</span> &lt;T <span class="java_keyword">extends</span> Throwable&gt; <span class="java_keyword">void</span> nonSneakyThrow(T t) <span class="java_keyword">throws</span> T {
    <span class="java_keyword">throw</span> t;
}</pre>
  </div>
  <p class="bekezd">Mi teszi lehetővé, hogy a sneakyThrow hívásban dobott kivétel nem ellenőrzött, a másikban dobott viszont igen? (Ezeket a speciális eseteket, amikor ellenőrzött
    kivételből nem ellenőrzöttet csinálunk, az angol oldalakon általában sneaky throw-nak hívják.)</p>
  <p class="bekezd">
    A 8-as Java <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html">nyelvi specifikáció</a> nevű könnyed délutáni
    olvasmányban van a megfejtés (18.1.3-as és 18.4-es fejezet): a throws záradék valójában csak a metódus metaadata, futás közben semmiféle szerepe nincs. Ha pedig a típuskövetkeztetés
    során azt látja a fordító, hogy a T típusa Exception, Throwable vagy Object, akkor automatikusan RuntimeException-nek, vagyis nem ellenőrzött kivételnek fogja tekinteni. Mivel
    ellenőrzött kivételeket csak a fordító kezel, ha lefordult a kód, onnantól kezdve nagy probléma nincs. Ez a 8-as Java előtt még nem így volt: egy 7-es fordító a sneakyThrow hívásnál is
    panaszkodna arra, hogy a metódus által adott throws záradék kivétele nincs elkapva.
  </p>
  <p class="bekezd">
    De mi is történik a 8-as Java esetén? Nézzük először a nonSneakyThrow hívást: a fordító látja, hogy a metódust <span class="programkod">Exception</span> típusú paraméterrel hívjuk meg.
    A <span class="programkod">T</span> típusparaméter deklarációjának (<span class="programkod">T extends Exception</span>) ez megfelel, tehát nincs probléma. A fordító ki tudja
    következtetni azt is, hogy ha az átadott paraméter <span class="programkod">T==Exception</span> típusú, akkor a <span class="programkod">throws T</span> záradékban is <span
      class="programkod">Exception</span> típus a <span class="programkod">T</span>. Azt pedig tudjuk, hogy az <span class="programkod">Exception</span> (és a <span class="programkod">Throwable</span>)
    ha dobjuk, akkor ellenőrzött kivétel. Mivel a nonSneakyThrow ezek szerint ellenőrzött kivételt dob, de a hívó azt nem kapja el, ezért nem fog lefordulni az a sor.
  </p>
  <p class="bekezd">
    Na és mi van a sneakyThrow metódussal? Az első trükk, hogy átírtuk a paramétert <span class="programkod">T</span> típusról <span class="programkod">Exception</span> típusra. A hívás
    során nem lesz probléma, az aktuális paraméter is <span class="programkod">Exception</span> típusú. A fordító azonban semmiből nem tudja kikövetkeztetni a <span class="programkod">T</span>
    típusparaméter aktuális értékét! Annyit tud, hogy az <span class="programkod">Exception</span>. A fenti definíció szerint viszont tudjuk, hogy ez esetben azt Java 8 óta a fordító
    automatikusan <span class="programkod">RuntimeException</span>-nek fogja tekinteni. Vagyis a <span class="programkod">throws T</span> záradék <b>a fordító szerint</b> nem ellenőrzött
    kivételt fog dobni, tehát nem kötelező elkapni. A kód pedig lefordul. A fenti definíció értelmében akkor is lefordul, ha mondjuk a metódus definíciójában a <span class="programkod">T</span>
    típusparamétert <span class="programkod">Throwable</span>-ből származtatjuk. Ha viszont a paraméterben átírjuk az <span class="programkod">Exception t</span> definícót <span
      class="programkod">T t</span>-re akkor már az első eset lép érvénybe: a fordító már ki tudja következtetni, hogy a <span class="programkod">T</span> aktuális típusa Exception, vagyis
    onnantól ellenőrzött kivétel lesz, tehát ez sem fog lefordulni.
  </p>
  <p class="bekezd">Tehát a sneakyThrow metódust így is hívhatjuk, mert az IOExceptzion ellenőrzött kivételből is nem ellenőrzöttet csinál:</p>
  <div class="programkod">
    <pre>IOException e = new IOException();
sneakyThrow(e); </pre>
  </div>
  <p class="bekezd">
    Visszatérve a problémára: ezzel a megoldással ki tudjuk cselezni a fordítót, hogy immár valóban <span class="programkod">LegoParseException</span> dobódjon a mapCheckedToUnchecked
    metódusból. Az átláthatóság kedvéért tegyük ki most már az egész kivételekkel foglalkozó kódot egy külön osztályba:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8.lego;

<span class="java_keyword">import</span> java.util.stream.Stream;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> ExceptionMapper {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;R, E <span class="java_keyword">extends</span> Exception&gt; Stream&lt;R&gt; of(Stream&lt;R&gt; stream) <span
        class="java_keyword">throws</span> E {
        <span class="java_keyword">return</span> stream;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;R, E <span class="java_keyword">extends</span> Exception&gt; R rethrow(Exception ex) <span
        class="java_keyword">throws</span> E {
        <span class="java_keyword">throw</span> (E) ex;
    }

    <span class="java_annotation">@FunctionalInterface</span>
    <span class="java_keyword">public</span> <span class="java_keyword">interface</span> FunctionWithThrows&lt;R, T&gt; {
        R apply(T t) <span class="java_keyword">throws</span> Exception;
    }

    <span class="java_keyword">public</span> <span class="java_keyword">static</span> &lt;R, T&gt; R mapCheckedToUnchecked(FunctionWithThrows&lt;R, T&gt; wrappable, T t) {
        <span class="java_keyword">try</span> {
            <span class="java_keyword">return</span> wrappable.apply(t);
        } <span class="java_keyword">catch</span> (Exception ex) {
            <span class="java_keyword">return</span> rethrow(ex);
        }
    }

} </pre>
  </div>
  <p class="bekezd">
    Meghagytuk a map-hez szükséges <span class="programkod">FunctionWithThrows</span> funkcionális interfészt, a lambda kifejezést most már újra a hívó adja meg, itt pedig már nem <span
      class="programkod">RuntimeException</span>-t fogunk dobni, hanem <span class="programkod">Exception</span>-t. Ezt a típustörléssel meg tudjuk tenni a rethrow metódusban. Bár a fordító
    panaszkodni fog: <i>&quot;Note: hu\egalizer\java8\lego\ExceptionMapper.java uses unchecked or unsafe operations.&quot;</i> de rá se rántsunk, mert működik. Ezzel a következőképp tudjuk
    átírni a CSV beolvasó metódusunkat:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> loadFromCSW(LegoDatabase database) {
    Path path = Paths.get(<span class="java_constant">FILENAME</span>);
    <span class="java_keyword">try</span> (Stream&lt;String&gt; stream = Files.lines(path)) {
        database.mySets = ExceptionMapper.&lt;String, LegoParseException&gt; of(stream).onClose(() -&gt; System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Beolvasás kész!&quot;</span>))
                .map(s -&gt; ExceptionMapper.mapCheckedToUnchecked(LegoSet::parseFromCsvString, s)).collect(Collectors.toList());
    } <span class="java_keyword">catch</span> (IOException | LegoParseException ioe) {
        ioe.printStackTrace();
    }
}</pre>
  </div>
  <p class="bekezd">
    Vagyis az <span class="programkod">ExceptionMapper.&lt;String, LegoParseException&gt; of(stream)</span> kifejezés miatt fel kell vennünk a catch ágba a <span class="programkod">LegoParseException</span>-t,
    viszont azt a mapCheckedToUnchecked fogja dobni sima <span class="programkod">Exception</span>-né szárítva, tehát nem ellenőrzötté (unchecked) téve. A catch pedig már el tudja kapni
    (hiszen futás közben nem számít a nem ellenőrzött-ellenőrzött különbség). Az <span class="programkod">ExceptionMapper</span> osztályt ezek után már igény szerint bármilyen funkcionális
    interfész bevezetésével ki tudjuk egészíteni.
  </p>
  <h3>Generikus kötések</h3>
  <p class="bekezd">Egy generikus kifejezés több kötéssel is definiálható az &amp; jellel: &lt;T extends A &amp; B &amp; C &amp; ... Z&gt;. Ezt a fajta generikus paraméterdefiníciót
    ritkán használjuk, viszont korlátai miatt van némi hatása a lambda kifejezésekre:</p>
  <ul>
    <li>az elsőt kivéve minden kötésnek interfésznek kell lenni</li>
    <li>az osztály nyers verziója ebből generikus definícióból csak az első kötést veszi figyelembe</li>
  </ul>
  <p class="bekezd">A második megszorítás eltérő módon viselkedik fordítási időben és futásidőben, amikor a lambda kifejezések befűzése megtörténik. Ezt a következő példakóddal elő
    lehet hozni:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.function.IntConsumer;
<span class="java_keyword">import</span> java.util.function.IntSupplier;

<span class="java_keyword">final</span> <span class="java_keyword">class</span> MutableInteger <span class="java_keyword">extends</span> Number <span class="java_keyword">implements</span> IntSupplier, IntConsumer {
    <span class="java_keyword">private</span> <span class="java_keyword">int</span> value;

    <span class="java_keyword">public</span> MutableInteger(<span class="java_keyword">int</span> value) {
        <span class="java_keyword">this</span>.value = value;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">int</span> intValue() {
        <span class="java_keyword">return</span> value;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">long</span> longValue() {
        <span class="java_keyword">return</span> value;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">float</span> floatValue() {
        <span class="java_keyword">return</span> value;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">double</span> doubleValue() {
        <span class="java_keyword">return</span> value;
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">int</span> getAsInt() {
        <span class="java_keyword">return</span> intValue();
    }

    <span class="java_annotation">@Override</span>
    <span class="java_keyword">public</span> <span class="java_keyword">void</span> accept(<span class="java_keyword">int</span> value) {
        <span class="java_keyword">this</span>.value = value;
    }
} </pre>
  </div>
  <div class="programkod">
    <pre>
<span class="java_keyword">package</span> hu.egalizer.java8;

<span class="java_keyword">import</span> java.util.Arrays;
<span class="java_keyword">import</span> java.util.Collection;
<span class="java_keyword">import</span> java.util.List;
<span class="java_keyword">import</span> java.util.OptionalInt;
<span class="java_keyword">import</span> java.util.function.IntSupplier;

<span class="java_keyword">public</span> <span class="java_keyword">class</span> MultipleBoundMess {
    <span class="java_keyword">public</span> <span class="java_keyword">static</span> <span class="java_keyword">void</span> main(String[] args) {
        <span class="java_keyword">final</span> List&lt;MutableInteger&gt; integers = Arrays.asList(<span class="java_keyword">new</span> MutableInteger(1), <span class="java_keyword">new</span> MutableInteger(2));
        <span class="java_keyword">final</span> <span class="java_keyword">int</span> min = findMin(integers).orElse(Integer.<span class="java_constant">MIN_VALUE</span>);
        System.<span class="java_constant">out</span>.println(min);
    }

    <span class="java_keyword">static</span> &lt;T <span class="java_keyword">extends</span> Number &amp; IntSupplier&gt; OptionalInt findMin(<span class="java_keyword">final</span> Collection&lt;T&gt; elements) {
        <span class="java_keyword">return</span> elements.stream().mapToInt(IntSupplier::getAsInt).min();
    }

} </pre>
  </div>
  <p class="bekezd">
    A kód hibátlan és sikeresen le is fordul. A <span class="programkod">MutableInteger</span> osztály kielégíti a <span class="programkod">T</span> többszörös kötését:
  </p>
  <ul>
    <li>a Number-től örököl</li>
    <li>az IntSupplier-t implementálja</li>
  </ul>
  <p class="bekezd">Futás közben azonban kivételt kapunk:</p>
  <div class="programkod">
    <pre>Exception in thread "main" java.lang.BootstrapMethodError: call site initialization exception
  at java.lang.invoke.CallSite.makeSite(CallSite.java:341)
  at java.lang.invoke.MethodHandleNatives.linkCallSiteImpl(MethodHandleNatives.java:307)
  at java.lang.invoke.MethodHandleNatives.linkCallSite(MethodHandleNatives.java:297)
  at hu.egalizer.java8.MultipleBoundMess.findMin(MultipleBoundMess.java:17)
  at hu.egalizer.java8.MultipleBoundMess.main(MultipleBoundMess.java:12)
Caused by: java.lang.invoke.LambdaConversionException: Invalid receiver type class java.lang.Number; not a subtype of implementation type interface java.util.function.IntSupplier
  at java.lang.invoke.AbstractValidatingLambdaMetafactory.validateMetafactoryArgs(AbstractValidatingLambdaMetafactory.java:233)
  at java.lang.invoke.LambdaMetafactory.metafactory(LambdaMetafactory.java:303)
  at java.lang.invoke.CallSite.makeSite(CallSite.java:302)
  ... 4 more</pre>
  </div>
  <p class="bekezd">
    A kivételt azért kapjuk, mert egy stream csővezetéke csak nyers típust fogad, ami a findMin metódus generikus definíciója szerint <span class="programkod">Number</span>, hiszen az az
    első. Ez pedig nem implementálja közvetlenül az <span class="programkod">IntSupplier</span>-t. A probléma kiküszöbölhető egy paraméter típus explicit definíciójával a metódus
    referenciához használt különálló metódusban:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private</span> <span class="java_keyword">static</span> <span class="java_keyword">int</span> getInt(<span class="java_keyword">final</span> IntSupplier supplier) {
    <span class="java_keyword">return</span> supplier.getAsInt();
}

<span class="java_keyword">static</span> &lt;T <span class="java_keyword">extends</span> Number &amp; IntSupplier&gt; OptionalInt findMin(<span class="java_keyword">final</span> Collection&lt;T&gt; elements) {
    <span class="java_keyword">return</span> elements.stream().mapToInt(MultipleBoundMess::getInt).min();
} </pre>
  </div>
  <h2>Eszközök és konkurencia</h2>
  <p class="bekezd">
    <b>Kibővített tömbfeldolgozás</b>
  </p>
  <p class="bekezd">
    A Java 8 a régi jó <span class="programkod">java.util.Arrays</span> osztályba 50 új metódust hozott. Ezek 6 csoportra oszthatók:
  </p>
  <ul>
    <li><b>stream létrehozás</b>: a 8 új stream metódussal közvetlenül létre tudunk hozni tömbből primitív típusos vagy osztállyal paraméterezett streamet. Ezekre az előző fejezetekben
      már volt példa.</li>
    <li><b>spliterator létrehozás</b>: a 8 új stream metódussal közvetlenül létre tudunk hozni tömbből primitív típusos vagy osztállyal paraméterezett spliteratort. Ezekre az előző
      fejezetekben már volt példa.</li>
    <li><b>setAll</b>: 3 primitív (a szokásos <span class="programkod">double</span>, <span class="programkod">int</span>, <span class="programkod">long</span>) vagy osztály típusú
      tömb feltöltése. A feltöltésről a metódus második paramétereként megadott funkcionális interfész gondoskodik. Az alábbi példában egy int tömböt töltünk fel, ahol az adott elem 0 lesz
      ha az indexe páros és 1 ha az indexe páratlan. A feltöltés után ki is írjuk a tömböt:</li>
  </ul>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span>[] intArr = new <span class="java_keyword">int</span>[100];
Arrays.setAll(intArr, i -&gt; i % 2);
Arrays.stream(intArr).forEach(System.<span class="java_constant">out</span>::println);</pre>
  </div>
  <ul>
    <li><b>parallelSetAll</b>: a setAll párhuzamos végrehajtású megfelelője többmagos processzorokhoz. Ha a setAll vagy parallelSetAll végrehajtása közben a generator kifejezés
      kivételt dob, akkor az tovább dobódik a hívónak, a tömb pedig részben feltöltetlen állapotban marad.</li>
    <li><b>parallelSort</b>: a korábban már meglévő sort rendezőfüggvény párhuzamos végrehajtású megfelelője. Mivel sort-ból minden primitív típushoz volt külön paraméterezhető
      változat, ezért a Java feljesztői úgy gondolták, hogy parallelSort-ot is mindegyikhez csinálnak.</li>
    <li><b>parallelPrefix</b>: egy tömb elemeinek halmozott feldolgozása a megadott kétoperandusú operátorral. A legegyszerűbb példa:</li>
  </ul>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span>[] intArr = new <span class="java_keyword">int</span>[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Arrays.parallelPrefix(intArr, (a, b) -&gt; a + b);</pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">intArr</span> tartalma a művelet után:
  </p>
  <pre class="programkod">[1, 3, 6, 10, 15, 21, 28, 36, 45, 55]</pre>
  <p class="bekezd">A parallelPrefix tehát bejárja a tömböt és két operandust vár, az első a tömb aktuális eleme, a második pedig a következő, elvégzi rajtuk az operátort, majd az
    eredménnyel lecseréli a második elemet. A műveletnek asszociatívnak és mellékhatás-mentesnek kell lennie, mert a párhuzamos feldolgozást csak így lehet elvégezni. Ha ennek nem felel
    meg, akkor a metódus futásának végeredménye; vagyis a tömb tartalma meghatározatlan lesz. A parallelPrefix is a három primitív típusra és T osztály típusú tömbre van implementálva. Ha a
    műveletet csak a tömb egy résztömbjére szeretnénk elvégezni, azt is megtehetjük ha megadjuk a kívánt tömbindexeket:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span>[] intArr = new <span class="java_keyword">int</span>[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Arrays.parallelPrefix(intArr, 5, 8, (a, b) -&gt; a + b);</pre>
  </div>
  <p class="bekezd">Eredmény:</p>
  <pre class="programkod">[1, 2, 3, 4, 5, 6, 13, 21, 9, 10]</pre>
  <p class="bekezd">
    <b>Jobb HashMap</b>
  </p>
  <p class="bekezd">
    A <a onclick="window.open(this.href);return false;" href="https://hu.wikipedia.org/wiki/Szolgáltatásmegtagadással_járó_támadás">DoS támadások</a> egyik kedvenc módszere, hogy a
    szervereknek tömegesen küldenek olyan <span class="programkod">String</span> objektumokat, amelyeknek ugyanaz a hash kódja. Ez a támadási módszer nem túl jól érintette a Java 7 <span
      class="programkod">HashMap</span> implementációját, de a Java 8-ban ezt is megváltoztatták: belsőleg már nem lista, hanem bináris fa tárolja az azonos hash kódú elemeket. Ez
    ellenállóbbá tette a DoS támadásokkal szemben. Nem árt tudni, hogy az új implementáció csak akkor működik igazán hatékonyan, ha a HashTáblánk kulcsai implementálják a <span
      class="programkod">Comparable</span> interfészt (mivel a kulcsok általában <span class="programkod">String</span>-ek, ezzel legtöbbször nincs is teendő). (De akárhogy is van, még
    mindig igaz, hogy sok, azonos hashCode értéket adó kulcs használata a biztos mód bármilyen HashMap implementáció teljesítményének lerontására.)
  </p>
  <p class="bekezd">
    <b>StringJoiner</b>
  </p>
  <p class="bekezd">
    Az új <span class="programkod">java.util.StringJoiner</span> segítségével könnyen lehet olyan karaktersorozatokat létrehozni, amiket egy határoló karakter választ el és lehetőség van
    arra is, hogy az eredmény megadott prefixszel kezdődjön és megadott utótaggal (suffix) végződön. Ha még nem adtunk semmit a <span class="programkod">StringJoiner</span>-hez, a toString
    metódusa alapértelmezetten a prefix+suffix összeget fogja visszaadni. Kivéve ha a setEmptyValue metódussal beállítunk egy üres értéket, mert akkor a megadott <span class="programkod">emptyValue</span>
    értéket adja vissza.
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// StringJoiner létrehozása: a konstruktornak megadjuk az elválasztót, elő- és utótagot.</span>
<span class="java_comment">// Az elő- és utótag el is hagyható.</span>
StringJoiner sj = <span class="java_keyword">new</span> StringJoiner(<span class="java_string">&quot;;&quot;</span>, <span class="java_string">&quot;[&quot;</span>, <span
        class="java_string">&quot;]&quot;</span>);
sj.add(<span class="java_string">&quot;első&quot;</span>).add(<span class="java_string">&quot;második&quot;</span>).add(<span class="java_string">&quot;harmadik&quot;</span>);
String vegeredmeny = sj.toString();<span class="java_comment">// eredmény: [első;második;harmadik]</span> </pre>
  </div>
  <p class="bekezd">
    A merge metódussal két <span class="programkod">StringJoiner</span>-t lehet összefűzni, ilyenkor a paraméter elő- és utótagja figyelmen kívül lesz hagyva:
  </p>
  <div class="programkod">
    <pre>StringJoiner sj = <span class="java_keyword">new</span> StringJoiner(<span class="java_string">&quot;;&quot;</span>, <span class="java_string">&quot;[&quot;</span>, <span
        class="java_string">&quot;]&quot;</span>);
sj.add(<span class="java_string">&quot;első&quot;</span>).add(<span class="java_string">&quot;második&quot;</span>).add(<span class="java_string">&quot;harmadik&quot;</span>);

StringJoiner sj2 = <span class="java_keyword">new</span> StringJoiner(<span class="java_string">&quot;,&quot;</span>, <span class="java_string">&quot;{&quot;</span>, <span
        class="java_string">&quot;}&quot;</span>);
sj2.add(<span class="java_string">&quot;negyedik&quot;</span>).add(<span class="java_string">&quot;ötödik&quot;</span>).add(<span class="java_string">&quot;hatodik&quot;</span>);

sj.merge(sj2);
String vegeredmeny = sj.toString();<span class="java_comment">// eredmény: [első;második;harmadik;negyedik,ötödik,hatodik]</span> </pre>
  </div>
  <h3>Konkurencia</h3>
  <p class="bekezd">
    A Java 8 nem hagyta érintetlenül a <span class="programkod">java.util.concurrent</span> csomagot sem. A változások nagy része a lambdákhoz és a streamekhez kötődik, de sokat javult a
    konkurencia kezelés is:
  </p>
  <ul>
    <li>bekerült a <span class="programkod">java.util.concurrent.ForkJoinPool</span> osztályba a közös pool támogatása
    </li>
    <li>új <span class="programkod">ForkJoinTask</span> implementáció: <span class="programkod">java.util.concurrent.CountedCompleter</span> (A Java 7-ben megjelent Fork/Join
      keretrendszer <a onclick="window.open(this.href);return false;" href="http://www.coopsoft.com/ar/CalamityArticle.html">erős kritikákat kapott</a> iparági szakértőktől, ezért ezt az új
      osztályt nem tárgyalom ebben a cikkben.)
    </li>
    <li>4 új osztály került a <span class="programkod">java.util.concurrent.atomic</span> csomagba a többszálú környezetben használható frissíthető változók támogatásához
    </li>
    <li>teljesen újraírták a <span class="programkod">java.util.concurrent.ConcurrentHashMap</span> osztályt
    </li>
    <li>bekerült a <span class="programkod">java.util.concurrent</span> csomagba az új <span class="programkod">CompletableFuture</span> osztály és <span class="programkod">CompletionStage</span>
      interfész
    </li>
    <li>új <span class="programkod">java.util.concurrent.locks.StampedLock</span> osztály hárommódú képesség-alapú zárolás biztosításához a read/write elérések vezérléséhez
    </li>
  </ul>
  <p class="bekezd">
    Egyébként a legtöbb, konkurenciával foglalkozó régi és új osztályt is a stream fejezetben már megismert Douglas Lea professzor írta. (Ilyen például a <span class="programkod">StampedLock</span>,
    a <span class="programkod">CompletableFuture</span> és társai, a <span class="programkod">java.util.concurrent.atomic</span> csomag nagy része, a <span class="programkod">ForkJoinPool</span>,
    az <span class="programkod">Executor</span>, stb.)
  </p>
  <p class="bekezd">
    <b>Common pool</b>
  </p>
  <p class="bekezd">
    Java 8-ban a <span class="programkod">java.util.concurrent.ForkJoinPool</span> három új metódussal lett gazdagabb. Az <span class="programkod">awaitQuiescence(long timeout,
      TimeUnit unit)</span> metódussal arra várakozhatunk, hogy a pool-ban lévő minden taszk <span class="programkod">isQuiescent()</span> állapota <span class="programkod">true</span> legyen
    (vagyis mindegyik idle állapotba kerüljön). Ez esetben az awaitQuiescence is <span class="programkod">true</span> értékkel tér vissza. Ha viszont a paraméterben megadott timeout ideig
    várt és még nem minden taszk került ebbe az állapotba akkor <span class="programkod">false</span>-t ad vissza.
  </p>
  <p class="bekezd">
    A másik újdonság a globális, általános használatú közös pool támogatása. Egy <span class="programkod">java.util.concurrent.ForkJoinPool</span> példány megszerzéséhez immár a
    legegyszerűbb mód a statikus <span class="programkod">ForkJoinPool.commonPool()</span> metódus meghívása. A közös pool párhuzamossági beállítását (ami alapértelmezetten az elérhető
    processzormagok száma) a szintén statikus <span class="programkod">getCommonPoolParallelism()</span> metódussal kérdezhetjük le. Konstruktorokkal persze továbbra is létrehozhatunk saját
    poolt. A később részletezendő szintén Java 8 újdonságnak számító <span class="programkod">CompletableFuture</span> osztály például erősen épít a <span class="programkod">ForkJoinPool.commonPool()</span>
    használatára.
  </p>
  <p class="bekezd">
    <b>Akkumulátor és adder</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">java.util.concurrent.atomic</span> csomagba bekerült négy új osztály amelyek segítik a szálak közötti adatok kezelését:
  </p>
  <ul>
    <li>DoubleAccumulator</li>
    <li>DoubleAdder</li>
    <li>LongAccumulator</li>
    <li>LongAdder</li>
  </ul>
  <p class="bekezd">
    Alább a két <span class="programkod">long</span> kezelő osztályt mutatom be, de az elmondottak értelemszerűen vonatkoznak a <span class="programkod">double</span> kezelőkre is.
  </p>
  <p class="bekezd">
    Tegyük fel, hogy a legó nyilvántartó programot webes felületűvé alakítjuk. A felhasználók kézzel felvehetik saját készleteiket. Van egy üzenőfal, ahol folyamatosan látható, hogy milyen
    elemszámú volt az aznap addig felvett legnagyobb elemszámú készlet. Vagyis több szál által kezelt értékek közül kellene folyamatosan számon tartanunk a példa szerint épp a legnagyobbat.
    Ehhez tárolnunk kell egy olyan változót amit minden szál elér és frissíthet (legnagobb elemszám) és bizonyos időben lekérdezzük az értékét. Erre volt jó régen az <span
      class="programkod">AtomicLong</span>, amiben szálbiztosan tudtunk tárolni egy értéket. Nagy számú versengő szálnál viszont az <span class="programkod">AtomicLong</span> nem túl
    hatékony, mert a frissítés idején zárolja a belső állapotát. Ezért jött a négy új típus.
  </p>
  <p class="bekezd">
    A <span class="programkod">LongAccumulator</span> típussal egy ilyen feladatot hatékonyabban meg tudunk oldani, ráadásul lambda kifejezéseket is tudunk használni. Az accumulator a
    külvilág felé egy, belsőleg egy vagy több változót tárol, amivel fenntart egy adott függvénnyel módosítható <span class="programkod">long</span> (vagy <span class="programkod">double</span>)
    értéket. Amikor módosítások (az <span class="programkod">accumulate(long)</span> metódus) versenyeznek egymással a szálak között, a belsőleg tárolt változók halmaza dinamikusan nőhet
    így csökkentve a versengést. A <span class="programkod">get()</span> (vagy ennek megfelelően a <span class="programkod">longValue()</span>) metódus visszaadja az aktuális értéket amit a
    frissítések által fenntartott változókból számít.
  </p>
  <p class="bekezd">
    Ezt az osztályt érdemes alkalmazni az <span class="programkod">AtomicLong</span> helyett amikor több szál használ és frissít egy közös értéket amit statisztika gyűjtésére nem pedig a
    szinkronizáció finomvezérlésére használunk. Alacsony versengés esetén a két osztálynak hasonló a működési karakterisztikája. Nagy versengés esetén viszont a <span class="programkod">LongAccumulator</span>
    osztálynak nagyobb az áteresztőképessége azon az áron, hogy nagyobb a tárhely felhasználása is.
  </p>
  <p class="bekezd">
    Az akkumulálás sorrendje viszont nem garantált a szálak között és nem lehet rá logikát építeni. Ez az osztály tehát csak olyan függvényekhez használható, ahol az akkumulálás sorrendje
    lényegtelen. A megadott accumulator függvénynek mellékhatás-mentesnek kell lennie, mert újra meghívódhat amikor a frissítések kísérletei sikertelenek a szálak közötti versengés miatt.
    Az osztály a függvényt az aktuális érték mint első paraméterrel hívja meg és a megadott frissítéssel mint második paraméter. Egy frissített maximáils érték tárolásához például <span
      class="programkod">Long::max</span>-al érdemes példányosítani <span class="programkod">Long.MIN_VALUE</span> identity értékkel.
  </p>
  <p class="bekezd">
    Mind a négy új osztály a <span class="programkod">Number</span>-ből származik, de nem definiálják az equals, hashCode és compareTo metódusokat mert a példányok mutable tulajdonságúak és
    így nem használhatók collection-ökben kulcsként.
  </p>
  <p class="bekezd">A legónyilvántartó megoldásához tehát kell egy közös mező amit minden szál elér:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public static final</span> LongAccumulator <span class="java_constant">counter</span> = <span class="java_keyword">new</span> LongAccumulator(Long::max, 0);</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">counter</span> fogja tárolni a legnagyobb elemszámot. A konstruktornak megadunk egy kifejezést, amivel két érték közül el lehet dönteni, melyiket hagyjuk meg
    (a példában <span class="programkod">Long::max</span>) és megadunk egy kezdőértéket is (0). Most így tudjuk immár szálbiztosan minden requesten belül frissíteni az értékét:
  </p>
  <div class="programkod">
    <pre>
<span class="java_constant">counter</span>.accumulate(set.getPieceCount());</pre>
  </div>
  <p class="bekezd">
    A már ismert <span class="programkod">set.getPieceCount()</span> adja meg az aktuálisan felvett készlet elemszámát. A kiíráshoz pedig le tudjuk kérdezni az aktuálisan akkumulált értéket
    (a lekérdezéshez többféle visszatérési típusú metódusból is választhatunk.):
  </p>
  <div class="programkod">
    <pre>
<span class="java_constant">counter</span>.get()</pre>
  </div>
  <p class="bekezd">
    Tehát először létrejön a <span class="programkod">counter</span> 0 kezdőértékkel. Az accumulate metódus különböző szálakból meghívódhat. Ha a híváskor a belsőleg tárolt előző érték
    éppen frissítés alatt van egy másik szálból (vagyis zárolt), akkor megpróbálkozik a következő tárolt érték frissítésével. Ha ilyen nincs, akkor egy új bejegyzés jön létre az új érték
    tárolására. A keretrendszer megpróbálja minden egyes híváskor alkalmazni a konstruktornak átadott kifejezést, de ha más szálak épp blokkolják a hívást, akkor nem várakozik rájuk, hanem
    feladja a kísérletet. Amikor végül meghívjuk a <span class="programkod">get()</span> metódust, akkor a keretrendszer végigmegy az összes tárolt értéken és mindegyikre páronként
    alkalmazza a kifejezést is kiszámítja a végértéket. Mivel ez a számítás nem garantálja a sorrendet, ezért a lambda kifejezésünknek asszociatívnak kell lennie! Ez a módszer nagy
    sebességbeli előnyt jelenthet a korábbi Atomic típusokhoz.
  </p>
  <p class="bekezd">
    A <span class="programkod">LongAdder</span> pedig lényegében a <span class="programkod">LongAccumulator</span> specializált változata. Csak paraméter nélküli konstruktora van ami 0
    kezdőértékkel létrehoz egy osztályt. A <span class="programkod">new LongAdder()</span> kifejezés ekvivalens a <span class="programkod">new LongAccumulator((x, y) -&gt; x + y, 0L)</span>
    kifejezéssel. A következő műveletei vannak:
  </p>
  <ul>
    <li><b>add</b>: long érték hozzáadása</li>
    <li><b>increment</b>: ekvivalens az add(1) művelettel</li>
    <li><b>sum</b>: az aktuális összeg lekérdezése</li>
    <li><b>reset</b>: visszaállítja az értéket 0-ra</li>
    <li><b>sumThenReset</b>: sum aztán reset</li>
    <li><b>decrement</b>: ekvivalens az add(-1) művelettel</li>
  </ul>
  <h3>ConcurrentHashMap</h3>
  <p class="bekezd">
    A népszerű <span class="programkod">java.util.concurrent.ConcurrentHashMap</span> osztályt teljesen újraírták és 43 új metódus került bele.
  </p>
  <div class="keretes">
    <p>
      A <span class="programkod">ConcurrentHashMap</span> a <span class="programkod">Hashtable</span> szálbiztos változata és egyébként a konkurencia kezelésén kívül teljesen felcserélhető
      a kettő. De mire is jó?
    </p>
    <p>
      Tegyük fel, hogy biztosítani kell a szálbiztosságot és előre tudjuk, hogy nagyon sok lesz az olvasás de kevés az írás. A <span class="programkod">HashMap</span> gyors megoldás, de
      sajnos nem szálbiztos. A <span class="programkod">Hashtable</span> ismerői tudják, hogy ilyen feladatra az sem tökéletes. Bár szálbizos, de elég rosszul muzsikál többszálú
      környezetben, mert az összes metódusa - a <span class="programkod">get()</span>-et is beleértve - <span class="programkod">synchronized</span>. Így bármely metódushívás esetén a
      többi, ugyanazt az objektumot használó szálnak várakoznia kell míg az aktuális hívás be nem fejeződik. Tehát egy írási művelet esetén az összes olvasás várakozik még akkor is ha nem
      is ugyanazzal a kulcssal foglalkoznak. Nos itt jön be a <span class="programkod">ConcurrentHashMap</span> ami szintén szálbiztos, de a lekérdező műveletekben nincs zárolás és nem is
      támogatja a teljes tábla zárolását.
    </p>
  </div>
  <p class="bekezd">
    A <span class="programkod">ConcurrentHashMap</span> korábbi, Java 7-es implementációja a zárolás optimalizálása érdekében ún. szegmenseket használt egy tömbbe rendezve:
  </p>
  <pre class="programkod">final Segment&lt;K,V&gt;[] segments;</pre>
  <p class="bekezd">
    A szegmens tartalmazta a <span class="programkod">HashEntry</span>-k tömbjét és egy szegmensből zárolás nélkül lehetett olvasni. Java 8-ban a szegmenseket felváltotta a <span
      class="programkod">Node</span>, ami már közvetlenül tartalmazza a kulcs-érték párokat és láncolt listába van szervezve:
  </p>
  <pre class="programkod">transient volatile Node&lt;K,V&gt;[] table;
...
    static class Node&lt;K,V&gt; implements Map.Entry&lt;K,V&gt; {
        final int hash;
        final K key;
        volatile V val;
        volatile Node&lt;K,V&gt; next;
...</pre>
  <p class="bekezd">
    A <span class="programkod">Node</span> tábla bejegyzései az első beillesztéskor, lustán értékelődnek ki. Mindegyik bejegyzés a többitől függetlenül zárolható úgy, hogy a bejegyzés első
    <span class="programkod">Node</span>-ját zároljuk (ezt persze maga az implementáció megcsinálja nekünk). A módosítási versengés minimálisra lett szorítva (az újraíráskor ez volt a fő
    cél). A lekérdező művelet (get) pedig már nem zárolja a map-et és a módosító műveletekkel (put, remove) átfedésben futhat. Egy lekérdezés a legutoljára befejezett módosítás eredményét
    adja vissza.
  </p>
  <p class="bekezd">
    A következőket érdemes észben tartani a <span class="programkod">ConcurrentHashMap</span> használatakor:
  </p>
  <ul>
    <li>az aggregáló állapot-metódusok eredménye (size, isEmpty, containsValue) általában csak akkor pontos amikor más szálakból nincsenek konkurens módosítási műveletek. Egyébként
      ezen metódusok átmeneti állapotról adnak eredményt, ami megfelelő lehet monitorozásra vagy különböző becslésekre, de program vezérlésére nem. Ha a konkurens módosításokat szigorú
      kontroll alatt tartjuk, akkor az aggregált státusz is megbízhatóbb eredményt ad.</li>
    <li>sok azonos hash kódú kulcs használata biztos módja a teljesítmény lecsökkentésének (de ez bármely hashtáblára igaz). Ha a beillesztések során túl sok ütközés van (például olyan
      kulcsok esetén amiknek különböző a hash kódjuk, de a ugyanabba a bejegyzésbe esnek) akkor a tábla dinamikusan kiterjesztődik.</li>
    <li>az iterátorokat úgy tervezték, hogy csak egy szál használhassa őket. Ezek a hash táblának az iterátor létrehozása utáni valamely állapotát fogják feldolgozni és nem dobnak <span
      class="programkod">ConcurrentModificationException</span> kivételt.
    </li>
    <li>a hash táblák átméretezése elég költséges művelet ezért érdemes a konstruktornak egy becsült kezdeti méretet (<span class="programkod">initialCapacity</span>) megadni (az
      alapértelmezett kezdeti táblakapacitás 16). A <span class="programkod">loadFactor</span> és a <span class="programkod">concurrencyLevel</span> konstruktor paraméterek csak a
      visszamenőleges kompatibilitás miatt léteznek.
    </li>
    <li>a leképező függvényekkel (compute, merge) nem árt óvatosan bánni: bár megcsinálják a leképezést, de a megadott map kifejezéseket érdemes gyorsnak, rövidnek és egyszerűnek írni,
      hogy elkerüljük a váratlan zárolásokat</li>
    <li>a <span class="programkod">ConcurrentHashMap</span> kulcsai nincsenek rendezve, tehát amikor rendezésre van szükségünk, a <span class="programkod">ConcurrentSkipListMap</span>
      típust érdemes használni
    </li>
    <li><span class="programkod">null</span> használata kulcsként vagy értékként továbbra sem megengedett</li>
  </ul>
  <p class="bekezd">
    A <span class="programkod">ConcurrentHashMap</span> háromféle tömeges műveletet támogat: forEach, search, reduce. Ezek biztonságosan és többnyire ésszerűen működnek még olyan map-eknél
    is amiket konkurensen módosít több szál. Mindhárom műveletnek négyféle változata van, amelyek funkcionális interfészeket (általában <span class="programkod">Function</span>) várnak
    kulcs, érték, (kulcs,érték) vagy <span class="programkod">Map.Entry</span> paraméterekkel (tömeges műveleteknél a <span class="programkod">Map.Entry</span> objektumok nem támogatják a
    setValue metódust). A paraméterként megadott függvények helyessége nem szabad, hogy bármilyen rendezettségtől függjön sem pedig bármi olyan értéktől ami megváltozhat míg a számítás
    folyamatban van. Emellett asszociatívnak és kommutatívnak kell lennie. A forEach műveleteket kivéve pedig nem árt, ha mellékhatástól is mentesek.
  </p>
  <p class="bekezd">
    A tömeges műveletek várnak egy <span class="programkod">parallelismThreshold</span> paramétert is. Ha az aktuális map méret becsülhetően kisebb mint ez a küszöbérték, akor mindenképen
    soros lesz a feldolgozás. A párhuzamosságot a <span class="programkod">Long.MAX_VALUE</span> paraméterrel iktathatjuk ki. 1 paraméterrel pedig maximális párhuzamosságot kapunk, ilyenkor
    a keretrendszer annyi partíciót hoz létre hogy teljesen kihasználja a <span class="programkod">ForkJoinPool.commonPool()</span>-t.
  </p>
  <p class="bekezd">
    Alább kifejtem a <span class="programkod">ConcurrentHashMap</span> összes új funkcióját.
  </p>
  <p class="bekezd">
    <u>mappingCount</u>: visszaadja a map méretét. Ezt ajánlott használni a size helyett, mivel <span class="programkod">long </span>a visszatérési értéke és a <span class="programkod">ConcurrentSkipListMap
      int</span>-nél nagyobb elemszámot is képes kezelni. A metódus eredménye inkább csak hozzávetőleges érték, hiszen a lekérdezés közben is lehetnek folyamatban lévő módosítások.
  </p>
  <p class="bekezd">
    <u>computeIfAbsent</u>: korábban is létezett már a putIfAbsent metódus ami akkor illesztett be egy kulcs-érték párost ha a kulcs még nem szerepelt a map-ben. A Java 8 elhozta a
    computeIfAbsent metódust: ha a megadott kulcs még nincs a map-ben, akkor ez megpróbálja kiszámítani az értékét a paraméterként adott mappingFunction funkcionális interfésszel. Ha az
    eredmény nem <span class="programkod">null</span>, akkor beteszi a map-be a kulccsal. Az egész metódushívás atomi, tehát a függvény egyszerre legfeljebb egy kulcsot fog a map-be tenni.
    A más szálaktól érkező módosító műveletek blokkolódhatnak a végrehajtás során ezért a számítást olyan egyszerűre és rövidre írjuk amennyire csak lehet. És természetesen nem szabad, hogy
    a map többi elemére hatással legyen.
  </p>
  <p class="bekezd">Tekintsük a következő példakódot:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">private</span> Map&lt;String, Object&gt; myLittleMap = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;();
...
<span class="java_keyword">public</span> Object getAndCreateIfAbsent(String key) {
    Object value = myLittleMap.get(key);
    <span class="java_keyword">if</span> (value == <span class="java_keyword">null</span>) {
        value = <span class="java_keyword">new</span> Object();
        myLittleMap.put(key, value);
    }
    <span class="java_keyword">return</span> value;
}</pre>
  </div>
  <p class="bekezd">
    A példa getAndCreateIfAbsent metódus megnézi, szerepel-e már a <span class="programkod">myLittleMap</span>-ben a lekérdezni kívánt kulcs. Ha igen, visszaadja az értékét, ha nem akkor
    létrehozza majd visszaadja. Bár a konkurenciával a map-ben nincs gond, a fenti metódussal viszont lesz ha több külön szálból is meghívjuk. Könnyen galiba történhet ha véletlenül több
    szál vezérlése egyszerre ér az <span class="programkod">if</span> kifejezéshez. Ha a metódust <span class="programkod">synchronized</span>-dá tesszük az megoldja a problémát, de Java
    8-ban a szép megoldás már a computeIfAbsent metódus használata, amivel az egész getAndCreateIfAbsent metódust kiválthatjuk:
  </p>
  <div class="programkod">
    <pre>Object value = myLittleMap.computeIfAbsent(key, k -&gt; <span class="java_keyword">new</span> Object());</pre>
  </div>
  <p class="bekezd">
    Ez a kód rövidebb és tisztább, szárazabb, biztonságosabb érzést ad! A példa ráadásul arra is rávilágít, hogy a <span class="programkod">ConcurrentHashMap</span> önmagában nem
    csodafegyver: az sem mindegy, hogyan használjuk.
  </p>
  <p class="bekezd">
    A computeIfAbsent és egy <span class="programkod">LongAdder</span> segítségével a skálázható, gyakoriságot számoló map-ek használata is egyszerű lesz. Legyen egy <span
      class="programkod">freqs</span> map-ünk:
  </p>
  <div class="programkod">
    <pre>ConcurrentHashMap&lt;String, LongAdder&gt; freqs = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;();</pre>
  </div>
  <p class="bekezd">Ebben sztringek előfordulási gyakoriságát fogjuk számolni. Egy sztring hozzáadását a következő sor szépen lekezeli:</p>
  <div class="programkod">
    <pre>freqs.computeIfAbsent(<span class="java_string">&quot;új sztring&quot;</span>, k -&gt; <span class="java_keyword">new</span> LongAdder()).increment();</pre>
  </div>
  <p class="bekezd">
    <u>computeIfPresent</u>: akkor számol ki új értéket a remappingFunction paraméterrel, ha a megadott kulcs már létezik a collection-ben. Ha a remappingFunction eredménye null, akkor a
    kulcs törlődik a map-ből. Itt is célszerű az átadott lambda kifejezést egyszerűnek írni.
  </p>
  <p class="bekezd">
    <u>compute</u>: az előzőekkel ellentétben ez nem foglalkozik vele, hogy a kulcs létezik-e már a map-ben vagy sem. A <span class="programkod">remappingFunction</span> függvénnyel
    kiszámol egy új értéket és beteszi a megadott kulccsal. (Ha a kiszámolt érték netalán <span class="programkod">null</span> lesz és korábban volt azzal a kulccsal más akkor meg törli
    azt.) A compute esetén a <span class="programkod">remappingFunction</span> egy <span class="programkod">BiFunction</span>, ahol az első paraméter a kulcs, a második a korábbi érték (ha
    volt):
  </p>
  <div class="programkod">
    <pre>Map&lt;String, Object&gt; myMap = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;();
...
Object computed = myMap.compute(<span class="java_string">&quot;kulcs&quot;</span>, (k, v) -&gt; calculateValue());</pre>
  </div>
  <p class="bekezd">
    <u>merge</u>: a compute metódusban nekünk kell kezelni azt az esetet, ha eltérő értéket szeretnénk amikor a map még nem tartalmazza az adott kulcsot és ha igen. A merge erre ad
    megoldást: ha a kulcs még nem szerepelt a map-ban akkor a második paraméter lesz az értéke. Ha már szerepelt, akkor hívódik meg a <span class="programkod">remappingFunction</span>. Ez
    paraméterként megkapja a map-ben lévő korábbi értéket és a merge-nek megadott újat. Az eredményével felülíródik a map-ben lévő korábbi érték.
  </p>
  <div class="programkod">
    <pre>Map&lt;String, Integer&gt; myIntMap = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;();
...
myIntMap.merge(<span class="java_string">&quot;kulcs&quot;</span>, 2, (v1, v2) -&gt; v1 * v2);</pre>
  </div>
  <p class="bekezd">
    Ha a &quot;kulcs&quot; még nem szerepelt a <span class="programkod">myIntMa</span>p-ben akkor 2 értékkel betesszük, egyébként a korábbi értéket (<span class="programkod">v1</span>)
    megszorozzuk <span class="programkod">v2</span>-vel (ami ez esetben 2).
  </p>
  <p class="bekezd">
    <u>forEach</u>: a <span class="programkod">ConcurrentHashMap</span> 9 új forEach típusú metódust is kapott. Ezek 4 típusra oszthatók:
  </p>
  <p class="bekezd">1. forEach</p>
  <ul>
    <li><u>forEach(BiConsumer&lt;? super K,? super V&gt; action)</u>: a map összes elemén végrehajtja a megadott eljárást a bejegyzések sorrendjében (ha közben kivétel dobódik akkor
      megáll félúton)</li>
    <li><u>forEach(long parallelismThreshold, BiConsumer&lt;? super K,? super V&gt; action)</u>: az előző kiegészítve a <span class="programkod">parallelismThreshold</span>
      paraméterrel</li>
    <li><u>forEach(long parallelismThreshold, BiFunction&lt;? super K,? super V,? extends U&gt; transformer, Consumer&lt;? super U&gt; action)</u>: hasonló az előzőhöz, annyi
      eltéréssel, hogy minden elemre előbb végrehajtódik a <span class="programkod">transformer BiFunction</span> majd ha annak az eredménye nem <span class="programkod">null</span>, akkor
      arra az <span class="programkod">action</span>. A következő példában a legóadatbázisunkat használjuk fel. Előbb átalakítjuk <span class="programkod">ConcurrentHashMap</span>-pé aztán
      a <span class="programkod">LegoSet</span> osztályban tárolt készleteinkből a transzformáció során kivesszük a nevet, majd kiírjuk az eredményt:</li>
  </ul>
  <div class="programkod">
    <pre>ConcurrentHashMap&lt;String, LegoSet&gt; myMap = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;(mySets.stream().collect(Collectors.toMap(LegoSet::getName, (set) -&gt; set)));
myMap.forEach(Long.<span class="java_constant">MAX_VALUE</span>, (k, v) -&gt; <span class="java_string">&quot;A készletünk neve: &quot;</span> + v.getName(), System.<span
        class="java_constant">out</span>::println);</pre>
  </div>
  <p class="bekezd">
    2. forEachEntry: az előzőekhez képest annyi a különbség, hogy az <span class="programkod">action</span> itt nem kulcsot és értéket kap paraméterként, hanem egy <span class="programkod">Map.Entry</span>
    példányt.
  </p>
  <ul>
    <li><u>forEachEntry(long parallelismThreshold, Consumer&lt;? super Map.Entry&lt;K,V&gt;&gt; action)</u></li>
    <li><u>forEachEntry(long parallelismThreshold, Function&lt;Map.Entry&lt;K,V&gt;,? extends U&gt; transformer, Consumer&lt;? super U&gt; action)</u></li>
  </ul>
  <p class="bekezd">
    3. forEachKey: az <span class="programkod">action</span> itt kulcsot kap paraméterként
  </p>
  <ul>
    <li><u>forEachKey(long parallelismThreshold, Consumer&lt;? super K&gt; action)</u></li>
    <li><u>forEachKey(long parallelismThreshold, Function&lt;? super K,? extends U&gt; transformer, Consumer&lt;? super U&gt; action)</u></li>
  </ul>
  <p class="bekezd">4. forEachValue: az action itt értéket kap paraméterként</p>
  <ul>
    <li><u>forEachValue(long parallelismThreshold, Consumer&lt;? super V&gt; action)</u></li>
    <li><u>forEachValue(long parallelismThreshold, Function&lt;? super V,? extends U&gt; transformer, Consumer&lt;? super U&gt; action)</u></li>
  </ul>
  <p class="bekezd">
    <u>getOrDefault(Object key, V defaultValue)</u>: lekérdezés, ami a <span class="programkod">defaultValue</span>-t adja vissza ha a <span class="programkod">key</span> nem szerepel a
    collection-ünkben, egyébként pedig a hozzá tartozó értéket.
  </p>
  <p class="bekezd">
    <u>search(long parallelismThreshold, BiFunction&lt;? super K,? super V,? extends U&gt; searchFunction)</u>: a legegyszerűbb keresés. Végigmegy a map-ben lévő bejegyzéseken, átadja a
    kulcsokat és értékeket a paraméterben megadott <span class="programkod">searchFunction</span>-nek. Amint annak a visszatérési értéke nem <span class="programkod">null</span>, a keresés
    megáll és visszaadja a <span class="programkod">searchFunction</span> eredményét (vagy <span class="programkod">null</span>-t ha egyáltalán nem volt ilyen). A <span class="programkod">parallelismThreshold</span>
    már ismert paraméter. A <span class="programkod">searchEntries</span> metódus <span class="programkod">searchFunction</span>-je bejegyzéseket kap, a <span class="programkod">searchKeys</span>
    metódus esetén kulcsokat, a <span class="programkod">searchValues</span> esetén pedig értékeket.
  </p>
  <p class="bekezd">
    <u>replaceAll(BiFunction&lt;? super K,? super V,? extends V&gt; function)</u>: a map összes kulcs-érték párjával meghívja a <span class="programkod">function</span> paramétert és annak
    eredményével lecseréli a korábbi értéket.
  </p>
  <p class="bekezd">
    <u>reduce</u>: a <span class="programkod">ConcurrentHashMap</span> 19 redukciós metódust tartalmaz. A redukció is várja a már ismert <span class="programkod">parallelismThreshold</span>
    paramétert. Emellett ezek a metódusok várnak egy redukciós kifejezést is ami összegzi a transzformált elemeket. A redukciós kifejezés nem függhet a rendezettségtől.
  </p>
  <p class="bekezd">A redukciós metódusok a következő csoportokba oszthatók:</p>
  <ul>
    <li>normál redukció: ennek a metódusnak a változatai egy <span class="programkod">reducer</span> kifejezést várnak, ami akkumulálja a metódustól függően: kulcsokat, értékeket vagy
      <span class="programkod">Map.Entry</span> bejegyzéseket
    </li>
    <li>leképezett redukció: egy <span class="programkod">transformer</span> kifejezést várnak az elemek átalakításához és egy <span class="programkod">reducer</span> kifejezést az
      átalakított elemek összegzéséhez. A normál redukcióhoz hasonlóan kulcs, érték párt feldolgozó <span class="programkod">BiFunction</span> funkcionális interfészt vár. De létezik
      bejegyzéseket (<span class="programkod">Map.Entry</span>), kulcsokat vagy értékeket feldolgozó változata is. Ha a <span class="programkod">transformer null</span> eredményt ad
      valamely elemnél, az nem vesz részt a redukcióban.
    </li>
  </ul>
  <p class="bekezd">Az alábbi példa szintén a legóadatbázisunkat használja: összeadja az összes készletünk elemeinek számát:</p>
  <div class="programkod">
    <pre>ConcurrentHashMap&lt;String, LegoSet&gt; myMap = <span class="java_keyword">new</span> ConcurrentHashMap&lt;&gt;(mySets.stream().collect(Collectors.toMap(LegoSet::getName, (set) -&gt; set)));
<span class="java_keyword">int</span> count = myMap.reduce(Long.<span class="java_constant">MAX_VALUE</span>, (k, v) -&gt; v.getPieceCount(), (v1, v2) -&gt; v1 + v2);</pre>
  </div>
  <ul>
    <li>a leképezett redukció speciális formája, amikor a <span class="programkod">transformer</span> függvény skalár <span class="programkod">double</span>, <span class="programkod">long</span>
      vagy <span class="programkod">int</span> értéket eredményez, majd a <span class="programkod">reducer</span> függvény ezeket akkumulálja. Ezek a metódusok várnak egy skalár <span
      class="programkod">basis</span> kezdőértéket is.
    </li>
  </ul>
  <p class="bekezd">
    A fenti példa skaláris <span class="programkod">transformer</span> kifejezést használva 0 kezdőértékkel:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">int</span> count = myMap.reduceToInt(Long.<span class="java_constant">MAX_VALUE</span>, (k, v) -&gt; v.getPieceCount(), 0, (v1, v2) -&gt; v1 + v2);</pre>
  </div>
  <p class="bekezd">
    És természetesen mindebből létezik szintén <span class="programkod">Entry</span>-ket, kulcsokat vagy értékeket feldolgozó változat is.
  </p>
  <p class="bekezd">
    <b>ConcurrentHashSet</b>
  </p>
  <p class="bekezd">
    A Java 8-ban ugyan nincs különálló <span class="programkod">ConcurrentHashSet</span>, de azért létrehozhatunk ilyet a <span class="programkod">ConcurrentHashMap</span> új statikus <span
      class="programkod">newKeySet()</span> vagy <span class="programkod">newKeySet(int)</span> metódusaival (ez utóbbival az alapértelmezett méretet is meg tudjuk adni):
  </p>
  <div class="programkod">
    <pre>Set&lt;String&gt; hashSet = ConcurrentHashMap.newKeySet();</pre>
  </div>
  <p class="bekezd">
    A korábban is létező keySet(Object) példánymetódussal a map-ünk <span class="programkod">Set</span> leképezését tudtuk megadni ahol csak a kulcsok voltak az érdekesek, a leképezett
    értékek nem voltak használatban vagy mindegyik ugyanolyan értéket tartalmazott. A newKeySet statikus metódusok viszont üres <span class="programkod">Set</span>-et adnak vissza.
    Valójában a háttérben ezek is <span class="programkod">ConcurrentHashMap</span>-re építenek, de az értékek belsőleg <span class="programkod">Boolean</span> típusúak.
  </p>
  <p class="bekezd">
    <b>ConcurrentSkipListMap</b>
  </p>
  <p class="bekezd">
    Java 8-ban a <span class="programkod">ConcurrentSkipListMap</span> is változott, ez is megkapta a <span class="programkod">ConcurrentHashMap</span> új metódusai közül a következőket:
  </p>
  <ul>
    <li>compute</li>
    <li>computeIfAbsent</li>
    <li>computeIfPresent</li>
    <li>forEach</li>
    <li>getOrDefault</li>
    <li>merge</li>
    <li>replaceAll</li>
  </ul>
  <p class="bekezd">Ezeket fentebb már tárgyaltam, a működésük itt is ugyanaz.</p>
  <h3>StampedLock</h3>
  <div class="keretes">
    <p>
      Bizonyára mindenki ismeri a Java zárolási technikáit, de ha valaki esetleg mégsem, annak itt egy nagyon gyors összefoglaló (természetesen csak a felszínt kapargatva). Ennek fényében
      érthetőbb lesz a <span class="programkod">StampedLock</span>.
    </p>
    <p>
      Legismertebb a <span class="programkod">synchronized</span> kulcsszó. Ezzel metódusokat vagy kódblokkokat tudunk megjelölni és a JVM egy monitornak hívott technika segítségével
      biztosítja, hogy a synchronized kódot egyszerre csak egy szál tudja végrehajtani (a többiek addig a <span class="programkod">synchronized</span> utasításnál várakoznak). A <span
        class="programkod">synchronized</span> újrahívható (reentrant) ami azt jelenti, hogy az aktuálisan benne futó szál újra meg tudja hívni ugyanazt a metódust anélkül hogy holtpontot
      kapnánk.
    </p>
    <p>
      Mivel a <span class="programkod">synchronized</span> nem minden feladatra optimális, a <span class="programkod">Lock</span> interfész segítségével a JDK már eddig is biztosított más
      lehetőségeket is a zárolásra. A <span class="programkod">ReentrantLock</span> a <span class="programkod">synchronized</span>-hoz hasonló működésű kölcsönösen kizáró zárolás. Ahogy a
      neve is sugallja, ez is újrahívható. Használatának mintája:
    </p>
    <div class="programkod">
      <pre>Lock lock = <span class="java_keyword">new</span> ReentrantLock();
...
lock.lock();
<span class="java_keyword">try</span> {
    <span class="java_comment">// zárolást igényló kódrészlet</span>
} <span class="java_keyword">finally</span> {
    lock.unlock();
}</pre>
    </div>
    <p>
      A zárolást a <span class="programkod">lock()</span>, a feloldást az <span class="programkod">unlock()</span> metódus végzi. Egyszerre csak egy szál szerezhet zárolást, minden további
      szál a <span class="programkod">lock()</span> hívásnál várakozik, hogy az épp végrehajtó befejezze a feladatát. Fontos, hogy a zárolást igénylő kód <span class="programkod">try</span>-<span
        class="programkod">catch</span> blokkban legyen, különben bármely kivétel esetén a feloldás nem történne meg. Ez a fenti kódrészlet ugyanúgy szálbiztos, mint a <span
        class="programkod">synchronized</span> kulcsszóval megvalósított, viszont a fejlesztőtől több odafigyelést igényel. Ugyanakkor nagyobb rugalmasságot is ad, ráadásul a <span
        class="programkod">ReentrantLock</span> számos metódust biztosít a tulajdonságai lekérdezésére. Bővebben nem megyek bele, ez már az 1.5-ös Java óta létezik.
    </p>
    <p>
      A <span class="programkod">ReadWriteLock</span> egy másik típusú zárolás interfész, szintén az 1.5-ös Java óta. Az volt az alapötlete, hogy általában biztonságos dolog konkurensen
      olvasni módosítható változókat addig míg senki nem akarja azt írni is. Az olvasási zárolást ezért egyszerre több szál is megtarthatja, míg egyetlen szál sem kér írási zárolást. Ez
      növelheti az áteresztőképességet ha az olvasások sokkal gyakoribbak mint az írások. Ezt az interfészt a <span class="programkod">ReentrantReadWriteLock</span> implementálja. Read lock
      megszerzése:
    </p>
    <div class="programkod">
      <pre>ReadWriteLock rwlock = <span class="java_keyword">new</span> ReentrantReadWriteLock();
...
rwlock.readLock().lock();
<span class="java_keyword">try</span> {
    <span class="java_comment">// csak olvasni akarunk</span>
} <span class="java_keyword">finally</span> {
    rwlock.readLock().unlock();
}</pre>
    </div>
    <p>
      Write lock megszerzése ugyanígy megy, csak a <span class="programkod">writeLock()</span> metódust kell meghívni.
    </p>
  </div>
  <p class="bekezd">
    A <span class="programkod">ReentrantReadWriteLock</span>-nak számos hátulütője volt. Könnyen kiéheztetéses helyzetekhez vezetett (pláne ha rosszul használták), nem lehetett egy read
    lockot write lock-ká konvertálni és nem támogatta az optimistic read-eket. Az új <span class="programkod">java.util.concurrent.locks.StampedLock</span> osztály megpróbálta kiküszöbölni
    ezeket a hiányosságokat és ún. képesség-alapú zárolást (capability-based lock) biztosít. Támogatja az olvasási és írási zárolásokat is és néhány egyszerű elv betartásával jobb
    teljesítményt biztosít.
  </p>
  <p class="bekezd">
    A <span class="programkod">StampedLock</span> belső állapota egy verziót és egy módot tartalmaz. A zárolási metódusok egy <span class="programkod">long</span> típusú bélyeget (stamp)
    adnak vissza ami egy zárolási állapotnak megfelelő elérést reprezentál és vezérel. Ezzel a bélyeggel lehet ellenőrizni és feloldani a zárolást. A &quot;try&quot; metódusok pedig 0
    értéket is visszaadhatnak ami azt jelzi, hogy a zárolás kérése nem sikerült. A zárolás feloldási és konverziós metódusoknak át kell adni a bélyeget paraméterként és hibát dobnak ha az
    nem egyezik a zárolás állapotával. A <span class="programkod">StampedLock</span> három módot támogat:
  </p>
  <ul>
    <li><u>read</u>: a <span class="programkod">readLock()</span> metódus egy nem-kizárólagos elérésre várakozik és visszaad egy bélyeget ami az <span class="programkod">unlockRead(long)</span>
      metódussal használható a zárolás feloldására. A legegyszerűbb eset olvasási zároláshoz:</li>
  </ul>
  <div class="programkod">
    <pre>StampedLock stamped = <span class="java_keyword">new</span> StampedLock();
...
<span class="java_keyword">long</span> stamp = stamped.readLock();
<span class="java_keyword">try</span> {
    <span class="java_comment">// valamit olvasunk</span>
} <span class="java_keyword">finally</span> {
    stamped.unlockRead(stamp);
}</pre>
  </div>
  <ul>
    <li><u>write</u>: a <span class="programkod">writeLock()</span> blokkol és kizárólagos elérésre várakozik majd visszaad egy bélyeget ami az <span class="programkod">unlockWrite(long)</span>
      metódussal használható a zárolás feloldására. Write lock fennállása esetén nem adódnak ki read lock-ok és az összes optimistic read validálás hibára fut.</li>
    <li><u>optimistic read</u>: a <span class="programkod">tryOptimisticRead()</span> visszaad egy nemnulla bélyeget az aktuális szálra vonatkozó blokkolás és zárolás <b>nélkül</b> ha
      a zárolás jelenleg nem áll write módban (különben 0-t ad vissza). A <span class="programkod">validate(long)</span> metódust tudjuk később használni a zárolás validálására: ez <span
      class="programkod">true</span>-t ad vissza ha az adott bélyeg megszerzése óta a zárolást még nem szerezték meg write módban. Az optimistic read mód gyakorlatilag a read lock rendkívül
      gyenge verziója, amit egy író bármikor megszakíthat. Ha rövid, csak olvasást végző kódoknál használjuk akkor az optimistic read legtöbbször csökkenti a versengést és növeli az
      áteresztőképességet. A használatakor azért érdemes észnél lenni: az optimistic read kódrészletek csak mezőket olvassanak és az értéküket lokális változókban tárolják későbbi
      használatra validálás után. Optimistic módban a mezők olvasása inkonzisztens is lehet, tehát csak akkor érdemes használni ha ellenőrizni tudjuk az adatok konzisztenciáját és/vagy
      folyamatosan hívogatjuk a <span class="programkod">validate()</span> metódust. Általános jótanács, hogy olvasási módok használatakor érdemes mellékhatás-mentes kódot írni. A
      validálatlan optimistic read-et használó kódrészek pedig ne hívjanak olyan metódusokat amikről nem tudják biztosan, hogy kezelik-e a lehetséges inkonzisztenciát. Az alábbi példa
      bemutatja az optimistic read lock működését.</li>
  </ul>
  <div class="programkod">
    <pre>ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = <span class="java_keyword">new</span> StampedLock();

executor.submit(() -&gt; {
    <span class="java_keyword">long</span> stamp = lock.tryOptimisticRead();
    <span class="java_keyword">try</span> {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Optimistic lock valid: &quot;</span> + lock.validate(stamp));
        Thread.sleep(1000);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Optimistic lock valid: &quot;</span> + lock.validate(stamp));
        Thread.sleep(2000);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Optimistic lock valid: &quot;</span> + lock.validate(stamp));
    } <span class="java_keyword">catch</span> (InterruptedException e) {
        <span class="java_comment">// <span class="java_todo">TODO</span> kivételkezelés</span>
    } <span class="java_keyword">finally</span> {
        lock.unlock(stamp);
    }
});

executor.submit(() -&gt; {
    <span class="java_keyword">long</span> stamp = lock.writeLock();
    <span class="java_keyword">try</span> {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Write lock megszerezve&quot;</span>);
        Thread.sleep(2000);
    } <span class="java_keyword">catch</span> (InterruptedException e) {
        <span class="java_comment">// <span class="java_todo">TODO</span> kivételkezelés</span>
    } <span class="java_keyword">finally</span> {
        lock.unlock(stamp);
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Write lock feloldva&quot;</span>);
    }
});

executor.shutdown();</pre>
  </div>
  <p class="bekezd">A példa kimenete:</p>
  <pre class="programkod">Optimistic lock valid: true
Write lock megszerezve
Optimistic lock valid: false
Write lock feloldva
Optimistic lock valid: false</pre>
  <p class="bekezd">
    Az optimistic lock azonnal a zárolás megszerzése után még érvényes. A normál read lock-kal ellentétben ez nem gátolja meg a másik szálat a write lock megszerzésétől. Ezután 1
    másodpercre leállítjuk az első szálat, ezalatt a második megszerzi a write lockot. Ettől a ponttól kezdve az optimistic lock többé nem lesz érvényes. Még azután is érvénytelen marad
    miután a write lockot már megszüntettük. Tehát az ilyen típusú zárolásoknál minden esetben validálni kell a zárolást <b>miután</b> kiolvastuk egy megosztott módosítható változó értékét,
    hogy megbizonyosodjunk róla, az érték tutira érvényes.
  </p>
  <p class="bekezd">Az optimistic lock használatának ajánlott mintája:</p>
  <div class="programkod">
    <pre>StampedLock lock = <span class="java_keyword">new</span> StampedLock();

...

<span class="java_comment">// megszerezzük az optimistic read &quot;zárolás&quot; bélyegét</span>
<span class="java_keyword">long</span> stamp = lock.tryOptimisticRead();
<span class="java_comment">// a kívánt mező értékeket (value1, value2, stb) beolvassuk lokális változókba</span>
<span class="java_keyword">double</span> actualValue1 = value1, actualValue2 = value2;<span class="java_comment">// ...stb</span>
<span class="java_comment">// megnézzük, hogy közben nem lett-e write lock is kiadva</span>
<span class="java_keyword">if</span> (!lock.validate(stamp)) {
<span class="java_comment">// ha menet közben lett write lock kiadva, akkor a megszerzett állapotunk nem biztos, hogy konzisztens</span>
<span class="java_comment">// ilyenkor kérünk egy read lockot és újra kiolvassuk a szükséges értékeket a lokális változóinkba</span>
    stamp = lock.readLock();
    <span class="java_keyword">try</span> {
        actualValue1 = value1;
        actualValue2 = value2;
    } <span class="java_keyword">finally</span> {
        lock.unlockRead(stamp);
    }
}

<span class="java_comment">// aztán a megszerzett értékekkel számolunk valamit, amit akarunk</span>
<span class="java_keyword">double</span> result = calculate(actualValue1, actualValue2);</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">StampedLock</span> nem újrahívható, mint a <span class="programkod">synchronized</span>, <span class="programkod">Lock</span> vagy <span class="programkod">ReadWriteLock</span>,
    úgyhogy a zárolt metódustörzsek jobb ha nem hívnak más, ismeretlen metódusokat amik esetleg megpróbálhatják újra megszerezni a zárolásokat (bár egyébként átadhatunk bélyegeket más
    metódusoknak amik felhasználhatják vagy átkonvertálhatják azokat). A <span class="programkod">StampedLock</span> által generált bélyegek nem kriptográfiailag biztonságosak, vagyis egy
    érvényes bélyeget ki lehet találni. A bélyeg értékeket újra lehet hasznosítani nem hamarább mint egy év folyamatos használat után. Ennél hosszabb ideig használat vagy validálás nélkül
    megtartott bélyeg validálása hibát dobhat. A <span class="programkod">StampedLock</span> objektumok szerializálhatóak, de mindig alapértelmezett zárolatlan állapotba deszerializálódnak,
    vagyis nem használhatók távoli zárolásokhoz.
  </p>
  <p class="bekezd">
    A <span class="programkod">StampedLock</span> rendelkezik &quot;try&quot; metódusokkal is amik segítségével feltételesen is kérhetünk zárolást illetve feltételesen konvertálhatunk a
    zárolási módok között (a &quot;feltételesen&quot; itt azt jelenti, hogy a művelet nem biztos, hogy sikeres lesz):
  </p>
  <ul>
    <li><u>tryReadLock()</u>, <u>tryOptimisticRead()</u>, <u>tryWriteLock()</u>: adott módú zárolás kérése. Ha ezek a metódusok 0 értékkel térnek vissza, akkor a zárolás nem elérhető.
      Ezen metódusoknak az a legfontosabb különbsége, hogy nem blokkolnak mint a try nélküli változataik, amelyek ugyebár addig várnak míg nem lesz elérhető a zárolás. A 0 visszatérési
      érték semmiféle információt nem jelent egy zárolás állapotáról, tehát egy újabb meghívás sikerrel járhat.</li>
    <li><u>tryReadLock(long time, TimeUnit unit)</u>, <u>tryWriteLock(long time, TimeUnit unit)</u>: a try metódusok nem blokkolnak, ezeknek viszont megadhatunk timeout-ot, ameddig
      mégis szeretnénk ha várnának a zárolás megszerzésére. Ha ennyi idő alatt sem sikerült, akkor 0 a visszatérési értékük.</li>
    <li><u>tryConvertToReadLock(long stamp)</u>, <u>tryConvertToWriteLock(long stamp)</u>: ha az átadott bélyeghez tartozó zárolás megfelelő típusú akkor ugyanabban a módban lévő
      zárolás esetén visszaadja a bélyeget változatlanul. Ha nem ugyanabban a módban vagyunk amire váltani szeretnénk és a metódus által kívánt zárolási módra át lehet váltani, akkor
      lezárja az aktuális zárolást, az új módra szerez egy újat majd visszaadja az új bélyeget. Minden más esetben a visszatérési értéke 0. Vagyis például a <span class="programkod">tryConvertToWriteLock(long)</span>
      visszaad egy érvényes írási bélyeget ha:
      <ol>
        <li>már eleve write módban voltunk</li>
        <li>read módban voltunk és nincsenek további read módú felhasználók vagy</li>
        <li>optimistic módban voltunk és a zárolás elérhető</li>
      </ol></li>
    <li><u>tryConvertToOptimisticRead(long stamp)</u>: annyiban különbözik az első kettőtől hogy ha eredetileg is optimistic read módban voltunk akkor is 0 a visszatérési értéke, ha a
      zárolás már nem validálható. Ez a metódus egyfajta &quot;tryUnlock&quot;-ként is felfogható.</li>
  </ul>
  <p class="bekezd">
    Az osztály három &quot;as&quot; metódust is tartalmaz, ami visszafelé kompatibilitást biztosít: a <span class="programkod">StampedLock</span>-ból <span class="programkod">Lock</span>
    vagy <span class="programkod">ReadWriteLock</span>-ot csinálhatunk:
  </p>
  <ul>
    <li><u>asReadLock()</u>, <u>asWriteLock()</u>, <u>asReadWriteLock()</u></li>
  </ul>
  <p class="bekezd">
    Az alábbi példában egy <span class="programkod">field</span> nevű statikus változót használ közösen több szál. Egy szál működését mutatom be az <span class="programkod">executor</span>-ban.
    Az elképzelt kívánt működés az, hogy ha a <span class="programkod">field</span>-nek egy adott értéke van (0) akkor megváltoztatjuk 42-re. A példában megszerezzük a read lock-ot majd
    megnézzük, hogy a <span class="programkod">field</span> értéke megfelelő-e. Ha igen, akkor megpróbálunk write lockba konvertálni. A try nem blokkol, ha nem sikerült, akkor 0 a
    visszatérési értéke. Ez esetben lezárjuk a read lockot majd most már a blokkoló writeLock metódussal szerzünk write lockot. Ezután beállítjuk a <span class="programkod">field</span>
    értékét. Végül lezárjuk a lockot, a stamp mindenképpen a megfelelő bélyeget tartalmazza majd.
  </p>
  <div class="programkod">
    <pre>ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = <span class="java_keyword">new</span> StampedLock();

executor.submit(() -&gt; {
    <span class="java_keyword">long</span> stamp = lock.readLock();<span class="java_comment">// kizáró read lock megszerzése</span>
    <span class="java_keyword">try</span> {
        <span class="java_keyword">if</span> (field == 0) {<span class="java_comment">// ha a feltétel megfelelő</span>
            <span class="java_keyword">long</span> writeStamp = lock.tryConvertToWriteLock(stamp);
            <span class="java_keyword">if</span> (writeStamp == 0L) {
                <span class="java_comment">// ha nem sikerült átváltani</span>
                lock.unlock(stamp);
                stamp = lock.writeLock();
            } <span class="java_keyword">else</span> {
                <span class="java_comment">// ha sikerült átváltani</span>
                stamp = writeStamp;
            }
            field = 42;
        }
    } <span class="java_keyword">finally</span> {
        lock.unlock(stamp);<span class="java_comment">// lehet write vagy read lock is</span>
    }
});

executor.shutdown();</pre>
  </div>
  <p class="bekezd">
    A példában ugyan egy <span class="programkod">if</span> vizsgálja, hogy megvan-e a kívánt feltétel, de ha mindenképpen szükséges a váltás és biztos, hogy az meg fog történni, akkor akár
    ciklussal is várakozhatunk rá:
  </p>
  <div class="programkod">
    <pre>ExecutorService executor = Executors.newFixedThreadPool(2);
StampedLock lock = <span class="java_keyword">new</span> StampedLock();

executor.submit(() -&gt; {
    <span class="java_keyword">long</span> stamp = lock.readLock();<span class="java_comment">// kizáró read lock megszerzése</span>
    <span class="java_keyword">try</span> {
        <span class="java_keyword">while</span> (field == 0) {<span class="java_comment">// ha a feltétel megfelelő</span>
            <span class="java_keyword">long</span> writeStamp = lock.tryConvertToWriteLock(stamp);
            <span class="java_keyword">if</span> (writeStamp == 0L) {
                <span class="java_comment">// ha nem sikerült átváltani</span>
                lock.unlock(stamp);
                stamp = lock.writeLock();
            } <span class="java_keyword">else</span> {
                <span class="java_comment">// ha sikerült átváltani</span>
                stamp = writeStamp;
                field = 42;
                <span class="java_keyword">break</span>;
            }
        }
    } <span class="java_keyword">finally</span> {
        lock.unlock(stamp);<span class="java_comment">// lehet write vagy read lock is</span>
    }
});

executor.shutdown();</pre>
  </div>
  <p class="bekezd">
    Ez azért is jó megoldás, mert ha nem sikerült átváltani akkor az explicit write lock megszerzése után újra megvizsgáljuk a feltételt, hiszen a <span class="programkod">writeStamp
      == 0L</span> feltételvizsgálat alatt akár meg is változhatott a <span class="programkod">field</span> értéke. A <span class="programkod">try</span> metódus akkor már - mivel eleve write lock
    módban vagyunk - csak visszaadja ugyanazt a bélyeg értéket, nem változtat semmin. Végül pedig, történjen bármi is, mindenképpen feloldjuk a zárolást a <span class="programkod">finally</span>
    részben.
  </p>
  <h3>CompletableFuture</h3>
  <p class="bekezd">A programvégrehajtást általában úgy képzeljük, mint adott lépések sorozatát. Az aszinkron végrehajtás viszont némileg eltér ettől az elképzeléstől. Aszinkron
    végrehajtáskor egy műveletet úgy indítunk el, hogy nem kell megvárnunk a befejeződését. Ez hasznos lehet erőforrásigényes feladatok esetén vagy amikor egy művelet várakozni kénytelen
    külső folyamatra (például fájlbeolvasás). Így a fő kód folytatni tudja a feladatát és csak akkor kéri el az aszonkron folyamat eredményét (ha van) amikor már feltétlenül szükséges.
    Aszinkron műveletek esetén lehetőség van annak megadására is, hogy amikor egy művelet végetért, milyen kód (callback) fusson le. Egy aszinkron műveletet a Java 8 terminológiája taszknak
    hív.</p>
  <p class="bekezd">
    Java 5-ben jelent meg elsőként aszinkron számításokat segítő API: a <span class="programkod">Future</span> interfész (és első implementációja, a <span class="programkod">FutureTask</span>).
    Egy <span class="programkod">Future</span> egy adott aszinkron számítást és eredményét reprezentálja. Az interfész egyszerű műveleteket biztosított: cancel a számítás megszakítására,
    get és get timeouttal az eredmény lekérdezésére valamint két állapotlekérdezés (isDone és isCancelled).
  </p>
  <p class="bekezd">
    Az interfész sajnos messze volt a tökéletestől. Nem volt benne beépített hibakezelés, nem lehetett több <span class="programkod">Future</span>-t kötegelten és egymásba fűzve kezelni, de
    a legnagyobb hátránya az volt, hogy nem értesített a befejeződéséről. A get metódus blokkolta a hívót amíg az eredmény elő nem állt - ha ez valami hiba miatt soha nem történt meg, akkor
    örökké. Volt ugyan timeout-ot használó változata is, illetve az isDone metódussal le lehetett kérdezni, hogy elkészült-e már de mégsem volt az igazi.
  </p>
  <p class="bekezd">
    A Java 8 új <span class="programkod">java.util.concurrent.CompletableFuture</span> megoldása sokkal több funkcionalitást biztosít és ezeket a hátrányosságokat is megoldja. (A <span
      class="programkod">Future</span> 5 metódusával szemben 60 metódussal rendelkezik, de ezek legtöbbje néhány használati esethez tartozik.) A <span class="programkod">CompletableFuture</span>
    a régi <span class="programkod">Future</span> interfészt is implementálja, tehát visszamenőlegesen kompatibilis.
  </p>
  <p class="bekezd">
    Egyszerű befejezetlen <span class="programkod">CompletableFuture</span> létrehozása (ez még igazából semmilyen aszinkron folyamatot nem tartalmaz):
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;String&gt; future = <span class="java_keyword">new</span> CompletableFuture&lt;&gt;();</pre>
  </div>
  <p class="bekezd">Ez azért befejezetlen, mert ha meghívjuk ezután a következő kódot:</p>
  <div class="programkod">
    <pre>String result = future.get();</pre>
  </div>
  <p class="bekezd">
    akkor a <span class="programkod">get()</span> örökké fog blokkolni. Ahhoz, hogy egy <span class="programkod">CompletableFuture</span> végetérjen, annak valamikor befejezett állapotba
    kell kerülni a <span class="programkod">complete()</span> metódussal:
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;String&gt; future = <span class="java_keyword">new</span> CompletableFuture&lt;&gt;();
future.complete(<span class="java_string">&quot;eredmény&quot;</span>);
String result = future.get();</pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">get()</span>-nek létezik olyan változata is, ahol timeoutot megadhatunk, így adott idő után mindenképpen visszatér. Ha lejárt a timeout, akkor <span
      class="programkod">TimeoutException</span> dobásával.
  </p>
  <p class="bekezd">
    A <span class="programkod">CompletableFuture</span>-ök jellemzően olyan kódot tartalmaznak, amit egy másik szál hajt végre, de nem mindig ez a helyzet. Lehet például olyan <span
      class="programkod">CompletableFuture</span>-t létrehozni, ami egy jövőben biztosan bekövetkező eseményt reprezentál, például egy JMS üzenet érkezését. Ilyenkor létrehozhatunk például
    egy <span class="programkod">CompletableFuture&lt;Message&gt;</span> objektumot ami semmilyen aszinkron folyamatot nem tartalmaz. Ezt egyszerűen csak be akarjuk fejezni mikor a JMS
    üzenet megérkezik (ezt egy esemény váltja ki). Ebben az esetben egyszerűen létrehozunk egy <span class="programkod">CompletableFuture</span>-t, visszaadjuk a kliensnek és amikor úgy
    gondoljuk, hogy az eredmény már elérhető, csak meghívjuk a <span class="programkod">complete()</span> metódusát és ezzel értesítjük az összes klienst aki arra <span class="programkod">CompletableFuture</span>
    által reprezentált eseményre vár.
  </p>
  <p class="bekezd">
    Kezdetben létrehozunk egy <span class="programkod">CompletableFuture</span>-t és visszaadjuk a kliensnek:
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;String&gt; <span class="java_constant">future</span>;
...
<span class="java_keyword">public</span> <span class="java_keyword">synchronized</span> CompletableFuture&lt;String&gt; ask() {
    <span class="java_keyword">if</span> (<span class="java_constant">future</span> == <span class="java_keyword">null</span>) {
        <span class="java_constant">future</span> = <span class="java_keyword">new</span> CompletableFuture&lt;&gt;();
    }
    <span class="java_keyword">return</span> <span class="java_constant">future</span>;
}</pre>
  </div>
  <p class="bekezd">
    Itt még semmiféle aszinkron varázslat nincs. Ha most a kliens meghívja az <span class="programkod">ask()</span> által visszaadott <span class="programkod">CompletableFuture get()</span>
    metódusát, akkor blokkolódni fog. Ha viszont később valahol ezt mondjuk:
  </p>
  <div class="programkod">
    <pre>
<span class="java_constant">future</span>.complete(<span class="java_string">&quot;gezemize&quot;</span>);</pre>
  </div>
  <p class="bekezd">
    akkor az összes kliens ami blokkolódott a <span class="programkod">get()</span>-nél, visszakapja a paraméterként megadott eredmény sztringet. A <span class="programkod">complete()</span>
    csak egyszer fut le, minden további meghívás figyelmen kívül lesz hagyva. Bár van egy <span class="programkod">obtrudeValue()</span> metódus amivel felül lehet írni az előző értéket, de
    ezt csak nagyon óvatosan érdemes használni (legjobb sehogy).
  </p>
  <p class="bekezd">
    Ha hibát akarunk jelezni, akkor ott van a <span class="programkod">completeExceptionally(ex)</span> metódus (és az <span class="programkod">obtrudeException(ex)</span> amivel ezt is
    felülírhatjuk). A <span class="programkod">completeExceptionally(ex)</span> szintén feloldja a várakozó klienseket, de ezúttal a <span class="programkod">get()</span> kivételt fog nekik
    dobni:
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;String&gt; future = <span class="java_keyword">new</span> CompletableFuture&lt;&gt;();
...
future.completeExceptionally(<span class="java_keyword">new</span> RuntimeException(<span class="java_string">&quot;Bibi van!&quot;</span>));
...
future.get();<span class="java_comment">// ez ExecutionException kivételt fog dobni</span> </pre>
  </div>
  <p class="bekezd">
    A completeExceptionally normál <span class="programkod">complete()</span>-el befejezett <span class="programkod">CompletableFuture</span> esetén már figyelmen kívül lesz hagyva. A <span
      class="programkod">get()</span> egyébként ugyanazt a két kivételt dobhatja mint Java 8 előtt is: <span class="programkod">InterruptedException</span> (a végrehajtó szálat
    megszakították) és <span class="programkod">ExecutionException</span> (a végrehajtás közben valamilyen hiba történt). Amikor több szál hívja egyszerre a complete, completeExceptionally,
    vagy cancel metódust egy <span class="programkod">CompletableFuture</span>-ön, csak az egyik hívás lesz hatásos.
  </p>
  <p class="bekezd">
    Ha egyébként nem szeretjük a <span class="programkod">get()</span> blokkoló működését akkor ott van a <span class="programkod">getNow(T valueIfAbsent)</span> ami nem blokkol és ha a <span
      class="programkod">CompletableFuture</span> még nem fejeződött be, akkor a paraméterben megadott alapértelmezett értéket adja vissza.
  </p>
  <p class="bekezd">
    A kivételek kapcsán meg kell említeni a <span class="programkod">join()</span> metódust. Ez lényegében ugyanazt csinálja, mint a <span class="programkod">get()</span>, csak kisebb
    eltéréssel a hibakezelésben:
  </p>
  <pre class="programkod">public T get() throws InterruptedException, ExecutionException</pre>
  <pre class="programkod">public T join()</pre>
  <p class="bekezd">
    Mint látható a join nem dob ellenőrzött kivételeket, ehelyett nem ellenőrzött <span class="programkod">CompletionException</span>-t fog dobni hiba esetén. Ez például akkor hasznos, ha a
    <span class="programkod">CompletableFuture</span>-t stream-ekben használjuk.
  </p>
  <p class="bekezd">
    Egy <span class="programkod">CompletableFuture</span> futását a <span class="programkod">cancel(boolean mayInterruptIfRunning)</span> metódussal bármely, a példányt használó szálból
    megszakíthatjuk. A metódus paramétere a Java 8-ban semmire sincs használva, tehát mindegy, mit adunk meg. Cancel esetén a <span class="programkod">CompletableFuture</span> egy <span
      class="programkod">CancellationException</span> kivétellel fejeződik be, a tőle függő még nem befejezett <span class="programkod">CompletableFuture</span>-ök pedig egy <span
      class="programkod">CancellationException</span> cause-t tartalmazó <span class="programkod">ExecutionException</span>-nel (a függő <span class="programkod">CompletableFuture</span>-ökről
    később lesz szó). (Tehát a cancel lényegében ugyanaz, mint a <span class="programkod">completeExceptionally(new CancellationException())</span>.)
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">final</span> CompletableFuture&lt;String&gt; cFuture = <span class="java_keyword">new</span> CompletableFuture&lt;&gt;();

Executors.newCachedThreadPool().submit(() -&gt; {
    Thread.sleep(500);
    cFuture.cancel(<span class="java_keyword">false</span>);
    <span class="java_keyword">return</span> <span class="java_keyword">null</span>;
});

cFuture.get();<span class="java_comment">// Kis idő elteltével CancellationException-t fog dobni</span> </pre>
  </div>
  <p class="bekezd">
    Az aszinkron futás természetéből adódóan a kivétel a létrehozó számára csak akkor fog jelentkezni, amikor a <span class="programkod">get()</span> metódust hívja, nem pedig amikor az a <span
      class="programkod">CompletableFuture</span> futása során bekövetkezik. Természetesen nem kell, hogy a <span class="programkod">get()</span>-nél derüljön ki, hogy baj van: az <span
      class="programkod">isCancelled()</span> és <span class="programkod">isCompletedExceptionally()</span> metódusokkal szépen le lehet kérdezni a <span class="programkod">CompletableFuture</span>
    állapotát. Ez uóbbi minden kivételes befejeződést jelez, tehát a cancel-t is és azt is, ha valahol a <span class="programkod">CompletableFuture</span>-nek adott taszkunk kivétellel
    elszáll (a <span class="programkod">CompletableFuture</span>-nek taszk átadását lásd lejjebb). Az <span class="programkod">isDone()</span> meg azt mondja meg, hogy a <span
      class="programkod">CompletableFuture</span> futása befejeződött-e már. (Akár normál módon akár kivétellel.)
  </p>
  <p class="bekezd">
    <span class="programkod">CompletableFuture</span> létrehozásának a konstruktor használatától eltérő másik módja az amikor már létező eljárásokkal példányosítjuk őket a következő,
    funkcionális interfészeket váró statikus metódusokkal:
  </p>
  <ul>
    <li>supplyAsync(Supplier&lt;U&gt; supplier);</li>
    <li>supplyAsync(Supplier&lt;U&gt; supplier, Executor executor);</li>
    <li>runAsync(Runnable runnable);</li>
    <li>runAsync(Runnable runnable, Executor executor);</li>
  </ul>
  <p class="bekezd">
    A runAsync az egyik legegyszerűbb használati eset. Ez egy <span class="programkod">Runnable</span>-t vár és <span class="programkod">CompletableFuture&lt;Void&gt;</span>-ot ad vissza,
    mivel a <span class="programkod">Runnable</span>-nek nincs visszatérési értéke.
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;Void&gt; future = CompletableFuture.runAsync(() -&gt; {
    System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Ezt már külön szál fogja futtatni!&quot;</span>);
});</pre>
  </div>
  <p class="bekezd">
    Ha szeretnénk várni a <span class="programkod">future</span> végetérésére, akkor a <span class="programkod">future.get()</span> használható, bár ez esetben természetesen semmit nem fog
    visszaadni.
  </p>
  <p class="bekezd">
    Ha valami eredményt akarunk aszinkron módon számolni, akkor a <span class="programkod">Supplier</span>-t használó supplyAsync változatokat érdemes használni. Ezek egy <span
      class="programkod">Supplier&lt;U&gt;</span> típusú paramétert várnak és egy <span class="programkod">CompletableFuture&lt;U&gt;</span> típusú értéket adnak vissza.
  </p>
  <p class="bekezd">Egy egyszerű használati eset:</p>
  <div class="programkod">
    <pre>CompletableFuture&lt;Integer&gt; cFuture = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::compute);
...
Integer result = cFuture.get();
...
<span class="java_keyword">public</span> Integer compute() {
...
}</pre>
  </div>
  <p class="bekezd">
    Ez a megoldás <span class="programkod">CompletableFuture</span>-t hoz létre olyan <span class="programkod">Supplier</span>-rel ami egy <span class="programkod">Integer</span> értéket
    számít ki. Amikor elkészült, a <span class="programkod">get()</span> metódussal felhasználjuk az eredményt.
  </p>
  <p class="bekezd">Egyébként ha az a speciális eset áll fenn, hogy már a létrehozáskor tudjuk a végrehajtás eredményét akkor használhatjuk a completedFuture statikus metódust (ez
    például tesztelési célokra hasznos):</p>
  <div class="programkod">
    <pre>Future&lt;String&gt; completed = CompletableFuture.completedFuture(<span class="java_string">&quot;Completed&quot;</span>);</pre>
  </div>
  <p class="bekezd">Ez esetben a get azonnal visszatér az eredménnyel.</p>
  <p class="bekezd">
    Ha azt szeretnénk, hogy a <span class="programkod">CompletableFuture</span> ne az alapértelmezett <span class="programkod">ForkJoinPool.commonPool()</span>-t használja, akkor az <span
      class="programkod">Executor</span>-t váró metódusváltozatokat kell hívni:
  </p>
  <div class="programkod">
    <pre>Executor executor = Executors.newFixedThreadPool(8);
CompletableFuture&lt;String&gt; future = CompletableFuture.supplyAsync(() -&gt; {
    <span class="java_keyword">return</span> <span class="java_string">&quot;Ezt már saját executor-unk számította ki.&quot;</span>;
}, executor);</pre>
  </div>
  <p class="bekezd">
    <b>CompletionStage és callback-ek</b>
  </p>
  <p class="bekezd">
    Mint láttuk, a <span class="programkod">get()</span> blokkol, vagyis vár míg a <span class="programkod">CompletableFuture</span> be nem fejeződik. Ha nem szeretnénk külön figyelni a
    számítás befejeződésére, csak megadni, hogy mi történjen ekkor, akkor callback-et is adhatunk a <span class="programkod">CompletableFuture</span>-nek. Sőt, több <span class="programkod">CompletableFuture</span>-t
    is tudunk kombinálni.
  </p>
  <p class="bekezd">
    Ezt a funkcionalitást definiálja a szintén Java 8-ban megjelent <span class="programkod">java.util.concurrent.CompletionStage</span> interfész. Ezt is implementálja a <span
      class="programkod">CompletableFuture</span>. Ráadásul annak sok metódusa <span class="programkod">CompletionStage</span>-t vár, így egyszerűen tudunk egymásba fűzött számításokat
    definiálni. Ezzel akár mindegyik lépés (stage) aszinkron módon futhat. Egy lépés végrehajthat egy műveletet vagy kiszámíthat egy eredményt amit továbbadhat a tőle függő lépéseknek (a
    korábbi lépés ilyenkor végetér). A <span class="programkod">CompletionStage</span> használatával létrehozhatunk egyetlen <span class="programkod">CompletableFuture</span>-t úgy, hogy az
    <span class="programkod">CompletionStage</span> lépések láncolatát tartalmazza, ahol minden lépés akkor fut le amikor egy másik <span class="programkod">CompletionStage</span>
    befejeződik.
  </p>
  <p class="bekezd">Az interfész 39 metódust tartalmaz, ezek az alábbi elveken alapulnak:</p>
  <ul>
    <li>egy lépés által végzett számítás kifejezhető <span class="programkod">Function</span>, <span class="programkod">Consumer</span> vagy <span class="programkod">Runnable</span>
      típusként attól függően, hogy szüksége van-e paraméterekre és/vagy létrehoz-e eredményeket. Egy speciális forma (compose) magukra a lépésekre, nem pedig az eredményükre alkalmaz
      függvényt.
    </li>
    <li>egy lépés végrehajtását egy vagy több másik lépés vagy több másik lépés egyike válthatja ki. A then prefixű metódusokkal lehet egyes lépések között függőségeket megadni,
      amelyek akkor futnak le amikor az előző lépés befejeződött.</li>
    <li>az API-ban a legtöbb metódusból háromféle típus van:
      <ul>
        <li>&quot;normál&quot; változat (például <span class="programkod">thenAccept(Consumer&lt;? super T&gt; action)</span>): az ezeknek adott műveleteket akár az a szál is
          végrehajthatja, ami az aktuális <span class="programkod">CompletableFuture</span>-t hajtja végre, de akár bármely másik meghívójának szála is
        </li>
        <li>&quot;async&quot; utótagú változat (például <span class="programkod">thenAcceptAsync(Consumer&lt;? super T&gt; action)</span>): ezek a <span class="programkod">ForkJoinPool.commonPool()</span>
          használatával lesznek végrehajtva
        </li>
        <li>&quot;async&quot; utótagú változat <span class="programkod">Executor</span>-ral (például <span class="programkod">thenAcceptAsync(Consumer&lt;? super T&gt; action,
            Executor executor)</span>): az alapértelmezett <span class="programkod">ForkJoinPool.commonPool()</span> helyett ennek saját <span class="programkod">Executor</span>-t adhatunk át a
          taszk végrehajtásához
        </li>
      </ul>
    </li>
    <li>a lépések közötti függőségek vezérlik a számítások kiváltását, de ezek nem garantálnak semmiféle rendezettséget. Ráadásul a háromféle metódustípus még bonyolítja is a
      helyzetet.</li>
    <li>kétféle metódus támogatja a kivételek kezelését: whenComplete és handle (ezekről lesz később szó). Minden más esetben ha egy lépés számítása nem ellenőrzött kivétellel ér véget
      akkor a <span class="programkod">get()</span> egy <span class="programkod">ExecutionException</span> kivételt dob ami cause-ként tartalmazza az eredeti kivételt. Ha egy lépés pontosan
      két korábbi lépéstől függ és mindkettő kivétellel ért véget, akkor az <span class="programkod">ExecutionException</span> a kettő közül valamelyiket tartalmazhatja. Ha egy lépés két
      korábbi lépés közül valamelyiktől függ és azok közül csak az egyik ér véget kivétellel, nincs semmi biztosíték rá, hogy a függő lépés is kivétellel fog végetérni.
    </li>
  </ul>
  <p class="bekezd">
    A számítási eredményt váró végrehajtási lépések egyébként akár <span class="programkod">null</span> értéket is kaphatnak, az API ezt nem gátolja meg. Ezért készülnünk kell rá ha ilyen a
    számításban előfordulhat, különben <span class="programkod">NullPointerException</span>-t kapunk.
  </p>
  <p class="bekezd">
    <u>thenAccept, thenRun</u>
  </p>
  <p class="bekezd">Az első példában egy hosszú számítást futtatunk, és megadunk callback-et, ami jelezni fogja ha a számítás elkészült:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> Integer compute() {
    <span class="java_comment">// itt van egy baromi hosszú számítás ami visszaad egy Integer-t</span>
    ...
}

CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::compute).thenAccept(System.<span class="java_constant">out</span>::println);</pre>
  </div>
  <p class="bekezd">
    A thenAccept egy <span class="programkod">Consumer</span>-t vár, ami feldolgozza az előző számításunk végeredményét miután elkészült. Általában ez a metódus egy <span class="programkod">CompletableFuture</span>
    lánc utolsó eleme. Ebből is háromféle létezik, a két &quot;Async&quot; utótagú változat aszinkron módon (a <span class="programkod">ForkJoinPool.commonPool()</span> használatával vagy
    saját <span class="programkod">Executor</span>-al) futtatja a <span class="programkod">Consumer</span>-t.
  </p>
  <p class="bekezd">
    A thenRun metódus annyiban tér el, hogy egy <span class="programkod">Runnable</span> paramétert vár aminek nincs is hozzáférése az előzőleg kiszámolt értékhez. Ez például akkor jó ha
    nem akarjuk azt felhasználni, csak logolni, hogy készen vagyunk. Ezek a metódusok tehát nem blokkolnak, hanem lényegében eseménykezelőként működnek.
  </p>
  <p class="bekezd">
    <u>thenApply</u>
  </p>
  <p class="bekezd">
    Ha egyik callbak-ből egy másikba szeretnénk továbbadni értéket, azt a thenAccept-tel nem tehetjük meg, mivel a <span class="programkod">Consumer</span> nem ad vissza semmit (a
    thenAccept visszatérési típusa <span class="programkod">CompletionStage&lt;Void&gt;</span>). Ez esetben a thenApply-t használhatjuk, ami egy <span class="programkod">Function</span>-t
    vár:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> Integer getParameter() {
...
}

<span class="java_keyword">public</span> Integer compute(Integer param) {
...
}

CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getParameter).thenApply(<span class="java_keyword">this</span>::compute).thenAccept(System.<span class="java_constant">out</span>::println);</pre>
  </div>
  <p class="bekezd">
    A thenApply használatával például átalakításokat tudunk végezni <span class="programkod">CompletableFuture</span>-ök között. Így csinálhatunk egy <span class="programkod">String</span>
    generikus típusúból <span class="programkod">Integer</span>-t:
  </p>
  <div class="programkod">
    <pre>
CompletableFuture&lt;String&gt; futureString = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getSomeNumberString);
CompletableFuture&lt;Integer&gt; futureInt = futureString.thenApply(Integer::parseInt);
CompletableFuture&lt;Double&gt; futureDouble = futureInt.thenApply(r -&gt; Math.sin(r));</pre>
  </div>
  <p class="bekezd">Vagy egy kifejezésben:</p>
  <div class="programkod">
    <pre>CompletableFuture&lt;Double&gt; futureDouble = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getSomeNumberString)
    .thenApply(Integer::parseInt)
    .thenApply(r -&gt; Math.sin(r));</pre>
  </div>
  <p class="bekezd">
    Fontos megérteni, hogy ezek az átalakítások nem futnak le azonnal de nem is blokkolnak. Amikor a <span class="programkod">futureString</span> befejeződik, akkor fognak lefutni.
    Természetesen itt is átadhatunk saját <span class="programkod">Executor</span>-t paraméterként az Async metódusverzióknak.
  </p>
  <p class="bekezd">
    Eddig a callback-jaink ugyanabban a szálban futottak mint a hívóik. De ennek nem kell mindig így lennie! A callback-et át is adhatjuk a <span class="programkod">ForkJoinPool.commonPool()</span>-nak
    a <span class="programkod">CompletionStage</span> &quot;async&quot; utótagú metódusaival. Tegyük fel, hogy két műveletet is el akarunk végezni ugyanazzal a paraméterrel:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> Integer compute(Integer param) {
...
}

<span class="java_keyword">public</span> Integer computeOther(Integer param) {
...
}

CompletableFuture&lt;Integer&gt; argument = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getParameter);
argument.thenApply(<span class="java_keyword">this</span>::compute);
argument.thenApply(<span class="java_keyword">this</span>::computeOther);</pre>
  </div>
  <p class="bekezd">
    Ekkor minden abban a szálban fog megtörténni, amit az argument létrehozott. Vagyis a második számítás addig fog várni, míg az első véget nem ér. A következő megoldásban viszont mindkét
    számítás a <span class="programkod">ForkJoinPool.commonPool()</span> külön szálaiban fog végrehajtódni. Vagyis mindkét callback végrehajtódik amikor a getParameter végetér:
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;Integer&gt; argument = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getParameter);
argument.thenApplyAsync(<span class="java_keyword">this</span>::compute);
argument.thenApplyAsync(<span class="java_keyword">this</span>::computeOther);</pre>
  </div>
  <p class="bekezd">Az aszinkron verzió tehát akkor hasznos amikor különböző callback-jaink vannak, amik ugyanattól a megelőző számítástól függnek. Természetesen ilyen esetekben
    ajánlatos immutable vagy szálbiztos paramétert használni.</p>
  <p class="bekezd">
    <u>thenCompose</u>
  </p>
  <p class="bekezd">
    Eleddig a compute metódusunk normál blokkoló típusú volt. Ha szeretnénk egy kódot aszinkron műveletekből összerakosgatni, akkor <span class="programkod">CompletionStage</span>-eket
    visszaadó metódusokat kell létrehoznunk. De az is előfordulhat, hogy olyan osztálykönyvtárat használunk ami bizonyos műveleteknél <span class="programkod">CompletionStage</span> típusú
    visszatérési értéket ad. Ilyenkor az eddigi thenApply metódust nem tudnánk használni, mert egymásba ágyazott <span class="programkod">CompletionStage</span>-eket adna vissza: <span
      class="programkod">CompletionStage&lt;CompletionStage&lt;Integer&gt;&gt;</span>. Ekkor jó a thenCompose metódus ami olyan <span class="programkod">Function</span>-t vár ami egy <span
      class="programkod">CompletionStage</span>-t ad vissza.
  </p>
  <p class="bekezd">A függvények szignatúrája segít megérteni a különbséget:</p>
  <pre class="programkod">&lt;U&gt; CompletionStage&lt;U&gt; thenApply(Function&lt;? super T,? extends U&gt; fn)
&lt;U&gt; CompletionStage&lt;U&gt; thenCompose(Function&lt;? super T,? extends CompletionStage&lt;U&gt;&gt; fn)</pre>
  <p class="bekezd">
    Tehát ha <span class="programkod">CompletableFuture</span>-t visszaadó metódust fűzünk össze, akkor a thenCompose-t érdemes használni. (Ha valaki ez alapján a flatMap metódusokra
    gondol, az nem a véletlen műve.)
  </p>
  <p class="bekezd">Legyen a következő két metódusunk:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> Integer getParameter() {
    <span class="java_keyword">return</span> 42;
}

<span class="java_keyword">public</span> CompletionStage&lt;Integer&gt; computeAsync(Integer param) {
    <span class="java_keyword">return</span> CompletableFuture.completedFuture(param).thenApply(i -&gt; i + 10);
}</pre>
  </div>
  <p class="bekezd">
    A computeAsync itt már <span class="programkod">CompletionStage</span> típusú értéket ad vissza. Ezt tudjuk a thenCompose segítségével használni:
  </p>
  <div class="programkod">
    <pre>CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getParameter).thenCompose(<span class="java_keyword">this</span>::computeAsync);</pre>
  </div>
  <p class="bekezd">
    <u>thenCombine</u>
  </p>
  <p class="bekezd">Néha olyan callback-ünk van ami két számítás eredményét igényli. Ekkor jó a thenCombine metódus. Ez lehetővé teszi, hogy BiFunction callback-et adjunk meg ami két
    CompletionStage eredményét várja:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> Integer getParameter1() {
...
}

<span class="java_keyword">public</span> Integer getParameter2() {
...
}

<span class="java_keyword">public</span> Integer compute(Integer param1, Integer param2) {
...
}

CompletableFuture&lt;Integer&gt; param1 = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getParameter1);
CompletableFuture&lt;Integer&gt; param2 = CompletableFuture.supplyAsync(<span class="java_keyword">this</span>::getParameter2);
CompletableFuture&lt;Integer&gt; future = param1.thenCombine(param2, <span class="java_keyword">this</span>::compute);</pre>
  </div>
  <p class="bekezd">
    <u>thenAcceptBoth, runAfterBoth</u>
  </p>
  <p class="bekezd">
    Ha nem szeretnénk új <span class="programkod">CompletableFuture</span> példányt létrehozni két eredmény egyesítésével, csak értesítést akarunk kapni róla mikor befejeződtek, akkor
    használhatjuk a thenAcceptBoth és runAfterBoth metódusokat. Hasonlóan működnek a thenAccept és thenRun metódusokhoz, de várnak egy plusz <span class="programkod">CompletableFuture</span>-t:
  </p>
  <div class="programkod">
    <pre>String original = <span class="java_string">&quot;eReDeTi&quot;</span>;
StringBuilder result = <span class="java_keyword">new</span> StringBuilder();
CompletableFuture.completedFuture(original).thenApply(String::toLowerCase)
    .thenAcceptBoth(CompletableFuture.completedFuture(original)
        .thenApply(String::toUpperCase), (s1, s2) -&gt; result.append(s1 + s2));
<span class="java_comment">// eredmény: eredetiEREDETI</span> </pre>
  </div>
  <p class="bekezd">
    <u>acceptEither, runAfterEither</u>
  </p>
  <p class="bekezd">
    Ha viszont nem szeretnénk mindkét eredményt felhasználni, az acceptEither vagy runAfterEither metódussal megtehetjük, hogy csak azt vesszük figyelembe amelyik hamarább előáll. Ezek nem
    <span class="programkod">BiFunction</span>-t várnak, hanem <span class="programkod">Consumer</span>-t. Ez akkor jó ha például két taszkunk van ami ugyanolyan típusú eredményt állít elő,
    de csak a válaszidő érdekel minket, nem pedig az, hogy melyik végzett először. Például van két rendszerünk amikkel integráltan kell dolgoznunk. Az egyiknek rövidebb átlagos válaszideje
    van, de nagy a szórása. A másik általában lassabb, de jobban előre jelezhető. Ha azt szeretnénk, hogy a két rendszer legjobbját hozzuk ki (teljesítmény és előrejelezhetőség) akkor
    mindkét rendszert meghívjuk egyszerre és megvárjuk az elsőt amelyik válaszol. Általában ez az első lesz, de ha ez lelassul, akkor a második is elfogadható időn belül válaszol.
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">void</span> processResult(Integer i) {
...
}

CompletableFuture&lt;Integer&gt; fastService = ... 
CompletableFuture&lt;Integer&gt; predictableService = ...
CompletableFuture&lt;Void&gt; processed = fastService.acceptEither(predictableService, <span class="java_keyword">this</span>::processResult);</pre>
  </div>
  <p class="bekezd">
    A példában van egy <span class="programkod">fastService</span> (gyorsan válaszoló) és egy <span class="programkod">predictableService</span> (jobban előrejelezhető) <span
      class="programkod">CompletableFuture</span>-ünk. Az acceptEither metódussal a kettő közül a gyorsabban lefutó eredményét fogja megkapni a <span class="programkod">processResult</span>.
  </p>
  <p class="bekezd">
    <u>applyToEither</u>
  </p>
  <p class="bekezd">
    Az applyToEither az acceptEither testvére. Míg ez utóbbi egyszerűen meghív egy kódot, amikor két <span class="programkod">CompletableFuture</span>-ből a gyorsabb befejeződik, az
    applyToEither visszaad egy új <span class="programkod">CompletableFuture</span>-t. Ez akkor fog befejeződni amikor a két alatta lévő <span class="programkod">CompletableFuture</span>-ből
    az első befejeződik. A fenti példa átalakítva:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> Integer processResult(Integer i) {
...
}

CompletableFuture&lt;Integer&gt; processed = fastService.applyToEither(predictableService, <span class="java_keyword">this</span>::processResult);</pre>
  </div>
  <p class="bekezd">
    Észrevehetjük, hogy a kliens szempontjából az a tény rejtett, hogy a processed mögött valójában két <span class="programkod">CompletableFuture</span> van. A kliens egyszerűen csak vár,
    hogy a <span class="programkod">CompletableFuture</span> lefusson és az applyToEither-é a felelősség, hogy jelezze a kliensnek amikor a kettő közül a gyorsabb lefutott.
  </p>
  <p class="bekezd">
    <u>allOf, anyOf</u>
  </p>
  <p class="bekezd">
    Láttuk, hogy lehet megoldani, hogy két <span class="programkod">CompletableFuture</span> lefusson (thenCombine) vagy hogy az első lefusson (applyToEither). De mi van ha ezt ki
    szeretnénk terjeszteni tetszőleges számú <span class="programkod">CompletableFuture</span>-re? Erre az API statikus metódusokat biztosít.
  </p>
  <p class="bekezd">
    Az allOf veszi a <span class="programkod">CompletableFuture</span>-ök tömbjét és visszaad egy <span class="programkod">CompletableFuture</span>-t ami akkor fejeződik be amikor az összes
    hozzá tartozó <span class="programkod">CompletableFuture</span> befejeződik. Az anyOf ezzel szemben csak a tömbben megadott <span class="programkod">CompletableFuture</span>-ök közül az
    első befejeződésére vár.
  </p>
  <p class="bekezd">
    Megjegyzendő, hogy a két metódusnak nem ugyanolyan a visszatérési értéke! Az allOf <span class="programkod">Void</span> generikus típusú (hiszen nem tartalmazhatja az összes
    visszatérési értéket egyben), az anyOf pedig <span class="programkod">Object</span> generikus típusú <span class="programkod">CompletableFuture</span>-el tér vissza! (Ez azért probléma,
    mert külön tudnunk kell, hogy milyen is az aktuális visszatérési típus. Ha többféle generikus típusú <span class="programkod">CompletableFuture</span>-t adtunk meg neki, akkor ez
    mindenféle instanceof-írásra fog minket ihletni.)
  </p>
  <p class="bekezd">
    Tegyük fel, hogy egy webservice-t akarunk meghívni mondjuk 50 különbőző paraméterrel. Ezt sorosan is megtehetjük, de az sokáig tart. Ha a kiszolgáló bírni fogja, akkor meghívhatjuk
    ezeket párhuzamosan is. Írunk egy függvényt, ami megkapja a paramétert (ami mondjuk sztring) és visszaad mondjuk egy <span class="programkod">CompletableFuture</span>-t. Ez aszinkron
    módon meghívja a webservice-t.
  </p>
  <div class="programkod">
    <pre>
CompletableFuture&lt;String&gt; callWebService(String pageLink) {
    <span class="java_keyword">return</span> CompletableFuture.supplyAsync(() -&gt; {
        <span class="java_comment">// Ide kerül a kód ami meghívja a webservice-t</span>
    });
}</pre>
  </div>
  <p class="bekezd">Amikor minden hívás lement, akkor megnézzük, milyen eredmények tartalmaznak:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; parameterList = Arrays.asList(<span class="java_string">&quot;1&quot;</span>, <span class="java_string">&quot;2&quot;</span>, <span class="java_string">&quot;3&quot;</span>);<span
        class="java_comment">// paraméterek listája</span>
<span class="java_comment">// Minden webservice meghívása aszinkron módon</span>
List&lt;CompletableFuture&lt;String&gt;&gt; resultFutures = parameterList.stream().map(actualParameter -&gt; callWebService(actualParameter
    .collect(Collectors.toList());
<span class="java_comment">// Összesített Future létrehozása az allOf() metódussal</span>
CompletableFuture&lt;Void&gt; allFutures = CompletableFuture.allOf(resultFutures.toArray(<span class="java_keyword">new</span> CompletableFuture[resultFutures.size()]));
</pre>
  </div>
  <p class="bekezd">
    Az allOf tehát gyakorlatilag több <span class="programkod">CompletableFuture</span> párhuzamos végrehajtását is elvégzi, de az vele a baj, hogy <span class="programkod">CompletableFuture&lt;Void&gt;</span>
    a visszatérési értéke. Az összes benne foglalt <span class="programkod">CompletableFuture</span> eredményét meg tudjuk szerezni például így:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// Amikor minden Future befejeződik, meghívjuk a &quot;future.join()&quot; metódust,</span>
<span class="java_comment">// hogy megszerezzük az eredményüket és összegyűjtjük egy listában</span>
CompletableFuture&lt;List&lt;String&gt;&gt; allResultsFuture = allFutures.thenApply(v -&gt; {
    <span class="java_keyword">return</span> resultFutures.stream().map(actualResult -&gt; actualResult.join()).collect(Collectors.toList());
});
<span class="java_comment">// feldolgozás:</span>
List&lt;String&gt; results = allResultsFuture.get();
<span class="java_comment">// ...</span> </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">actualResult.join()</span> hívással akkor történik meg a lekérdezés amikor az összes future végetért, ezért semmi nem blokkol semmit.
  </p>
  <p class="bekezd">
    <b>Hibakezelés</b>
  </p>
  <p class="bekezd">
    Mint tudjuk, a hibák bekövetkeznek. Szerencsére a <span class="programkod">CompletableFuture</span> segítségével ez nem érhet váratlanul (legfeljebb kellemetlenül) minket! Tegyük fel,
    hogy a következő láncunk van:
  </p>
  <div class="programkod">
    <pre>CompletableFuture&lt;Void&gt; future = CompletableFuture.supplyAsync(() -&gt; {
<span class="java_comment">// itt a kód valami kivételt dob</span>
    <span class="java_keyword">return</span> <span class="java_string">"eredmény"</span><span class="java_string">"eredmény"</span>;
}).thenApply(result -&gt; {
    <span class="java_keyword">return</span> <span class="java_string">"feldolgozott eredmény"</span><span class="java_string">"feldolgozott eredmény"</span>;
}).thenApply(result -&gt; {
    <span class="java_keyword">return</span> <span class="java_string">"feldolgozott eredmény feldolgozása"</span><span class="java_string">"feldolgozott eredmény feldolgozása"</span>;
}).thenAccept(result -&gt; {
    <span class="java_comment">// valamit csinálunk az utolsó eredménnyel</span>
});</pre>
  </div>
  <p class="bekezd">
    Ha egy kivétel jön az eredeti supplyAsync metódusban, akkor egyetlen thenApply callback sem fog lefutni. Ha az első thenApply metódusban jön a kivétel, akkor a 2. és 3. callback nem fog
    lefutni, és így tovább. A kivételek továbbdobódnak ezekben a lépésekben és a létrehozó számára - ahogy korábban is írtam - a kivétel csak a <span class="programkod">get()</span> vagy <span
      class="programkod">join()</span> hívásakor fog megjelenni.
  </p>
  <p class="bekezd">Hibakezelést az exceptionally metódussal tudunk kivétel esetén végrehajtani. Ha ezt betesszük egy callback-láncba akkor a kivétel helyett egy alternatív eredménnyel
    folytatja a következő lépésnél:</p>
  <div class="programkod">
    <pre>
Integer magassag = -1;

CompletableFuture&lt;String&gt; domborzatFuture = CompletableFuture.supplyAsync(() -&gt; {
    <span class="java_keyword">if</span> (magassag &lt; 0) {
        <span class="java_keyword">throw</span> <span class="java_keyword">new</span> IllegalArgumentException(<span class="java_string">&quot;Magasság nem lehet negatív!&quot;</span>);
    }
    <span class="java_keyword">if</span> (magassag &gt;= 500) {
        <span class="java_keyword">return</span> <span class="java_string">&quot;Hegység&quot;</span>;
    } <span class="java_keyword">else</span> {
        <span class="java_keyword">return</span> <span class="java_string">&quot;Dombság&quot;</span>;
    }
}).exceptionally(ex -&gt; {
    System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Kivétel történt: &quot;</span> + ex.getMessage());
    <span class="java_keyword">return</span> <span class="java_string">&quot;Nem meghatározható&quot;</span>;
});

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Típus magasság alapján: &quot;</span> + domborzatFuture.get());</pre>
  </div>
  <p class="bekezd">
    Az exceptionally törzse csak akkor fut le amikor az előtte lévő lépések valamelyikében kivétel dobódott (egyébként a vezérlés tovább adódik a következő lépésre). A segítségével
    gyakorlatilag helyreállítjuk a kivételt egy olyan értékre aminek a típusa megfelel a <span class="programkod">CompletableFuture</span> típusának. A kivétel tehát már nem megy tovább a
    láncban, miután egyszer lekezeltük.
  </p>
  <p class="bekezd">
    A handle egy rugalmasabb megoldás, mivel minden esetben lefut és olyan <span class="programkod">Function</span>-t adhatunk meg neki ami helyes eredményt és kivételt is kaphat:
  </p>
  <div class="programkod">
    <pre>
Integer magassag = -1;

CompletableFuture&lt;String&gt; domborzatFuture = CompletableFuture.supplyAsync(() -&gt; {
    <span class="java_keyword">if</span> (magassag &lt; 0) {
         <span class="java_keyword">throw</span> <span class="java_keyword">new</span> IllegalArgumentException(<span class="java_string">&quot;Magasság nem lehet negatív!&quot;</span>);
    }
    <span class="java_keyword">if</span> (magassag &gt;= 500) {
        <span class="java_keyword">return</span> <span class="java_string">&quot;Hegység&quot;</span>;
    } <span class="java_keyword">else</span> {
        <span class="java_keyword">return</span> <span class="java_string">&quot;Dombság&quot;</span>;
    }
}).handle((res, ex) -&gt; {
    <span class="java_keyword">if</span> (ex != <span class="java_keyword">null</span>) {
        System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Kivétel történt: &quot;</span> + ex.getMessage());
        <span class="java_keyword">return</span> <span class="java_string">&quot;Nem meghatározható&quot;</span>;
    }
    <span class="java_keyword">return</span> res;
});

System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Típus magasság alapján: &quot;</span> + domborzatFuture.get());</pre>
  </div>
  <p class="bekezd">
    A handle mindig meghívódik, akár elkészült eredmény (az <span class="programkod">ex null</span>) akár kivétel dobódott (a <span class="programkod">result null</span>). És ebből az
    exceptionally-től eltérően léteznek szokásos async változatok is.
  </p>
  <p class="bekezd">
    <b>whenComplete</b>
  </p>
  <p class="bekezd">
    A whenComplete hasonló a handle-höz, bár nem feltétlenül úgy működik ahogy előre várnánk. A handle <span class="programkod">BiFunction</span>-t vár, a whenComplete <span
      class="programkod">BiConsumer</span>-t. A handle tehát kezelni tudja az esetleges hibát úgy, hogy valamilyen értékre feloldja, a whenComplete-nek viszont nincs visszatérési értéke, a
    korábbi lépés eredményét adja tovább. Az esetleges kivételt pedig változatlanul tovább dobja. A következő kódrészlet szépen bemutatja ezt:
  </p>
  <div class="programkod">
    <pre>CompletableFuture.supplyAsync(() -> {
    <span class="java_keyword">throw new</span> RuntimeException(<span class="java_string">&quot;1&quot;</span>);
}).whenComplete((i, ex) -> {
    System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Abrakadabra&quot;</span>);
    <span class="java_keyword">throw new</span> RuntimeException(<span class="java_string">&quot;2&quot;</span>);
}).join();</pre>
  </div>
  <p class="bekezd">
    Mit várnánk ennek a futásától? Hát hibakezelés nélkül dobódna az 1-es <span class="programkod">RuntimeException</span>, hibakezeléssel kiíródna, hogy Abrakadabra és dobódna a 2-es <span
      class="programkod">RuntimeException</span>. Ehelyett viszont ez történik:
  </p>
  <pre class="programkod">
Abrakadabra
Exception in thread "main" java.util.concurrent.CompletionException: 
        java.lang.RuntimeException: 1
  at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
  at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
  at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)
  at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
  at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
  at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
  at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
  at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
Caused by: java.lang.RuntimeException: 1
  ... 7 more</pre>
  <p class="bekezd">
    Vagyis dobódik az 1-es <span class="programkod">RuntimeException</span>, lefut a whenComplete, de az abban dobott 2-es <span class="programkod">RuntimeException</span> már elnyelődik.
    Erre jó tehát a whenComplete... Persze mivel ez is megkapja a korábbi esetleges kivételt paraméterben (vagy <span class="programkod">null</span> ha nem volt), ezért azzal is tudunk a
    metódus törzsében bármit kezdeni. A korábbi esetleges eredményt pedig továbbadja (mivel a <span class="programkod">BiConsumer</span>-nek egyébként nincs visszatérési értéke):
  </p>
  <div class="programkod">
    <pre>String result = CompletableFuture.supplyAsync(() -> {
    <span class="java_keyword">return</span> <span class="java_string">&quot;4&quot;</span>;
}).whenComplete((i, err) -> {
    System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Abrakadabra&quot;</span>);
}).thenApply(s -> s + <span class="java_string">&quot;2&quot;</span>).join();
System.<span class="java_constant">out</span>.println(result);
    </pre>
  </div>
  <p class="bekezd">Ennek az eredménye:</p>
  <pre class="programkod">Abrakadabra
42</pre>
  <h2>Date/Time API</h2>
  <p class="bekezd">
    A dátum és időkezelés a Java fejlesztők számára mindig is az egyik legfájóbb pont volt. Az API nem volt szálbiztos, rosszul volt megtervezve, lényeges funkciókat nem támogatott, nem
    kezelte az időzónákat. A szabványos <span class="programkod">java.util.Date</span>-et követő <span class="programkod">java.util.Calendar</span> nem sokat javított a helyzeten (egyesek
    szerint inkább csak rontott...). Ekkor jött létre kerülő megoldásként Stephen Colebourne vezetésével a JodaTime, ami remek alternatíva lett a beépített date/time API helyett. A JodaTime
    nagy hatással volt a hivatalos Java fejlesztésére: a Java 8-ban bevezetett új date/time API nagyrészt ezen alapul: a fejlesztők megpróbálták a legjobb dolgokat átvenni belőle.
  </p>
  <p class="bekezd">
    Az új API alapértelmezettként a világon legelterjedtebb és ISO-8601-ben szabványosított naptárrendszert támogatja. Ez az 1582-ben bevezetett és nyugati kultúrkörben ma is használatos
    Gergely-naptáron alapul. A szintén Java 8-ban megjelent <span class="programkod">java.time.chrono</span> csomag arra is biztosít lehetőséget, hogy más naptárrendszereket is
    használhassunk, mint például a Hijrah vagy a thai buddhista naptár. Természetesen semmi nem akadályoz meg abban sem, hogy saját naptárrendszert írjuk. Az API az Unicode Common Locale
    Data Repository-t (CLDR) használja. Ez a repository támogatja a világ nyelveit és a lokalizációs adatok legnagyobb adatbázisát tartalmazza. A repositoryban lévő információkat szinte
    minden nyelvre honosították. A date-time API emellett a Time-Zone Database (TZDB)-t is használja, ami 1970-től kezdve tartalmaz információkat az összes időzónáról és azok
    módosításairól.
  </p>
  <p class="bekezd">
    Az adattípusok tervezésekor az immutable filozófiát nagyon komolyan vették: semmiféle módosítás nem engedélyezett rajtuk (ezt a leckét a fejlesztők még a <span class="programkod">java.util.Calendar</span>-ból
    tanulták meg). Módosítás esetén a megfelelő osztályból mindig új példány jön létre. Ez a megoldás definíció szerint szálbiztos és segíti a lambdákkal való használatot.
  </p>
  <p class="bekezd">Az új date-time API négy alcsomagra oszlik:</p>
  <p class="bekezd">
    <u>java.time</u>: a dátum és idő ábrázolására szolgáló API magja. Tartalmazza a dátumhoz, időhöz, ezek kombinációjához, időzónákhoz, instantokhoz, időtartamokhoz és órákhoz tartozó
    adattípusokat. A <span class="programkod">java.time</span> package lehetőséget biztosít, hogy az adott feladathoz legmegfelelőbb adattípust használjuk.
  </p>
  <p class="bekezd">
    <u>java.time.chrono</u>: az alapértelmezett ISO-8601-től eltérő naptárrendszerek ábrázolására szolgáló API. Segítségével saját naptárrendszert is létrehozhatunk.
  </p>
  <p class="bekezd">
    <u>java.time.format</u>: dátum és idő formázására és parse-olására szolgáló osztályok
  </p>
  <p class="bekezd">
    <u>java.time.temporal</u>: kiterjesztett API, főként keretrendszer és osztálykönyvtár írók számára. Lehetővé teszi a dátum és idő osztályok közötti keresztműveleteket, lekérdezést és
    beállítást. Ebben a csomagban vannak definiálva mezők (<span class="programkod">TemporalField</span> és <span class="programkod">ChronoField</span>) és egységek (<span
      class="programkod">TemporalUnit</span> és <span class="programkod">ChronoUnit</span>).
  </p>
  <p class="bekezd">
    <u>java.time.zone</u>: az időzónákat kezelő osztályok: offszetek és időzónaszabályok. A legtöbb fejlesztőnek csak a <span class="programkod">ZonedDateTime</span> és <span
      class="programkod">ZoneId</span> vagy <span class="programkod">ZoneOffset</span> osztályokat kell használnia.
  </p>
  <p class="bekezd">
    Az ezredmásodperces pontosságot kezelő <span class="programkod">java.util.Date</span> osztállyal ellentétben az új adattípusok már nanoszekundumos (milliárdod másodperc) pontossággal
    dolgoznak. A <span class="programkod">java.time</span> csomag dátum/idő tárolásához a következő osztályokat tartalmazza (mindegyik osztály implementálja a <span class="programkod">Comparable</span>
    interfészt is):
  </p>
  <ul>
    <li><b>LocalDate</b>: dátum tárolására idő nélkül időzóna-mentesen</li>
    <li><b>LocalTime</b>: idő tárolására dátum nélkül időzóna-mentesen</li>
    <li><b>Year</b>: év</li>
    <li><b>YearMonth</b>: év és hónap</li>
    <li><b>MonthDay</b>: egy hónap és annak napja. Ezek a rész-dátumot tartalmazó osztályok (<span class="programkod">Year</span>, <span class="programkod">YearMonth</span> és <span
      class="programkod">MonthDay</span>) speciális számításokhoz használhatók. A <span class="programkod">YearMonth</span> például hitelkártya lejárati dátum tárolására alkalmas.</li>
    <li><b>LocalDateTime</b>: dátum és idő időzóna nélkül</li>
    <li><b>ZonedDateTime</b>: teljes dátum és idő időzónával együtt. Ha csak lehetséges, az API az időzóna nélküli adattípusok (Local...) használatát ajánlja.</li>
    <li><b>Instant</b>: egy pillanatnyi időbélyeg. Egy <span class="programkod">Instant</span> egy <span class="programkod">Clock</span>-tól bármikor lekérdezhető és például logoláshoz
      vagy bármilyen időbélyeget igénylő művelethez használható. Bizonyos tekintetben a korábbi <span class="programkod">System.currentTimeMillis()</span> korszerű megfelelője.</li>
    <li><b>OffsetDateTime</b>: dátum és idő Greenwich/UTC-hez képesti ofszettel (például 2018-12-28T11:25:20+02:00). Ilyesmit XML-ekben vagy más perzisztens tárolókban (adatbázisokban)
      láthatunk, ez teljes időzónánál kevesebb információt tárol.</li>
    <li><b>OffsetTime</b>: idő Greenwich/UTC-hez képesti ofszettel (például 11:35:20+02:00, ahol a <span class="programkod">ZoneOffset</span> &quot;+02:00&quot;). Az <span
      class="programkod">OffsetDateTime</span>-hoz hasonlóan ez is főként hálózati protokollokhoz (webservice) és adatbázisokhoz készült.</li>
  </ul>
  <p class="bekezd">A dátum/idő kezelés mellett a csomag időmennyiségekhez való osztályokat is biztosít:</p>
  <ul>
    <li><b>Period</b>: dátum alapú időtartam, például 1 év 2 hónap 5 nap</li>
    <li><b>Duration</b>: idő (nanoszekundum) alapú időtartam, például 12,5 másodperc</li>
  </ul>
  <p class="bekezd">
    A csomagban lévő új (nem ellenőrzött) <span class="programkod">DateTimeException</span> kivétel illetve leszármazottai jeleznek minden dátum/időszámítással kapcsolatos hibát. Mindezek
    mellett néhány kiegészítő enumot is tartalmaz a package:
  </p>
  <ul>
    <li><b>Month</b>: egyetlen hónap</li>
    <li><b>DayOfWeek</b>: a nét napja</li>
  </ul>
  <p class="bekezd">
    Az API úgy lett tervezve, hogy a <span class="programkod">null</span> paramétereket ésszerűen kezelje. Ez azt jelenti, hogy <span class="programkod">null</span> paraméterek esetén nem
    valamilyen &quot;alapértelmezett&quot; értéket ad, hanem <span class="programkod">NullPointerException</span>-t dob. Kivétel ez alól a boolean visszatérési értékeket előállító validáló
    vagy ellenőrző metódusok: ezek <span class="programkod">null</span> paraméter esetén általában false értékeket adnak.
  </p>
  <p class="bekezd">A különböző osztályok nagyon sok metódust tartalmaznak, de a metódusnevek ahol csak lehetett konzisztens elnevezési szabályok szerint lettek megtervezve (ezek nagy
    része nem is igényelne magyarázatot, hiszen a Java-ban széleskörűen használatosak). Sok osztályban van például now metódus, ami az ahhoz az osztályhoz megfelelő aktuális pillanat dátum
    vagy idő értékét adja. Mivel a legtöbb osztály immutable, az API nem tartalmaz set metódusokat. A következő lista felsorolja az általánosan használt prefixeket:</p>
  <ul>
    <li><b>of</b>: statikus gyártófüggvény, ami nem konvertálja, hanem validálja az inputot</li>
    <li><b>from</b>: a célosztály egy példányává konvertálja az input paramétereket, aminek eredményeként az inputból elveszhet információ</li>
    <li><b>parse</b>: statikus gyártófüggvény, ami a bemeneti sztringből megpróbálja létrehozni a célosztály egy példányát</li>
    <li><b>format</b>: adott formattert használva az objektum értékéből egy sztringet állít elő</li>
    <li><b>get</b>: valaminek az értéke</li>
    <li><b>is</b>: valamilyen állapot</li>
    <li><b>with</b>: visszaadja a célobjektum másolatát egy mező módosításával. Ez a set immutable megfelelője.</li>
    <li><b>plus</b>: visszaadja a célobjektum másolatát a megadott időmennyiséggel növelve</li>
    <li><b>minus</b>: visszaadja a célobjektum másolatát a megadott időmennyiséggel csökkentve</li>
    <li><b>to</b>: másik típussá konvertálja az objektumot</li>
    <li><b>at</b>: az objektumot egy másikkal kombinálja, például: <span class="programkod">date.atTime(time)</span></li>
  </ul>
  <p class="bekezd">A metódushívások egymásba láncolhatók, a visszaadott kód könnyen olvasható. Például:</p>
  <div class="programkod">
    <pre>LocalDate today = LocalDate.now();
LocalDate fizetes = today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); </pre>
  </div>
  <h3>Date/Time adattípusok összefoglalása</h3>
  <table cellpadding="0" cellspacing="0" width="95%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Osztály</b></th>
        <th class="normal"><b>Év</b></th>
        <th class="normal"><b>Hó</b></th>
        <th class="normal"><b>Nap</b></th>
        <th class="normal"><b>Óra</b></th>
        <th class="normal"><b>Perc</b></th>
        <th class="normal"><b>Másodperc<sup>1</sup></b></th>
        <th class="normal"><b>Zónaoffszet</b></th>
        <th class="normal"><b>Zóna ID</b></th>
        <th class="normal"><b>toString() kimenet</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>Instant</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td>2019-01-27T17:34:21.639Z</td>
      </tr>
      <tr class="tr2">
        <td>LocalDate</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>2019-01-27</td>
      </tr>
      <tr class="tr1">
        <td>LocalDateTime</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td>2019-01-27T18:37:13.625</td>
      </tr>
      <tr class="tr2">
        <td>ZonedDateTime</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td>2019-01-27T18:38:02.224+01:00[Europe/Prague]</td>
      </tr>
      <tr class="tr1">
        <td>LocalTime</td>
        <td></td>
        <td></td>
        <td></td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td>18:38:56.748</td>
      </tr>
      <tr class="tr2">
        <td>MonthDay</td>
        <td></td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>--01-27</td>
      </tr>
      <tr class="tr1">
        <td>Year</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>2019</td>
      </tr>
      <tr class="tr2">
        <td>YearMonth</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td>2019-01</td>
      </tr>
      <tr class="tr1">
        <td>OffsetDateTime</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td>2019-01-27T18:39:57.043+01:00</td>
      </tr>
      <tr class="tr2">
        <td>OffsetTime</td>
        <td></td>
        <td></td>
        <td></td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td>18:40:17.382+01:00</td>
      </tr>
      <tr class="tr1">
        <td>Duration</td>
        <td></td>
        <td></td>
        <td class="center"><sup>2</sup></td>
        <td class="center"><sup>2</sup></td>
        <td class="center"><sup>2</sup></td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td>PT-10H <i>(-10 óra)</i></td>
      </tr>
      <tr class="tr2">
        <td>Period</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td class="center">X</td>
        <td></td>
        <td></td>
        <td></td>
        <td class="center"><sup>3</sup></td>
        <td class="center"><sup>3</sup></td>
        <td>P2D <i>(2 nap)</i></td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    <sup>1</sup>: A másodpercek nanoszekundumos pontossággal
  </p>
  <p class="bekezd">
    <sup>2</sup>: Az osztály nem tárolja ezt az információt, de vannak olyan metódusai, amelyekkel megkapható az idő ezekben az egységekben
  </p>
  <p class="bekezd">
    <sup>3</sup>: Amikor egy <span class="programkod">Period</span> hozzáadódik egy <span class="programkod">ZonedDateTime</span>-hoz, nyári időszámítási vagy más helyi időbeli eltérések
    jelentkezhetnek
  </p>
  <p class="bekezd">
    Az új adattípusok egyébként a <span class="programkod">java.time.temporal.TemporalAccessor</span> interfészből származnak (kivéve a <span class="programkod">Period</span> és <span
      class="programkod">Duration</span>) és a különböző dátum/idő műveletek általában <span class="programkod">TemporalAccessor</span> típusú paramétert várnak vagy ilyen típussal térnek
    vissza.
  </p>
  <h3>A leggyakrabban használatos típusok</h3>
  <p class="bekezd">
    <b>DayOfWeek, Month</b>
  </p>
  <p class="bekezd">Ezzel a két enummal határozhatjuk meg a hónapokat és a hét napjait. Mivel angol elnevezéseket használnak, a kód jó olvashatóságát is biztosítják. Mindkét enum
    tartalmaz a többi dátum/idő osztályhoz hasonló metódusokat is.</p>
  <div class="programkod">
    <pre>
<span class="java_comment">// DayOfWeek példák</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;%s%n&quot;</span>, DayOfWeek.<span class="java_constant">MONDAY</span>.plus(3));<span
        class="java_comment">// THURSDAY</span>
System.<span class="java_constant">out</span>.println(DayOfWeek.from(LocalDate.now()));<span class="java_comment">// az aktuális nap enum-ja</span>
System.<span class="java_constant">out</span>.println(DayOfWeek.<span class="java_constant">SATURDAY</span>.getDisplayName(TextStyle.<span class="java_constant">FULL</span>, Locale.forLanguageTag(<span
        class="java_string">"hu"</span>)));<span class="java_comment">// szombat</span>

<span class="java_comment">// Month példák</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;%d%n&quot;</span>, Month.<span class="java_constant">FEBRUARY</span>.maxLength());<span
        class="java_comment">// 29</span>
System.<span class="java_constant">out</span>.println(Month.<span class="java_constant">OCTOBER</span>.getValue());<span class="java_comment">// 10. Ennél az enumnál a getValue()-t használjuk mindig az ordinal() helyett!</span>
System.<span class="java_constant">out</span>.println(Month.<span class="java_constant">SEPTEMBER</span>.firstMonthOfQuarter());<span class="java_comment">// a hónapot tartalmazó negyedév első hónapja: JULY</span> </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">java.time.format.TextStyle</span> enum megmondja, hogy milyen stílusban szeretnénk megjeleníteni az eredményt:
  </p>
  <ul>
    <li><b>FULL</b>: teljes (hétfő)</li>
    <li><b>NARROW</b>: általában egy vagy két betű (&quot;H&quot; mint hétfő, &quot;Sz&quot; mint szombat)</li>
    <li><b>SHORT</b>: rövidítés (&quot;Szo&quot;)</li>
  </ul>
  <p class="bekezd">Az enumok int-eken alapulnak, amik pedig megfelelnek az ISO szabványnak, vagyis:</p>
  <ul>
    <li><b>DayOfWeek</b>: 1-7: hétfő-vasárnap</li>
    <li><b>Month</b>: 1-12: január-december</li>
  </ul>
  <p class="bekezd">
    <b>Clock</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">Clock</span> elérést biztosít az aktuális instanthoz, dátumhoz és időhöz egy időzónát használva. Bizonyos szempontból a <span class="programkod">System.currentTimeMillis()</span>
    és <span class="programkod">TimeZone.getDefault()</span> korszerűbb megfelelője. Használata opcionális, hiszen az összes fontos dátum/idő osztálynak van <span class="programkod">now()</span>
    gyártómetódusa, ami a rendszerórát és alapértelmezett időzónát használva visszaad egy példányt. A <span class="programkod">Clock</span> elsődleges célja, hogy szükség esetén lehetővé
    tegye eltérő idők használatát. Az alkalmazások statikus metódusok helyett így egy objektumot használhatnak az aktuális idő megszerzésére. Ez egyszerűsíti a globalizált alkalmazások
    tesztelését. Az a legcélszerűbb, ha minden gyártómetódusnak átadunk egy <span class="programkod">Clock</span> objektumot. Vagy beinjektáljuk egy keretrendszerrel egy bean-be:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">public</span> <span class="java_keyword">class</span> MyBean {
    <span class="java_keyword">private</span> Clock clock;<span class="java_comment">// injektálással</span>
    ...

    <span class="java_keyword">public</span> <span class="java_keyword">void</span> process(LocalDate eventDate) {
        <span class="java_keyword">if</span> (eventDate.isBefore(LocalDate.now(clock))) {
            ...
        }
    }
} </pre>
  </div>
  <p class="bekezd">
    Így lehetővé válik saját óra használata, például fix idővel vagy igény szerinti ofszettel. Mivel a <span class="programkod">Clock</span> absztrakt osztály, saját implementációt is
    készíthetünk belőle, de ennek fortélyaira ebben a cikkben nem térek ki. A <span class="programkod">Clock</span> 8 statikus metódusával szerezhetünk előre elkészített (szálbiztos)
    implementációkat. Ezen implementációk használhatják a rendszer időzónáját, de akár tetszőlegesen kiválasztott időzónát is. Néhány egyszerű használati példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">final</span> Clock clock = Clock.systemUTC();
clock.instant();<span class="java_comment">// az aktuális instant: 2019-02-07T20:14:49.415Z</span>
clock.millis();<span class="java_comment">// az aktuális milliszekundum, a System.currentTimeMillis() megfelelője</span>
LocalDateTime.now(Clock.systemUTC());<span class="java_comment">// az aktuális instant az UTC időzóna szerint. Ennek ajánlott a</span>
    <span class="java_comment">// használata amikor csak az instantra van szükségünk dátum és idő nélkül</span>
LocalDateTime.now(Clock.systemDefaultZone());<span class="java_comment">// az aktuális instant az aktuális időzóna szerint.</span>
    <span class="java_comment">// Ennek a használata bedrótozza a programunkba a helyi időzónát.</span>
LocalDateTime.now(Clock.system(ZoneId.of(<span class="java_string">&quot;Asia/Tokyo&quot;</span>)));<span class="java_comment">// az aktuális instant tetszőleges időzóna szerint</span>

<span class="java_comment">/*</span>
<span class="java_comment"> * Percekre kerekítetten működő (&quot;ketyegő&quot;) instant adott időzóna szerint.</span> 
<span class="java_comment"> * Másodperces verziója: tickSeconds()</span> 
<span class="java_comment"> * Mindig ugyanazt az időpontot visszaadó verzió: fixed(Instant fixedInstant, ZoneId zone)</span>
<span class="java_comment"> * Mindig ugyanannyi időtartamonként &quot;ketyegő&quot; verziója: tick(Clock baseClock, Duration tickDuration)</span>
<span class="java_comment">*/</span>
LocalDateTime.now(Clock.tickMinutes(ZoneId.of(<span class="java_string">&quot;Europe/Paris&quot;</span>)));<span class="java_comment">// 2019-02-07T21:14</span> </pre>
  </div>
  <p class="bekezd">
    <b>LocalDate, LocalTime, LocalDateTime, YearMonth, MonthDay, Year</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">LocalDate</span> dátumot tárol időzóna nélkül. Talán kitalálható, hogy a <span class="programkod">LocalTime</span> meg csak időt tárol szintén időzóna nélkül.
    A <span class="programkod">LocalDateTime</span> összehozza a kettőt: dátumot és időt tartalmaz időzóna nélkül. Mindhárom osztály a <span class="programkod">Clock</span> segítségével is
    létrehozható. A <span class="programkod">LocalTime</span> és <span class="programkod">LocalDateTime</span> az időt nanoszekundumos pontossággal tudja tárolni.
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// LocalDate létrehozása</span>
LocalDate date = LocalDate.now();
LocalDate date1 = LocalDate.of(2019, 2, 1);
LocalDate date2 = LocalDate.parse(<span class="java_string">&quot;2019-02-01&quot;</span>);

<span class="java_comment">// Műveletek és beállítások</span>
LocalDate holnap = LocalDate.now().plusDays(1);
LocalDate elozoHonapUgyanezenANapon = LocalDate.now().minus(1, ChronoUnit.<span class="java_constant">MONTHS</span>);
LocalDate aHonapElsoNapja = LocalDate.parse(<span class="java_string">&quot;2019-01-20&quot;</span>).with(TemporalAdjusters.firstDayOfMonth());
LocalDate haromHetMulva = LocalDate.now().plusWeeks(3);

LocalDate date5 = LocalDate.of(2019, Month.<span class="java_constant">NOVEMBER</span>, 21);
LocalDate nextWed = date5.with(TemporalAdjusters.next(DayOfWeek.<span class="java_constant">WEDNESDAY</span>));

<span class="java_comment">// 2019-11-21-hez képest a következő szerda 2019-11-27.</span>
System.out.printf(<span class="java_string">&quot;%s-hez képest a következő szerda %s.%n&quot;</span>, date5, nextWed);

LocalDate date4 = LocalDate.now(customClock).withMonth(4);<span class="java_comment">// tetszőleges Clock-kal április</span>

<span class="java_comment">// Lekérdezések</span>
DayOfWeek szombat = LocalDate.parse(<span class="java_string">&quot;2019-02-02&quot;</span>).getDayOfWeek();
<span class="java_keyword">int</span> husz = LocalDate.parse(<span class="java_string">&quot;2019-01-20&quot;</span>).getDayOfMonth();
<span class="java_keyword">int</span> honapNapjai = LocalDate.parse(<span class="java_string">&quot;2019-02-20&quot;</span>).lengthOfMonth();<span class="java_comment">// 28</span>

<span class="java_comment">// Feltételvizsgálatok</span>
<span class="java_keyword">boolean</span> szokoev = LocalDate.now().isLeapYear();
<span class="java_keyword">boolean</span> nemKorabban = LocalDate.parse(<span class="java_string">&quot;2019-02-01&quot;</span>).isBefore(LocalDate.parse(<span class="java_string">&quot;2019-02-02&quot;</span>));
<span class="java_keyword">boolean</span> kesobb = LocalDate.parse(<span class="java_string">&quot;2019-02-10&quot;</span>).isAfter(LocalDate.parse(<span class="java_string">&quot;2019-02-09&quot;</span>));

<span class="java_comment">// **********************</span>
<span class="java_comment">// LocalTime létrehozása</span>
LocalTime time1 = LocalTime.now();
LocalTime time2 = LocalTime.of(14, 13, 27);
LocalTime time3 = LocalTime.parse(<span class="java_string">&quot;14:13:27&quot;</span>);

<span class="java_comment">// Műveletek és beállítások</span>
LocalTime hetvenPercMulva = LocalTime.now().plusMinutes(70);
LocalTime egyOraja = LocalTime.now().minus(1, ChronoUnit.<span class="java_constant">HOURS</span>);

<span class="java_comment">// Lekérdezések</span>
<span class="java_keyword">long</span> nanosec = LocalTime.now().getNano();<span class="java_comment">// az aktuális időpont nanoszekundum része</span>
<span class="java_keyword">int</span> ora = LocalTime.ofSecondOfDay(15000).getHour();<span class="java_comment">// 4; vagyis a nap 15 ezredik másodperce a 4. órára esik</span>

<span class="java_comment">// *************************</span>
<span class="java_comment">// LocalDateTime létrehozása</span>
LocalDateTime dtime1 = LocalDateTime.now();
LocalDateTime dtime2 = LocalDateTime.of(LocalDate.of(2019, 2, 1), LocalTime.of(14, 13, 27));
LocalDateTime dtime3 = LocalDateTime.parse(<span class="java_string">&quot;2019-02-10T07:35:00&quot;</span>);

<span class="java_comment">// Műveletek: a LocalDateTime kombinálja a LocalDate és LocalTime műveleteit</span>
LocalDateTime haromHetTizOraMulva = LocalDateTime.now().plusWeeks(3).plusHours(10);

<span class="java_comment">// ***********************</span>
<span class="java_comment">// Adattípusok kombinálása</span>
LocalDateTime aNapKezdete = LocalDate.parse(<span class="java_string">&quot;2019-02-01&quot;</span>).atStartOfDay();
LocalDateTime napiIdopont = LocalDate.parse(<span class="java_string">&quot;2019-02-01&quot;</span>).atTime(15, 20, 10);
LocalDateTime napiIdopont2 = LocalDate.parse(<span class="java_string">&quot;2019-02-01&quot;</span>).atTime(LocalTime.parse(<span class="java_string">&quot;14:42&quot;</span>));
LocalDate date3 = LocalDate.from(LocalDateTime.of(2019, 2, 2, 15, 42, 20));
LocalDateTime dateWithTime = LocalDateTime.now().with(LocalTime.of(14, 45));

LocalDate dateResz = LocalDateTime.now().plusWeeks(3).plusHours(10).toLocalDate();<span class="java_comment">// LocalDateTime-nak csak a dátum mezői</span>
LocalTime timeResz = LocalDateTime.now().minusWeeks(3).minusHours(10).toLocalTime();<span class="java_comment">// LocalDateTime-nak csak az idő mezői</span>

<span class="java_comment">// ***********************</span>
<span class="java_comment">// YearMonth, MonthDay, Year</span>
<span class="java_keyword">int</span> honapHossza = YearMonth.now().lengthOfMonth();<span class="java_comment">// az aktuális hónap hossza</span>
<span class="java_keyword">boolean</span> ervenyesSzokoev = MonthDay.of(Month.<span class="java_constant">FEBRUARY</span>, 29).isValidYear(2010);<span class="java_comment">// false</span>
<span class="java_keyword">boolean</span> ervenyesSzokoev2 = Year.of(2012).isLeap();<span class="java_comment">// true</span> </pre>
  </div>
  <p class="bekezd">Azt hiszem a fenti példákból is látható, hogy az új API szinte az összes mindennapi feladatra kínál megoldást.</p>
  <p class="bekezd">
    <b>Instant</b>
  </p>
  <p class="bekezd">
    Az új date/time API egyik központi eleme az <span class="programkod">Instant</span> osztály, ami az <a onclick="window.open(this.href);return false;"
      href="https://hu.wikipedia.org/wiki/Egyezményes_koordinált_világidő">UTC időegyenesen</a> ábrázol egy pillanatnyi időt. Alkalmazásunk időbélyegeinek tárolására használható például.
  </p>
  <div class="keretes">
    <p>Bár enélkül is használható az adattípus, de az instant alaposabb megértéséhez mégsem árt megismerni néhány dolgot a (számítógépes) időmérésről. Akit ez a témakör nem érdekel,
      nyugodtan átugorhatja ezt a leírást.</p>
    <p>
      Az első fontos fogalom az epoch, ami a <a onclick="window.open(this.href);return false;" href="https://hu.wikipedia.org/wiki/Epocha">wikipédia szerint</a> egy meghatározott időpont,
      amihez a naptárhasználó népek az időszámításukat igazítják. A szó jelentése: korszak. Ettől a kezdőponttól számlált időadatok összessége az éra. (Bár magyarul epocha-nak írják, én
      maradok az informatikában elterjedt epoch-nál.)
    </p>
    <p>
      Nos a Java 8-at használó népek számára a szabványos Java epoch <b>1970-01-01T00:00:00Z</b>. Az <span class="programkod">Instant</span> ún. epoch-másodperceket számol, amelyek ettől a
      Java epoch-tól számolódnak. Az epoch utáni instantoknak pozitív, az epoch előtti instantoknak negatív előjele van. Egy időpillanathoz szükséges értékkészlet a <span class="programkod">long</span>
      típusnál nagyobb tárkapacitást igényel, ezért az osztály belsőleg egy <span class="programkod">long</span> értékben tárolja a másodperceket és egy <span class="programkod">int</span>-ben
      a másodperc nanoszekundumait. Ez utóbbi értéke mindig 0 és 999 999 999 között van. Mindkét egységre nézve a nagyobb érték későbbi időpillanatot jelent.
    </p>
    <p>
      <b>Időskálák</b>
    </p>
    <p>A mindennapi életben az időt szabványosan csillagnapban mérjük. Ez 24 órából áll, egy óra 60 percből, egy perc 60 másodpercből. Egy nap így 86400 másodpercből áll. Ez a
      mindennapi életben használt időfogalmunk. A pontos idő meghatározása azonban nem egyszerű. A modern világban többféle időskálát is használnak.</p>
    <p>
      <u>TAI</u>: a TAI atomórákra alapozott rendkívül egyenletes időskála. Ez jelenleg a világszerte elhelyezett nagyjából 400 atomóra értékének súlyozott átlaga, mivel még az atomórák
      által biztosított idők is ingadoznak illetve kis mértékben eltérnek egymástól. A polgári életben a TAI-t nagyon sok helyen használják, például a GPS navigációhoz, tudományos
      kutatáshoz. Az atomóra alapján definiálták az ún. SI-másodpercet, vagyis azt, hogy mennyi a világszerte elfogadott egy másodperc hossza. (Ha valakit furdal a kíváncsiság: &quot;1 s az
      alapállapotú (0 K) 133Cs céziumatom két hiperfinom energiaszintje közti átmenethez tartozó sugárzás 9 192 631 770 rezgésének idõtartama&quot;.)
    </p>
    <p>
      <u>UT1</u>: a Föld forgására alapozott (vagyis csillagászati alapú) időskála az Egyetemes Időskála (UT1). Ez jól együtt fut a csillagászati megfigyelésekkel, de mivel a Föld forgása
      nem eléggé egyenletes, nem alkalmas olyan célokra, mint a TAI. Ráadásul a Föld forgásának lassulása miatt növekszik is egy nap átlagos hossza, ezért egy csillagnap hossza 2018-ban
      kicsit hosszabb, mint 864000 SI másodperc. A Föld lassulásának mértéke pedig előre nem megjósolható, egy adott nap pontos hosszát mindig csak utólagos méréssel lehet meghatározni. Az
      SI másodperc hossza nagyon közel van egy csillagnap 1/86400 részéhez, de gyakorlati felhasználáshoz az UT1 nem alkalmas, hiszen egy másodperc pontos hossza előre nem meghatározható és
      nagyon kis mértékben mindig ingadozik. A <a onclick="window.open(this.href);return false;" href="https://www.timeanddate.com/worldclock/other/tai">TAI</a> időskálához képest az <a
        onclick="window.open(this.href);return false;" href="https://www.timeanddate.com/time/universal-time.html">UT1</a> ma már majdnem 40 másodperc késésben van.
    </p>
    <p>
      <u>UTC</u>: annak érdekében, hogy a Föld forgásából adódó másodperc hossz-ingadozás a TAI-hoz képest ki legyen egyenlítve, 1975-ben (más forrás szerint 1972-ben) bevezették az
      Összehangolt Világidő-skálát (UTC). Ez lépett a korábbi greenwich-i időskála (GMT) helyébe. Az UTC a szabványos módszer arra, hogy az UT1-ből származó &quot;töredék&quot;
      másodperceket összevonják egy másodperccé amit szökőmásodpercnek neveznek. Az UTC időskáláját a TAI-ból úgy származtatják, hogy a TAI értékét időnként 1-1 szökőmásodperccel
      korrigálják (elvesznek vagy hozzáadnak a Föld tengely körüli forgásának megfelelően). Az UTC lehetővé teszi, hogy egy nap 86399 vagy 86401 SI másodpercből álljon, de emellett
      szinkronban maradjon a Naphoz képest.
    </p>
    <p>A szökőmásodperces helyesbítések biztosítják, hogy a csillagászati UT1 és az UTC-ben megvalósított polgári időskála ne térjen el egymástól 0,9 s-nál jobban. A beiktatás
      gyakorisága a Föld forgásának rendszertelensége miatt maga is ingadozik: 1972 óta 27 alkalommal került sor szökőmásodperc beiktatására, eddig mindig pozitív irányban. Az UTC-ben nincs
      zónaidő, az egész Földön egységes. 1958 és 1972 között az UTC korai definíciója eléggé összetett volt, kisebb részmásodperces ugrásokat alkalmaztak. 2012-ben, amikor az új Java 8 API
      megjelent, újabb megbeszélések folytak, hogy újra módosítsák az UTC definícióját.</p>
    <p>Mindezekből látható, hogy a pontos időszámítás eléggé összetett kérdés. A Java ezért saját időskálát definiál, ez a Java Time-Scale. A Java időskála minden naptári napot pontosan
      864000 részre oszt, ezt nevezi másodpercnek. Ezek a másodpercek eltérhetnek az SI másodperctől de eléggé közel vannak a fentebb tárgyalt de facto nemzetközi polgári időskálához. Az
      idővonal különböző szegmenseihez kissé eltérő Java időskála tartozik, de ezek mindegyike a polgári életben használatos nemzetközi időskálán alapul. Amikor a nemzetközileg elfogadott
      időskálát módosítják vagy lecserélik, új Java időskálát kell definiálni. Minden szegmensnek a következő követelményeknek kell megfelelni:</p>
    <ul>
      <li>szorosan illeszkednie kell az alapjául szolgáló polgári időskálához</li>
      <li>pontosan meg kell egyeznie a nemzetközi időskálával minden nap délben</li>
      <li>pontosan meghatározott kapcsolata kell legyen a nemzetközi polgári időskálával</li>
    </ul>
    <p>A JDK 8 bevezetése tájékán, 2013-ban a Java időskálának két szegmenst definiáltak. Az 1972-11-03-tól lévő szegmenshez további értesítésig a nemzetközi (szökőmásodperces) UTC
      időskála használatos. A Java időskála megegyezik az UTC-vel azokon a napokon ahol nincs szökőmásodperc. Ahol viszont van, a szökőmásodperc egyenletesen kiterjed a nap utolsó 1000
      másodpercére, így megmarad a látszólag pontos napi 864000 másodperc.</p>
    <p>Az 1972-11-03 előtti szegmenshez használatos skála az UT1, ami megfelel a meridianon (Greenwich) lévő csillagidőnek. A két szegmens között a pontos határ az az időpillanat amikor
      az UT1==UTC vagyis 1972-11-03T00:00 és 1972-11-04T12:00 között.</p>
  </div>
  <p class="bekezd">
    Az <span class="programkod">Instant</span> nem dolgozik olyan emberi időegységekkel mint év, hónap vagy nap. Ha ilyen egységekben akarunk számolni akkor az instantot át kell
    konvertálnunk másik adttípussá, például <span class="programkod">LocalDateTime</span> osztállyá. Fordított irányban <span class="programkod">ZonedDateTime</span> és <span
      class="programkod">OffsetTimeZone</span> is konvertálható <span class="programkod">Instant</span> objektummá, mivel mindegyik pontos időt jelöl ki az idővonalon. Visszafelé azonban ez
    már nem igaz: ha egy <span class="programkod">Instant</span> objektumot szeretnénk <span class="programkod">ZonedDateTime</span> vagy <span class="programkod">OffsetDateTime</span>
    objektummá konvertálni, akkor szükségünk van időzónára vagy időzóna ofszetre.
  </p>
  <p class="bekezd">
    Bár eddig azt írtam, az <span class="programkod">Instant</span> nem tárol időzónát, ezzel hogy úgy mondjam nem bontottam ki a valóság minden részletét. Az <span class="programkod">Instant</span>
    ugyanis a Java időskálán dolgozik, az pedig az UTC-hez van igazítva. Tehát az <span class="programkod">Instant</span> valójában az UTC &quot;időzónában&quot; számol. Magyarországon a
    helyi időhöz képest az <span class="programkod">Instant.now()</span> egy órával kevesebbet fog adni. Instant példák:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// *******************</span>
<span class="java_comment">// Instant létrehozása</span>
Instant instant = Instant.now();<span class="java_comment">// a szokásos időlekérdezés itt is működik</span>
Instant instant1 = Instant.parse(<span class="java_string">&quot;2018-10-21T11:19:39Z&quot;</span>);
<span class="java_comment">// Az Instant mezőit - előjeles long és int - közvetlenül megadva is példányosíthatunk:</span>
Instant instant2 = Instant.ofEpochSecond(1500000000l, 999_100_100);<span class="java_comment">// epoch után: 2017-07-14T02:40:00.999100100Z</span>
Instant instant3 = Instant.ofEpochSecond(-1500000000l, -999_100_100);<span class="java_comment">// epoch előtt: 1922-06-20T21:19:59.000899900Z</span>
<span class="java_comment">// Az Instant sztringjében a &quot;Z&quot; jelzi, hogy az UTC időzónában értelmezett</span>

<span class="java_comment">// *********</span>
<span class="java_comment">// Műveletek: a ChronoUnit használatával emberi léptékű mértékegységeket itt is használhatunk</span>
instant = instant.minus(5, ChronoUnit.<span class="java_constant">DAYS</span>);<span class="java_comment">// 5 nap kivonása</span>
instant = instant.plusSeconds(82400);<span class="java_comment">// másodperc, ezredmásodperc és milliszekundum műveletek közvetlenül is rendelkezésre állnak</span>
instant = instant.truncatedTo(ChronoUnit.<span class="java_constant">DAYS</span>);<span class="java_comment">// napokra vagy annál kisebb egységekre csonkolhatunk is, ekkor a csonkolni</span>
<span class="java_comment">// kívántnál kisebb mezők értéke 0 lesz</span>

<span class="java_comment">// ************</span>
<span class="java_comment">// Lekérdezések</span>
<span class="java_comment">// A get az Instant esetén csak ezekre az enum értékekre működik: NANO_OF_SECOND, MICRO_OF_SECOND, MILLI_OF_SECOND,</span>
<span class="java_keyword">int</span> field = instant.get(ChronoField.<span class="java_constant">NANO_OF_SECOND</span>);<span class="java_comment">// lekérdezhetjük az int mező értékét</span>
<span class="java_comment">// vagy egyszerűen csak:</span>
<span class="java_keyword">int</span> nano = instant.getNano();
<span class="java_keyword">long</span> sec = instant.getLong(ChronoField.<span class="java_constant">INSTANT_SECONDS</span>);<span class="java_comment">// A long mező értékét így kapjuk meg...</span>
<span class="java_keyword">long</span> epoch = instant.getEpochSecond();<span class="java_comment">// ...vagy így</span>
System.<span class="java_constant">out</span>.println(instant);<span class="java_comment">// az Instant kiíratása ISO-8601 szabvány szerint</span>

<span class="java_comment">// ****************************</span>
<span class="java_comment">// Konvertálás más adattípusból</span>
instant = Instant.from(ZonedDateTime.now());<span class="java_comment">// Local... típusokkal a from nem működik, kivételt kapunk</span> </pre>
  </div>
  <p class="bekezd">
    <b>Összehasonlítás</b>
  </p>
  <p class="bekezd">
    Eddig megismert adattípusainknál összehasonlító metódusokban a bőség zavarával küzdhetünk (kivéve a <span class="programkod">Clock</span>). Nem árt tudni, melyik pontosan mit is művel,
    hogy azt használhassuk amit tényleg szeretnénk:
  </p>
  <ul>
    <li><b>compareTo</b>: a <span class="programkod">Comparable</span> interfész implementálása miatt. Intuitív módon, a megszokottak szerint használható. <span class="programkod">LocalDate</span>
      és <span class="programkod">LocalDateTime</span> esetén azonos típusoknál figyelembe veszi a használt kronológiát is. Ez akkor fontos, ha olyan programot írunk ami különböző
      kronológiákat is kezel (pl Gergely-naptár, thai buddhista, japán, stb.). A compareTo segítségével összehasonlíthatunk például <span class="programkod">LocalDate</span>-et és <span
      class="programkod">ThaiBuddhistDate</span>-et.</li>
    <li><b>equals</b>: csak pontosan azonos típusok esetén adhat egyáltalán true-t (mivel az equals ugyebár bármilyen objektumot elfogad). Egyébként megegyezik a compareTo működésével
      amikor az 0-t eredményez (ezt a JDK eleve erősen javasolja minden <span class="programkod">Comparable</span>-t implementáló osztálynak).</li>
    <li><b>isAfter, isBefore, isEqual</b>: fontos eltérés az előző kettőhöz képest, hogy ezek a metódusok nem veszik figyelembe a használt kronológiákat, csak magát a dátum értéket!
      isEqual csak <span class="programkod">LocalDate</span> és <span class="programkod">LocalDateTime</span> esetén van.</li>
  </ul>
  <h3>Az időzónák világa</h3>
  <p class="bekezd">
    Egy időzóna a Föld olyan területe, ahol ugyanaz a szabványidő. Minden időzónának van egy azonosítója. Az azonosító formátuma <i>régió/város</i> (pl. Europe/Paris) és minden időzóna
    tartalmaz egy Greenwich/UTC időhöz képesti ofszetet. Magyarország esetén az ofszet +01:00, Tokió esetén +09:00. Az új API-ban két osztály van a zóna és az ofszet kezeléséhez a <span
      class="programkod">java.time</span> csomagban:
  </p>
  <ul>
    <li><b>ZoneId</b>: egy időzóna azonosítót ad meg és szabályokat definiál amikkel <span class="programkod">Instant</span> és <span class="programkod">LocalDateTime</span> adatokat
      konvertálhatunk <span class="programkod">ZonedDateTime</span> objektummá</li>
    <li><b>ZoneOffset</b>: egy Greenwich/UTC-hez képesti időzóna ofszetet definiál</li>
  </ul>
  <p class="bekezd">Az új API három időzónákat kezelő adattípust biztosít:</p>
  <ul>
    <li><b>ZonedDateTime</b>: dátum és idő megfelelő időzónával és zónaofszettel</li>
    <li><b>OffsetDateTime</b>: dátum és idő zónaofszettel, de időzóna azonosító nélkül</li>
    <li><b>OffsetTime</b>: idő kezelése zónaofszettel, de időzóna azonosító nélkül</li>
  </ul>
  <p class="bekezd">
    <b>ZoneId</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">ZoneId</span> absztrakt osztály a Java időzóna-kezelés alapja. Ez adja meg azokat a szabályokat amelyek <span class="programkod">Instant</span> vagy <span
      class="programkod">LocalDateTime</span> konvertálásnál szükségesek. A szabályokat kétféle módon lehet azonosítani:
  </p>
  <ul>
    <li><b>fix ofszetek</b>: UTC/Greenwich-hez képest rögzített ofszet, ami minden helyi dátum-időnél használatos. Ezt reprezentálja a <span class="programkod">ZoneOffset</span>
      osztály.</li>
    <li><b>földrajzi területek</b>: egy adott terület amire olyan szabályok vonatkoznak, amelyek megadják az UTC/Greenwich-hez képest a pontos ofszetet. Ez valójában a <span
      class="programkod">java.time.ZoneRegion</span> osztály, ami csak a java.time csomagon belül látható.</li>
  </ul>
  <p class="bekezd">Az osztályok kapcsolatát segít megvilágítani az alábbi UML ábra:</p>
  <p class="kozep">
    <img alt="uml1" src="/informatika/essze/java/java8/uml1.jpg" />
  </p>
  <p class="bekezd">
    Az absztrakt <span class="programkod">ZoneId</span>-ből származik a <span class="programkod">ZoneRegion</span> és a <span class="programkod">ZoneOffset</span>, bár <span
      class="programkod">ZoneRegion</span> helyett mindig csak <span class="programkod">ZoneID</span> típust tudunk használni, mert az látható a <span class="programkod">java.time</span>
    csomagon kívülről. A legtöbb rögzített ofszetet a <span class="programkod">ZoneOffset</span> adja meg. Egy <span class="programkod">ZoneId</span> példányból a <span class="programkod">normalized()</span>
    metódussal tudunk <span class="programkod">ZoneOffset</span>-et csinálni, ha rögzített ofszetet tartalmaz. Azok a szabályok, amelyek valójában megmondják, hogy hogyan és mikor változik
    az ofszet egy zónán belül, a <span class="programkod">java.time.zone.ZoneRules</span> osztályban vannak leírva. Azért vannak a szabályok és azonosítók külön választva, mert a
    szabályokat a kormányok határozzák meg és ezért rendszeresen változnak, míg az azonosítók stabilak. A <span class="programkod">ZoneId.getRules()</span> adja vissza a zónához tartozó
    szabályokat.
  </p>
  <p class="bekezd">
    Ennek következménye, hogy két ID összehasonlítása csak az ID-ket vizsgálja, míg két szabály összehasonlítása a teljes adathalmazt. Másik következmény, hogy egy <span class="programkod">ZoneId</span>
    szerializálásakor csak az ID lesz elküldve, míg a szabályok szerializálása a teljes adathalmazt küldi. Egy <span class="programkod">ZoneId</span> olyan környezetben is
    deszerializálható, ahol az ID ismeretlen. Ha például egy szerveroldali Java már frissült egy új zóna ID-vel, de a kliensoldali párja még nem, a <span class="programkod">ZoneId</span>
    akkor is létezni fog kliensoldalon is; használható a getId, equals, hashCode, toString, getDisplayName és normalized metódus, de a getRules el fog szállni <span class="programkod">ZoneRulesException</span>
    kivétellel. Ez azért lett így tervezve, hogy egy <span class="programkod">ZonedDateTime</span> objektumot be lehessen tölteni, le lehessen kérdezni, de ne lehessen módosítani olyan
    környezeten, ahol hiányos az időzóna információ.
  </p>
  <p class="bekezd">Az ID a teljes rendszerben egyedi és háromféle létezik:</p>
  <ol>
    <li>a legegyszerűbb típus a normalizált vagyis az ami a <span class="programkod">ZoneOffset</span>-ből adódik. Ez UTC esetén egy &quot;Z&quot;, egyébként (UTC-hez képest)
      &quot;+&quot; vagy &quot;-&quot; jellel kezdődik
    </li>
    <li>a második típus is ofszet-stílusú, de valamilyen formájú prefixszel kiegészítve, például &quot;GMT+2&quot; vagy &quot;UTC+01:00&quot;. A felismert prefixek &quot;UTC&quot;,
      &quot;GMT&quot; és &quot;UT&quot;. Az ofszet a létrehozás során normalizálódik. Ezek az ID-k <span class="programkod">ZoneOffset</span>-té normalizálhatók a <span class="programkod">normalized()</span>
      metódussal.
    </li>
    <li>a harmadik típus a régió alapú ID. Egy régió alapú ID két vagy több karakter hosszú kell legyen és nem kezdődhet &quot;UTC&quot;, &quot;GMT&quot;, &quot;UT&quot;, &quot;+&quot;
      vagy &quot;-&quot; sztringgel. A régió alapú ID-ket konfiguráció definiálja a <span class="programkod">ZoneRulesProvider</span> osztályban. A konfiguráció rendeli hozzá az ID-hez a
      megfelelő ZoneRules-t.
    </li>
  </ol>
  <div class="keretes">
    <p>Az időzóna szabályokat a kormányok definiálják és ezért gyakran változnak. Számos szervezet (angol terminológiában group) monitorozza és egyezteti az időzóna változásokat. Az
      alapértelmezett az IANA Time Zone Database (TZDB). További ilyen szervezet az IATA (a légiközlekedési ipartestület) és a Microsoft. Mindegyikük saját formátumot definiál az általa
      adott régió ID-khoz. A TZDB által definiált ID-k például: &quot;Europe/London&quot; vagy &quot;America/New_York&quot;. Java környezetben a TZDB ID-k használata az elsődleges.</p>
    <p>A kavarodás elkerülése érdekében erősen ajánlott, hogy minden olyan ID, amit nem a TZDB adott, tartalmazza a szervezet nevét. Az IATA időzóna régió ID-i általában megegyeznek a
      hárombetűs reptér kódokkal. (Utrecht repülőterének UTC kódja nyilvánvalóan ütközne is a a fentebb írottakkal.) A TZDB-től eltérő szervezetek régió ID-ihez ajánlott formátum a
      &quot;group~régió&quot;. Vagyis ha IATA adatot definiálunk, akkor az Utrecht repülőtér kódja &quot;IATA~UTC&quot; lenne.</p>
    <p>
      Az aktuális TZDB egyébjént a JDK-ban alapértelmezetten betöltött <span class="programkod">java.time.zone.TzdbZoneRulesProvider</span> osztályban is benne van. Ez az osztály állítja be
      az időzóna szabályokat Java platform vagy környezeti szinten (az adatokat a JRE-n belül a lib\tzdb.dat fájlból olvassa föl). Az egy JVM példányhoz használatos <span class="programkod">ZoneRulesProvider</span>-ek
      konfigurációs fájlból vagy programozottan is megadhatók. De az esetek túlnyomó részében a <span class="programkod">TzdbZoneRulesProvider</span> is tökéletesen elegendő.
    </p>
  </div>
  <p class="bekezd">
    A <span class="programkod">ZoneId</span>-ből lekérdezhető <span class="programkod">ZoneRules</span> is rendelkezik néhány hasznos metódussal:
  </p>
  <div class="programkod">
    <pre>ZoneRules rulez = ZoneId.of(<span class="java_string">"Europe/Paris"</span>).getRules();
<span class="java_comment">// Téli/nyári időszámítás lekérdezése:</span>
<span class="java_keyword">boolean</span> ido1 = rulez.isDaylightSavings(Instant.parse(<span class="java_string">"2019-02-17T22:16:39Z"</span>));<span class="java_comment">// false</span>
<span class="java_keyword">boolean</span> ido2 = rulez.isDaylightSavings(Instant.parse(<span class="java_string">"2019-08-17T22:16:39Z"</span>));<span class="java_comment">// true</span>

<span class="java_comment">// Van egy téli időszámításban lévő időpontnuk:</span>
Instant teli = Instant.parse(<span class="java_string">"2019-02-17T22:16:39Z"</span>);
<span class="java_comment">// Megmondja a következő átállítás időpontját és hogy ott 1 órás hézag lesz ez esetben</span>
ZoneOffsetTransition kovetkezoOraAllitas = rulez.nextTransition(teli);<span class="java_comment">// Transition[Gap at 2019-03-31T02:00+01:00 to +02:00]</span>
<span class="java_comment">// Megmondja az előző átállítás időpontját és hogy ott 1 órás átfedés volt ez esetben</span>
ZoneOffsetTransition elozoOraAllitas = rulez.previousTransition(teli);<span class="java_comment">// Transition[Overlap at 2018-10-28T03:00+02:00 to +01:00]</span>

<span class="java_keyword">boolean</span> nyariIdoszamitas = rulez.isDaylightSavings(teli);<span class="java_comment">// false</span> </pre>
  </div>
  <p class="bekezd">
    <b>ZoneOffset</b>
  </p>
  <p class="bekezd">
    Greenwich/UTC-hez képesti időzóna ofszetet tárol (például +02:00). Egy időzóna ofszet az az időmennyiség, amenyivel az adott időzóna eltér a Greenwich/UTC-től. Ez általában rögzített
    számú óra és perc. A ZoneId osztályban rögzítik az ofszetek hely- és idő szerint változó szabályait. Párizs például egy órával van a Greenwich/UTC előtt télen és két órával nyáron. Egy
    <span class="programkod">ZoneId</span> pédány Párizshoz két <span class="programkod">ZoneOffset</span> példányt fog hivatkozni: egy +01:00 példányt télhez és egy +02:00 példányt
    nyárhoz.
  </p>
  <p class="bekezd">2019-ben az időzóna ofszetek a világon -11:00 és +14:00 között voltak. Azért, hogy későbbi bővítés még beleférjen, de mégis legyen valamiféle validálás, az API az
    ofszeteket -18:00 és +18:00 között limitálja.</p>
  <p class="bekezd">
    <b>Példák</b>
  </p>
  <p class="bekezd">
    Legegyszerűbben a <span class="programkod">ZoneId</span> of metódusával hozhatunk létre <span class="programkod">ZoneId</span> példányokat. Megjegyzésben megadom, hogy az adott példa
    valójában milyen osztály példányát generálja.
  </p>
  <div class="programkod">
    <pre>ZoneId zid1 = ZoneId.of(<span class="java_string">&quot;UTC&quot;</span>);<span class="java_comment">// UTC időzóna; ZoneRegion példány</span>
ZoneId zid2 = ZoneId.of(<span class="java_string">&quot;Z&quot;</span>);<span class="java_comment">// szintén UTC; ZoneOffset példány</span>
ZoneId zid3 = ZoneId.of(<span class="java_string">&quot;Europe/Paris&quot;</span>);<span class="java_comment">// Magyarországra vonatkozó időzóna; ZoneRegion példány</span>
ZoneId zid4 = ZoneId.of(<span class="java_string">&quot;UTC+1&quot;</span>);<span class="java_comment">// Magyarországra vonatkozó időzóna; ZoneRegion példány</span>
ZoneId zid5 = ZoneId.of(<span class="java_string">&quot;+1&quot;</span>);<span class="java_comment">// Magyarországra vonatkozó időzóna; ZoneOffset példány</span>
ZoneId zid6 = ZoneId.of(<span class="java_string">&quot;+01:00&quot;</span>);<span class="java_comment">// Magyarországra vonatkozó időzóna; ZoneOffset példány</span> </pre>
  </div>
  <p class="bekezd">
    <span class="programkod">ZoneOffset példányok létrehozása:</span>
  </p>
  <div class="programkod">
    <pre>ZoneOffset zof1 = ZoneOffset.of(<span class="java_string">&quot;Z&quot;</span>);
ZoneOffset zof2 = ZoneOffset.of(<span class="java_string">&quot;+1&quot;</span>);
ZoneOffset zof3 = ZoneOffset.of(<span class="java_string">&quot;+01:00&quot;</span>);
ZoneOffset zof4 = ZoneOffset.of(<span class="java_string">&quot;-0300&quot;</span>);
ZoneOffset zof5 = ZoneOffset.of(<span class="java_string">&quot;-053010&quot;</span>);<span class="java_comment">// ofszetnek másodperc pontosságot is megadhatunk</span> </pre>
  </div>
  <p class="bekezd">A ZoneOffset az UTC időzónát beépítve is tartalmazza:</p>
  <div class="programkod">
    <pre>ZoneId zid7 = ZoneOffset.<span class="java_constant">UTC</span>;<span class="java_comment">// UTC időzóna; ZoneOffset példány</span> </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">ZoneOffset</span> és <span class="programkod">ZoneId</span> a fenti leírás értelmében eltér még ha látszólag ugyanarra az időzónára vonatkozik is! Tekintsük a
    következő példát:
  </p>
  <div class="programkod">
    <pre>ZonedDateTime now = ZonedDateTime.now();
<span class="java_keyword">boolean</span> utcEquals = now.withZoneSameInstant(ZoneOffset.<span class="java_constant">UTC</span>)
    .equals(now.withZoneSameInstant(ZoneId.of(<span class="java_string">&quot;UTC&quot;</span>))); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">utcEquals</span> értéke <span class="programkod">false</span> lesz, pedig mindkét esetben UTC időzónával dolgozunk! Ha két <span class="programkod">ZonedDateTime</span>
    értékét megnézzük, akkor látjuk is, mi a különbség:
  </p>
  <div class="programkod">
    <pre>System.<span class="java_constant">out</span>.println(now.withZoneSameInstant(ZoneOffset.<span class="java_constant">UTC</span>));
<span class="java_comment">// Ezt írja ki: 2019-02-17T18:35:09.215Z</span>
System.<span class="java_constant">out</span>.println(now.withZoneSameInstant(ZoneId.of(<span class="java_string">&quot;UTC&quot;</span>)));
<span class="java_comment">// Ezt írja ki: 2019-02-17T18:35:09.215Z[UTC]</span>

<span class="java_comment">// Mivel ez a forma zóna azonosítót is tartalmaz,</span>
<span class="java_comment">// ezért eltér a kizárólag ofszetet tartalmazó normalizált változattól:</span>
ZoneId zid = ZoneId.of(<span class="java_string">&quot;UTC+2&quot;</span>);
System.<span class="java_constant">out</span>.println(zid);<span class="java_comment">// UTC+02:00</span>
zid = zid.normalized();
System.<span class="java_constant">out</span>.println(zid);<span class="java_comment">// +02:00</span> </pre>
  </div>
  <p class="bekezd">
    A fenti példában az equals metódust használtuk összehasonlításra, ami objektum egyenlőséget vizsgál. Ezért a két objektum nem lesz ugyanaz. Ha az isEqual metódust hasznájuk, ami a
    dátum-időt vizsgálja, akkor már <span class="programkod">true</span> értéket kapunk. Fentebb pedig már említettem, hogy a <span class="programkod">normalized()</span> metódussal is
    tudunk <span class="programkod">ZoneOffset</span>-et csinálni a <span class="programkod">ZoneId</span>-ből, tehát:
  </p>
  <div class="programkod">
    <pre>
      <span class="java_keyword">boolean</span> utcEquals2 = now.withZoneSameInstant(ZoneOffset.<span class="java_constant">UTC</span>)
    .equals(now.withZoneSameInstant(ZoneId.of(<span class="java_string">"UTC"</span>).normalized())); </pre>
  </div>
  <p class="bekezd">
    Ez esetben <span class="programkod">true</span> lesz az <span class="programkod">utcEquals2</span> értéke. A normalize a konkrét zónainformáción kívül minden esetben működik, tehát a
    fenti példák közül egyedül a <span class="programkod">ZoneId zid3 = ZoneId.of(&quot;Europe/Paris&quot;);</span> változatnál maradna <span class="programkod">ZoneRegion</span> a példány
    normalizálás után, az összes többinél <span class="programkod">ZoneOffset</span>-et kapnánk. A &quot;Z&quot;, &quot;+1&quot;, &quot;+01:00&quot; példák esetén pedig az of a <span
      class="programkod">ZoneId</span> esetén is egyből <span class="programkod">ZoneOffset</span> példányt hoz létre.
  </p>
  <p class="bekezd">Az ofszetek a világban általában egész órákban vannak rögzítve, de vannak kivételek. A következő kód kilistázza az összes időzónát ami nem egész órákat használ az
    UTC-től:</p>
  <div class="programkod">
    <pre>List&lt;String&gt; zoneList = <span class="java_keyword">new</span> ArrayList&lt;String&gt;(ZoneId.getAvailableZoneIds());
Collections.sort(zoneList);
<span class="java_keyword">for</span> (String s : zoneList) {
    ZoneId zone = ZoneId.of(s);
    ZonedDateTime zdt = ZonedDateTime.now(zone);
    ZoneOffset offset = zdt.getOffset();
    <span class="java_keyword">int</span> secondsOfHour = offset.getTotalSeconds() % (60 * 60);
    <span class="java_comment">// Csak azon zónákat írjuk ki amiknek nem egész órányi ofszetjük van</span>
    <span class="java_keyword">if</span> (secondsOfHour != 0) {
        System.out.printf(<span class="java_string">&quot;%25s %8s%n&quot;</span>, zone, offset);
    }
} </pre>
  </div>
  <p class="bekezd">
    <b>ZonedDateTime</b>
  </p>
  <p class="bekezd">
    Amennyiben egy adott időzónában lévő dátumra/időre van szükségünk, a <span class="programkod">ZonedDateTime</span> segít. A nanoszekundumos pontossággal tárolt idő mellett egy időzónát
    és egy zóna ofszetet is tárol (gyakorlatilag egybefoglal egy <span class="programkod">LocalDateTime</span>, egy <span class="programkod">ZoneId</span> és egy <span class="programkod">ZoneOffset</span>
    objektumot). Egy tárolható érték például &quot;2019. november 2.-a 11:45.30.123456789 +02:00 az Europe/Paris időzónában&quot;.
  </p>
  <p class="bekezd">
    Az osztály konvertálni tud <span class="programkod">LocalDateTime</span> idővonalból <span class="programkod">Instant</span> idővonalba; a kettő között a <span class="programkod">ZoneOffset</span>
    által tárolt ofszet a különbség. A konvertáláshoz figyelembe kell venni a <span class="programkod">ZoneId</span> által meghatározott szabályokat is. Láttuk, hogy egy <span
      class="programkod">Instant</span>-hoz könnyű ofszetet meghatározni, mert az <span class="programkod">Instant</span>-hoz pontosan egy érvényes ofszet tartozik. A <span
      class="programkod">LocalDateTime</span> ofszetjének meghatározása viszont nem egyszerű. Három eset van:
  </p>
  <ul>
    <li>normál eset, egy érvényes ofszettel. Az év nagy részére a normál eset vonatkozik, ahol a <span class="programkod">LocalDateTime</span>-hoz egyetlen érvényes ofszet tartozik.
    </li>
    <li>hézag (<i>gap</i>) nulla érvényes ofszettel. Ez akkor van amikor az óra előreugrik, tipikusan például tavaszi óraátállításnál. Egy résben olyan <span class="programkod">LocalDateTime</span>
      értékek vannak amiknek nincs érvényes ofszetjük.
    </li>
    <li>átfedés (<i>overlap</i>) két érvényes ofszettel. Ez akkor van amikor az órát visszaállítjuk, tipikusan például őszi óraátállításnál. Átfedésnél olyan <span class="programkod">LocalDateTime</span>
      értékek vannak amiknek két érvényes ofszetjük van.
    </li>
  </ul>
  <p class="bekezd">
    Esélyes, hogy a <span class="programkod">LocalDateTime</span>-ból <span class="programkod">Instant</span>-ba közvetlenül konvertálás bonyolult lesz. Hézagok esetén a Java azt csinálja,
    hogy ha a <span class="programkod">LocalDateTime</span> a rés közepébe esik, akkor az eredmény zónázott date-time a rés hosszával eltolt local date-time értéke lesz, így olyan értéket
    ad, ami tipikusan a nyári időszámítás késői ofszetjében van. Átfedések esetén az általános stratégia, hogy ha a <span class="programkod">LocalDateTime</span> az átfedés közepébe esik,
    akkor az előző ofszet lesz megtartva. Ha nincs előző ofszet, vagy érvénytelen, akkor a korábbi ofszet lesz használatos, ami tipikusan a &quot;nyári&quot; időszámítás. Két metódus, a <span
      class="programkod">withEarlierOffsetAtOverlap()</span> és <span class="programkod">withLaterOffsetAtOverlap()</span> segít kezelni az átfedés jelenségét.
  </p>
  <p class="bekezd">
    A következő módokon tudunk <span class="programkod">ZonedDateTime</span> példányokat létrehozni:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// *************************</span>
<span class="java_comment">// ZonedDateTime létrehozása</span>
ZonedDateTime zoned1 = ZonedDateTime.now();
ZonedDateTime zoned2 = ZonedDateTime.now(ZoneId.of(<span class="java_string">&quot;Europe/Paris&quot;</span>));
ZonedDateTime zoned3 = ZonedDateTime.parse(<span class="java_string">&quot;2015-05-03T10:15:30+01:00[Europe/Paris]&quot;</span>);

<span class="java_comment">// Más adattípusokból</span>
LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zoned4 = ZonedDateTime.of(ldt, ZoneId.of(<span class="java_string">"Europe/Paris"</span>));<span class="java_comment">// LocalDateTime -&gt; ZonedDateTime</span>
Instant instant = Instant.now();
ZonedDateTime zoned5 = ZonedDateTime.ofInstant(instant, ZoneId.of(<span class="java_string">"Asia/Tokyo"</span>));<span class="java_comment">// Instant -&gt; ZonedDateTime</span> </pre>
  </div>
  <p class="bekezd">
    <span class="programkod">Instant</span> és <span class="programkod">LocalDateTime</span> példányokból az atZone metódussal tudunk <span class="programkod">ZonedDateTime</span>-ot
    csinálni:
  </p>
  <div class="programkod">
    <pre>ZonedDateTime zoned6 = LocalDateTime.now().atZone(ZoneId.of(<span class="java_string">&quot;+07:00&quot;</span>));
ZonedDateTime zoned7 = Instant.now().atZone(ZoneId.of(<span class="java_string">&quot;Australia/Sydney&quot;</span>)); </pre>
  </div>
  <p class="bekezd">A ZonedDateTime természetesen rendelkezik mindazon műveletekkel is amelyeket már korábban láttunk a többi adattípusnál. Különböző időzónák közötti konvertálásokat
    mutat be a következő példa:</p>
  <div class="programkod">
    <pre>
<span class="java_comment">// Indítunk egy helyi idővel: 2018-12-24T19:00</span>
LocalDateTime local = LocalDateTime.of(2018, 12, 24, 19, 0);
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;LocalDateTime: &quot;</span> + local);

<span class="java_comment">// Beállítjuk az Europe/Paris időzónát a LocalDateTime-unknak</span>
ZonedDateTime magyarIdoSzerint = local.atZone(ZoneId.of(<span class="java_string">&quot;Europe/Paris&quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Magyar idő szerint: &quot;</span> + magyarIdoSzerint);

<span class="java_comment">// A mi időnk az UTC időzónában</span>
ZonedDateTime magyarIdoToUTC = magyarIdoSzerint.withZoneSameInstant(ZoneId.of(<span class="java_string">&quot;UTC+00:00&quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;UTC időzónába konvertálva: &quot;</span> + magyarIdoToUTC);

<span class="java_comment">// A mi időnk japán idő szerint</span>
ZonedDateTime magyarIdoJapanban = magyarIdoSzerint.withZoneSameInstant(ZoneId.of(<span class="java_string">&quot;Asia/Tokyo&quot;</span>));
System.<span class="java_constant">out</span>.println(<span class="java_string">&quot;Japán idővé konvertálva: &quot;</span> + magyarIdoJapanban);</pre>
  </div>
  <p class="bekezd">
    A következő példában Budapestről Dubajba repülünk. Az indulási időt magyar időzónában adjuk meg <span class="programkod">ZonedDateTime</span> példányként. A withZoneSameInstant és
    plusMinutes metódusok segítségével kiszámoljuk, hogy mikor érkezünk helyi idő szerint Dubajba 5 óra 15 perc utazást követően. A ZoneRules.isDaylightSavings metódus megmondja, hogy nyári
    időszámítás van-e amikor a repülő megérkezik Dubajba. <span class="programkod">DateTimeFormatter</span> példányt használunk a <span class="programkod">ZonedDateTime</span> példány
    megjelenítéséhez (később bővebben lesz róla szó):
  </p>
  <div class="programkod">
    <pre>DateTimeFormatter format = DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy MMM d  hh:mm a&quot;</span>);

<span class="java_comment">// 2019. február 19-én indulunk Budapestről délután 2:50-kor</span>
ZoneId indulasiZona = ZoneId.of(<span class="java_string">&quot;Europe/Paris&quot;</span>);
ZonedDateTime indulas = ZonedDateTime.of(LocalDateTime.of(2019, Month.<span class="java_constant">FEBRUARY</span>, 19, 14, 50), indulasiZona);
<span class="java_keyword">try</span> {
    System.out.printf(<span class="java_string">&quot;Indulás:  %s (%s)%n&quot;</span>, indulas.format(format), indulasiZona);
} <span class="java_keyword">catch</span> (DateTimeException ex) {
    System.out.printf(<span class="java_string">&quot;%s nem formázható!%n&quot;</span>, indulas);
}

<span class="java_comment">// 5 óra 15 perc utazás után érkezünk</span>
ZoneId celZona = ZoneId.of(<span class="java_string">&quot;Asia/Dubai&quot;</span>);
ZonedDateTime erkezes = indulas.withZoneSameInstant(celZona).plusMinutes(5 * 60 + 15);
<span class="java_keyword">try</span> {
    System.out.printf(<span class="java_string">&quot;Érkezés: %s (%s)%n&quot;</span>, erkezes.format(format), celZona);
} <span class="java_keyword">catch</span> (DateTimeException ex) {
    System.out.printf(<span class="java_string">&quot;%s nem formázható!%n&quot;</span>, erkezes);
}

<span class="java_keyword">if</span> (celZona.getRules().isDaylightSavings(erkezes.toInstant())) {
    System.out.printf(<span class="java_string">&quot;  (%s időzónában nyári időszámítás lesz.)%n&quot;</span>, celZona);
} <span class="java_keyword">else</span> {
    System.out.printf(<span class="java_string">&quot;  (%s időzónában téli időszámítás lesz.)%n&quot;</span>, celZona);
}</pre>
  </div>
  <p class="bekezd">A program kimenete:</p>
  <pre class="programkod">Indulás:  2019 febr. 19  02:50 DU (Europe/Paris)
Érkezés: 2019 febr. 19  11:05 DU (Asia/Dubai)
  (Asia/Dubai időzónában téli időszámítás lesz.)</pre>
  <p class="bekezd">
    <b>OffsetTime, OffsetDateTime</b>
  </p>
  <p class="bekezd">
    Ezek a típusok az idő és dátum/idő mellett adott UTC-hez képesti ofszetet tárolnak. Kezelésük az eddigiek alapján már semmi újdonságot nem jelenthet. Azt viszont érdemes átgondolni,
    hogy mikor használjuk ezeket a <span class="programkod">ZonedDateTime</span> helyett? Ha olyan szoftverrendszert írunk ami földrajzi helyek alapján saját szabályokat definiál a
    dátum-idő számításokhoz vagy ha időbélyegeket olyan adatbázisban tárolunk ami csak UTC-hez képesti abszolút ofszeteket tárol, akkor érdemes az <span class="programkod">OffsetDateTime</span>-ot
    használni. Emellett XML vagy egyéb formátumok is definiálnak dátum-idő átviteli adattípusokat <span class="programkod">OffsetDateTime</span> vagy <span class="programkod">OffsetTime</span>
    típusként. Bár mindhárom osztály tárol ofszetet, csak a <span class="programkod">ZonedDateTime</span> használja a <span class="programkod">ZoneRules</span>-t, hogy meghatározza, egy
    időzónában egy ofszet hogyan változik. A legtöbb időzóna tartalmazza például az 1 órás rést amikor a nyári időszámításhoz előre állítjuk az órát és átfedést amikor a téli időszámításhoz
    vissza (ilyenkor az állítás előtti utolsó óra megismétlődik). A <span class="programkod">ZonedDateTime</span> kezeli ezeket az eseteket, de az OffsetDateTime és <span class="programkod">OffsetTime</span>
    osztályok nem.
  </p>
  <h3>Időtartamok</h3>
  <p class="bekezd">Időmennyiségek kezeléséhez a Java 8 két osztályt és egy metódust biztosít:</p>
  <ul>
    <li><b>Duration</b>: idő alapú (másodperc, nanoszekundum) mennyiség</li>
    <li><b>Period</b>: dátum alapú (év, hónap, nap) mennyiség</li>
    <li><b>ChronoUnit.between</b>: metódus két objektum közötti időtartam meghatározásához</li>
  </ul>
  <p class="bekezd">
    <span class="programkod">Duration</span> esetén egy nap pontosan 24 óra hosszú. <span class="programkod">Period</span> esetén viszont időzónától függően változhat egy nap hossza amikor
    <span class="programkod">Period</span> objektumot <span class="programkod">ZonedDateTime</span>-hoz adunk (például a nyári időszámítás első vagy utolsó napján).
  </p>
  <p class="bekezd">
    <b>Duration</b>
  </p>
  <p class="bekezd">
    Időmennyiség számolása másodpercekben és nanoszekundumokban. Egy <span class="programkod">Duration</span> objektum tartalma természetesen ezektől eltérő időtartam-egységekben is
    lekérdezhető (percek, órák vagy napok). A <span class="programkod">Duration</span> nem követ időzónát vagy nyári időszámítást. 1 napnyi <span class="programkod">Duration</span>
    hozzáadása egy <span class="programkod">ZonedDateTime</span> értékhez, vagy nap mértékegységben való lekérdezés minden esetben úgy működik, hogy egy nap 24 órából áll, függetlenül a
    nyári időszámítástól vagy egyéb időbeli eltérésektől. A <span class="programkod">Duration</span> esetén használt másodperc nem feltétlenül azonos az SI-féle, atomórán alapuló
    másodperccel, de ez csak a szökőmásodpercek közelében lévő számításokat befolyásolja, ezért a legtöbb alkalmazásra semmi hatása nincs.
  </p>
  <p class="bekezd">
    A <span class="programkod">Duration</span> ugyanolyan felbontást használ, mint az <span class="programkod">Instant</span>, vagyis nanoszekundumokat tárol <span class="programkod">int</span>,
    másodperceket pedig <span class="programkod">long</span> primitív típusban. Egy fizikai időtartam végtelen hosszú is lehet, de az <span class="programkod">Instant</span>-hoz hasonló
    megszorítás mindenesetre jó kompromisszum a használhatóság terén. A tárolható időtartam így is nagyobb, mint a világegyetemünk jelenlegi becsült élettartama. Az adattárolásból az is
    következik, hogy ez egy irányított időtartam, vagyis az értéke negatív is lehet. Például akkor, ha olyan vég-időponttal hoztuk létre ami az indulási időpont előttre esik.
  </p>
  <p class="bekezd">
    A <span class="programkod">Duration</span> olyan helyzetekben alkalmas, amikor gépi alapú időt mérünk, mint például <span class="programkod">Instant</span>-okat használó kódnál.
    Példányt a between metódussal tudunk a legegyszerűbben létrehozni:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// ********************</span>
<span class="java_comment">// Duration létrehozása</span>
LocalTime kezdet = LocalTime.parse(<span class="java_string">&quot;19:30&quot;</span>);
LocalTime veg = LocalTime.parse(<span class="java_string">&quot;23:45&quot;</span>);
Duration dur1 = Duration.between(kezdet, veg);
<span class="java_keyword">long</span> percek = dur1.toMinutes();<span class="java_comment">// 255</span> </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">long</span> típussal visszatérő <span class="programkod">toDays()</span>, <span class="programkod">toHours()</span>, <span class="programkod">toMillis()</span>,
    <span class="programkod">toMinutes()</span>, <span class="programkod">toNanos()</span> metódusokkal pedig le tudjuk kérdezni az értékét.
  </p>
  <p class="bekezd">A between metódusnak egyébként eltérő típusokat is megadhatunk. Az időtartam számításakor ilyen esetekben az első paraméter típusából fogja meghatározni a második
    típusát.</p>
  <div class="programkod">
    <pre>LocalDateTime vegDatum = LocalDateTime.of(2019, 2, 19, 20, 10);
LocalTime kezdetIdo = LocalTime.parse(<span class="java_string">&quot;11:30&quot;</span>);
Duration dur2 = Duration.between(kezdetIdo, vegDatum); <span class="java_comment">// 520 perc</span> </pre>
  </div>
  <p class="bekezd">Ha viszont felcserélnénk a kettőt:</p>
  <div class="programkod">
    <pre>Duration dur2 = Duration.between(vegDatum, kezdetIdo); </pre>
  </div>
  <p class="bekezd">
    Akkor <span class="programkod">DateTimeException</span> kivételt kapunk, mert a <span class="programkod">kezdetIdo</span>-t nem tudja <span class="programkod">LocalDateTime</span>-ként
    használni, hiszen dátum információt a <span class="programkod">LocalTime</span> nem tárol. (Ugyanígy kivételt kapunk ha az első paraméter <span class="programkod">ZonedDateTime</span>,
    a második pedig <span class="programkod">LocalDateTime</span>, mert ez esetben a <span class="programkod">LocalDateTime</span> példányból nem tud <span class="programkod">ZoneId</span>-t
    szerezni.)
  </p>
  <p class="bekezd">További létrehozási lehetőségek:</p>
  <div class="programkod">
    <pre>Duration dur2 = Duration.ofDays(2);<span class="java_comment">// pontosan 2x24 óra</span>
Duration dur3 = Duration.ofMinutes(-256);<span class="java_comment">// -256 perc</span>
Duration dur4 = Duration.parse(<span class="java_string">&quot;PT25M&quot;</span>);<span class="java_comment">// 25 perc</span>
Duration dur5 = Duration.parse(<span class="java_string">&quot;-PT2H12M&quot;</span>);<span class="java_comment">// -2 óra -12 perc; -132 perc</span>
Duration dur6 = Duration.parse(<span class="java_string">&quot;PT-2H12M&quot;</span>);<span class="java_comment">// -2 óra +12 perc; -108 perc</span> </pre>
  </div>
  <p class="bekezd">
    (A parse metódus <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/time/Duration.html#parse-java.lang.CharSequence-">leírásánál
      az API-ban</a> egyébként több hibás példa is szerepel, amikkel valójában kivételt dob a metódus; a T jel hiányzik több esetben.)
  </p>
  <p class="bekezd">A Duration is rendelkezik a szokásos műveletekkel, sőt még egy picit többel is, például:</p>
  <div class="programkod">
    <pre>Duration duration1 = Duration.ofDays(2).plusMinutes(20).minusMillis(230);
System.<span class="java_constant">out</span>.println(duration1);<span class="java_comment">// PT48H19M59.77S</span> 
<span class="java_comment">//vagyis 48 óra 19 perc 59 másodperc 770 ezredmásodperc</span>

Duration duration2 = Duration.ofHours(-436);
duration2 = duration2.abs();<span class="java_comment">// abszolútérték</span>
duration2 = duration2.negated();<span class="java_comment">// negált érték</span>
duration2 = duration2.dividedBy(10);<span class="java_comment">// osztjuk 10-zel</span>
System.<span class="java_constant">out</span>.println(duration2.toHours());<span class="java_comment">// -43</span>

<span class="java_comment">// A Duration-t más típusok műveleteiben is felhasználhatjuk.</span>
<span class="java_comment">// Itt például a mai időponthoz hozzáadunk 40 órát:</span>
LocalDateTime ldt = LocalDateTime.now().plus(Duration.ofHours(40)); </pre>
  </div>
  <p class="bekezd">
    <b>Period</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">Period</span> osztállyal dátum alapú (évek, hónapok, napok) időtartamot tudunk ábrázolni. A get metódusaival (getMonths, getDays és getYears) tudjuk kinyerni
    belőle az időmennyiséget a kívánt mértékegységben, értékei lehetnek nullák is.
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// ********************</span>
<span class="java_comment">// Period létrehozása</span>
LocalDate kezdet = LocalDate.parse(<span class="java_string">"2017-07-19"</span>);
LocalDate veg = kezdet.plus(Period.ofDays(9));
<span class="java_keyword">int</span> eredmeny = Period.between(veg, kezdet).getDays();<span class="java_comment">// -9</span>

<span class="java_comment">// További létrehozási lehetőségek</span>
Period per1 = Period.of(10, 6, 12);<span class="java_comment">// 10 év 6 hónap 12 nap</span>
Period per2 = Period.ofWeeks(12);<span class="java_comment">// 12 hét</span>
Period per3 = Period.parse(<span class="java_string">"P1Y2M5D"</span>);<span class="java_comment">// 1 év 2 hónap 5 nap</span>
Period per4 = Period.parse(<span class="java_string">"P-3Y5M"</span>);<span class="java_comment">// -3 év 5 hónap, vagy:</span>
Period per5 = Period.of(-3, 5, 0);<span class="java_comment">// ugyanaz mint a fönti, per4.equals(per5)==true</span>

<span class="java_comment">// Műveletek</span>
LocalDateTime ldt = LocalDateTime.now();
Period per = Period.ofDays(3);
<span class="java_comment">// a Period tartalmaz addTo és subtractFrom metódust,</span>
<span class="java_comment">// de a JDK inkább a kívánt típus plus és minus metódusának használatát javasolja</span>
<span class="java_comment">// tehát az alábbi két sor működése megegyezik, de a második a javasolt:</span>
ldt = (LocalDateTime) per.addTo(ldt);
ldt.plus(per);
System.<span class="java_constant">out</span>.println(Period.of(2019, 2, 22).toTotalMonths());<span class="java_comment">// a teljes hónapok száma a dátumban: 24230</span> </pre>
  </div>
  <p class="bekezd">
    A megszokott plus és minus műveletekkel a <span class="programkod">Period</span> esetén vigyázni kell, ezek ugyanis nem normalizálják az eredményt automatikusan!
  </p>
  <div class="programkod">
    <pre>Period per6 = Period.of(2019, 2, 22).minusDays(40);
System.<span class="java_constant">out</span>.println(per6);<span class="java_comment">// P2019Y2M-18D vagyis 2019 02 -18</span>
Period per7 = Period.of(2019, 2, 22).plusMonths(13);
System.<span class="java_constant">out</span>.println(per7);<span class="java_comment">// P2019Y15M22D vagyis 2019 15 22</span> </pre>
  </div>
  <p class="bekezd">
    Természetesen ha ezeket a <span class="programkod">Period</span> példányokat date/time adattípusok esetén használjuk (hozzáadjuk, elvesszük) akkor az eredmény date/time adattípusok
    normalizáltak lesznek, csak az önmagában álló <span class="programkod">Period</span> kezelésekor fontos tudni róla. A hónapokat a normalize() metódussal normalizálhatjuk (ez a napokat
    változatlanul hagyja):
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// A normalized a napokkal nem törődik, csak a hónapokkal:</span>
System.<span class="java_constant">out</span>.println(per6.normalized());<span class="java_comment">// P2019Y2M-18D, vagyis semmi nem változott</span>
System.<span class="java_constant">out</span>.println(per7.normalized());<span class="java_comment">// P2020Y3M22D vagyis 2020 03 22</span> </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">Period</span> különbözik a <span class="programkod">Duration</span> típustól abban, hogy míg a <span class="programkod">Duration</span> mindig rögzített időt
    kezel (egy nap mindig 24 órából áll), a <span class="programkod">Period</span> megpróbálja fenntartani a helyi időt. Adjunk hozzá épp a tavaszi óraátállítás előtti napon egy <span
      class="programkod">ZonedDateTime</span> dátumhoz egy napnyi <span class="programkod">Period</span>-ot és <span class="programkod">Duration</span>-t. Mondjuk 16:00-kor. A <span
      class="programkod">Period</span> egy naptári napot fog hozzáadni és egy olyan <span class="programkod">ZonedDateTime</span>-ot eredményez ami a következő nap 16:00-n áll. Ezzel
    szemben a <span class="programkod">Duration</span> pontosan 24 órát fog hozzáadni, így olyan <span class="programkod">ZonedDateTime</span>-ot eredményez ami a következő nap 17:00-n áll
    (a mi időzónánkban a rés pont egy óra).
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// óraátállítás: 2019-03-31T02:00+01:00-kor +02:00-re</span>
<span class="java_comment">// alább az előző nap 16:00-ra hozunk létre egy példányt:</span>
ZonedDateTime zdt = ZonedDateTime.of(2019, 3, 30, 16, 0, 0, 0, ZoneId.of(<span class="java_string">"Europe/Paris"</span>));<span class="java_comment">// 2019-03-30T16:00+01:00[Europe/Paris]</span>
System.<span class="java_constant">out</span>.println(zdt.plus(Duration.ofHours(24)));<span class="java_comment">// 2019-03-31T17:00+02:00[Europe/Paris]</span>
System.<span class="java_constant">out</span>.println(zdt.plus(Period.ofDays(1)));<span class="java_comment">// 2019-03-31T16:00+02:00[Europe/Paris]</span> </pre>
  </div>
  <p class="bekezd">
    <b>ChronoUnit.between</b>
  </p>
  <p class="bekezd">
    Ha az eltelt időt nem a <span class="programkod">Period</span> vagy <span class="programkod">Duration</span> által adott bontásban hanem egyetlen mennyiségben szeretnénk használni,
    akkor jó a ChronoUnit.between metódus. Meg kell adni a kezdő és vég időpontot, amelyeknek kompatibilis típusoknak kell lenniük. A metódus az eredmény kiszámítása előtt átkonvertálja a
    második paramétert az első típus példányává. Az eredmény negatív is lehet, ha a végdátum a kezdő dátum elé esik. Példa:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">long</span> napok = ChronoUnit.DAYS.between(LocalDate.of(2010, 4, 5), LocalDate.of(2019, 2, 22));<span class="java_comment">// 3245</span> </pre>
  </div>
  <p class="bekezd">A számítás mindig egész számot eredményez: a két idő közötti teljes egységekben. Tehát a 11:30 és 13:29 között ha órákban számolunk akkor csak egy órát kapunk, mert
    még egy perc hiányzik a két órához.</p>
  <p class="bekezd">
    Létezik a metódusnak egy egyenértékű változata is, mégpedig a <span class="programkod">Temporal.until(Temporal, TemporalUnit)</span> metódus:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">long</span> napok2 = LocalDate.of(2010, 4, 5).until(LocalDate.of(2019, 2, 22), ChronoUnit.<span class="java_constant">DAYS</span>);<span class="java_comment">//3245</span> </pre>
  </div>
  <p class="bekezd">
    A két metódus használata ekvivalens, mindig a kód olvashatósága szerint érdemes köztük dönteni. Számításainkat a <span class="programkod">ChronoUnit</span> vagy a <span
      class="programkod">ChronoField</span> mezőit használva végezhetjük. Ha az egység számítása nem támogatott, akkor <span class="programkod">UnsupportedTemporalTypeException</span>
    kivételt kapunk.
  </p>
  <h3>Parse és formázás</h3>
  <p class="bekezd">
    Láttuk, hogy a dátum/idő adattípusoknak van beépített parse metódusuk sztringek közvetlen beolvasásához. Az adattartalom ISO-8601-nek megfelelő formában való kiírásához eddig a <span
      class="programkod">toString()</span> metódust használtuk, de ezek az osztályok tartalmaznak format metódust is, amivel kívánt formára alakíthatjuk a tárolt információt. A parse pedig
    rendelkezik olyan kétparaméteres változattal is, ahol a második paraméter egy tetszőleges formátum. Ez kétparaméteres parse és a format esetén is ugyanaz: egy <span class="programkod">DateTimeFormatter</span>
    példány. A <span class="programkod">DateTimeFormatter</span> tartalmaz néhány <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#predefined">előre megadott formázót</a>, de sajátot is definiálhatunk.
  </p>
  <p class="bekezd">
    A <span class="programkod">java.time</span> csomagban lévő új adattípusok egyébként közvetlenül is használhatók a már 1.5-ös Java óta létező <span class="programkod">java.util.Formatter</span>
    és <span class="programkod">String.format</span> eszközökkel ugyanúgy mint a régi <span class="programkod">java.util.Date</span> és <span class="programkod">java.util.Calendar</span>.
    Erre már volt korábban néhány példa a régi <span class="programkod">System.out.printf()</span> metódus használatakor is:
  </p>
  <div class="programkod">
    <pre>String minta = <span class="java_string">&quot;2019-02-25&quot;</span>;
Date dat = <span class="java_keyword">null</span>;
<span class="java_keyword">try</span> {
    dat = <span class="java_keyword">new</span> SimpleDateFormat(<span class="java_string">&quot;yyyy-MM-d&quot;</span>).parse(minta);
} <span class="java_keyword">catch</span> (ParseException ex) {
    <span class="java_comment">// <span class="java_todo">TODO</span> hibakezelés</span>
}
String output = String.format(<span class="java_string">&quot;%tB havában %tA napon&quot;</span>, dat, dat);
LocalDate local = LocalDate.parse(minta);
String output2 = String.format(<span class="java_string">&quot;%tB havában %tA napon&quot;</span>, local, local); </pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">output</span> és <span class="programkod">output2</span> értéke is &quot;<span class="programkod">február havában hétfő napon</span>&quot;.
  </p>
  <p class="bekezd">
    Az új parse és formázó API immár teljesen szálbiztos és immutable, nem úgy mint a régi DateFormat. A JDK <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/tutorial/datetime/iso/format.html">tutorialja szerint</a> ezt már ajánlatos statikus konstansként kezelni, ha lehetséges.
  </p>
  <p class="bekezd">A parse és format metódusok bármely konverziós probléma esetén kivételt dobnak:</p>
  <ul>
    <li><span class="programkod">DateTimeParseException</span>: beolvasási hiba</li>
    <li><span class="programkod">DateTimeException</span>: formázási hiba</li>
  </ul>
  <p class="bekezd">Ezek nem ellenőrzött kivételek, tehát a fordító nem követeli meg a kezelésüket, nekünk kell figyelni rá, hogy try-catch blokkot definiáljunk.</p>
  <p class="bekezd">
    <b>Parse</b>
  </p>
  <p class="bekezd">
    Az adattípusok egy paramétert váró <span class="programkod">parse(CharSequence)</span> változata mindig az ISO szerinti alapértelmezett formázót használja. Az alábbi táblázatban
    összefoglalom, hogy mely adattípusok mely <span class="programkod">DateTimeFormatter</span> konstanst használják és megadok példa sztringet is az egy paraméteres parse metódushoz:
  </p>
  <table cellpadding="0" cellspacing="0" width="75%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>Osztály</b></th>
        <th class="normal"><b>DateTimeFormatter konstans</b></th>
        <th class="normal"><b>Példa sztring</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>Instant</td>
        <td>ISO_INSTANT</td>
        <td>&quot;2019-02-25T20:57:30Z&quot;</td>
      </tr>
      <tr class="tr2">
        <td>LocalDate</td>
        <td>ISO_LOCAL_DATE</td>
        <td>&quot;2019-02-25&quot;</td>
      </tr>
      <tr class="tr1">
        <td>LocalDateTime</td>
        <td>ISO_LOCAL_DATE_TIME</td>
        <td>&quot;2007-12-03T10:15:30&quot;</td>
      </tr>
      <tr class="tr2">
        <td>LocalTime</td>
        <td>ISO_LOCAL_TIME</td>
        <td>&quot;15:15&quot;</td>
      </tr>
      <tr class="tr1">
        <td>OffsetDateTime</td>
        <td>ISO_OFFSET_DATE_TIME</td>
        <td>&quot;2019-02-25T21:03:30+01:00&quot;</td>
      </tr>
      <tr class="tr2">
        <td>OffsetTime</td>
        <td>ISO_OFFSET_TIME</td>
        <td>&quot;21:03:30+01:00&quot;</td>
      </tr>
      <tr class="tr1">
        <td>ZonedDateTime</td>
        <td>ISO_ZONED_DATE_TIME</td>
        <td>&quot;2019-02-25T21:06:30+01:00[Europe/Paris]&quot;</td>
      </tr>
    </tbody>
  </table>
  <p class="bekezd">
    Ha ezektől a mintáktól szeretnénk eltérni akkor használhatjuk a két paramétert váró <span class="programkod">parse(CharSequence, DateTimeFormatter)</span> metódust, például más előre
    definiált <span class="programkod">DateTimeFormatter</span> konstansokkal:
  </p>
  <div class="programkod">
    <pre>
<span class="java_comment">// 2019. 10. hetének 5. napja: 2019-03-08</span>
LocalDate date = LocalDate.parse(<span class="java_string">&quot;2019-W10-5&quot;</span>, DateTimeFormatter.<span class="java_constant">ISO_WEEK_DATE</span>); </pre>
  </div>
  <p class="bekezd">Vagy akár saját mintával:</p>
  <div class="programkod">
    <pre>LocalDateTime ldt = LocalDateTime.parse(&quot;5 2 2019-16.30&quot;, 
    DateTimeFormatter.ofPattern(&quot;d M yyyy-H.m&quot;)); </pre>
  </div>
  <p class="bekezd">
    Ebben egy karakter reprezentálja a hónapot, egy a hónap napját és négy az évet. Ezzel a mintával a parse felismeri az <span class="programkod">&quot;5 2 2019-16.30&quot;</span> vagy <span
      class="programkod">&quot;06 12 2019-16.30&quot;</span> sztringeket. Viszont ha a formátumot <span class="programkod">&quot;dd MM yyyy-H.m&quot;</span> formában adjuk meg, vagyis két
    karakterrel a hónapot és a hónap napját, akkor ezeknek mindig két karaktert is kell megadnunk, az egy számjegyű (hó)napoknak 0-val kell kezdődnia. A <span class="programkod">DateTimeFormatter</span>
    <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html#patterns">dokumentációja</a> megadja a teljes
    leírást, itt nem térek ki minden lehetőségre.
  </p>
  <p class="bekezd">
    Nem csak az adattípusok, hanem a <span class="programkod">DateTimeFormatter</span> parse metódusaival is dolgozhatunk. A parse egyébként egy kétfázisú művelet. Első lépésben a bemenet
    feldolgozása a formázó által adott minta alapján megtörténik és egy map készül a mező-érték párokból egy <span class="programkod">ZoneId</span> és egy <span class="programkod">Chronology</span>
    példánnyal. Második lépés az adat feloldása (resolve): ekkor történik a validálás, összefűzés és egyszerűsítés. A parse műveletet a <span class="programkod">DateTimeFormatter</span>-ben
    ötféle metódus szolgálja, ebből négy mindkét fázist elvégzi, az ötödik viszont a feloldást nem:
  </p>
  <p class="bekezd">
    <u>parse(CharSequence text)</u>: alapértelmezett parse:
  </p>
  <div class="programkod">
    <pre>DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy. MM. dd. - HH:mm&quot;</span>);
LocalDateTime ldt = LocalDateTime.from(formatter.parse(<span class="java_string">&quot;2019. 03. 02. - 19:45&quot;</span>));<span class="java_comment">// 2019-03-02T19:45</span> </pre>
  </div>
  <p class="bekezd">
    <u>parse(CharSequence text, ParsePosition position)</u>: egy sztring tetszőleges részére végezhetjük el a parse műveletet. A <span class="programkod">position</span> paraméter az az
    index ahol a szövegben elkezdődik a beolvasandó rész. Amikor a beolvasás eléri a minta végét, a beolvasás is végetér, a sztring további része nem lesz feldolgozva (csak ez a metódus
    működik így, a többinél a beolvasandó sztringnek pontosan meg kell felelnie a mintának):
  </p>
  <div class="programkod">
    <pre>String minta = <span class="java_string">&quot;A mai nap: 2019. 03. 02. Ez a sztring már nem lesz feldolgozva.&quot;</span>;
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy. MM. dd.&quot;</span>);
LocalDate ld = LocalDate.from(formatter.parse(minta, new ParsePosition(11)));<span class="java_comment">// 2019-03-02</span> </pre>
  </div>
  <p class="bekezd">
    <u>parse(CharSequence text, TemporalQuery&lt;T&gt; query)</u>: a <span class="programkod">TemporalQuery</span> egy funkcionális interfész amit az új API a dátum/idő kezeléshez vezetett
    be. Ennek segítségével egy lépésben el tudjuk végezni a beolvasás műveletet:
  </p>
  <div class="programkod">
    <pre>DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy. MM. dd. - HH:mm&quot;</span>);
LocalDateTime ldt = formatter.parse(<span class="java_string">&quot;2019. 03. 02. - 19:45&quot;</span>, LocalDateTime::from);<span class="java_comment">// 2019-03-02T19:45</span> </pre>
  </div>
  <p class="bekezd">
    <u>parseBest(CharSequence text, TemporalQuery&lt;?&gt;... queries)</u>: olyan esetekhez való amikor a parse opcionális paramétereket is tud kezelni (ezt a mintában a [] karakterek
    között lehet jelezni). Az <span class="programkod">&quot;uuuu-MM-dd HH.mm[ VV]&quot;</span> minta például <span class="programkod">ZonedDateTime</span> vagy <span class="programkod">LocalDateTime</span>
    példánnyá is szépen beolvasható. Ilyen esetekben a parseBest második paraméterének sorrendje is fontos: először a legjobban illeszkedő adattípust kell megadni, aztán haladni a
    legkevésbé illeszkedő felé. (A <span class="programkod">queries</span> itt is tipikusan from-ra hivatkzó metódusreferenciákat tartalmaz.) Az eredmény a legjobban illeszkedő adattípussal
    tér vissza (vagy kivételt dob ha nincs ilyen) amit aztán típusellenőrzéssel célszerű feldolgozni:
  </p>
  <div class="programkod">
    <pre>DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;uuuu-MM-dd HH:mm[ VV]&quot;</span>);
TemporalAccessor dt = formatter.parseBest(<span class="java_string">&quot;2019-03-02 19:45 +01:11&quot;</span>, ZonedDateTime::from, LocalDateTime::from);
<span class="java_keyword">if</span> (dt <span class="java_keyword">instanceof</span> ZonedDateTime) {
    <span class="java_comment">// <span class="java_todo">TODO</span> ZonedDateTime feldolgozás</span>
} <span class="java_keyword">else</span> {
    <span class="java_comment">// <span class="java_todo">TODO</span> LocalDateTime feldolgozás</span>
} </pre>
  </div>
  <p class="bekezd">
    A példában <span class="programkod">ZonedDateTime</span> lesz a <span class="programkod">dt</span> aktuális típusa. Ha például <span class="programkod">&quot;2019-03-02
      19:45&quot;</span> sztringgel próbálkoznánk, akkor <span class="programkod">LocalDateTime</span> lenne.
  </p>
  <p class="bekezd">
    <u>parseUnresolved(CharSequence text, ParsePosition position)</u>: alacsonyszintű művelet, amely kihagyja a második, feloldás lépést. Ezzel az általános felhasználás során semmi
    dolgunk.
  </p>
  <p class="bekezd">
    A parse művelet feloldás lépését a <span class="programkod">ResolverStyle</span> enum három értékével lehet vezérelni:
  </p>
  <ul>
    <li><b>STRICT</b>: minden mező értékének az adott mező érvényes értelmezési tartományán belül kell lenni, különben <span class="programkod">DateTimeParseException</span> kivételt
      kapunk. Adott mező ezen kívül speciális további feltételeket is szabhat.</li>
    <li><b>SMART</b>: minden mezőhöz az ésszerű feloldást fogja választani, ami lehet STRICT, LENIENT vagy egy harmadik megoldás. Minden mező egyedi módon kezelheti ezt a működést.
      Például ha olyan napot adunk meg ami abban a hónapban nem értelmezhető (de egyébként érvényes, vagyis 1-31 között van) akkor a parse azt átkonvertálja a hónap legutolsó érvényes
      napjára:</li>
  </ul>
  <div class="programkod">
    <pre>DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;uuuu-MM-dd&quot;</span>).withResolverStyle(ResolverStyle.<span class="java_constant">SMART</span>);
LocalDate ld = formatter.parse(<span class="java_string">&quot;2019-11-31&quot;</span>, LocalDate::from);
<span class="java_comment">// az ld tartalma: 2019-11-30</span> </pre>
  </div>
  <ul>
    <li><b>LENIENT</b>: minden mező egyedileg értelmezheti ezt a módot. Hónap esetén például a lenient lehetővé teszi, hogy az érték az 1-12 intervallumon kívülre essen. Például:</li>
  </ul>
  <div class="programkod">
    <pre>DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;uuuu-MM-dd HH:mm&quot;</span>).withResolverStyle(ResolverStyle.<span
        class="java_constant">LENIENT</span>);
LocalDateTime dt = formatter.parse(<span class="java_string">&quot;2019-15-02 29:45&quot;</span>, LocalDateTime::from);
<span class="java_comment">// a dt tartalma: 2020-03-03T05:45</span> </pre>
  </div>
  <p class="bekezd">Az alapértelmezett működés a SMART.</p>
  <p class="bekezd">
    <b>Formázás</b>
  </p>
  <p class="bekezd">
    A formázást használata sem tér el lényegében a parse-tól. Az adattípusok format metódusával tetszőleges <span class="programkod">DateTimeFormatter</span> példány segítségével
    alakíthatjuk az objektumot sztringgé:
  </p>
  <div class="programkod">
    <pre>LocalDateTime ldt = LocalDateTime.now();
System.<span class="java_constant">out</span>.println(ldt.format(DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy. MM. dd. G - H.m&quot;</span>)));</pre>
  </div>
  <p class="bekezd">De a DateTimeFormatter.format is ugyanezt csinálja bármely dátum/idő adattípussal (a mintában a G azt adja meg, hogy időszámításunk előtt vagy után):</p>
  <div class="programkod">
    <pre>System.<span class="java_constant">out</span>.println(DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy. MM. dd. G - H.m&quot;</span>).format(ldt));</pre>
  </div>
  <p class="bekezd">Az írás pillanatában mindkét sor kimenete:</p>
  <pre class="programkod">2019. 02. 26. i.u. - 22.17</pre>
  <p class="bekezd">
    Ha <span class="programkod">StringBuilder</span>-ünk vagy fájlunk van, a formatTo metódussal egyből abba írathatjuk az eredményt:
  </p>
  <div class="programkod">
    <pre>StringBuilder sb = <span class="java_keyword">new</span> StringBuilder();
LocalDateTime ldt = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern(<span class="java_string">&quot;yyyy. MM. dd. - H.m&quot;</span>);
formatter.formatTo(ldt, sb);</pre>
  </div>
  <p class="bekezd">
    Bár az <span class="programkod">Appendable</span> metódusok <span class="programkod">IOException</span>-t dobhatnak, a formatTo ezt <span class="programkod">DateTimeException</span>-be
    csomagolja.
  </p>
  <p class="bekezd">
    A lokalizált formázást (és parse-olást) külön metódusok segítik. Ezeket egy külön enum vezérli: <span class="programkod">java.time.format.FormatStyle</span>. Így tudjuk őket használni:
  </p>
  <div class="programkod">
    <pre>LocalDateTime ldt = LocalDateTime.parse(<span class="java_string">"2019-02-10T19:30:10"</span>);

<span class="java_comment">// csak a dátum stílusának megadása az alapértelmezett locale szerint</span>
System.<span class="java_constant">out</span>.println(ldt.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.<span class="java_constant">SHORT</span>)));
<span class="java_comment">// 2019.02.10.</span>

<span class="java_comment">// csak az idő stílusának megadása az alapértelmezett locale szerint</span>
System.<span class="java_constant">out</span>.println(ldt.format(DateTimeFormatter.ofLocalizedTime(FormatStyle.<span class="java_constant">SHORT</span>)));
<span class="java_comment">// 19:30</span>

<span class="java_comment">// csak a dátum stílusának megadása tetszőleges locale szerint</span>
System.<span class="java_constant">out</span>.println(ldt.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.<span class="java_constant">SHORT</span>).withLocale(Locale.<span
        class="java_constant">ENGLISH</span>)));
<span class="java_comment">// 2/10/19</span>

<span class="java_comment">// dátum és idő stílusának megadása az alapértelmezett locale szerint</span>
System.<span class="java_constant">out</span>.println(ldt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.<span class="java_constant">SHORT</span>)));
<span class="java_comment">// 2019.02.10. 19:30</span>

<span class="java_comment">// dátum és idő stílusának eltérő megadása az alapértelmezett locale szerint</span>
System.<span class="java_constant">out</span>.println(ldt.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.<span class="java_constant">LONG</span>, FormatStyle.<span
        class="java_constant">SHORT</span>)));
<span class="java_comment">// 2019. február 10. 19:30</span> </pre>
  </div>
  <p class="bekezd">
    A <span class="programkod">FormatStyle</span> különböző értékeit az alábbi táblázat foglalja össze. Megmutatja, hogy melyik <span class="programkod">DateTimeFormatter</span> melyik
    metódusában az enumot az adott adattípusnál használva milyen eredményt kapunk (a teljes minta időpont &quot;<span class="programkod">2019-02-10T19:30:10+01:00[Europe/Paris]</span>&quot;).
    A - azt jelenti, hogy az adott adattípusnál az az enum érték azzal a metódussal nincs értelmezve. Ha így próbáljuk használni, <span class="programkod">DateTimeException</span>-t kapunk.
    Az ofLocalizedDateTime két paramétere megfelel az ofLocalizedDate és ofLocalizedTime paramétereinek, ezért a táblában nem szerepel.
  </p>
  <table cellpadding="0" cellspacing="0" width="75%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>FormatStyle</b></th>
        <th class="normal"><b>Metódus</b></th>
        <th class="normal"><b>LocalTime</b></th>
        <th class="normal"><b>LocalDate</b></th>
        <th class="normal"><b>LocalDateTime</b></th>
        <th class="normal"><b>ZonedDateTime</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>SHORT</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr class="tr2">
        <td></td>
        <td>ofLocalizedTime</td>
        <td>&quot;19:30&quot;</td>
        <td>-</td>
        <td>&quot;19:30&quot;</td>
        <td>&quot;19:30&quot;</td>
      </tr>
      <tr class="tr1">
        <td></td>
        <td>ofLocalizedDate</td>
        <td>-</td>
        <td>&quot;2019.02.10.&quot;</td>
        <td>&quot;2019.02.10.&quot;</td>
        <td>&quot;2019.02.10.&quot;</td>
      </tr>
      <tr class="tr2">
        <td>MEDIUM</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr class="tr1">
        <td></td>
        <td>ofLocalizedTime</td>
        <td>&quot;19:30:10&quot;</td>
        <td>-</td>
        <td>&quot;19:30:10&quot;</td>
        <td>&quot;19:30:10&quot;</td>
      </tr>
      <tr class="tr2">
        <td></td>
        <td>ofLocalizedDate</td>
        <td>-</td>
        <td>&quot;2019.02.10.&quot;</td>
        <td>&quot;2019.02.10.&quot;</td>
        <td>&quot;2019.02.10.&quot;</td>
      </tr>
      <tr class="tr1">
        <td>LONG</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr class="tr2">
        <td></td>
        <td>ofLocalizedTime</td>
        <td>-</td>
        <td>-</td>
        <td>-</td>
        <td>&quot;19:30:10 CET&quot;</td>
      </tr>
      <tr class="tr1">
        <td></td>
        <td>ofLocalizedDate</td>
        <td>-</td>
        <td>&quot;2019. február 10.&quot;</td>
        <td>&quot;2019. február 10.&quot;</td>
        <td>&quot;2019. február 10.&quot;</td>
      </tr>
      <tr class="tr2">
        <td>FULL</td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
        <td></td>
      </tr>
      <tr class="tr1">
        <td></td>
        <td>ofLocalizedTime</td>
        <td>-</td>
        <td>-</td>
        <td>-</td>
        <td>&quot;19:30:10 CET&quot;</td>
      </tr>
      <tr class="tr2">
        <td></td>
        <td>ofLocalizedDate</td>
        <td>-</td>
        <td>&quot;2019. február 10.&quot;</td>
        <td>&quot;2019. február 10.&quot;</td>
        <td>&quot;2019. február 10.&quot;</td>
      </tr>
    </tbody>
  </table>
  <h3>A Temporal csomag</h3>
  <p class="bekezd">
    Már említettem, hogy a dátum/idő kezelő osztályok paraméterei általában valamilyen &quot;Temporal...&quot; paramétert várnak. A <span class="programkod">java.time.temporal</span> csomag
    tartalmazza azokat az interfészeket, osztályokat és enumokat, amelyekre felépülnek a dátum/idő számítások. Ezek az interfészek az alacsony szintű műveleteket tartalmazzák, egy általános
    alkalmazásnak a változóit és paramétereit a konkrét típusban kell kezelnie. Tehát például <span class="programkod">LocalDateTime</span> vagy <span class="programkod">ZonedDateTime</span>,
    nem pedig <span class="programkod">Temporal</span> interfész. (Ez pont ugyanaz, hogy a változóink <span class="programkod">String</span> típusúak, nem pedig <span class="programkod">CharSequence</span>.)
    Az alábbiakban röviden átfutom az alap-interfészeket és bemutatok néhány olyan jellemzőt és műveletet amiket viszont az általános célú programjainkban is használhatunk.
  </p>
  <p class="bekezd">
    <b>Temporal és TemporalAccessor</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">TemporalAccessor</span> interfész minden idő/dátum adattípus őse (már amennyire egy interfész ős lehet...). Csak olvasható elérést definiál a leendő mezőkhöz.
    Mivel a legtöbb idő-információnk valamilyen számmal reprezentálható, a get és getLong metódusai egésszel térnek vissza. A kronológia és az időzóna nem reprezentálható számokkal, ezek
    lekérdezésére a query metódust definiálja.
  </p>
  <p class="bekezd">
    A <span class="programkod">TemporalAccessor</span>-ból származó Temporal interfész definiálja azon metódusokat, amelyek hozzáadnak vagy kivonnak időegységeket (minus, plus), elvégzik az
    idő alapú aritmetikát a sok dátum és idő kezelő osztályon keresztül (until, with). Az idő/dátum tárolására szolgáló mezők definiálásának őse a <span class="programkod">TemporalField</span>
    interfész. Ezt a <span class="programkod">ChronoField</span> enum implementálja, ami egész sor konstanst tartalmaz. Például <span class="programkod">DAY_OF_WEEK</span>, <span
      class="programkod">MINUTE_OF_HOUR</span> vagy <span class="programkod">MONTH_OF_YEAR</span>.
  </p>
  <p class="bekezd">
    A mezőkhöz tartozó egységeket a <span class="programkod">TemporalUnit</span> interfész definiálja. Ezt a <span class="programkod">ChronoUnit</span> enum implementálja. Egy példa és
    rögtön érthető lesz: a <span class="programkod">ChronoField.DAY_OF_WEEK</span> például a <span class="programkod">ChronoUnit.DAYS</span> és a <span class="programkod">ChronoUnit.WEEKS</span>
    kombinációja.
  </p>
  <p class="bekezd">
    A <span class="programkod">Temporal</span> interfészben lévő aritmetikai metódusoknak (minus, plus) van <span class="programkod">TemporalAmount</span> értéket váró változatuk is. <span
      class="programkod">TemporalAmount</span> definiálja az időmennyiségeket, ezt implementálják a már jólismert <span class="programkod">Period</span> és <span class="programkod">Duration</span>
    osztályok.
  </p>
  <p class="bekezd">
    <u>ChronoField</u> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/ChronoField.html">javadoc</a>): a <span
      class="programkod">ChronoField</span> enum értékeivel olyan fogalmakat gyárthatunk, mint például &quot;az év harmadik hete&quot; vagy &quot;a nap 11. órája&quot; vagy &quot;a hónap
    első hétfője&quot;. Ha ismeretlen típusú <span class="programkod">Temporal</span>-lal találkozunk, akkor a <span class="programkod">TemporalAccessor.isSupported(TemporalField)</span>
    metódust használhatjuk, ami megmondja, hogy a <span class="programkod">Temporal</span> támogatja-e az adott mezőt. Az alábbi sor <span class="programkod">false</span> eredményt ad,
    vagyis a <span class="programkod">LocalDate</span> nem támogatja a <span class="programkod">ChronoField.CLOCK_HOUR_OF_DAY</span>-t:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">boolean</span> isSupported = LocalDate.now().isSupported(ChronoField.<span class="java_constant">CLOCK_HOUR_OF_DAY</span>);</pre>
  </div>
  <p class="bekezd">
    <u>IsoFields</u> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/IsoFields.html">javadoc</a>): ebben az osztályban
    az ISO-8601 naptárrendszernek megfelelő kiegészítő mezők találhatóak. A következő két példa megmutatja, hogyan szerezzük meg egy mező értékét a <span class="programkod">ChronoField</span>
    és <span class="programkod">IsoFields</span> használatával:
  </p>
  <div class="programkod">
    <pre>Instant time = Instant.now();
<span class="java_keyword">int</span> milli = time.get(ChronoField.<span class="java_constant">MILLI_OF_SECOND</span>);<span class="java_comment">// az aktuális instant ezredmásodperc része</span>
LocalDate ld = LocalDate.now();
<span class="java_keyword">int</span> negyedev = ld.get(IsoFields.<span class="java_constant">QUARTER_OF_YEAR</span>);<span class="java_comment">// megadja, hányadik negyedévben tartunk</span>
    </pre>
  </div>
  <p class="bekezd">
    <u>WeekFields</u> (<a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/api/java/time/temporal/WeekFields.html">javadoc</a>): a hét napjához,
    hónap hetéhez és év hetéhez szolgáló lokalizált mezők. A szabvány hét mindenhol hét nap, de egyes kultúrákban a hét néhány tulajdonsága eltérő. Az ISO-8601 szerint a hét első napja a
    hétfő, míg az Egyesült Államokban a vasárnap. Néhány példa:
  </p>
  <div class="programkod">
    <pre>LocalDate ld = LocalDate.parse(<span class="java_string">&quot;2019-03-24&quot;</span>);
WeekFields wf = WeekFields.of(Locale.forLanguageTag(<span class="java_string">&quot;hu&quot;</span>));
<span class="java_keyword">int</span> nap = ld.get(wf.dayOfWeek());<span class="java_comment">// 7 lesz mert a fenti dátum vasárnapra esett</span>
<span class="java_keyword">int</span> nap2 = ld.get(wf.weekOfMonth());<span class="java_comment">// 3, ez a hónap harmadik hete</span>
<span class="java_keyword">int</span> nap3 = ld.get(wf.weekOfYear());<span class="java_comment">// 12, ez az év 12. hete</span>
System.<span class="java_constant">out</span>.println(wf.getFirstDayOfWeek());<span class="java_comment">// MONDAY</span> </pre>
  </div>
  <p class="bekezd">
    <u>JulianFields</u>: a ritkán használt Julián naptárhoz használatos dátum mezők. A julián dátumot a csillagászatban használják. (Epocha i. e. 4713. január 1. 12:00:00) Példa:
  </p>
  <div class="programkod">
    <pre>LocalDate ld = LocalDate.parse(<span class="java_string">&quot;2019-03-24&quot;</span>);
<span class="java_keyword">long</span> nap = ld.getLong(JulianFields.<span class="java_constant">JULIAN_DAY</span>);<span class="java_comment">// a mapi nap: 2458567 (hogy telik az idő! :-)</span> </pre>
  </div>
  <p class="bekezd">
    <u>ChronoUnit</u>: dátumhoz és időhöz használatos mértékegységek az ezredmásodperctől az évezredig. Akárcsak a <span class="programkod">ChronoField</span> esetén, nem minden <span
      class="programkod">ChronoUnit</span> értéket támogat minden osztály. Az <span class="programkod">Instant</span> például nem támogatja a <span class="programkod">ChronoUnit.MONTHS</span>
    vagy <span class="programkod">ChronoUnit.YEARS</span>-t. A <span class="programkod">TemporalAccessor.isSupported(TemporalUnit)</span> metódussal ellenőrizhetjük, hogy egy osztály
    támogat-e egy adott időegységet. A következő példa <span class="programkod">false</span> eredményt ad, mert az <span class="programkod">Instant</span> nem támogatja a <span
      class="programkod">ChronoUnit.DAYS</span> egységet:
  </p>
  <div class="programkod">
    <pre>
<span class="java_keyword">boolean</span> isSupported = instant.isSupported(ChronoUnit.<span class="java_constant">DAYS</span>);</pre>
  </div>
  <p class="bekezd">A ChronoUnit.between metódusról korábban már volt szó. Ez a metódus minden temporal-alapú objektummal működik.</p>
  <p class="bekezd">Elég sok interfészről volt szó már eddig ebben a fejezetben, talán kissé áttekinthetőbb (vagy még zavarosabb) lesz, ha a következő ábrán bemutatom, hogyan is néz ki
    a típusfánk:</p>
  <p class="kozep">
    <img alt="fa1" src="/informatika/essze/java/java8/fa1.jpg" />
  </p>
  <p class="bekezd">
    <b>TemporalAdjuster</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">java.time.temporal.TemporalAdjuster</span> funkcionális interfész <span class="programkod">adjustInto(Temporal temporal)</span> metódusán keresztül lehet
    dátum/idő objektumokon műveleteket végezni. Ha egy adjuster-t <span class="programkod">ZonedDateTime</span>-mal használunk, akkor az új dátum úgy számolódik ki, hogy megtartja az
    eredeti idő és időzóna értékeket. Vannak előre definiált adjusterek de sajátot is fabrikálhatunk. Két egymással ekvivalens mód van egy <span class="programkod">TemporalAdjuster</span>
    használatára:
  </p>
  <ul>
    <li>közvetlenül meghívjuk az interfész metódusát: <span class="programkod">adjustInto(Temporal temporal)</span></li>
    <li>az adattípusok <span class="programkod">Temporal.with(TemporalAdjuster)</span> metódusát használjuk. Ez az ajánlott, mert olvashatóbb.
    </li>
  </ul>
  <p class="bekezd">
    A <span class="programkod">java.time.temporal.TemporalAdjusters</span> osztály tartalmazza az előre definiált adjustereket. Ezek statikus metódusként vannak megvalósítva, így static
    import kifejezéssel is használhatók. 14 ilyen statikus metódus van, ezek mind <span class="programkod">TemporalAdjuster</span>-el térnek vissza:
  </p>
  <ul>
    <li>firstDayOfMonth(), lastDayOfMonth(): a hónap első/utolsó napja. Példa:</li>
  </ul>
  <div class="programkod">
    <pre>LocalDate ld = LocalDate.of(2019, Month.<span class="java_constant">SEPTEMBER</span>, 15);<span class="java_comment">// ez egy vasárnapi nap</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap első napja: %s%n&quot;</span>, ld.with(TemporalAdjusters.firstDayOfMonth()));<span
        class="java_comment">// A hónap első napja: 2019-09-01</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap utolsó napja: %s%n&quot;</span>, ld.with(TemporalAdjusters.lastDayOfMonth()));<span
        class="java_comment">// A hónap utolsó napja: 2019-09-30</span> </pre>
  </div>
  <ul>
    <li>firstDayOfNextMonth(): a következő hónap első napja:</li>
  </ul>
  <div class="programkod">
    <pre>
<span class="java_comment">// A következő hónap első napja: 2019-10-01:</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A következő hónap első napja: %s%n&quot;</span>, ld.with(TemporalAdjusters.firstDayOfNextMonth())); </pre>
  </div>
  <ul>
    <li>firstDayOfNextYear(): következő év első napja:</li>
  </ul>
  <div class="programkod">
    <pre>
      <span class="java_comment">// A következő év első napja: 2020-01-01:</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A következő év első napja: %s%n&quot;</span>, ld.with(TemporalAdjusters.firstDayOfNextYear())); </pre>
  </div>
  <ul>
    <li>lastDayOfYear(), firstDayOfYear(): az év első vagy utolsó napja:</li>
  </ul>
  <div class="programkod">
    <pre>System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;Az év első napja: %s%n&quot;</span>, ld.with(TemporalAdjusters.firstDayOfYear()));<span
        class="java_comment">// Az év első napja: 2019-01-01</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;Az év utolsó napja: %s%n&quot;</span>, ld.with(TemporalAdjusters.lastDayOfYear()));<span
        class="java_comment">// Az év utolsó napja: 2019-12-31</span> </pre>
  </div>
  <ul>
    <li>firstInMonth(DayOfWeek dayOfWeek), lastInMonth(DayOfWeek dayOfWeek): hónapon belül a hét első vagy utolsó napja, mint például &quot;szeptember utolsó vasárnapja&quot;:</li>
  </ul>
  <div class="programkod">
    <pre>
<span class="java_comment">// A hónap első hétfője: 2019-09-02:</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap első hétfője: %s%n&quot;</span>, ld.with(TemporalAdjusters.firstInMonth(DayOfWeek.<span
        class="java_constant">MONDAY</span>)));
<span class="java_comment">// A hónap utolsó vasárnapja: 2019-09-29:</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap utolsó vasárnapja: %s%n&quot;</span>, ld.with(TemporalAdjusters.lastInMonth(DayOfWeek.<span
        class="java_constant">SUNDAY</span>))); </pre>
  </div>
  <ul>
    <li>next(DayOfWeek dayOfWeek), nextOrSame(DayOfWeek dayOfWeek), previous(DayOfWeek dayOfWeek), previousOrSame(DayOfWeek dayOfWeek): következő vagy előző hét napja, mint például
      &quot;következő szombat&quot; (az OrSame verzió önmagát adja vissza ha most épp olyan napon állunk):</li>
  </ul>
  <div class="programkod">
    <pre>
<span class="java_comment">// A hónap következő hétfője: 2019-09-16</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap következő hétfője: %s%n&quot;</span>, ld.with(TemporalAdjusters.next(DayOfWeek.<span
        class="java_constant">MONDAY</span>)));
<span class="java_comment">// A hónap előző vasárnapja: 2019-09-08</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap előző vasárnapja: %s%n&quot;</span>, ld.with(TemporalAdjusters.previous(DayOfWeek.<span
        class="java_constant">SUNDAY</span>))); </pre>
  </div>
  <ul>
    <li>dayOfWeekInMonth(int ordinal, DayOfWeek dayOfWeek): az aktuális hónap megadott hetének napja. Például &quot;szeptember második keddje&quot;. Az <span class="programkod">ordinal</span>
      paraméternek negatív értéket is adhatunk.
    </li>
  </ul>
  <div class="programkod">
    <pre>
<span class="java_comment">// A hónap második vasárnapja: 2019-09-15</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap második vasárnapja: %s%n&quot;</span>, ld.with(TemporalAdjusters.dayOfWeekInMonth(2, DayOfWeek.<span
        class="java_constant">SUNDAY</span>)));
<span class="java_comment">// A hónap második vasárnapja visszafelé: 2019-09-22</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap második vasárnapja visszafelé: %s%n&quot;</span>, ld.with(TemporalAdjusters.dayOfWeekInMonth(-2, DayOfWeek.<span
        class="java_constant">SUNDAY</span>)));
<span class="java_comment">// A hónap 12. vasárnapjával túlcsordulunk novemberre: 2019-11-17</span>
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;A hónap 12. vasárnapjával túlcsordulunk novemberre: %s%n&quot;</span>, ld.with(TemporalAdjusters.dayOfWeekInMonth(12, DayOfWeek.<span
        class="java_constant">SUNDAY</span>))); </pre>
  </div>
  <p class="bekezd">Az előzőeken túl akár saját adjustereket is létrehozhatunk. Legegyszerűbb módja, ha az ofDateAdjuster statikus metódust használjuk lambda kifejezéssel:</p>
  <div class="programkod">
    <pre>
<span class="java_comment">// Két nap múlva: 2019-09-17</span>
<span class="java_keyword">final</span> TemporalAdjuster KET_NAP_MULVA = TemporalAdjusters.ofDateAdjuster(date -&gt; date.plusDays(2));
System.<span class="java_constant">out</span>.printf(<span class="java_string">&quot;Két nap múlva: %s%n&quot;</span>, ld.with(KET_NAP_MULVA));</pre>
  </div>
  <p class="bekezd">
    Bonyolultabb műveletek esetén létrehozhatunk saját osztályt is, ami implementálja a <span class="programkod">TemporalAdjuster</span> interfész <span class="programkod">adjustInto(Temporal)</span>
    metódusát.
  </p>
  <p class="bekezd">
    <b>TemporalQuery</b>
  </p>
  <p class="bekezd">
    A <span class="programkod">TemporalQuery</span> funkcionális interfész queryFrom metódusa használható temporal-alapú objektumkból információ lekérdezésére. Vannak előre definiált
    lekérdezések de sajátot is barkácsolhatunk. Két egymással ekvivalens mód van egy <span class="programkod">TemporalQuery</span> használatára:
  </p>
  <ul>
    <li>közvetlenül meghívjuk az interfész metódusát: <span class="programkod">queryFrom(Temporal temporal)</span></li>
    <li>az adattípusok <span class="programkod">Temporal.query(TemporalQuery)</span> metódusát használjuk. Ez az ajánlott, mert olvashatóbb.
    </li>
  </ul>
  <p class="bekezd">
    A <span class="programkod">java.time.temporal.TemporalQueries</span> osztály tartalmazza az előre definiált lekérdezéseket. Ezekkel meg lehet határozni például egy adott objektum
    tulajdonságait akkor is, ha az alkalmazás egyébként nem tudja meghatározni a temporális alapú objektum típusát. Ezek statikus metódusokként vannak megvalósítva, így static import
    kifejezéssel is használhatók. 7 ilyen statikus metódus van, ezek mind <span class="programkod">TemporalQuery</span>-vel térnek vissza:
  </p>
  <ul>
    <li><b>chronology()</b>: a kronológia lekérdezése. Hacsak nem valamilyen egzotikus időszámítással kell dolgoznunk, akkor minden dátumot is tároló adattípus esetén <span
      class="programkod">IsoChronology.INSTANCE</span> a visszatérése. A csak időt vagy zónainformációt tartalmazó adattípusok esetén <span class="programkod">null</span>-al tér vissza.</li>
    <li><b>localDate()</b>: ha az adattípus támogatja <span class="programkod">LocalDate</span> típust, akkor visszaadja, egyébként <span class="programkod">null</span>-al tér vissza.</li>
  </ul>
  <div class="programkod">
    <pre>TemporalQuery query = TemporalQueries.localDate();
LocalDate ld1 = (LocalDate) LocalDateTime.now().query(query);<span class="java_comment">// itt nyilván lesz LocalDate</span>
LocalDate ld2 = (LocalDate) Year.now().query(query);<span class="java_comment">// ez null lesz</span>
LocalDate ld3 = (LocalDate) ZonedDateTime.now().query(query);<span class="java_comment">// itt is LocalDate</span> </pre>
  </div>
  <ul>
    <li><b>localTime()</b>: ha az adattípus támogatja a <span class="programkod">LocalTime</span> típust, akkor azzal, egyébként <span class="programkod">null</span>-al tér vissza</li>
    <li><b>offset()</b>: ha az adattípus támogatja a <span class="programkod">ZoneOffset</span> típust, akkor azzal, egyébként <span class="programkod">null</span>-al tér vissza</li>
    <li><b>zoneId()</b>: <span class="programkod">ZoneId</span> lekérdezésére vonatkozó query. A zónát ez a lekérdezés csak akkor adja vissza, ha az adattípus fogalmilag tartalmaz egy
      <span class="programkod">ZoneId</span>-t. Ha nem tartalmaz ilyet vagy <span class="programkod">ZoneOffset</span>-et tartalmaz, akkor null a visszatérési érték. Tehát <span
      class="programkod">ZonedDateTime</span> adattípus esetén visszakapjuk a <span class="programkod">getZone()</span> eredményét, de egy <span class="programkod">OffsetDateTime</span>
      típus esetén nullt kapunk.</li>
    <li><b>zone()</b>: a <span class="programkod">zoneId()</span> megengedőbb változata. Először megpróbálja a <span class="programkod">zoneId()</span> használatával megszerezni a zóna
      információt. Ha ilyet nem talál, akkor próbálkozik az <span class="programkod">offset()</span> használatával. Így aztán <span class="programkod">ZonedDateTime</span> esetén a <span
      class="programkod">getZone()</span> eredményét adja vissza, <span class="programkod">OffsetDateTime</span> esetén pedig a <span class="programkod">getOffset()</span> eredményét. A <span
      class="programkod">zoneId()</span> helyett ezt ajánlott használni.</li>
    <li><b>precision()</b>: megmondja az adott objektum típusa által támogatott legkisebb egységet (<span class="programkod">ChronoUnit</span>-ban)</li>
  </ul>
  <p class="bekezd">
    Természetesen saját lekérdezéseket is létrehozhatunk a <span class="programkod">TemporalAdjuster</span>-nél megismert módszerrel.
  </p>
  <p class="bekezd">
    A <span class="programkod">TemporalAdjuster</span> és a <span class="programkod">TemporalAccessor</span> az alábbi módon viszonyul a dátum/idő adattípusokhoz:
  </p>
  <p class="kozep">
    <img alt="fa2" src="/informatika/essze/java/java8/fa2.jpg" />
  </p>
  <h3>Kompatibilitás</h3>
  <p class="bekezd">
    Az új API-val a régi kódjainkat nem kell egyből kidobni, mert a <span class="programkod">java.util</span>-ban lévő régi dátum/idő típusok kaptak olyan metódusokat, amelyekkel oda-vissza
    konvertálhatunk a két API között. Ezekkel simábbá tehetjük az új API-ra való átállást:
  </p>
  <ul>
    <li><b>Calendar.toInstant()</b>: <span class="programkod">Calendar</span> -&gt; <span class="programkod">Instant</span></li>
    <li><b>GregorianCalendar.toZonedDateTime()</b>: <span class="programkod">GregorianCalendar</span> -&gt; <span class="programkod">ZonedDateTime</span></li>
    <li><b>GregorianCalendar.from(ZonedDateTime)</b>: <span class="programkod">GregorianCalendar</span> objektum létrehozása egy <span class="programkod">ZonedDateTime</span>
      példányból alapértelmezett locale információval. Mivel a <span class="programkod">ZonedDateTime</span> nagyobb időskálát fog át mint a régi <span class="programkod">GregorianCalendar</span>,
      ha olyat próbálunk konvertálni ami abban nem ábrázolható, <span class="programkod">IllegalArgumentException</span>-t kapunk.</li>
    <li><b>Date.from(Instant)</b>: <span class="programkod">Date</span> objektum létrehozása <span class="programkod">Instant</span> objektumból. Mivel a <span class="programkod">Date</span>
      csak ezredmásodpercekben képes időt tárolni, az <span class="programkod">Instant</span> nanoszekundumnyi része csonkolva lesz. Mivel az <span class="programkod">Instant</span> nagyobb
      időskálát fog át mint a régi <span class="programkod">Date</span>, ha olyat próbálunk konvertálni ami abban nem ábrázolható, <span class="programkod">IllegalArgumentException</span>-t
      kapunk.</li>
    <li><b>Date.toInstant()</b>: <span class="programkod">Date</span> -&gt; <span class="programkod">Instant</span></li>
    <li><b>TimeZone.toZoneId()</b>: <span class="programkod">TimeZone</span> -&gt; <span class="programkod">ZoneId</span></li>
  </ul>
  <p class="bekezd">Néhány példa:</p>
  <div class="programkod">
    <pre>ZonedDateTime zdt = ZonedDateTime.ofInstant(Calendar.getInstance().toInstant(), ZoneId.systemDefault());
ZonedDateTime zdt2 = <span class="java_keyword">new</span> GregorianCalendar().toZonedDateTime();
GregorianCalendar calendarFromZonedDateTime = GregorianCalendar.from(ZonedDateTime.now());
Date dateFromInstant = Date.from(Instant.now());
Instant instantFromDate = <span class="java_keyword">new</span> Date().toInstant();
ZoneId zoneIdFromTimeZone = TimeZone.getTimeZone(<span class="java_string">&quot;PST&quot;</span>).toZoneId();</pre>
  </div>
  <p class="bekezd">Mivel a date/time API teljesen át lett írva, nem lehet a régi és az új metódusok között egyszerű megfeleltetést adni. A legegyszerűbb módszer az új API használatára,
    ha a régi kódnál használat előtt egyszerűen átkonvertáljuk a fenti metódusokkal az adatokat az újakra. Ha ez nem elegendő akkor sajnos át kell írni a régi kódot. Az alábbi táblázat ad
    egy vázlatos áttekintést arról, hogy a régi metódusoknak nagyjából melyik felel meg az új API-ban.</p>
  <table cellpadding="0" cellspacing="0" width="75%" class="datatable">
    <thead class="datatable">
      <tr>
        <th class="normal"><b>java.util funkció</b></th>
        <th class="normal"><b>java.time funkció</b></th>
        <th class="normal"><b>Megjegyzés</b></th>
      </tr>
    </thead>
    <tbody>
      <tr class="tr1">
        <td>java.util.Date</td>
        <td>java.time.Instant</td>
        <td>Az <span class="programkod">Instant</span> és <span class="programkod">Date</span> osztályok hasonlóak. Mindkettő:
          <ul>
            <li>egy pillanatnyi időpontot reprezentál az idősávon (UTC)</li>
            <li>időzónától független időt tárol</li>
            <li>epoch-másodpercekben és nanoszekundumokban ábrázolható (1970-01-01T00:00:00Z óta)</li>
          </ul> A <span class="programkod">Date.from(Instant)</span> és <span class="programkod">Date.toInstant()</span> metódusokkal lehet konvertálni ezek között az osztályok között.
        </td>
      </tr>
      <tr class="tr2">
        <td>java.util.GregorianCalendar</td>
        <td>java.time.ZonedDateTime</td>
        <td>A <span class="programkod">ZonedDateTime</span> osztály cseréli le a <span class="programkod">GregorianCalendar</span>-t. Mindkettő emberi léptékben ábrázolja az időt az
          alábbiakban hasonlóképpen:
          <ul>
            <li><span class="programkod">LocalDate</span>: év, hónap, nap</li>
            <li><span class="programkod">LocalTime</span>: órák, percek, másodpercek, nanoszekundumok</li>
            <li><span class="programkod">ZoneId</span>: időzóna</li>
            <li><span class="programkod">ZoneOffset</span>: GMT-hez képesti aktuális ofszet</li>
          </ul> A két osztály között a <span class="programkod">GregorianCalendar.from(ZonedDateTime)</span> és <span class="programkod">GregorianCalendar.to(ZonedDateTime)</span> metódusok
          végzik a konverziót.
        </td>
      </tr>
      <tr class="tr1">
        <td>java.util.TimeZone</td>
        <td>java.time.ZoneId / java.time.ZoneOffset</td>
        <td>A <span class="programkod">ZoneId</span> osztály megad egy időzóna azonosítót és hozzáfér az egyes időzónákat meghatározó szabályokhoz. A <span class="programkod">ZoneOffset</span>
          osztály csak egy Greenwich/UTC-hez képesti ofszetet ad meg.
        </td>
      </tr>
      <tr class="tr2">
        <td>GregorianCalendar 1970-01-01 dátumra beállítva</td>
        <td>java.time.LocalTime</td>
        <td>Azon kód ami eddig 1970-01-01-et állított be egy <span class="programkod">GregorianCalendar</span> példánynak kizárólag az idő számítása céljából, most már lecserélhető <span
          class="programkod">LocalTime</span> példánnyal.
        </td>
      </tr>
      <tr class="tr1">
        <td>GregorianCalendar 00:00 időre beállítva</td>
        <td>java.time.LocalDate</td>
        <td>Azon kód ami eddig 00:00-t állított be egy <span class="programkod">GregorianCalendar</span> példányba abból a célból, hogy csak a dátum komponenseit használja, most már
          lecserélhető egy <span class="programkod">LocalDate</span> példánnyal. (Ez a <span class="programkod">GregorianCalendar</span>-féle megoldás egyébként is hibás volt, mivel néhány
          országban évente egyszer nincs éjfél amikor nyári időszámításra állnak át.)
        </td>
      </tr>
    </tbody>
  </table>
  <h2>Base64</h2>
  <p class="bekezd">
    Java 8-cal végre a Base64 kódolás támogatása is bekerült a szabványos osztálykönyvtárba. Az ezzel kapcsolatos műveleteket a <span class="programkod">java.util.Base64</span> osztály
    valósítja. Ez kizárólag statikus metódusokat tartalmaz, amelyekkel megkaphatjuk a Base64 enkódereket és dekódereket. Az osztály a következő Base64 típusokat támogatja:
  </p>
  <ul>
    <li><b>Basic</b>: a &quot;<a onclick="window.open(this.href);return false;" href="https://tools.ietf.org/html/rfc4648">Base64 ábécét</a>&quot; használja (röviden:
      &quot;A-Za-z0-9+/&quot;). Ennek az enkódere nem generál soremelés karaktert, dekódere pedig visszadobja az olyan adatot, ami a Base64 ábécén kívül eső karaktert merészel tartalmazni.</li>
    <li><b>URL és fájlnév-biztos</b>: &quot;<a onclick="window.open(this.href);return false;" href="https://tools.ietf.org/html/rfc4648">URL és fájlnévbiztos ábécét</a>&quot; használ
      (röviden: &quot;A-Za-z0-9+_&quot;). Ennek az enkódere nem generál soremelés karaktert, dekódere pedig visszadobja az olyan adatot, ami a Base64 ábécén kívül eső karaktert merészel
      tartalmazni.</li>
    <li><b>MIME</b>: a &quot;<a onclick="window.open(this.href);return false;" href="https://www.ietf.org/rfc/rfc2045.txt">Base64 ábécét</a>&quot; használja. Az enkódolt kimenet 76
      karakternél nem hosszabb sorokat tartalmaz, ezeket sortörés-kocsivissza (&quot;\r\n&quot;) karakterek követik. Az enkódolt kimenet legvégén már nincs sortörés. A dekódolás során
      figyelmen kívül van hagyva minden olyan sortörés vagy egyéb karakter ami nincs benne az ábécében.</li>
  </ul>
  <p class="bekezd">Az osztály 7 statikus metódust tartalmaz, amelyek a fenti típusoknak megfelelő dekódereket és enkódereket adnak vissza. Ezt nem részletezem tovább, a nevükből
    szerintem egyértelmű.</p>
  <p class="bekezd">A használata egyszerűbb nem is lehetne. Enkódolás:</p>
  <div class="programkod">
    <pre>String inputString = <span class="java_string">&quot;Hello Base64 world!\nMásodik sor&quot;</span>;
String encodedString = Base64.getEncoder().encodeToString(inputString.getBytes());</pre>
  </div>
  <p class="bekezd">Dekódolás:</p>
  <div class="programkod">
    <pre>
<span class="java_keyword">byte</span>[] decodedByteArray = Base64.getDecoder().decode(encodedString);
String decodedString = <span class="java_keyword">new</span> String(decodedByteArray);</pre>
  </div>
  <p class="bekezd">
    Az <span class="programkod">getEncoder()</span> és <span class="programkod">getDecoder()</span> által visszaadott objektumok egyébként szálbiztosak.
  </p>
  <p class="bekezd">Talán kevéssé ismert tény, hogy Base64 kódolásnál az enkódolt sztring kimenet hossza 3-mal osztható kell legyen. Ha nem így sikerül, akkor az eredményhez kiegészítő
    &quot;=&quot; karakterek kerülnek, hogy ez a feltétel teljesüljön. Dekódoláskor ezeket a kiegészítő karaktereket a dekóder figyelmen kívül hagyja. De ha mi úgy véljük, nincs szükségünk
    rájuk akkor enélkül is enkódolhatunk:</p>
  <div class="programkod">
    <pre>String encodedString = Base64.getEncoder().withoutPadding().encodeToString(inputString.getBytes());</pre>
  </div>
  <p class="bekezd">A URL kódolás az előzőhöz hasonlóan működik:</p>
  <div class="programkod">
    <pre>String inputUrl = <span class="java_string">&quot;http://www.egalizer.hu/zene/cdk/kritika.htm?csubpage=/zene/cdk/cd21.htm&quot;</span>;
String encodedUrl = Base64.getUrlEncoder().encodeToString(inputUrl.getBytes());
<span class="java_keyword">byte</span>[] decodedByteArray = Base64.getUrlDecoder().decode(encodedUrl);
String decodedUrl = <span class="java_keyword">new</span> String(decodedByteArray);</pre>
  </div>
  <p class="bekezd">A MIME kódolás ezek után már aligha meglepetés:</p>
  <div class="programkod">
    <pre>String inputString = <span class="java_string">&quot;Hosszú várakozás után 2014. március 18-án jelent meg a Java 8 fejlesztői környezet&quot;</span>
    + <span class="java_string">&quot; és programozási nyelv (JDK 1.8.0). A 8-as Java talán a legnagyobb előrelépés a 2004-ben megjelent&quot;</span>;
String mimeEncodedString = Base64.getMimeEncoder().encodeToString(inputString.getBytes());
<span class="java_keyword">byte</span>[] decodedByteArray = Base64.getMimeDecoder().decode(mimeEncodedString);
String decodedString = <span class="java_keyword">new</span> String(decodedByteArray);</pre>
  </div>
  <p class="bekezd">Ez az enkóder 76 karakternél nem hosszabb kimenetet produkál, szükség esetén újsorokkal elválasztva. A fenti esetben:</p>
  <pre class="programkod">SG9zc3rDuiB2w6FyYWtvesOhcyB1dMOhbiAyMDE0LiBtw6FyY2l1cyAxOC3DoW4gamVsZW50IG1l
ZyBhIEphdmEgOCBmZWpsZXN6dMWRaSBrw7ZybnllemV0IMOpcyBwcm9ncmFtb3rDoXNpIG55ZWx2
IChKREsgMS44LjApLiBBIDgtYXMgSmF2YSB0YWzDoW4gYSBsZWduYWd5b2JiIGVsxZFyZWzDqXDD
qXMgYSAyMDA0LWJlbiBtZWdqZWxlbnQ=</pre>
  <p class="bekezd">A MIME enkódernek van egy olyan változata is aminek kézzel adhatjuk meg egy sor hosszát és az újsor karaktert:</p>
  <div class="programkod">
    <pre>String mimeEncodedString = Base64.getMimeEncoder(10, <span class="java_string">&quot;\n&quot;</span>.getBytes()).encodeToString(inputString.getBytes());</pre>
  </div>
  <p class="bekezd">A karakterhossz lefelé lesz kerekítve a 4 legközelebbi többszörösére (10 esetén tehát 8 lesz).</p>
  <h2>Nashorn JavaScript motor</h2>
  <p class="bekezd">
    A Java 6-ban jelent meg a ScriptEngine, ami lehetővé tette Java kódból különböző szkriptnyelvek beágyazott használatát. Új szcriptnyelvhez egyszerűen a <span class="programkod">javax.script.ScriptEngine</span>-t
    kell implementálni. A támogatott nyelvek közé tartozott a JavaScript is. A Java 8 egy új, Nashorn nevű JavaScript motort hozott be, ami felváltotta a korábbi Rhino-t. A Nashorn lehetővé
    teszi a metódushívást JavaScript és a Java között, collection-ök kezelését és még mindenféle nyalánkságot. Mivel ez a Java 11-ben már tovább nem támogatott (deprecated), nem fogok
    belemélyedni az izgalmaiba. Egy kis példa:
  </p>
  <div class="programkod">
    <pre>ScriptEngineManager manager = <span class="java_keyword">new</span> ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName(<span class="java_string">&quot;JavaScript&quot;</span>);
System.out.println(engine.getClass().getName());
engine.eval(<span class="java_string">&quot;print('Hello World!');&quot;</span>);
System.out.println(engine.eval(<span class="java_string">&quot;function f() { return 1; }; f() + 1;&quot;</span>));</pre>
  </div>
  <h2>Új Java eszközök</h2>
  <p class="bekezd">A Java 8 egy csapat parancssori eszközt is hozott magával.</p>
  <p class="bekezd">
    <b>jjs</b>
  </p>
  <p class="bekezd">A jjs egy különálló paracssori Nashorn motor. JavaScript forráskód fájllistát vár paraméterként és lefuttatja azokat. Csináljunk például egy func.js fájlt a
    következő tartalommal:</p>
  <pre class="programkod">function f() {
    return 1;
};
print( f() + 1 );</pre>
  <p class="bekezd">Parancssorból ezt a fájlt így tudjuk lefuttatni:</p>
  <pre class="programkod">jjs func.js</pre>
  <p class="bekezd">
    Akit bővebben érdekel a jjs, <a onclick="window.open(this.href);return false;" href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jjs.html">itt a teljes dokumentáció</a>.
  </p>
  <p class="bekezd">
    <b>jdeps</b>
  </p>
  <p class="bekezd">
    A jdeps megmutatja a class fájlok csomagszintű vagy osztályszintű függőségeit. Bemenetként .class fájlt, könyvtárat vagy jar fájlt vár. A függőségeket alapesetben a konzolra írja és
    csomagonként csoportosítja. Ha a függőség nem érhető el a classpath-on akkor not found jelenik meg. <a onclick="window.open(this.href);return false;"
      href="https://docs.oracle.com/javase/8/docs/technotes/tools/unix/jdeps.html">Hivatalos dokumentáció</a>.
  </p>
  <p class="bekezd">Példaként a népszerű spring framework core API függőségeinek egy részlete:</p>
  <pre class="programkod">jdeps.exe spring-core-5.1.6.RELEASE.jar
  
spring-core-5.1.6.RELEASE.jar -&gt; not found
spring-core-5.1.6.RELEASE.jar -&gt; c:\Program Files\Java\jdk1.8.0_162\jre\lib\rt.jar
   org.springframework.asm (spring-core-5.1.6.RELEASE.jar)
      -&gt; java.io                                            
      -&gt; java.lang                                          
      -&gt; java.lang.reflect                                  
      -&gt; java.util                                          
   org.springframework.cglib (spring-core-5.1.6.RELEASE.jar)
      -&gt; java.lang                                          
   org.springframework.cglib.beans (spring-core-5.1.6.RELEASE.jar)
      -&gt; java.beans                                         
      -&gt; java.lang                                          
      -&gt; java.lang.reflect                                  
      -&gt; java.security                                      
      -&gt; java.util                                          
      -&gt; org.springframework.asm                            spring-core-5.1.6.RELEASE.jar
      -&gt; org.springframework.cglib.core                     spring-core-5.1.6.RELEASE.jar
 [...]
   org.springframework.cglib.transform (spring-core-5.1.6.RELEASE.jar)
      -&gt; java.io                                            
      -&gt; java.lang                                          
      -&gt; java.net                                           
      -&gt; java.security                                      
      -&gt; java.util                                          
      -&gt; java.util.zip                                      
      -&gt; org.apache.tools.ant                               not found
      -&gt; org.apache.tools.ant.types                         not found
      -&gt; org.springframework.asm                            
 [...]</pre>
  <p class="bekezd">
    <b>JVM kapcsolók</b>
  </p>
  <p class="bekezd">
    Mivel eltűnt a permgen terület és bejött a metaspace, az ezekhez tartozó parancssori kapcsolók is módosultak. Eltűntek a <span class="programkod">-XX:PermSize</span> és <span
      class="programkod">–XX:MaxPermSize</span> kapcsolók és bejöttek a <span class="programkod">-XX:MetaSpaceSize</span> és <span class="programkod">-XX:MaxMetaspaceSize</span> kapcsolók.
  </p>
</body>
</html>