Interface Either<L,R>

Type Parameters:
L - The left type
R - The right type
All Known Implementing Classes:
Either.Left, Either.Right

public sealed interface Either<L,R> permits Either.Right<L,R>, Either.Left<L,R>
Represents either a Either.Left or a Either.Right. By convention, right is used for success and left for error.

Some usage examples:

 Either.right(1).get() // => 1
 Either.left("an error occurred").getLeft() // => "an error occurred"
 
A right cannot be left (and vice-versa), so you'll need to check it at runtime:

 Either<String, Integer> x = Either.right(1);
 if (x.isRight()) { // is true
   x.getLeft(); // throws NoSuchElementException
 }
 
Either works great if you have complex logic that needs to be executed under complex conditions. Consider the following code example, in which both method1 and method2 return Either a Right if the method was successful, or a Left if the method failed somehow.

 method1()
   .flatMap(i -> method2(i))
   .ifRightOrLeft(
     ok -> System.out.println("Both methods were successful"),
     error -> System.err.println("Either method1 or method2 failed")
   )
 
In this example, method2 is called if and only if method1 returned a Right (i.e. was successful). Then to finish, a line is printed to std out or err depending on the successful/failure of the called methods. Note, that the flatMap can also be used to change the type of the Right value. Also note that the mapping functions can be chained to further change the resulting values at any stage of the call chain.
  • Method Details

    • right

      static <L, R> Either<L,R> right(R right)
      Returns a Either.Right describing the given value.
      Type Parameters:
      L - the type of the left value
      R - the type of the right value
      Parameters:
      right - the value to describe
      Returns:
      a Either.Right of the value
    • left

      static <L, R> Either<L,R> left(L left)
      Returns a Either.Left describing the given value.
      Type Parameters:
      L - the type of the left value
      R - the type of the right value
      Parameters:
      left - the value to describe
      Returns:
      a Either.Left of the value
    • ofOptional

      static <R> Either.EitherOptional<R> ofOptional(Optional<R> right)
      Convenience method to convert an Optional<R> of R to an Either<?, R>, using an intermediary representation of the Optional in the form of Either.EitherOptional.

      Example:

      
       Either.ofOptional(Optional.of(1))
         .orElse("left value")
         .ifRightOrLeft(
           right -> System.out.println("If Optional is present, right is the contained value"),
           left -> System.out.println("If Optional is empty, left is the value provided by orElse")
         );
       
      Type Parameters:
      R - the type of the right value
      Parameters:
      right - The optional that may contain the right value
      Returns:
      An intermediary representation Either.EitherOptional
    • collector

      static <L, R> Collector<Either<L,R>,Tuple<List<L>,List<R>>,Either<List<L>,List<R>>> collector()
      Returns a collector for Either<L,R> that collects them into Either<List<L>,List<R> and favors Either.Left over Either.Right.

      This is commonly used to collect a stream of either objects where a right is considered a success and a left is considered an error. If any error has occurred, we're often only interested in the errors and not in the successes. Otherwise, we'd like to collect all success values.

      This collector groups all the lefts into one List and all the rights into another. When all elements of the stream have been collected and any lefts were encountered, it outputs a left of the list of encountered left values. Otherwise, it outputs a right of all right values it encountered.

      Examples:

      
       Stream.of(Either.right(1), Either.right(2), Either.right(3))
         .collect(Either.collector()) // => a Right
         .get(); // => List.of(1,2,3)
      
       Stream.of(Either.right(1), Either.left("oops"), Either.right(3))
         .collect(Either.collector()) // => a Left
         .getLeft(); // => List.of("oops")
       
      Type Parameters:
      L - the type of the left values
      R - the type of the right values
      Returns:
      a collector that favors left over right
    • collectorFoldingLeft

      static <L, R> Collector<Either<L,R>,Tuple<Optional<L>,List<R>>,Either<L,List<R>>> collectorFoldingLeft()
      Returns a collector for Either<L,R> that collects them into Either<L,List<R> and favors Either.Left over Either.Right. While collecting the rights, it folds the left into the first encountered.

      This is commonly used to collect a stream of either objects where a right is considered a success and a left is considered an error. If any error has occurred, we're often only interested in the first error and not in the successes. Otherwise, we'd like to collect all success values.

      This collector looks for a left while it groups all the rights into one List. When all elements of the stream have been collected and a left was encountered, it outputs the encountered left. Otherwise, it outputs a right of all right values it encountered.

      Examples:

      
       * Stream.of(Either.right(1), Either.right(2), Either.right(3))
       *   .collect(Either.collector()) // => a Right
       *   .get(); // => List.of(1,2,3)
       *
       * Stream.of(Either.right(1), Either.left("oops"), Either.left("another oops"))
       *   .collect(Either.collector()) // => a Left
       *   .getLeft(); // => "oops"
       *
       
      Type Parameters:
      L - the type of the left values
      R - the type of the right values
      Returns:
      a collector that favors left over right
    • isRight

      boolean isRight()
      Returns true if this Either is a Either.Right.
      Returns:
      true if right, false if left
    • isLeft

      boolean isLeft()
      Returns true if this Either is a Either.Left.
      Returns:
      true if left, false if right
    • get

      R get()
      Returns the right value, if this is a Either.Right.
      Returns:
      the right value
      Throws:
      NoSuchElementException - if this is a Either.Left
    • getOrElse

      R getOrElse(R defaultValue)
      Returns the right value, or a default value if this is a Either.Left.
      Parameters:
      defaultValue - the default value
      Returns:
      the right value, or the default value if this is a Either.Left
    • getLeft

      L getLeft()
      Returns the left value, if this is a Either.Left.
      Returns:
      the left value
      Throws:
      NoSuchElementException - if this is a Either.Right
    • map

      <T> Either<L,T> map(Function<? super R,? extends T> right)
      Maps the right value, if this is a Either.Right.
      Type Parameters:
      T - the type of the resulting right value
      Parameters:
      right - the mapping function for the right value
      Returns:
      a mapped Either.Right or the same Either.Left
    • mapLeft

      <T> Either<T,R> mapLeft(Function<? super L,? extends T> left)
      Maps the left value, if this is a Either.Left.
      Type Parameters:
      T - the type of the resulting left value
      Parameters:
      left - the mapping function for the left value
      Returns:
      a mapped Either.Left or the same Either.Right
    • flatMap

      <T> Either<L,T> flatMap(Function<? super R,? extends Either<L,T>> right)
      Flatmaps the right value into a new Either, if this is a Either.Right.

      A common use case is to map a right value to a new right, unless some error occurs in which case the value can be mapped to a new left. Note that this flatMap does not allow to alter the type of the left side. Example:

      
       Either.<String, Integer>right(0) // => Right(0)
         .flatMap(x -> Either.right(x + 1)) // => Right(1)
         .flatMap(x -> Either.left("an error occurred")) // => Left("an error occurred")
         .getLeft(); // => "an error occurred"
       
      Type Parameters:
      T - the type of the right side of the resulting either
      Parameters:
      right - the flatmapping function for the right value
      Returns:
      either a mapped Either.Right or a new Either.Left if this is a right; otherwise the same left, but cast to consider the new type of the right.
    • thenDo

      Either<L,R> thenDo(Consumer<R> action)
      Executes the given action with the right value if this is a Either.Right, otherwise does nothing. This method facilitates side-effect operations on the right value without altering the state or the type of the Either. After executing the action, the original Either<L, R> is returned, allowing for further chaining of operations in a fluent API style.

      When the instance is a Either.Right, the action is executed, and the original Either<L, R> is returned. This maintains the right value's type and allows the action to be performed as a side-effect without changing the outcome. When the instance is a Either.Left, no action is performed, and the Left instance is returned unchanged, preserving the error information.

      Usage example when Either is a Either.Right:

      
       Either<Exception, String> rightEither = Either.right("Success");
       Either<Exception, String> result = rightEither.thenDo(value -> System.out.println("Processed value: " + value));
       // Output: Processed value: Success
       // result remains a Right<Exception, String>, with the value "Success"
       

      Usage example when Either is a Either.Left:

      
       Either<String, Integer> leftEither = Either.left("Error occurred");
       Either<String, Integer> result = leftEither.thenDo(value -> System.out.println("This will not be printed"));
       // No output as the action is not executed
       // result remains an unchanged Left<String, Integer>, containing the original error message
       
      Parameters:
      action - the consuming function to perform with the right value, if present
      Returns:
      Eitherinvalid input: '<'L, R> the original Either instance, allowing further operations.
    • ifRight

      void ifRight(Consumer<R> action)
      Performs the given action with the value if this is a Either.Right, otherwise does nothing.
      Parameters:
      action - the consuming function for the right value
    • ifLeft

      void ifLeft(Consumer<L> action)
      Performs the given action with the value if this is a Either.Left, otherwise does nothing.
      Parameters:
      action - the consuming function for the left value
    • ifRightOrLeft

      void ifRightOrLeft(Consumer<R> rightAction, Consumer<L> leftAction)
      Performs the given right action with the value if this is a Either.Right, otherwise performs the given left action with the value.
      Parameters:
      rightAction - the consuming function for the right value
      leftAction - the consuming function for the left value
    • fold

      <T> T fold(Function<? super R,? extends T> rightFn, Function<? super L,? extends T> leftFn)
      Maps the right or left value into a new type using the provided functions, depending on whether this is a Either.Left or Either.Right.

      A common use case is to map to a new common value in success and error cases. Example:

      
       * Either<String, Integer> success = Either.right(42); // => Right(42)
       * Either<String, Integer> failure = Either.left("Error occurred"); // => Left("Error occurred")
       *
       * var rightFn = result -> "Success: " + result;
       * var leftFn = error -> "Failure: " + error;
       *
       * success.fold(rightFn, leftFn); // => "Success: 42"
       * failure.fold(rightFn, leftFn); // => "Failure: Error occurred"
       
      Type Parameters:
      T - the type of the resulting value
      Parameters:
      rightFn - the mapping function for the right value
      leftFn - the mapping function for the left value
      Returns:
      either a mapped Either.Left or Either.Right, folded to the new type