Miért kell a lambdákban használt helyi változóknak véglegesnek vagy ténylegesen véglegesnek lenniük?
1. Bemutatkozás
A Java 8 megadja nekünk a lambdákat, és az asszociáció révén a gyakorlatilag végleges változók. Elgondolkodott már azon, hogy miért kell a lambdákban rögzített helyi változóknak véglegeseknek vagy ténylegesen véglegeseknek lenniük?
Nos, a JLS ad nekünk egy kis tippet, amikor azt mondja: „A tényleges végső változók korlátozása tiltja a dinamikusan változó helyi változókhoz való hozzáférést, amelyek rögzítése valószínűleg egyidejűségi problémákat vet fel.” De mit jelent ez?
A következő szakaszokban mélyebben belemerülünk ebbe a korlátozásba, és megtudjuk, miért vezette be a Java. Mutatunk példákat a bemutatásra hogyan befolyásolja az egyszálú és egyidejű alkalmazásokat, és mi is megtesszük elvet egy közös anti-mintát a korlátozás kiküszöbölése érdekében.
2. Lambdas elfogása
A Lambda kifejezések külső hatókörben definiált változókat használhatnak. Ezekre a lambdákra hivatkozunk lambdas elfogása. Statikus változókat, példányváltozókat és helyi változókat képesek rögzíteni, de csak a helyi változóknak véglegesnek vagy ténylegesen véglegesnek kell lenniük.
Korábbi Java verziókban akkor találkoztunk ezzel, amikor egy névtelen belső osztály egy változó helyi értéket rögzített az azt körülvevő módszerhez - hozzá kellett adnunk a végső kulcsszó a helyi változó előtt, hogy a fordító boldog legyen.
Kicsit szintaktikus cukorként most a fordító felismeri azokat a helyzeteket, ahol, míg a végső kulcsszó nincs, a hivatkozás egyáltalán nem változik, vagyis az hatékonyan végső. Mondhatnánk, hogy egy változó ténylegesen végleges, ha a fordító nem panaszkodna, ha véglegesnek nyilvánítanánk.
3. Helyi változók a lambdas elfogásában
Egyszerűen fogalmazva, ez nem állítja össze:
Beszállítói növekmény (int start) {return () -> start ++; }
Rajt egy lokális változó, és megpróbáljuk módosítani egy lambda kifejezés belsejében.
Ennek az az oka, hogy ez nem áll össze, az az, hogy a lambda az értékének megragadása Rajt, vagyis másolat készítése. A változó véglegesre kényszerítése elkerüli a növekmény benyomását Rajt a lambda belsejében valóban módosíthatja a Rajt method paraméter.
De miért készít másolatot? Nos, vegye észre, hogy a lambdát visszaküldjük a módszerünkből. Így a lambda csak a Rajt A method paraméter összegyűjti a szemetet. A Java-nak másolatot kell készítenie Rajt annak érdekében, hogy ez a lambda ezen a módszeren kívül éljen.
3.1. Egyidejűségi kérdések
A móka kedvéért képzeljük el egy pillanatra azt a Java-t tette lehetővé teszi a helyi változók számára, hogy valahogy kapcsolatban maradjanak elfogott értékeikkel.
Mit tegyünk itt:
public void localVariableMultithreading () {logikai futás = igaz; végrehajtó.execute (() -> {while (futtatás) {// művelet végrehajtása}}); fut = hamis; }
Ez ugyan ártatlannak tűnik, de alattomos problémája van a „láthatóság”. Emlékezzünk vissza arra, hogy minden szálnak megvan a maga vereme, és így tudjuk biztosítani, hogy a míg hurok lát változás a fuss változó a másik veremben? A válasz más összefüggésekben a következő lehet: szinkronizált blokkok vagy a illó kulcsszó.
Azonban, mivel a Java ténylegesen végleges korlátozást szab ki, nem kell aggódnunk az ilyen bonyolultságok miatt.
4. Statikus vagy példányváltozók a lambdák rögzítésében
Az előző példák felvethetnek néhány kérdést, ha összehasonlítjuk őket statikus vagy példányváltozók lambda-kifejezésben való használatával.
Első példánkat csak a mi átalakításával készíthetjük Rajt változó egy példány változóba:
privát int start = 0; Beszállítói növekmény () {return () -> start ++; }
De miért változtathatjuk meg az értékét Rajt itt?
Egyszerűen fogalmazva, arról van szó, hogy a tagváltozókat hol tárolják. A helyi változók a veremben vannak, de a tagváltozók a kupacban vannak. Mivel halom memóriával van dolgunk, a fordító garantálhatja, hogy a lambda hozzáférjen a legújabb értékéhez Rajt.
A második példánkat ugyanezzel kijavíthatjuk:
privát volatile boolean run = true; public void instanceVariableMultithreading () {végrehajtó.execute (() -> {while (futtatás) {// művelet}}); fut = hamis; }
A fuss változó már akkor is látható a lambda számára, ha egy másik szálban futtatják, mióta hozzáadtuk a illó kulcsszó.
Általánosságban elmondható, hogy egy példányváltozó rögzítésekor úgy gondolhatunk rá, mint a végső változó elfogására ez. Egyébként is, az a tény, hogy a fordító nem panaszkodik, nem jelenti azt, hogy ne tegyünk óvintézkedéseket, főleg többszálas környezetben.
5. Kerülje a megoldásokat
Annak érdekében, hogy megkerülje a helyi változók korlátozását, valaki gondolkodhat azon, hogy változótulajdonosokkal módosítja a helyi változó értékét.
Lássunk egy példát, amely tömböt használ egy változó tárolására egyszálú alkalmazásban:
public int workaroundSingleThread () {int [] holder = new int [] {2}; IntStream összegek = IntStream .of (1, 2, 3) .map (val -> val + tartó [0]); tartó [0] = 0; return sums.sum (); }
Azt gondolhatnánk, hogy a folyam 2-et összesít az egyes értékekhez, de valójában 0-t összegez, mivel ez a legfrissebb elérhető érték a lambda végrehajtásakor.
Menjünk egy lépéssel tovább, és hajtsuk végre az összeget egy másik szálban:
public void workaroundMultithreading () {int [] holder = new int [] {2}; Futható futható = () -> System.out.println (IntStream .of (1, 2, 3) .map (val -> val + tartó [0]) .sum ()); új Szál (futható) .start (); // néhány feldolgozás szimulálása: próbáld ki a {Thread.sleep (new Random (). nextInt (3) * 1000L); } catch (InterruptedException e) {dobjon új RuntimeException (e); } tartó [0] = 0; }
Milyen értéket összegezünk itt? Attól függ, hogy a szimulált feldolgozásunk mennyi ideig tart. Ha elég rövid ahhoz, hogy a metódus végrehajtása befejeződjön, mielőtt a másik szál végrehajtásra kerülne, akkor 6-ot nyomtat, különben 12-et.
Általában az ilyen megoldások hibára hajlamosak és kiszámíthatatlan eredményeket hozhatnak, ezért ezeket mindig kerülnünk kell.
6. Következtetés
Ebben a cikkben elmagyaráztuk, hogy a lambda kifejezések miért használhatják csak a végső vagy hatékonyan a végső helyi változókat. Mint láttuk, ez a korlátozás e változók eltérő jellegéből és abból adódik, hogy a Java hogyan tárolja őket a memóriában. Megmutattuk a közös megoldás használatának veszélyeit is.
Mint mindig, a példák teljes forráskódja elérhető a GitHubon.