☰
\(\newcommand{\footnotename}{footnote}\) \(\def \LWRfootnote {1}\) \(\newcommand {\footnote }[2][\LWRfootnote ]{{}^{\mathrm {#1}}}\) \(\newcommand {\footnotemark }[1][\LWRfootnote ]{{}^{\mathrm {#1}}}\) \(\let \LWRorighspace \hspace \) \(\renewcommand {\hspace }{\ifstar \LWRorighspace \LWRorighspace }\) \(\newcommand {\TextOrMath }[2]{#2}\) \(\newcommand {\mathnormal }[1]{{#1}}\) \(\newcommand \ensuremath [1]{#1}\) \(\newcommand {\LWRframebox }[2][]{\fbox {#2}} \newcommand {\framebox }[1][]{\LWRframebox } \) \(\newcommand {\setlength }[2]{}\) \(\newcommand {\addtolength }[2]{}\) \(\newcommand {\setcounter }[2]{}\) \(\newcommand {\addtocounter }[2]{}\) \(\newcommand {\arabic }[1]{}\) \(\newcommand {\number }[1]{}\) \(\newcommand {\noalign }[1]{\text {#1}\notag \\}\) \(\newcommand {\cline }[1]{}\) \(\newcommand {\directlua }[1]{\text {(directlua)}}\) \(\newcommand {\luatexdirectlua }[1]{\text {(directlua)}}\) \(\newcommand {\protect }{}\) \(\def \LWRabsorbnumber #1 {}\) \(\def \LWRabsorbquotenumber "#1 {}\) \(\newcommand {\LWRabsorboption }[1][]{}\) \(\newcommand {\LWRabsorbtwooptions }[1][]{\LWRabsorboption }\) \(\def \mathchar {\ifnextchar "\LWRabsorbquotenumber \LWRabsorbnumber }\) \(\def \mathcode #1={\mathchar }\) \(\let \delcode \mathcode \) \(\let \delimiter \mathchar \) \(\def \oe {\unicode {x0153}}\) \(\def \OE {\unicode {x0152}}\) \(\def \ae {\unicode {x00E6}}\) \(\def \AE {\unicode {x00C6}}\) \(\def \aa {\unicode {x00E5}}\) \(\def \AA {\unicode {x00C5}}\) \(\def \o {\unicode {x00F8}}\) \(\def \O {\unicode {x00D8}}\) \(\def \l {\unicode {x0142}}\) \(\def \L {\unicode {x0141}}\) \(\def \ss {\unicode {x00DF}}\) \(\def \SS {\unicode {x1E9E}}\) \(\def \dag {\unicode {x2020}}\) \(\def \ddag {\unicode {x2021}}\) \(\def \P {\unicode {x00B6}}\) \(\def \copyright {\unicode {x00A9}}\) \(\def \pounds {\unicode {x00A3}}\) \(\let \LWRref \ref \) \(\renewcommand {\ref }{\ifstar \LWRref \LWRref }\) \( \newcommand {\multicolumn }[3]{#3}\) \(\require {textcomp}\) \(\newcommand {\intertext }[1]{\text {#1}\notag \\}\) \(\let \Hat \hat \) \(\let \Check \check \) \(\let \Tilde \tilde \) \(\let \Acute \acute \) \(\let \Grave \grave \) \(\let \Dot \dot \) \(\let \Ddot \ddot \) \(\let \Breve \breve \) \(\let \Bar \bar \) \(\let \Vec \vec \) \(\renewcommand {\vec }{\boldsymbol }\) \(\newcommand {\Edge }{\ensuremath {\,\textemdash \,}}\) \(\newcommand \Const [1]{\text {\textsf {#1}}}\) \(\DeclareMathOperator {\lerp }{lerp}\) \(\DeclareMathOperator {\bitlen }{bitlen}\) \(\DeclareMathOperator {\sign }{sign}\) \(\newcommand {\I }{\mathrm {i}}\) \(\newcommand \AND {\mathbin {\&}}\) \(\newcommand \OR {\mathbin {|}}\) \(\newcommand \XOR {\mathbin {{}^{\wedge }}}\) \(\newcommand \shl {\ll }\) \(\newcommand \shr {\ggg }\) \(\newcommand \asr {\gg }\) \(\newcommand \NOT {\ensuremath {\mathord {\sim }}}\) \(\newcommand {\isep }{\mathrel {{.}\,{.}}}\) \(\newcommand {\Id }[1]{\mathit {#1}}\) \(\newcommand {\const }[1]{\mathsf {#1}}\) \(\newcommand {\algorithmname }[1]{\text {\textsc {#1}}}\) \(\newcommand {\bits }[1]{\text {#1}}\) \(\newcommand {\hexa }[1]{\mathtt {0x#1}}\) \(\newcommand {\num }[1]{#1}\) \(\newcommand {\qed }{\quad \square }\) \(\newcommand {\idiv }[2]{\lfloor #1/#2\rfloor }\) \(\newcommand \attribdot {\ensuremath {\mkern 1.5mu.\mkern 1.5mu}}\) \(\newcommand \attribxr [2]{#1\attribdot \text {#2}}\) \(\newcommand \attribir [2]{\Id {#1}\attribdot \text {#2}}\) \(\newcommand \attribii [2]{\Id {#1}\attribdot \Id {#2}}\) \(\newcommand \textsc [1]{#1}\) \(\require {colortbl}\) \(\let \LWRorigcolumncolor \columncolor \) \(\renewcommand {\columncolor }[2][named]{\LWRorigcolumncolor [#1]{#2}\LWRabsorbtwooptions }\) \(\let \LWRorigrowcolor \rowcolor \) \(\renewcommand {\rowcolor }[2][named]{\LWRorigrowcolor [#1]{#2}\LWRabsorbtwooptions }\) \(\let \LWRorigcellcolor \cellcolor \) \(\renewcommand {\cellcolor }[2][named]{\LWRorigcellcolor [#1]{#2}\LWRabsorbtwooptions }\) \(\newcommand {\tcbset }[1]{}\) \(\newcommand {\tcbsetforeverylayer }[1]{}\) \(\newcommand {\tcbox }[2][]{\boxed {\text {#2}}}\) \(\newcommand {\tcboxfit }[2][]{\boxed {#2}}\) \(\newcommand {\tcblower }{}\) \(\newcommand {\tcbline }{}\) \(\newcommand {\tcbtitle }{}\) \(\newcommand {\tcbsubtitle [2][]{\mathrm {#2}}}\) \(\newcommand {\tcboxmath }[2][]{\boxed {#2}}\) \(\newcommand {\tcbhighmath }[2][]{\boxed {#2}}\) \(\newcommand {\LWRmarginnote }[1][]{}\) \(\newcommand {\marginnote }[2][]{\qquad {\small \textrm {#2}}\LWRmarginnote }\) \(\def \LWRsidenote {1}\) \(\newcommand {\sidenotemark }[1][\LWRsidenote ]{{}^{\mathrm {#1}}}\) \(\newcommand {\toprule }[1][]{\hline }\) \(\let \midrule \toprule \) \(\let \bottomrule \toprule \) \(\def \LWRbooktabscmidruleparen (#1)#2{}\) \(\newcommand {\LWRbooktabscmidrulenoparen }[1]{}\) \(\newcommand {\cmidrule }[1][]{\ifnextchar (\LWRbooktabscmidruleparen \LWRbooktabscmidrulenoparen }\) \(\newcommand {\morecmidrules }{}\) \(\newcommand {\specialrule }[3]{\hline }\) \(\newcommand {\addlinespace }[1][]{}\) \(\newcommand {\LWRsubmultirow }[2][]{#2}\) \(\newcommand {\LWRmultirow }[2][]{\LWRsubmultirow }\) \(\newcommand {\multirow }[2][]{\LWRmultirow }\) \(\newcommand {\mrowcell }{}\) \(\newcommand {\mcolrowcell }{}\) \(\newcommand {\STneed }[1]{}\) \(\newcommand {\LWRldelimtwo }[1][]{\text {#1}~\LWRbigdelim }\) \(\newcommand {\LWRldelimone }[2][]{\LWRldelimtwo }\) \(\def \ldelim #1#2{\def \LWRbigdelim {#1}\LWRldelimone }\) \(\newcommand {\LWRrdelimtwo }[1][]{\LWRbigdelim ~\text {#1}}\) \(\newcommand {\LWRrdelimone }[2][]{\LWRrdelimtwo }\) \(\def \rdelim #1#2{\def \LWRbigdelim {#1}\LWRrdelimone }\) \(\let \symnormal \mathit \) \(\let \symliteral \mathrm \) \(\let \symbb \mathbb \) \(\let \symbbit \mathbb \) \(\let \symcal \mathcal \) \(\let \symscr \mathscr \) \(\let \symfrak \mathfrak \) \(\let \symsfup \mathsf \) \(\let \symsfit \mathit \) \(\let \symbfsf \mathbf \) \(\let \symbfup \mathbf \) \(\newcommand {\symbfit }[1]{\boldsymbol {#1}}\) \(\let \symbfcal \mathcal \) \(\let \symbfscr \mathscr \) \(\let \symbffrak \mathfrak \) \(\let \symbfsfup \mathbf \) \(\newcommand {\symbfsfit }[1]{\boldsymbol {#1}}\) \(\let \symup \mathrm \) \(\let \symbf \mathbf \) \(\let \symit \mathit \) \(\let \symsf \symsfit \) \(\let \symtt \mathtt \) \(\let \symbffrac \mathbffrac \) \(\newcommand {\mathfence }[1]{\mathord {#1}}\) \(\newcommand {\mathover }[1]{#1}\) \(\newcommand {\mathunder }[1]{#1}\) \(\newcommand {\mathaccent }[1]{#1}\) \(\newcommand {\mathbotaccent }[1]{#1}\) \(\newcommand {\mathalpha }[1]{\mathord {#1}}\) \(\def\Alpha{\unicode{x1D6E2}}\) \(\def\Beta{\unicode{x1D6E3}}\) \(\def\Gamma{\unicode{x1D6E4}}\) \(\def\Digamma{\mathit{\unicode{x03DC}}}\) \(\def\Delta{\unicode{x1D6E5}}\) \(\def\Epsilon{\unicode{x1D6E6}}\) \(\def\Zeta{\unicode{x1D6E7}}\) \(\def\Eta{\unicode{x1D6E8}}\) \(\def\Theta{\unicode{x1D6E9}}\) \(\def\Vartheta{\unicode{x1D6F3}}\) \(\def\Iota{\unicode{x1D6EA}}\) \(\def\Kappa{\unicode{x1D6EB}}\) \(\def\Lambda{\unicode{x1D6EC}}\) \(\def\Mu{\unicode{x1D6ED}}\) \(\def\Nu{\unicode{x1D6EE}}\) \(\def\Xi{\unicode{x1D6EF}}\) \(\def\Omicron{\unicode{x1D6F0}}\) \(\def\Pi{\unicode{x1D6F1}}\) \(\def\Rho{\unicode{x1D6F2}}\) \(\def\Sigma{\unicode{x1D6F4}}\) \(\def\Tau{\unicode{x1D6F5}}\) \(\def\Upsilon{\unicode{x1D6F6}}\) \(\def\Phi{\unicode{x1D6F7}}\) \(\def\Chi{\unicode{x1D6F8}}\) \(\def\Psi{\unicode{x1D6F9}}\) \(\def\Omega{\unicode{x1D6FA}}\) \(\def\alpha{\unicode{x1D6FC}}\) \(\def\beta{\unicode{x1D6FD}}\) \(\def\varbeta{\unicode{x03D0}}\) \(\def\gamma{\unicode{x1D6FE}}\) \(\def\digamma{\mathit{\unicode{x03DD}}}\) \(\def\delta{\unicode{x1D6FF}}\) \(\def\epsilon{\unicode{x1D716}}\) \(\def\varepsilon{\unicode{x1D700}}\) \(\def\zeta{\unicode{x1D701}}\) \(\def\eta{\unicode{x1D702}}\) \(\def\theta{\unicode{x1D703}}\) \(\def\vartheta{\unicode{x1D717}}\) \(\def\iota{\unicode{x1D704}}\) \(\def\kappa{\unicode{x1D705}}\) \(\def\varkappa{\unicode{x1D718}}\) \(\def\lambda{\unicode{x1D706}}\) \(\def\mu{\unicode{x1D707}}\) \(\def\nu{\unicode{x1D708}}\) \(\def\xi{\unicode{x1D709}}\) \(\def\omicron{\unicode{x1D70A}}\) \(\def\pi{\unicode{x1D70B}}\) \(\def\varpi{\unicode{x1D71B}}\) \(\def\rho{\unicode{x1D70C}}\) \(\def\varrho{\unicode{x1D71A}}\) \(\def\sigma{\unicode{x1D70E}}\) \(\def\varsigma{\unicode{x1D70D}}\) \(\def\tau{\unicode{x1D70F}}\) \(\def\upsilon{\unicode{x1D710}}\) \(\def\phi{\unicode{x1D719}}\) \(\def\varphi{\unicode{x1D711}}\) \(\def\chi{\unicode{x1D712}}\) \(\def\psi{\unicode{x1D713}}\) \(\def\omega{\unicode{x1D714}}\) \(\def\upAlpha{\unicode{x0391}}\) \(\def\upBeta{\unicode{x0392}}\) \(\def\upGamma{\unicode{x0393}}\) \(\def\upDigamma{\unicode{x03DC}}\) \(\def\upDelta{\unicode{x0394}}\) \(\def\upEpsilon{\unicode{x0395}}\) \(\def\upZeta{\unicode{x0396}}\) \(\def\upEta{\unicode{x0397}}\) \(\def\upTheta{\unicode{x0398}}\) \(\def\upVartheta{\unicode{x03F4}}\) \(\def\upIota{\unicode{x0399}}\) \(\def\upKappa{\unicode{x039A}}\) \(\def\upLambda{\unicode{x039B}}\) \(\def\upMu{\unicode{x039C}}\) \(\def\upNu{\unicode{x039D}}\) \(\def\upXi{\unicode{x039E}}\) \(\def\upOmicron{\unicode{x039F}}\) \(\def\upPi{\unicode{x03A0}}\) \(\def\upVarpi{\unicode{x03D6}}\) \(\def\upRho{\unicode{x03A1}}\) \(\def\upSigma{\unicode{x03A3}}\) \(\def\upTau{\unicode{x03A4}}\) \(\def\upUpsilon{\unicode{x03A5}}\) \(\def\upPhi{\unicode{x03A6}}\) \(\def\upChi{\unicode{x03A7}}\) \(\def\upPsi{\unicode{x03A8}}\) \(\def\upOmega{\unicode{x03A9}}\) \(\def\itAlpha{\unicode{x1D6E2}}\) \(\def\itBeta{\unicode{x1D6E3}}\) \(\def\itGamma{\unicode{x1D6E4}}\) \(\def\itDigamma{\mathit{\unicode{x03DC}}}\) \(\def\itDelta{\unicode{x1D6E5}}\) \(\def\itEpsilon{\unicode{x1D6E6}}\) \(\def\itZeta{\unicode{x1D6E7}}\) \(\def\itEta{\unicode{x1D6E8}}\) \(\def\itTheta{\unicode{x1D6E9}}\) \(\def\itVartheta{\unicode{x1D6F3}}\) \(\def\itIota{\unicode{x1D6EA}}\) \(\def\itKappa{\unicode{x1D6EB}}\) \(\def\itLambda{\unicode{x1D6EC}}\) \(\def\itMu{\unicode{x1D6ED}}\) \(\def\itNu{\unicode{x1D6EE}}\) \(\def\itXi{\unicode{x1D6EF}}\) \(\def\itOmicron{\unicode{x1D6F0}}\) \(\def\itPi{\unicode{x1D6F1}}\) \(\def\itRho{\unicode{x1D6F2}}\) \(\def\itSigma{\unicode{x1D6F4}}\) \(\def\itTau{\unicode{x1D6F5}}\) \(\def\itUpsilon{\unicode{x1D6F6}}\) \(\def\itPhi{\unicode{x1D6F7}}\) \(\def\itChi{\unicode{x1D6F8}}\) \(\def\itPsi{\unicode{x1D6F9}}\) \(\def\itOmega{\unicode{x1D6FA}}\) \(\def\upalpha{\unicode{x03B1}}\) \(\def\upbeta{\unicode{x03B2}}\) \(\def\upvarbeta{\unicode{x03D0}}\) \(\def\upgamma{\unicode{x03B3}}\) \(\def\updigamma{\unicode{x03DD}}\) \(\def\updelta{\unicode{x03B4}}\) \(\def\upepsilon{\unicode{x03F5}}\) \(\def\upvarepsilon{\unicode{x03B5}}\) \(\def\upzeta{\unicode{x03B6}}\) \(\def\upeta{\unicode{x03B7}}\) \(\def\uptheta{\unicode{x03B8}}\) \(\def\upvartheta{\unicode{x03D1}}\) \(\def\upiota{\unicode{x03B9}}\) \(\def\upkappa{\unicode{x03BA}}\) \(\def\upvarkappa{\unicode{x03F0}}\) \(\def\uplambda{\unicode{x03BB}}\) \(\def\upmu{\unicode{x03BC}}\) \(\def\upnu{\unicode{x03BD}}\) \(\def\upxi{\unicode{x03BE}}\) \(\def\upomicron{\unicode{x03BF}}\) \(\def\uppi{\unicode{x03C0}}\) \(\def\upvarpi{\unicode{x03D6}}\) \(\def\uprho{\unicode{x03C1}}\) \(\def\upvarrho{\unicode{x03F1}}\) \(\def\upsigma{\unicode{x03C3}}\) \(\def\upvarsigma{\unicode{x03C2}}\) \(\def\uptau{\unicode{x03C4}}\) \(\def\upupsilon{\unicode{x03C5}}\) \(\def\upphi{\unicode{x03D5}}\) \(\def\upvarphi{\unicode{x03C6}}\) \(\def\upchi{\unicode{x03C7}}\) \(\def\uppsi{\unicode{x03C8}}\) \(\def\upomega{\unicode{x03C9}}\) \(\def\italpha{\unicode{x1D6FC}}\) \(\def\itbeta{\unicode{x1D6FD}}\) \(\def\itvarbeta{\unicode{x03D0}}\) \(\def\itgamma{\unicode{x1D6FE}}\) \(\def\itdigamma{\mathit{\unicode{x03DD}}}\) \(\def\itdelta{\unicode{x1D6FF}}\) \(\def\itepsilon{\unicode{x1D716}}\) \(\def\itvarepsilon{\unicode{x1D700}}\) \(\def\itzeta{\unicode{x1D701}}\) \(\def\iteta{\unicode{x1D702}}\) \(\def\ittheta{\unicode{x1D703}}\) \(\def\itvartheta{\unicode{x1D717}}\) \(\def\itiota{\unicode{x1D704}}\) \(\def\itkappa{\unicode{x1D705}}\) \(\def\itvarkappa{\unicode{x1D718}}\) \(\def\itlambda{\unicode{x1D706}}\) \(\def\itmu{\unicode{x1D707}}\) \(\def\itnu{\unicode{x1D708}}\) \(\def\itxi{\unicode{x1D709}}\) \(\def\itomicron{\unicode{x1D70A}}\) \(\def\itpi{\unicode{x1D70B}}\) \(\def\itvarpi{\unicode{x1D71B}}\) \(\def\itrho{\unicode{x1D70C}}\) \(\def\itvarrho{\unicode{x1D71A}}\) \(\def\itsigma{\unicode{x1D70E}}\) \(\def\itvarsigma{\unicode{x1D70D}}\) \(\def\ittau{\unicode{x1D70F}}\) \(\def\itupsilon{\unicode{x1D710}}\) \(\def\itphi{\unicode{x1D719}}\) \(\def\itvarphi{\unicode{x1D711}}\) \(\def\itchi{\unicode{x1D712}}\) \(\def\itpsi{\unicode{x1D713}}\) \(\def\itomega{\unicode{x1D714}}\) \(\let \lparen (\) \(\let \rparen )\) \(\newcommand {\cuberoot }[1]{\,{}^3\!\!\sqrt {#1}}\,\) \(\newcommand {\fourthroot }[1]{\,{}^4\!\!\sqrt {#1}}\,\) \(\newcommand {\longdivision }[1]{\mathord {\unicode {x027CC}#1}}\) \(\newcommand {\mathcomma }{,}\) \(\newcommand {\mathcolon }{:}\) \(\newcommand {\mathsemicolon }{;}\) \(\newcommand {\overbracket }[1]{\mathinner {\overline {\ulcorner {#1}\urcorner }}}\) \(\newcommand {\underbracket }[1]{\mathinner {\underline {\llcorner {#1}\lrcorner }}}\) \(\newcommand {\overbar }[1]{\mathord {#1\unicode {x00305}}}\) \(\newcommand {\ovhook }[1]{\mathord {#1\unicode {x00309}}}\) \(\newcommand {\ocirc }[1]{\mathord {#1\unicode {x0030A}}}\) \(\newcommand {\candra }[1]{\mathord {#1\unicode {x00310}}}\) \(\newcommand {\oturnedcomma }[1]{\mathord {#1\unicode {x00312}}}\) \(\newcommand {\ocommatopright }[1]{\mathord {#1\unicode {x00315}}}\) \(\newcommand {\droang }[1]{\mathord {#1\unicode {x0031A}}}\) \(\newcommand {\leftharpoonaccent }[1]{\mathord {#1\unicode {x020D0}}}\) \(\newcommand {\rightharpoonaccent }[1]{\mathord {#1\unicode {x020D1}}}\) \(\newcommand {\vertoverlay }[1]{\mathord {#1\unicode {x020D2}}}\) \(\newcommand {\leftarrowaccent }[1]{\mathord {#1\unicode {x020D0}}}\) \(\newcommand {\annuity }[1]{\mathord {#1\unicode {x020E7}}}\) \(\newcommand {\widebridgeabove }[1]{\mathord {#1\unicode {x020E9}}}\) \(\newcommand {\asteraccent }[1]{\mathord {#1\unicode {x020F0}}}\) \(\newcommand {\threeunderdot }[1]{\mathord {#1\unicode {x020E8}}}\) \(\newcommand {\Bbbsum }{\mathop {\unicode {x2140}}\limits }\) \(\newcommand {\oiint }{\mathop {\unicode {x222F}}\limits }\) \(\newcommand {\oiiint }{\mathop {\unicode {x2230}}\limits }\) \(\newcommand {\intclockwise }{\mathop {\unicode {x2231}}\limits }\) \(\newcommand {\ointclockwise }{\mathop {\unicode {x2232}}\limits }\) \(\newcommand {\ointctrclockwise }{\mathop {\unicode {x2233}}\limits }\) \(\newcommand {\varointclockwise }{\mathop {\unicode {x2232}}\limits }\) \(\newcommand {\leftouterjoin }{\mathop {\unicode {x27D5}}\limits }\) \(\newcommand {\rightouterjoin }{\mathop {\unicode {x27D6}}\limits }\) \(\newcommand {\fullouterjoin }{\mathop {\unicode {x27D7}}\limits }\) \(\newcommand {\bigbot }{\mathop {\unicode {x27D8}}\limits }\) \(\newcommand {\bigtop }{\mathop {\unicode {x27D9}}\limits }\) \(\newcommand {\xsol }{\mathop {\unicode {x29F8}}\limits }\) \(\newcommand {\xbsol }{\mathop {\unicode {x29F9}}\limits }\) \(\newcommand {\bigcupdot }{\mathop {\unicode {x2A03}}\limits }\) \(\newcommand {\bigsqcap }{\mathop {\unicode {x2A05}}\limits }\) \(\newcommand {\conjquant }{\mathop {\unicode {x2A07}}\limits }\) \(\newcommand {\disjquant }{\mathop {\unicode {x2A08}}\limits }\) \(\newcommand {\bigtimes }{\mathop {\unicode {x2A09}}\limits }\) \(\newcommand {\modtwosum }{\mathop {\unicode {x2A0A}}\limits }\) \(\newcommand {\sumint }{\mathop {\unicode {x2A0B}}\limits }\) \(\newcommand {\intbar }{\mathop {\unicode {x2A0D}}\limits }\) \(\newcommand {\intBar }{\mathop {\unicode {x2A0E}}\limits }\) \(\newcommand {\fint }{\mathop {\unicode {x2A0F}}\limits }\) \(\newcommand {\cirfnint }{\mathop {\unicode {x2A10}}\limits }\) \(\newcommand {\awint }{\mathop {\unicode {x2A11}}\limits }\) \(\newcommand {\rppolint }{\mathop {\unicode {x2A12}}\limits }\) \(\newcommand {\scpolint }{\mathop {\unicode {x2A13}}\limits }\) \(\newcommand {\npolint }{\mathop {\unicode {x2A14}}\limits }\) \(\newcommand {\pointint }{\mathop {\unicode {x2A15}}\limits }\) \(\newcommand {\sqint }{\mathop {\unicode {x2A16}}\limits }\) \(\newcommand {\intlarhk }{\mathop {\unicode {x2A17}}\limits }\) \(\newcommand {\intx }{\mathop {\unicode {x2A18}}\limits }\) \(\newcommand {\intcap }{\mathop {\unicode {x2A19}}\limits }\) \(\newcommand {\intcup }{\mathop {\unicode {x2A1A}}\limits }\) \(\newcommand {\upint }{\mathop {\unicode {x2A1B}}\limits }\) \(\newcommand {\lowint }{\mathop {\unicode {x2A1C}}\limits }\) \(\newcommand {\bigtriangleleft }{\mathop {\unicode {x2A1E}}\limits }\) \(\newcommand {\zcmp }{\mathop {\unicode {x2A1F}}\limits }\) \(\newcommand {\zpipe }{\mathop {\unicode {x2A20}}\limits }\) \(\newcommand {\zproject }{\mathop {\unicode {x2A21}}\limits }\) \(\newcommand {\biginterleave }{\mathop {\unicode {x2AFC}}\limits }\) \(\newcommand {\bigtalloblong }{\mathop {\unicode {x2AFF}}\limits }\) \(\newcommand {\arabicmaj }{\mathop {\unicode {x1EEF0}}\limits }\) \(\newcommand {\arabichad }{\mathop {\unicode {x1EEF1}}\limits }\)

Appendix B Java Distilled

Java is a statically typed, object-oriented programming language that shares many features with popular languages such as C++, C#, or Swift. If you know any of these languages, you won’t find it hard to follow the examples in this book. Nevertheless, a few language features and programming techniques work differently in Java or use an unfamiliar syntax. The goal of this appendix is to summarize these features.

B.1 Classes and Packages

Classes are the main abstraction in Java for organizing code and storing data. Even the simplest programs, such as the canonical “Hello World” program, require that we define a class that contains a method called main(). This method must be declared exactly as shown in the following listing:

package hello;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

The main() method contains the program’s code, which in this example simply calls another method called println() that prints the string "Hello World!" to the screen. If we want to call a method that is not defined inside the current class, we must specify its exact location. Here, the prefix System.out indicates that println() is defined in an object called out that is defined in a class called System.

Java classes and interfaces are organized in packages. In the example above, the HelloWorld class is placed in the hello package. By default, only names defined in the same package are visible when defining a new class. For example, assume we define a new class Greetings, which defines constants for printing “Hello World” in different languages:

package greetings;

public class Greetings {
    public static String ENGLISH = "Hello World!";
    public static String GERMAN = "Hallo Welt!";
}

Because this class is contained in a separate package greetings, it must first be imported before it can be accessed by the code in our original package hello:

package hello;

import greetings.Greetings;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println(Greetings.ENGLISH);
    }
}

The only package that doesn’t need to be imported explicitly is java.lang, which defines classes and interfaces that are provided by the Java language and are used in almost every program. For example, in the HelloWorld class above, we were able to use the String and System classes from java.lang without first importing them. Among the many other classes defined in java.lang, the following are especially worth knowing:

  • • Byte, Integer, Float, and Double provide methods for working with the built-in number types byte, int, float, and double.

  • • Object is the base class of every other class in Java and defines methods that are provided by every object.

  • • Math contains mathematical functions such as sqrt() and sin() and the constants PI and E.

Two special forms of import statements are worth mentioning. First, it’s possible to import every public class and interface defined in a package by using an asterisk in place of the type name:

import java.util.*;

This can be used instead of a long sequence of individual import statements such as

import java.util.List;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
...

The other special form of import statement is used to import static members from other classes. For example, Java defines standard math functions and constants as static members inside a class called Math, so every access to those members must usually be prefixed with Math.:

double y = Math.sin(Math.PI / 2);

This can be shortened by importing the two static members sin and PI at the beginning of the file:

import static java.lang.Math.sin;
import static java.lang.Math.PI;

From then on you can simply write

double y = sin(PI / 2);

It is also possible to use the asterisk syntax to import all static members defined in a class:

import static java.lang.Math.*;

To keep the code examples short, we usually omit package and import statements from code listings in this book.

By default, fields and methods are only visible to classes defined in the same package, but you can use access modifiers to change their visibility:

  • • Members declared as private are only visible inside the current class.

  • • Members declared as protected are only visible inside the current class and all classes derived from it.

  • • Members declared as public are visible everywhere and can be used by any class defined in any package.

In practice, private and public are the most common access modifiers: private is used for things that are considered “implementation details” and public for things that are meant to be used by other code.

B.2 Primitive Types

In Java, basic data types such as int or float are referred to as primitive types. These types are built into the language itself and cannot be modified or extended by the programmer, but they can be used to declare variables, return types, and the types of function arguments, and the usual arithmetic, comparison, and logical operators are available to operate on them. The full list of primitive types and the available operators is shown in Table B.1.

Table B.1 Primitive types in Java
.
Type Literal Bits Operators

Wrapper

byte – 8 conversion to int

Byte

short – 16 conversion to int

Short

char 'a', 'Ñ' 16 conversion to int

Character

int 0x7f, -4096 32 + - * / %

Integer

~ ^ & | << >> >>>
long 0x7fL, -4096L 64 + - * / %

Long

~ ^ & | << >> >>>
float -1.6f, 2e-5f 32 + - * /

Float

double -1.6, 2e-5 64 + - * /

Double

boolean true, false – ! && ||

Boolean

void – –

Void

It is possible to convert between the numeric types listed in the upper two parts of the table. In cases where there can be no loss of data, these conversions are performed automatically; this is known as type promotion:

byte b = 17;
int c = b;            // byte promoted to int 
long l = 12 + 17L;    // 12 promoted to long
double d = 12 * 1.0f; // 12 promoted to float, then to double

In cases where data loss is possible, the conversion must be performed manually using type casts:

int i = (int)(12 + 17L);      // conversion from long to int
float f = (float)(12 * 1.0);  // conversion from double to float
int j = (int)f;               // conversion from float to int

Type conversion isn’t available for the boolean type. Instead, we have to use if statements or the ternary operator ?: to convert Boolean values to numbers and comparison operators such as != to convert numbers to Booleans:

int v = (c > b) ? 1 : 0;  // explicit conversion from boolean to int
boolean b = (v != 0);     // explicit conversion from int to boolean

Another peculiarity of Java worth noting is the relationship between primitive types and the wrapper classes indicated in the last column of Table B.1. As you can see, there is one wrapper class for every type. These classes serve two purposes. First, as mentioned in the previous section, each wrapper class contains a selection of useful static methods and constants for working with the corresponding primitive types. For instance, Float defines methods for hashing, comparing, and inspecting floating point numbers, for converting to and from decimal strings, and useful constants such as POSITIVE_INFINITY and MAX_VALUE.

The second purpose of these wrapper classes is to actually wrap values of the corresponding primitive types. As an example, the following listing demonstrates how to use the valueOf() method to create an instance of Integer that holds the number 13 and later retrieve this value using the intValue() method:

Integer i = Integer.valueOf(13);
int j = i.intValue();
assert j == 13;

Similar methods exist for the other wrapper classes. The process of wrapping a primitive value in its wrapper class is also referred to as boxing and retrieving the value as unboxing of primitive data types. Boxing and unboxing are so common in Java that they are often performed automatically. The previous example, for instance, can also be written as

Integer i = 13;  // automatic boxing
int j = i;       // automatic unboxing

Because all wrapper classes derive from Object, it is possible define variables that hold either an integer or a floating point number (or, in fact, any other object):

Object anything;
if (Math.random() < 0.5) {
    anything = 42;
} else {
    anything = 3.14159f;
}
if (anything instanceof Integer i) {
    System.out.println("It's an integer: " + i);
} else if (anything instanceof Float f) {
    System.out.println("It's a float: " + f);
}

In the early days of Java, this technique was used to implement data structures that can hold all kinds of values, including primitive types. Today, such data structures are usually implemented using generics, which we will discuss in more detail in Appendix B.5. For historical reasons, generics don’t work with primitive types such as int or float but only with types derived from Object1. If you want to create an ArrayList of integers, for example, you have to use the wrapper type Integer instead of int:

// var list = new ArrayList<int>();  // !! compile-time error
var list = new ArrayList<Integer>(); // OK
list.add(1);         // box 1 by converting it to Integer
int x = list.get(0); // unbox result by converting it to int

1 This includes all types defined using class, interface, record, and enum.

B.3 Data Classes

Data classes are classes that are primarily used for storing data. In contrast to most other classes, data classes don’t contain nontrivial logic or internal state. Here is a simple example of a data class that stores a person’s name in a field of type String and their age in a field of type int:

class Person {
    final String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

By default, classes and their fields and methods can only be accessed from code in the same package. To change this default visibility, the two access modifiers public and private can be used. In the following example, we use public to make Person and its constructor visible to other packages and private to hide its two fields:

public class Person {
    private final String name;
    private int age;
  
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
  
    ...
}

It is common practice to declare all fields of public classes as private and to define specialized accessor methods to allow selective access to the fields.

public String name() { return name; }
public String age() { return age; }
public String setAge(int age) { 
    if (age < 0)
        throw new IllegalArgumentException();
    this.age = age; 
}

The two methods name() and age() simply return the current values of the corresponding field; such methods are known as getter methods or simply getters. The setAge() methods can be used to change the value of the age field; such methods are known as setter methods or setters.

All classes in Java are derived from a common base class called Object, which defines several methods that you may want or need to override. The first is toString(), which produces a string representation of an object:

public String toString() {
    return String.format("Person(\"%s\", %d)", name, age));
}

toString() is often used for debugging, so its output should usually include the values of all fields.

For objects that are meant to be stored in containers such as hash tables and hash sets, you probably also need to override equals() and hashCode(). The purpose of equals() is to compare this to another object and return whether they are equal. In the case of data classes, two objects are generally considered equal if they have the same type and all fields have the same values:

public boolean equals(Object other) {
    return (other instanceof Person o)
        && name.equals(o.name) && age == o.age;
}

Here, we use String.equals() to compare the contents of the two name fields and the equality operator “==” to compare the values of the age fields.

In addition, objects that are used as keys in hash tables must override the hashCode() method, which computes an object’s hash code of type int:

public int hashCode() {
    return Objects.hash(person, age);
}

JDK: Objects

Classes that override equals() must usually also override hashCode() and vice versa. Containers such as HashMap that are based on hash tables require that hashCode() is consistent with equals() in the sense that two objects that are equal must also have the same hash code.2

2 There are a few additional subtleties regarding the implementation of toString(), equals(), and hashCode(); for details, see [17, Item 10–12].

Records

Starting with Java 14, records provide much simpler way of defining data classes. With records, a Person class similar to the one discussed above can be defined as follows:

public record Person(String name, int age) {
}

The fields of a record are declared by listing them in parentheses directly after the record’s name. From this definition, Java automatically generates a class with the following properties:

  • • The specified fields are defined as final and have package-internal visibility.

  • • There is one constructor that takes the initial values of all fields as its arguments.

  • • The class provides implementations of toString(), equals(), and hashCode() that are equivalent to the hand-written versions shown above.

  • • Public getters are defined for all declared fields, here name() and age(). There are no corresponding setter methods — all fields are implicitly final, so records are immutable.

If needed, you can provide additional constructors, override methods such as toString() or equals(), and even define your own methods. Records can also implement one or more interfaces.

Apart from the way they are defined, records behave like ordinary classes:

var person = new Person("Einstein", 1879);
System.out.printf("%s was born in %d\n", person.name(),
    person.yearOfBirth());

There are a few important restrictions, however. Unlike normal classes, records are always immutable since all its fields are implicitly declared as final and no setter methods are provided. In addition, records cannot extend other classes or used as base classes themselves.

B.4 Interfaces

Interfaces are one of Java’s core mechanisms for writing code that works with many different types; the other language feature is generics, which we will discuss in the following section. Consider the following example of a function that computes the sum of all integers in an array:

int sum(int[] numbers) {
    int sum = 0;
    for (int i = 0; i < numbers.length; i++)
        sum += numbers[i];
    return sum;
}

The function’s argument numbers is used in two places only: in the expression numbers.length that computes the length of the array, and in the expression numbers[i] that retrieves the element at position i. The rest of the function’s body doesn’t interact with the numbers variable at all.

Suppose we want to generalize sum() so that it can compute the sum of any kind of integer sequence, regardless of how it is stored in memory; all we need is a way to query its length and access the \(i\)th element. To express this constraint, we can define the following interface:

public interface IntegerSequence {
    int size();
    int get(int index);
}

This defines IntegerSequence as a data type that provides a method size() that returns an integer and a method get() that returns an integer for a given index. Notice that methods defined in an interface are always public, so we don’t need to specify access modifiers explicitly.

Here is how you could generalize the implementation of sum() by changing the type of the parameter numbers from an array of ints to IntegerSequence:

static int sum(IntegerSequence numbers) {
    int sum = 0;
    for (int i = 0; i < numbers.size(); i++)
        sum += numbers.get(i);
    return sum;
}

This updated version of sum() is now usable with any object that implements the IntegerSequence interface.

An interface is implemented by defining a class that provides all the methods required by the interface. As an example, let’s implement the IntegerSequence interface in two different ways. The following ArraySequence class implements IntegerSequence by wrapping an array of ints:

public record ArraySequence(int[] array)
        implements IntegerSequence {
    @Override
    public int size() {return array.length;}

    @Override
    public int get(int index) {return array[index];}
}

The class overrides the methods specified by IntegerSequence: here, size() is defined to return the length of the array and get() the element at position index.

Alternatively, we can define a version of IntegerSequence that computes its elements on the fly. The following example defines a class Range that represents the sequence of integers between start and end:

public record Range(int start, int end)
        implements IntegerSequence {
    @Override
    public int size() {return end - start;}

    @Override
    public int get(int index) {return start + index;}
}

In this case, size() simply returns the difference end - start and get() the number start + index. The sum() function defined above works with either implementation:

System.out.println(sum(new Range(0, 10)));
// prints 45
System.out.println(sum(new IntArray(new int[]{1, 2, 3})));
// prints 6

B.5 Generics and Collections

There are many programming problems that can be solved using slight variations of the same piece of code or the same data structure. As a somewhat contrived example, suppose we are given an array of strings and want to return either the string at a particular index or a default value if the index is out of range. This can be implemented as follows:

public static String getOrDefault(
        String[] array, int index, String dflt){
    return index >= 0 && index < array.length
        ? array[index] : dflt;
}

Except for the two occurrences of the type String, nothing about this algorithm is specific to strings.

Generics are the language feature provided by Java that makes it possible to write code that works with a wide range of types. In contrast to interfaces, which ostensibly serve a similar purpose, generics do not require the types to be part of a certain type hierarchy. Here is getOrDefault() rewritten using generics:

public static <T> T getOrDefault(
        T[] array, int index, T dflt) {
    return index >= 0 && index < array.length 
        ? array[index] : dflt;
}

The main difference is the declaration of a generic type before the method definition and the replacement of String with T. This new, generic version of getOrDefault() now works with arrays of any type.

Really any type? Unfortunately, not quite: Generics in Java only work with types that are derived from Object, but not with primitive types such as int, float, or char. For example, if you try to call getOrDefault() with an array of ints

getOrDefault(new int[]{1, 2, 3}, -1, 999);

the compiler will complain that the function cannot be applied to the given types. We can sometimes work around this limitation by using wrapper types such as Integer, Float, or Character, as in the following example:

getOrDefault(new Integer[]{1, 2, 3}, -1, 999);

If this isn’t feasible or too inefficient, it can be necessary to supplement generic methods that operate on types derived from Object with one or more specialized methods that handle primitive types. This technique is widely used in Java’s standard library, for example to provide specialized algorithms in the Arrays class.

Collections

Methods aren’t the only thing that can be generalized using generics. In fact, most Java programmers first encounter generics when they try to use one of the many collection classes provided by the standard library.

A collection is a data type that manages multiple objects and provides methods for accessing and changing them. Java’s standard library comes with a wide selection of generic collection classes that are parametrized by the types they hold. Perhaps the most widely used example is ArrayList, which is a resizable array of the given type T. Another example is HashMap, which takes two type arguments and provides a mapping from values of type T to values of type U. The following example uses these types to create an array of Integers and a mapping from Strings to Integers:

var firstPrimes = new ArrayList<Integer>();
firstPrimes.add(2);
firstPrimes.add(3);
firstPrimes.add(5);
firstPrimes.add(7);
var releaseYear = new HashMap<String, Integer>();
releaseYear.put("Star Trek: The Motion Picture", 1979);
releaseYear.put("Star Trek II: The Wrath of Khan", 1982);
// ...

The Java compiler uses the type arguments to ensure that only compatible values are stored in the respective container. For example, when trying to add a String to firstPrimes, Java complains about “incompatible types” because only integers are permitted. There are many collections beside ArrayList and HashMap; the most important ones are listed in Table B.2.

Table B.2 Commonly used collection classes
.
Class Interfaces

Description

ArrayList List

variable-length array

LinkedList List, Deque

doubly-linked list

ArrayDeque Deque

double-ended queue

HashSet Set

set based on hash tables

TreeSet NavigableSet

ordered set based on trees

PriorityQueue Queue

priority queue

HashMap Map

a key-value map based on hash tables

TreeMap NavigableMap

a key-value map based on trees

Underlying all these collection classes is a hierarchy of generic interfaces such as Iterable, Collection, List, Map, and Set that define the capabilities offered by different kinds of data structures. All collection classes provided by the standard library implement at least one of these interfaces, but some implement more. For instance, LinkedList implements both List and Deque because it can be used as a linear list and as a double-ended queue.

These interfaces are also commonly used in place of concrete collection classes when declaring variables and method signatures. If we wanted to write a method that converts a sequence of strings to upper-case, we could declare it as

static ArrayList<String> makeUppercase(ArrayList<String> strings) {
    var result = new ArrayList<String>();
    for (String s : strings)
        result.add(s.toUpperCase());
    return result;
}

But by changing the type of strings to Iterable and the return type to List, we obtain a function that is more flexible and easier to modify:

static List<String> makeUppercase(Iterable<String> strings) {
    // as above
}

This function definition is more flexible because it accepts many different collection types and not just ArrayLists. Its implementation is also easier to modify because we can change the type of collection allocated inside the method without having to adjust the return type. Using interfaces instead of concrete classes as return and argument types is sometimes referred to as “programming to interfaces, not implementations.”

Iterators and Iterables

Java defines two related interfaces for iterating over data structures: Iterator and Iterable. The Iterator interface is used to iterate over the elements of any data structure that contains elements of a certain type E. It is defined as follows:

interface Iterator<E> {
    boolean hasNext();
    E next();
    void remove();
}

The three methods are defined to work as follows:

  • • next() returns the next element and advances the iterator.

  • • hasNext() determines whether there are any elements remaining, that is, whether the next call to next() succeeds.

  • • remove() removes the previous element returned by next() from the data structure. The remove() method is optional and can be omitted.

The Iterable interface consists of a single method that creates a new iterator:

public interface Iterable<E> {
    Iterator<E> iterator();
    // ...
}

Usually, Iterable is implemented by classes that contain multiple other objects, and the Iterator returned by the iterator() method can then be used to access these objects sequentially.

Iterables can be used with for statements of the form

for (E value : collection) {
    // ...
}

where collection is some object that implements Iterable. This loop is equivalent to the following code:

Iterator<E> it = collection.iterator();
while (it.hasNext()) {
    E value = it.next();
    // ...
}

The iterator() method is first used to create a new Iterator, which is then used to access every element of collection.

All collection classes in Java’s standard library, including ArrayList, HashSet, and HashMap, implement the Iterable interface. The Iterator returned by each of these classes then handles the practical details of iterating over the elements of the particular data structure.

B.6 Functional Programming

The main idea behind functional programming is to elevate the role functions play in programming. In imperative programming, a function is something that has a name and performs a predefined computation. For example, the following function takes a value of type double and computes its square:

static double square(double x) {
    return x * x;
}

Such functions are sometimes referred to as first-order functions.

In functional programming, functions take on a much more active role: They can be created on the fly, they can be transformed, and they can be passed to and returned from other functions. A function that takes another function as one of its arguments or returns a new function is referred to as a higher-order function.

To implement such higher-order functions, Java uses a special kind of interface known as a functional interface. A functional interfaces is an interface that contains just a single method. In the following example, RealFunction is a functional interface because it defines a single method called evaluate():

public interface RealFunction {
    double evaluate(double x);
}

Functional interfaces are ordinary interfaces, and you can use them as such. For example, we can write a function tabulate() that takes any instance of RealFunction and tabulates the values of this function at a number of positions in an interval:

static double[] tabulate(double start, double end, int count,
        RealFunction func) {
    double[] result = new double[count];
    double spacing = (end - start) / (count - 1);
    for (int i = 0; i < count; i++)
        result[i] = func.eval(start + spacing * i);
    return result;
}

To call tabulate(), we need to provide an instance of RealFunction as the last argument. In older versions of Java, we have to create such an instance explicitly by creating a new RealFunction that defines an evaluate() method with the desired behavior. For example, if we want to tabulate \(\sin (x)\) at 100 positions in the interval \([0,2\pi ]\), older versions of Java require us to write something like this:

double[] values = tabulate(0, 2 * Math.PI, 100, 
        new RealFunction() {
            double evaluate(double x) { 
                return Math.sin(x);
            }
        });

The “new Interface() { ... }” notation used in the previous example constructs a so-called anonymous class. Normally, if we want to create an object that implements a certain interface, we first have to define a class that implements this interface, and then use the new operator to construct an object of this class. As a shorthand, Java lets us skip the intermediate step of defining a new class and directly instantiate an interface using new, provided we specify all of the interface’s methods in curly braces after the call to the constructor.

Lambda Expressions

Fortunately, Java provides another language features that take most of the effort (and typing) out of defining functional interfaces. The following listing shows how to shorten the previous example using a lambda expression:

double[] values = tabulate(0, 2 * Math.PI, 100,
        (double x) -> Math.sin(x));

Lambda expressions consist of three parts: an argument list, the arrow symbol ->, and the function’s body, which can either be a single expression, as in this example, or a code block enclosed in curly braces. Compared to anonymous classes, lambda expressions remove most of the syntactic overhead and let you focus on the interesting part: how to map one or more inputs (here x) to a value (here Math.sin(x)). Under the hood, Java implements the RealFunction interface, as before; lambda expressions are simply a convenient shorthand for implementing interfaces that contain a single method.

Lambda expressions of the form x -> Math.sin(x) are so common that there is a special notation to simplify them even further:

double[] values = tabulate(0, 2 * Math.PI 100, Math::sin);

The expression Math::sin is called a method reference.

Two more things are worth mentioning about lambda expressions. First, in many cases we can omit the types from the lambda expression’s argument list. In the previous example, we can shorten the (double x) to just x:

double[] values = tabulate(0, 2 * Math.PI 100,
        x -> Math.sin(x));

If you leave out the argument types, Java will try to infer them. In this case, lambda expression must implement the RealFunction interface, whose only method evaluate() expects an argument of type double. The type of x must therefore also be double.

Second, as mentioned above, the body of a lambda expression can be a block of code enclosed in curly braces, which is necessary when defining functions that consist of more than just a single expression. The following example uses this syntax to define and tabulate the so-called sinc function, which has a singularity at the origin:

double[] values = tabulate(-2, +2, 100, x -> {
            if (x == 0)
                return 1.0;
            return Math.sin(x) / x;
        });

Notice that we now need to use explicit return statements to return the lambda’s result.

Lambda expressions make it easy to define new functions “on the fly.” In the examples so far, we used lambda expressions to create new functions that are passed as arguments to higher-order functions such as tabulate(), but they have many other uses.

For example, assume we want to use the RealFunction interface to represent linear functions of the form

\begin{equation*} x\quad \mapsto \quad mx + y. \end{equation*}

This function describes a line whose slope is \(m\) and that crosses the y-axis at \(y\), the line’s intercept. We can now use lambda expressions to define a function linearFunction() that takes the slope and intercept as arguments and uses them to create a suitable instance of RealFunction:

RealFunction linearFunction(double slope, double intercept) {
    return x -> x * slope + intercept;
}

Every time linearFunction() is called, the lambda expression creates a new instance of RealFunction, which is then returned to the caller. Notice that the lambda expressions remembers the values of slope and intercept that were active at the point it was created. For example, if we write

RealFunction f = linearFunction(1.0, 0.0);
RealFunction g = linearFunction(2.0, 1.0);

we obtain two different RealFunctions with different values of slope and intercept.

Side Effects

Lambda expression have an unexpected and somewhat technical limitation: They can read but not modify local variables defined in their surrounding context. Consider the following attempt to write a function that finds the smallest element in a List of integers:

public static int findMin(List<Integer> numbers) {
    int result = Integer.MAX_VALUE;
    // error: "Variable used in lambda expression should be 
    //   final or effectively final"
    numbers.forEach(n -> result = Math.min(result, n));
    return result;
}

The idea is to iterate over the elements of numbers using forEach() and then use the provided lambda expression to update result with the current element if it is smaller. Although this definition of findMin() looks sensible, it doesn’t compile since the lambda expression writes to result, which is defined in the surrounding scope.

There are two simple workarounds to this problem, and both are based on the same idea: Even though lambda expressions cannot modify local variables defined in the surrounding scope, they can modify fields and array entries accessed through such variables. For instance, if we change the type of result in the example above from int to int[], we still cannot change the value of result itself, but we can change any element of the array result points to:

public static int findMin(List<Integer> numbers) {
    int[] result = {Integer.MAX_VALUE};
    numbers.forEach(n -> result[0] = Math.min(result[0], n));
    return result[0];
}

In other words, we can circumvent the limitation by wrapping any local variable we wish to modify from a lambda expression inside an array of size 1.

Alternatively, we can wrap local variables in anonymous objects, as shown in the following example:

public static int findMin(List<Integer> numbers) {
    var o = new Object() {
        int result = Integer.MAX_VALUE;
    };
    numbers.forEach(n -> o.result = Math.min(o.result, n));
    return o.result;
}

Again, it’s not the local variable itself that is modified but the field o.result accessed through it.

B.7 Predefined Functional Interfaces

The Java standard library defines a variety of common functional interfaces in the java.util.functional package. We use a few of them in this book:

  • • A consumer is a function that takes a single argument but doesn’t return anything:

    public interface Consumer<T> {
        void accept(T value);
    }
    

    JDK: Consumer

    Consumers are commonly used in combination with algorithms that produce a sequence of values:

    static void produceRange(int n, Consumer<Integer> consumer) {
        for (int i = 0; i < n; i++)
            consumer.accept(i);
    }
    

    By changing the type of consumer we pass to produceRange(), we can now perform different actions whenever a new number is generated:

    produceRange(10, System.out::println);  // print 0..10
    var list = new ArrayList<Integer>();
    produceRange(1000, list::add);          // add 0..999 to list
    

    The first example prints each number by itself, whereas the second examples collects all numbers in a list of integers.

  • • The opposite of a consumer is a supplier, a function that takes no arguments and produces a single value of type T:

    public interface Supplier<T> {
        T get();
    }
    

    As a practical example, the following function computes a list of n elements that are initialized using successive values generated by the supplier provided as the second argument:

    static <T> List<T> take(int n, Supplier<T> supplier) {
        List<T> result = new ArrayList<>(n);
        for (int i = 0; i < n; i++)
            result.add(supplier.get());
        return result;
    }
    

    Any function that doesn’t take any arguments and returns a value can be used as a Supplier. For example, the nextInt() method provided by Random returns a random integer value, so we can create ten random integers as follows:

    Random rng = new Random();
    List<Integer> values = take(10, rng::nextInt);
    

    JDK: List, Random

    We can also define suppliers using lambda expressions. To produce a list of ten random numbers less than six, we write

    Random rng = new Random();
    List<Integer> l = take(10, () -> rng.nextInt() % 6);
    

    JDK: List, Random

    A lambda expression that doesn’t take any arguments is distinguished by an empty argument list () before the arrow symbol ->.

    In some situations, you can also think of the new operator as a supplier that produces an object of a given type every time it is called. The special syntax ClassName::new can be used to create a method reference that calls the default constructor of the specified class. For example, we can use take() to create ten instances of the Random class using the method reference Random::new:

    List<Random> rngs = take(10, Random::new);
    

    JDK: Random

    Every time take() calls the method reference created by Random::new, the default constructor of Random is invoked, so the code above creates ten distinct random number generators. It is also possible to reference the new operator of generic classes. In this case, the type arguments must either be specified explicitly as part of the class name or it must be possible to infer it from the context. Consider the following example:

    List<List<Integer>> listOfLists = take(10, ArrayList::new);
    var listOfLists2 = take(10, ArrayList<Integer>::new);
    

    Both calls to take() create a list that contains ten empty ArrayLists. In the first line, Java is able to infer that the type argument of ArrayList must be Integer because that’s the only way to make the return value of take() compatible with List>. In the second line, we specify this type argument explicitly.

  • • A predicate is a function that tests whether a value or object has a certain property. Accordingly, the functional interface Predicate has a single method test() that takes a single argument and returns a Boolean value:

    interface Predicate<T> {
        boolean test(T value);
    }
    

    Predicates are often used to specify search conditions or termination criteria. For example, the following function findFirstIndex() searches a list for the first entry that satisfies the specified condition:

    static int findFirstIndex(List<Integer> list,
            Predicate<Integer> condition) {
        for (int i = 0; i < list.size(); i++) {
            if (condition.test(list.get(i)))
                return i;
        }
        return -1;
    }
    

    If we want to find the first even integer in a list, for example, we can call findFirstIndex() with a predicate that tests whether a given number is even:

    int index = findFirstIndex(List.of(1, 2, 3, 4), 
            v -> v % 2 == 0);
    
  • • A comparator is a function that performs a three-way comparison. It compares two objects of the same type and returns an integer that indicates their relative magnitude:

    public interface Comparator<T> {
        int compare(T o1, T o2);
    }
    

    By convention, the return value is negative if the first value is smaller, positive if it is larger, and zero if the two values are equal. This makes it easy to express conventional comparison operators such as \(<\), \(\le \), and \(=\) using compare():

    .
    \(p<q\) becomes compare(p, q) < 0
    \(p\le q\) becomes compare(p, q) <= 0
    \(p= q\) becomes compare(p, q) == 0

    and likewise for the remaining operators \(>\), \(\ge \), and \(\not =\).

All the functional interfaces we have discussed so far and a few additional ones are summarized in Table B.3.

Table B.3 Commonly used functional interfaces
.
Interface Main function

Description

Consumer void accept(T value)

consume a value of type T

BiConsumer void accept(T t, U u)

consume two values

Supplier T get()

produce a value of type T

Predicate boolean test(T value)

test whether a value of type T has a certain property

BiPredicate void test(T t, U u)

perform a test on a pair of values

Function R apply(T value)

map a value of type T to a result of type R

UnaryOperator T apply(T value)

map a value of type T to a result of the same type

BinaryOperator T apply(T v1, T v2)

map pairs of values of type T to a result of the same type

Comparator int compare(T v1, T v2)

perform a three-way comparison of v1 and v2