Either (Vagy)
Kategória: Modern minta (Modern Pattern)
Lényege és célja
Az Either egy generikus konténer típus, ami pontosan kétféle értéket tud magában hordozni: egy bal (Left) vagy egy jobb (Right) oldalit. Konvenció szerint a Right a sikeres eredmény, a Left pedig a hiba (a "right" magyarul "jobb" és "helyes" is – innen jön a megnevezés).
A minta a funkcionális programozásból érkezett (Haskell, Scala, Kotlin Arrow, Vavr), és a kivételek helyett ad egy típusos, explicit módot a hibakezelésre: a metódus visszatérési értéke maga árulja el, hogy sikerült-e a művelet.
Miért jobb ez a kivételeknél (legalábbis bizonyos esetekben)?
- A hiba szerepel a típusban:
Either<String, User>– a hívóra rá van kényszerítve, hogy gondoljon a hiba ágra. Egy simaUser findUser(int)szignatúrából nem derül ki, hogy dobhatUserNotFoundException-t. - Nincs control-flow kivétellel: a kivételek drágák, és könnyen "elveszhetnek" több réteg között. Az
Eitheregy normál visszatérési érték, ami pontosan ott van, ahol odatettük. - Komponálható: funkcionális stílusban (
map,flatMap) több, hibára futó művelet láncolható úgy, hogy az első Left esetén a többi automatikusan kimarad.
Either vs. Optional vs. Exception
Optional<T>: "van érték vagy nincs" – nem mondja meg, miért nincs.Either<L, R>: "van érték vagy van egy konkrét hibainfó" – a Left oldalon vissza tudunk adni hibaüzenetet, hibakódot, validációs listát stb.Exception: váratlan, "kivételes" eseményekre való (programhiba, IO megszakadás). Várt üzleti hibákra (pl. "nincs ilyen user") aEithertisztább.
Megjegyzés: A JDK alapból nem tartalmaz
Either-t. Éles projektekben legtöbbször a Vavr (io.vavr.control.Either) vagy a Functional Java könyvtár implementációját használjuk – a példában lévő saját változat csak a minta lényegét mutatja be.
Mikor használjuk?
- Amikor egy művelet várhatóan két, jól definiált kimenetel közül adhat egyet (sikeres eredmény, vagy ismert üzleti hiba), és mindkettőt típusosan akarjuk kezelni.
- Validációnál: a Left oldalon visszaadunk egy listányi hibaüzenetet, a Right oldalon a validált objektumot.
- Funkcionális stílusú láncolt műveleteknél, ahol a hibát nem akarjuk kivételként tovább dobni a teljes hívási láncon.
Mermaid Diagram
A diagram az Either<L, R> generikus szerkezetét és a két gyártófüggvényt (left, right) mutatja, valamint egy konkrét használatát (UserService), ahol a Left oldal egy hibaüzenet (String), a Right pedig a sikeres eredmény (User).
classDiagram
class Either~L, R~ {
-left: L
-right: R
-Either(left: L, right: R)
+left(value: L)$ Either~L, R~
+right(value: R)$ Either~L, R~
+isLeft() boolean
+isRight() boolean
+getLeft() L
+getRight() R
}
class UserService {
+findUser(id: int) Either~String, User~
}
class User {
-name: String
}
class Client
UserService ..> Either : "visszaad"
UserService ..> User : "létrehoz (Right)"
Client --> UserService : "hívja"
Client --> Either : "isLeft() / isRight() alapján ágazik"
note for UserService "id <= 0 → Either.left("Hibás azonosító")\nkülönben → Either.right(new User(...))" Forráskód
package modern.either;
class Either<L, R> {
private L left;
private R right;
private Either(L left, R right) {
this.left = left;
this.right = right;
}
public static <L, R> Either<L, R> left(L value) {
return new Either<>(value, null);
}
public static <L, R> Either<L, R> right(R value) {
return new Either<>(null, value);
}
public boolean isLeft() {
return left != null;
}
public boolean isRight() {
return right != null;
}
public L getLeft() {
return left;
}
public R getRight() {
return right;
}
}
record User(String name) {
}
class UserService {
public Either<String, User> findUser(int id) {
if (id <= 0) {
return Either.left("Hibás azonosító: " + id);
}
return Either.right(new User("Felhasználó_" + id));
}
}
class Main {
// modern Java vavr nevű külső könyvtár beépített Either osztályát szokták használni
static void main() {
var userService = new UserService();
var userFetch = userService.findUser(-1);
if (userFetch.isLeft()) {
System.out.println(userFetch.getLeft());
}
userFetch = userService.findUser(1);
if (userFetch.isRight()) {
System.out.println(userFetch.getRight());
}
}
}